UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2016 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "cgi.h" 30 31 #include <stdio.h> 32 #include <stdlib.h> 33 #include <unistd.h> 34 35 #include <sys/types.h> 36 #include <signal.h> 37 #include <sys/wait.h> 38 39 #include "../util/util.h" 40 #include "../util/pblock.h" 41 #include "../../ucx/string.h" 42 #include "../daemon/netsite.h" 43 #include "../util/io.h" 44 45 #include "cgiutils.h" 46 47 #define CGI_VARS 32 48 49 #define CGI_RESPONSE_PARSER_BUFLEN 2048 50 #define CGI_RESPONSE_MAX_LINE_LENGTH 512 51 52 int send_cgi(pblock *pb, Session *sn, Request *rq) { 53 char *path = pblock_findkeyval(pb_key_path, rq->vars); 54 char *ctlen = pblock_findkeyval(pb_key_content_length, rq->headers); 55 int64_t content_length = 0; 56 57 if(ctlen) { 58 if(!util_strtoint(ctlen, &content_length)) { 59 log_ereport( 60 LOG_FAILURE, 61 "send-cgi: content-length header is not an integer"); 62 protocol_status(sn, rq, 400, NULL); 63 return REQ_ABORTED; 64 } 65 } 66 67 struct stat s; 68 if(stat(path, &s)) { 69 int statuscode = util_errno2status(errno); 70 protocol_status(sn, rq, statuscode, NULL); 71 return REQ_ABORTED; 72 } 73 if(S_ISDIR(s.st_mode)) { 74 protocol_status(sn, rq, 403, NULL); 75 return REQ_ABORTED; 76 } 77 78 param_free(pblock_remove("content-type", rq->srvhdrs)); 79 80 const char *args = pblock_findval("query", rq->reqpb); 81 char **argv = cgi_create_argv(path, NULL, args); 82 83 char **env = http_hdrs2env(rq->headers); 84 env = cgi_common_vars(sn, rq, env); 85 env = cgi_specific_vars(sn, rq, args, env, 1); 86 87 CGIProcess cgip; 88 int ret = cgi_start(&cgip, path, argv, env); 89 if(ret != REQ_PROCEED) { 90 util_env_free(env); 91 cgi_free_argv(argv); 92 return ret; 93 } 94 95 util_env_free(env); 96 cgi_free_argv(argv); 97 98 char buf[4096]; // I/O buffer 99 ssize_t r; 100 101 if(content_length > 0) { 102 ssize_t n = 0; 103 while(n < content_length) { 104 r = netbuf_getbytes(sn->inbuf, buf, 4096); 105 if(r <= 0) { 106 // TODO: handle error 107 log_ereport( 108 LOG_FAILURE, 109 "send-cgi: script: %s: cannot read request body", 110 path); 111 kill(cgip.pid, SIGTERM); 112 cgi_close(&cgip); 113 return REQ_ABORTED; 114 } 115 ssize_t w = write(cgip.in[1], buf, r); 116 if(w <= 0) { 117 // TODO: handle error 118 log_ereport( 119 LOG_FAILURE, 120 "send-cgi: script: %s: cannot send request body to cgi process", 121 path); 122 kill(cgip.pid, SIGKILL); 123 cgi_close(&cgip); 124 return REQ_ABORTED; 125 } 126 n += r; 127 } 128 } 129 system_close(cgip.in[1]); 130 cgip.in[1] = -1; 131 132 // read from child 133 CGIResponseParser *parser = cgi_parser_new(sn, rq); 134 WSBool cgiheader = TRUE; 135 ssize_t wr = 0; 136 int result = REQ_PROCEED; 137 size_t response_length = 0; 138 while((r = read(cgip.out[0], buf, 4096)) > 0) { 139 if(cgiheader) { 140 size_t pos; 141 ret = cgi_parse_response(parser, buf, r, &pos); 142 if(ret == -1) { 143 log_ereport( 144 LOG_FAILURE, 145 "broken cgi script response: path: %s", path); 146 protocol_status(sn, rq, 500, NULL); 147 result = REQ_ABORTED; 148 break; 149 } else if(ret == 1) { 150 cgiheader = FALSE; 151 if(parser->status > 0) { 152 protocol_status(sn, rq, parser->status, parser->msg); 153 } 154 http_start_response(sn, rq); 155 if(pos < r) { 156 response_length += r-pos; 157 wr = net_write(sn->csd, &buf[pos], r-pos); 158 if(wr <= 0) { 159 result = REQ_ABORTED; 160 break; 161 } 162 } 163 } 164 } else { 165 response_length += r; 166 wr = net_write(sn->csd, buf, r); 167 if(wr <= 0) { 168 result = REQ_ABORTED; 169 break; 170 } 171 } 172 } 173 174 char *ctlen_header = pblock_findkeyval(pb_key_content_length, rq->srvhdrs); 175 if(ctlen_header) { 176 int64_t ctlenhdr; 177 if(util_strtoint(ctlen_header, &ctlenhdr)) { 178 if(ctlenhdr != response_length) { 179 log_ereport( 180 LOG_FAILURE, 181 "cgi-send: script: %s: content length mismatch", 182 path); 183 rq->rq_attr.keep_alive = 0; 184 result = REQ_ABORTED; 185 } 186 } 187 } 188 189 if(result == REQ_ABORTED) { 190 log_ereport(LOG_FAILURE, "cgi-send: kill script: %s", path); 191 kill(cgip.pid, SIGKILL); 192 } 193 cgi_close(&cgip); // TODO: check return value 194 195 cgi_parser_free(parser); 196 return result; 197 } 198 199 int cgi_start(CGIProcess *p, char *path, char *const argv[], char *const envp[]) { 200 if(pipe(p->in) || pipe(p->out)) { 201 log_ereport( 202 LOG_FAILURE, 203 "send-cgi: cannot create pipe: %s", 204 strerror(errno)); 205 return REQ_ABORTED; 206 } 207 208 p->pid = fork(); 209 if(p->pid == 0) { 210 // child 211 212 // get script directory and script name 213 sstr_t script = sstr(path); 214 sstr_t parent; 215 int len = strlen(path); 216 for(int i=len-1;i>=0;i--) { 217 if(path[i] == '/') { 218 script = sstrn(path + i + 1, len - i); 219 parent = sstrdup(sstrn(path, i)); 220 if(chdir(parent.ptr)) { 221 perror("cgi_start: chdir"); 222 free(parent.ptr); 223 exit(-1); 224 } 225 free(parent.ptr); 226 break; 227 } 228 } 229 230 if(dup2(p->in[0], STDIN_FILENO) == -1) { 231 perror("cgi_start: dup2"); 232 exit(EXIT_FAILURE); 233 } 234 if(dup2(p->out[1], STDOUT_FILENO) == -1) { 235 perror("cgi_start: dup2"); 236 exit(EXIT_FAILURE); 237 } 238 239 // we need to close this unused pipe 240 // otherwise stdin cannot reach EOF 241 system_close(p->in[1]); 242 243 // execute program 244 exit(execve(script.ptr, argv, envp)); 245 } else { 246 // parent 247 system_close(p->out[1]); 248 p->out[1] = -1; 249 } 250 251 return REQ_PROCEED; 252 } 253 254 int cgi_close(CGIProcess *p) { 255 int status = -1; 256 waitpid(p->pid, &status, 0); 257 258 if(p->in[0] != -1) { 259 system_close(p->in[0]); 260 } 261 if(p->in[1] != -1) { 262 system_close(p->in[1]); 263 } 264 if(p->out[0] != -1) { 265 system_close(p->out[0]); 266 } 267 if(p->out[1] != -1) { 268 system_close(p->out[1]); 269 } 270 271 return 0; 272 } 273 274 CGIResponseParser* cgi_parser_new(Session *sn, Request *rq) { 275 CGIResponseParser* parser = pool_malloc(sn->pool, sizeof(CGIResponseParser)); 276 parser->sn = sn; 277 parser->rq = rq; 278 parser->tmp = ucx_buffer_new(NULL, 64, UCX_BUFFER_AUTOEXTEND); 279 parser->status = 0; 280 parser->msg = NULL; 281 return parser; 282 } 283 284 void cgi_parser_free(CGIResponseParser *parser) { 285 if(parser->tmp) { 286 ucx_buffer_free(parser->tmp); 287 } 288 pool_free(parser->sn->pool, parser); 289 } 290 291 /* 292 * parses a cgi response line and adds the response header to rq->srvhdrs 293 * returns 0: incomplete line 294 * 1: successfully parsed lines 295 * 2: cgi response header complete (empty line) 296 * -1: error 297 */ 298 static int parse_lines(CGIResponseParser *parser, char *buf, size_t len, int *pos) { 299 UcxAllocator a = util_pool_allocator(parser->sn->pool); 300 sstr_t name; 301 sstr_t value; 302 WSBool space = TRUE; 303 int i; 304 305 int line_begin = 0; 306 int value_begin = 0; 307 for(i=0;i<len;i++) { 308 char c = buf[i]; 309 if(value_begin == line_begin && c == ':') { 310 name = sstrn(buf + line_begin, i - line_begin); 311 value_begin = i + 1; 312 } else if(c == '\n') { 313 if(value_begin == line_begin) { 314 if(space) { 315 *pos = i + 1; 316 return 2; 317 } else { 318 // line ends with content but without ':' -> error 319 return -1; 320 } 321 } 322 value = sstrn(buf + value_begin, i - value_begin); 323 324 name = sstrlower_a(&a, sstrtrim(name)); 325 value = sstrtrim(value); 326 327 if(name.length == 0 || value.length == 0) { 328 return -1; 329 } 330 331 if(!sstrcmp(name, S("status"))) { 332 sstr_t codestr = value; 333 int j; 334 for(j=0;j<codestr.length;j++) { 335 if(!isdigit(codestr.ptr[j])) { 336 break; 337 } 338 if(j > 2) { 339 break; 340 } 341 } 342 codestr.ptr[j] = '\0'; 343 344 int64_t s = 0; 345 util_strtoint(codestr.ptr, &s); 346 parser->status = (int)s; 347 348 sstr_t msg = sstrtrim(sstrsubs(value, j + 1)); 349 350 if(msg.length > 0) { 351 parser->msg = sstrdup_pool(parser->sn->pool, msg).ptr; 352 } 353 } else { 354 pblock_nvlinsert( 355 name.ptr, 356 name.length, 357 value.ptr, 358 value.length, 359 parser->rq->srvhdrs); 360 } 361 362 line_begin = i+1; 363 value_begin = line_begin; 364 space = TRUE; 365 } else if(!isspace(c)) { 366 space = FALSE; 367 } 368 } 369 370 if(i < len) { 371 *pos = i; 372 return 0; 373 } 374 return 1; 375 } 376 377 /* 378 * returns -1: error 379 * 0: response header incomplete 380 * 1: complete 381 */ 382 int cgi_parse_response(CGIResponseParser *parser, char *buf, size_t len, size_t *bpos) { 383 *bpos = 0; 384 int pos = 0; 385 if(parser->tmp->pos > 0) { 386 // the tmp buffer contains an unfinished line 387 // fill up the buffer until the line is complete 388 WSBool nb = FALSE; 389 for(pos=0;pos<len;pos++) { 390 if(buf[pos] == '\n') { 391 nb = TRUE; 392 break; 393 } 394 } 395 ucx_buffer_write(buf, 1, pos, parser->tmp); 396 397 if(nb) { 398 // line complete 399 int npos; 400 int r = parse_lines(parser, parser->tmp->space, parser->tmp->pos, &npos); 401 switch(r) { 402 case -1: return -1; 403 case 0: return -1; 404 case 1: break; 405 case 2: { 406 *bpos = pos + 1; 407 return 1; 408 } 409 } 410 // reset tmp buffer 411 parser->tmp->pos = 0; 412 } else { 413 if(parser->tmp->pos > CGI_RESPONSE_MAX_LINE_LENGTH) { 414 return -1; 415 } 416 } 417 } 418 419 int npos = 0; 420 int r = parse_lines(parser, buf + pos, len - pos, &npos); 421 switch(r) { 422 default: return -1; 423 case 0: 424 case 1: { 425 int newlen = len - npos; 426 if(npos > 0) { 427 ucx_buffer_write(buf + npos, 1, newlen, parser->tmp); 428 } 429 return 0; 430 } 431 case 2: { 432 *bpos = pos + npos; 433 return 1; 434 } 435 } 436 } 437