00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011 #include "klone_conf.h"
00012 #include <ctype.h>
00013 #include <sys/stat.h>
00014 #include <sys/types.h>
00015 #include <sys/wait.h>
00016 #include <unistd.h>
00017 #include <fcntl.h>
00018 #include <klone/http.h>
00019 #include <klone/supplier.h>
00020 #include <klone/io.h>
00021 #include <klone/utils.h>
00022 #include <klone/rsfilter.h>
00023 #include <klone/vhost.h>
00024
00025
00026 typedef struct cgi_env_s
00027 {
00028 char **env;
00029 int size, count;
00030 } cgi_env_t;
00031
00032 static int cgi_get_config(http_t *h, request_t *rq, u_config_t **pc)
00033 {
00034 u_config_t *config = NULL;
00035 vhost_t *vhost;
00036
00037 dbg_err_if(h == NULL);
00038 dbg_err_if(rq == NULL);
00039
00040 dbg_err_if((vhost = http_get_vhost(h, rq)) == NULL);
00041
00042 *pc = vhost->config;
00043
00044 return 0;
00045 err:
00046 return ~0;
00047 }
00048
00049
00050 static int cgi_script(http_t *h, request_t *rq, const char *fqn)
00051 {
00052 u_config_t *config, *sub, *base;
00053 const char *dir;
00054 int i, t;
00055
00056 if(fqn == NULL)
00057 return 0;
00058
00059
00060 dbg_err_if(cgi_get_config(h, rq, &base));
00061
00062
00063 nop_err_if(u_config_get_subkey(base, "cgi", &config));
00064
00065
00066 for(i = 0; !u_config_get_subkey_nth(config, "script_alias", i, &sub); ++i)
00067 {
00068 if((dir = u_config_get_value(sub)) == NULL)
00069 continue;
00070
00071
00072 for(t = strlen(dir) - 1; t > 0; --t)
00073 if(dir[t] == ' ' || dir[t] == '\t')
00074 break;
00075
00076 if(t == 0)
00077 continue;
00078
00079
00080 dir += ++t;
00081
00082
00083 if(!strncmp(fqn, dir, strlen(dir)) && !access(fqn, X_OK))
00084 return 1;
00085 }
00086
00087 err:
00088 return 0;
00089 }
00090
00091
00092 static int cgi_ext(http_t *h, request_t *rq, const char *fqn,
00093 const char **phandler)
00094 {
00095 u_config_t *config, *base;
00096 char buf[U_FILENAME_MAX];
00097 char *ext = NULL;
00098 const char *handler;
00099
00100 if(fqn == NULL)
00101 return 0;
00102
00103 for(ext = NULL; *fqn; ++fqn)
00104 if(*fqn == '.')
00105 ext = (char*)fqn;
00106
00107 if(ext == NULL)
00108 return 0;
00109
00110 ext++;
00111
00112
00113 dbg_err_if(cgi_get_config(h, rq, &base));
00114
00115
00116 nop_err_if(u_config_get_subkey(base, "cgi", &config));
00117
00118 dbg_err_if(u_snprintf(buf, sizeof(buf), "%s.handler", ext));
00119
00120
00121 handler = u_config_get_subkey_value(config, buf);
00122
00123 if(handler)
00124 {
00125 if(phandler)
00126 *phandler = handler;
00127 return 1;
00128 }
00129
00130 err:
00131 return 0;
00132 }
00133
00134 static int cgi_setenv(cgi_env_t *env, const char *name, const char *value)
00135 {
00136 enum { CHUNK = 32 };
00137 char *keyval = NULL, **nenv = NULL;
00138 int i, nl, vl;
00139
00140 dbg_return_if(!env || !name || !value, ~0);
00141
00142 if((nl = strlen(name)) == 0)
00143 return ~0;
00144
00145 vl = strlen(value);
00146
00147
00148 if(env->size == 0 || env->size == env->count)
00149 {
00150 env->size += CHUNK;
00151 if(env->env == NULL)
00152 nenv = u_zalloc(env->size * sizeof(char*));
00153 else {
00154 nenv = u_realloc(env->env, env->size * sizeof(char*));
00155 }
00156 dbg_err_if(nenv == NULL);
00157
00158 for(i = env->count; i < env->size; ++i)
00159 nenv[i] = NULL;
00160 env->env = nenv;
00161 }
00162
00163 keyval = u_malloc(nl + vl + 2);
00164 dbg_err_if(keyval == NULL);
00165
00166 sprintf(keyval, "%s=%s", name, value);
00167
00168 env->env[env->count++] = keyval;
00169
00170 return 0;
00171 err:
00172 U_FREE(keyval);
00173 U_FREE(nenv);
00174 return ~0;
00175 }
00176
00177 static int cgi_is_valid_uri(http_t *h, request_t *rq, const char *uri,
00178 size_t len, time_t *mtime)
00179 {
00180 struct stat st;
00181 char fqn[U_FILENAME_MAX];
00182
00183 dbg_return_if (uri == NULL, 0);
00184 dbg_return_if (mtime == NULL, 0);
00185 dbg_return_if (len >= U_FILENAME_MAX, 0);
00186
00187 memcpy(fqn, uri, len);
00188 fqn[len] = 0;
00189
00190
00191 if(strstr(fqn, ".."))
00192 return 0;
00193
00194 if( stat(fqn, &st) == 0 && S_ISREG(st.st_mode))
00195 {
00196
00197 if(!cgi_ext(h, rq, fqn, NULL) && !cgi_script(h, rq, fqn))
00198 return 0;
00199
00200 *mtime = st.st_mtime;
00201 return 1;
00202 } else
00203 return 0;
00204 }
00205
00206 static const char *cgi_addr_to_ip(addr_t *addr, char *buf, size_t bufsz)
00207 {
00208 const char *cstr;
00209
00210 #ifndef NO_IPV6
00211 cstr = inet_ntop( addr->type == ADDR_IPV4 ? AF_INET : AF_INET6,
00212 (addr->type == ADDR_IPV4 ?
00213 (const void*)&(addr->sa.sin.sin_addr) :
00214 (const void*)&(addr->sa.sin6.sin6_addr)),
00215 buf, bufsz);
00216 #else
00217 cstr = inet_ntoa(addr->sa.sin.sin_addr);
00218 #endif
00219
00220 dbg_err_if(cstr == NULL);
00221
00222 return cstr;
00223 err:
00224 return NULL;
00225 }
00226
00227 static int cgi_setenv_addr(cgi_env_t *env, addr_t *addr,
00228 const char *label_addr, const char *label_port)
00229 {
00230 const char *cstr;
00231 char buf[128];
00232
00233 dbg_return_if(addr->type == ADDR_UNIX, 0);
00234
00235 if((cstr = cgi_addr_to_ip(addr, buf, sizeof(buf))) != NULL)
00236 dbg_err_if(cgi_setenv(env, label_addr, cstr));
00237
00238 u_snprintf(buf, sizeof(buf), "%u", ntohs(addr->sa.sin.sin_port));
00239 dbg_err_if(cgi_setenv(env, label_port, buf));
00240
00241 return 0;
00242 err:
00243 return ~0;
00244 }
00245
00246 static int cgi_setenv_ctype(cgi_env_t *env, request_t *rq)
00247 {
00248 const char *ct;
00249
00250 if((ct = request_get_field_value(rq, "Content-type")) != NULL)
00251 dbg_err_if(cgi_setenv(env, "CONTENT_TYPE", ct));
00252
00253 return 0;
00254 err:
00255 return ~0;
00256 }
00257
00258 static int cgi_setenv_clen(cgi_env_t *env, request_t *rq)
00259 {
00260 char buf[32];
00261 ssize_t len;
00262
00263 if((len = request_get_content_length(rq)) > 0)
00264 {
00265 dbg_err_if(u_snprintf(buf, sizeof(buf), "%ld", len));
00266 dbg_err_if(cgi_setenv(env, "CONTENT_LENGTH", buf));
00267 }
00268
00269 return 0;
00270 err:
00271 return ~0;
00272 }
00273
00274 static int cgi_set_blocking(int fd)
00275 {
00276 int flags;
00277
00278 warn_err_sif((flags = fcntl(fd, F_GETFL)) < 0);
00279
00280 nop_err_if(fcntl(fd, F_SETFL, flags & (~O_NONBLOCK)) < 0);
00281
00282 return 0;
00283 err:
00284 return ~0;
00285 }
00286
00287 static int cgi_makeenv(request_t *rq, response_t *rs, cgi_env_t *env)
00288 {
00289 addr_t *addr;
00290 header_t *h;
00291 field_t *field;
00292 const char *cstr;
00293 char *p, buf[1024];
00294 int i;
00295
00296 u_unused_args(rs);
00297
00298 dbg_err_if(cgi_setenv(env, "SERVER_SOFTWARE", "klone/" KLONE_VERSION));
00299 dbg_err_if(cgi_setenv(env, "SERVER_PROTOCOL", "HTTP/1.0"));
00300 dbg_err_if(cgi_setenv(env, "GATEWAY_INTERFACE", "CGI/1.1"));
00301 dbg_err_if(cgi_setenv(env, "REDIRECT_STATUS", "200"));
00302
00303
00304 if((addr = request_get_addr(rq)) != NULL)
00305 {
00306 dbg_err_if(cgi_setenv_addr(env, addr, "SERVER_ADDR", "SERVER_PORT"));
00307 if((cstr = cgi_addr_to_ip(addr, buf, sizeof(buf))) != NULL)
00308 dbg_err_if(cgi_setenv(env, "SERVER_NAME", cstr));
00309 }
00310
00311
00312 if((addr = request_get_peer_addr(rq)) != NULL)
00313 dbg_err_if(cgi_setenv_addr(env, addr, "REMOTE_ADDR", "REMOTE_PORT"));
00314
00315
00316 switch(request_get_method(rq))
00317 {
00318 case HM_GET: cstr = "GET"; break;
00319 case HM_HEAD: cstr = "HEAD"; break;
00320 case HM_POST: cstr = "POST"; break;
00321 default:
00322 cstr = "UNKNOWN";
00323 }
00324 dbg_err_if(cgi_setenv(env, "REQUEST_METHOD", cstr));
00325
00326 if(io_is_secure(request_io(rq)))
00327 dbg_err_if(cgi_setenv(env, "HTTPS", "on"));
00328
00329 if((cstr = request_get_path_info(rq)) != NULL)
00330 dbg_err_if(cgi_setenv(env, "PATH_INFO", cstr));
00331
00332 if((cstr = request_get_resolved_path_info(rq)) != NULL)
00333 dbg_err_if(cgi_setenv(env, "PATH_TRANSLATED", cstr));
00334
00335 if((cstr = request_get_query_string(rq)) != NULL)
00336 dbg_err_if(cgi_setenv(env, "QUERY_STRING", cstr));
00337
00338
00339 dbg_err_if(cgi_setenv_clen(env, rq));
00340
00341
00342 dbg_err_if(cgi_setenv_ctype(env, rq));
00343
00344 if((cstr = request_get_filename(rq)) != NULL)
00345 dbg_err_if(cgi_setenv(env, "SCRIPT_NAME", cstr));
00346
00347 if((cstr = request_get_uri(rq)) != NULL)
00348 dbg_err_if(cgi_setenv(env, "REQUEST_URI", cstr));
00349
00350 if((cstr = request_get_resolved_filename(rq)) != NULL)
00351 dbg_err_if(cgi_setenv(env, "SCRIPT_FILENAME", cstr));
00352
00353 if((cstr = getenv("SYSTEMROOT")) != NULL)
00354 dbg_err_if(cgi_setenv(env, "SYSTEMROOT", cstr));
00355
00356 dbg_err_if((h = request_get_header(rq)) == NULL);
00357
00358
00359 for(i = 0; i < header_field_count(h); ++i)
00360 {
00361 field = header_get_fieldn(h, i);
00362 dbg_err_if(field == NULL);
00363
00364 dbg_err_if(u_snprintf(buf, sizeof(buf), "HTTP_%s",
00365 field_get_name(field)));
00366
00367
00368 for(p = buf; *p && *p != ':'; ++p)
00369 {
00370 if(*p == '-')
00371 *p = '_';
00372 else
00373 *p = toupper(*p);
00374 }
00375
00376 if(field_get_value(field))
00377 dbg_err_if(cgi_setenv(env, buf, field_get_value(field)));
00378 else
00379 dbg_err_if(cgi_setenv(env, buf, ""));
00380 }
00381
00382 return 0;
00383 err:
00384 return ~0;
00385 }
00386
00387 #define close_pipe(fd) \
00388 do { \
00389 if(fd[0] != -1) close(fd[0]); \
00390 if(fd[1] != -1) close(fd[1]); \
00391 } while(0);
00392
00393 static int cgi_exec(request_t *rq, response_t *rs, pid_t *pchild,
00394 int *pcgi_stdin, int *pcgi_stdout)
00395 {
00396 enum { RD_END , WR_END };
00397 int cgi_stdin[2] = { -1, -1 };
00398 int cgi_stdout[2] = { -1, -1 };
00399 cgi_env_t cgi_env = { NULL, 0, 0 };
00400 http_t *h;
00401 const char *argv[] = { NULL, NULL, NULL };
00402 const char *cgi_file, *handler;
00403 char *p, *cgi_path = NULL;
00404 pid_t child;
00405 int fd;
00406
00407 dbg_err_if((h = request_get_http(rq)) == NULL);
00408
00409
00410 dbg_err_if(pipe(cgi_stdin) < 0);
00411 dbg_err_if(pipe(cgi_stdout) < 0);
00412
00413 crit_err_if((child = fork()) < 0);
00414
00415 if(child == 0)
00416 {
00417
00418
00419 close(cgi_stdin[WR_END]);
00420 close(cgi_stdout[RD_END]);
00421
00422
00423 close(STDOUT_FILENO);
00424 crit_err_if(dup2(cgi_stdout[WR_END], STDOUT_FILENO) < 0);
00425 close(cgi_stdout[WR_END]);
00426
00427
00428 close(STDIN_FILENO);
00429 crit_err_if(dup2(cgi_stdin[RD_END], STDIN_FILENO) < 0);
00430 close(cgi_stdin[RD_END]);
00431
00432
00433 fd = open("/dev/null", O_WRONLY);
00434 dbg_err_if(fd < 0);
00435 crit_err_if(dup2(fd, STDERR_FILENO) < 0);
00436 close(fd);
00437
00438
00439 cgi_set_blocking(STDOUT_FILENO);
00440 cgi_set_blocking(STDIN_FILENO);
00441 cgi_set_blocking(STDERR_FILENO);
00442
00443
00444 for(fd = 3; fd < 255; ++fd)
00445 close(fd);
00446
00447
00448 dbg_err_if((cgi_file = request_get_resolved_filename(rq)) == NULL);
00449
00450 cgi_path = u_strdup(cgi_file);
00451 dbg_err_if(cgi_path == NULL);
00452
00453
00454 dbg_err_if((p = strrchr(cgi_path, '/')) == NULL);
00455 ++p; *p = 0;
00456
00457 crit_err_sifm(chdir(cgi_path) < 0, "unable to chdir to %s", cgi_path);
00458
00459 U_FREE(cgi_path);
00460
00461
00462 crit_err_sif(cgi_makeenv(rq, rs, &cgi_env));
00463
00464
00465
00466 if(!cgi_ext(h, rq, cgi_file, &handler) || !strcasecmp(handler, "exec"))
00467 {
00468
00469 argv[0] = cgi_file;
00470
00471 } else {
00472
00473 argv[0] = handler;
00474 argv[1] = cgi_file;
00475 }
00476
00477
00478 crit_err_sif(execve(argv[0], argv, cgi_env.env));
00479
00480
00481
00482 } else if(child > 0) {
00483
00484
00485
00486 close(cgi_stdin[RD_END]);
00487 close(cgi_stdout[WR_END]);
00488
00489
00490 *pcgi_stdin = cgi_stdin[WR_END];
00491 *pcgi_stdout = cgi_stdout[RD_END];
00492 *pchild = child;
00493
00494 return 0;
00495
00496 } else {
00497 warn_err("fork error");
00498 }
00499
00500 err:
00501 if(child == 0)
00502 _exit(1);
00503 close_pipe(cgi_stdin);
00504 close_pipe(cgi_stdout);
00505 return ~0;
00506 }
00507
00508 static int cgi_serve(request_t *rq, response_t *rs)
00509 {
00510 codec_t *filter = NULL;
00511 header_t *head = NULL;
00512 field_t *field = NULL;
00513 const char *fqn, *filename;
00514 char buf[4096];
00515 io_t *out = NULL, *cgi_in = NULL, *cgi_out = NULL;
00516 ssize_t n, tot = 0, clen;
00517 int cgi_stdin = -1, cgi_stdout = -1, status;
00518 pid_t child;
00519
00520 dbg_err_if (rq == NULL);
00521 dbg_err_if (rs == NULL);
00522
00523
00524 dbg_err_if((out = response_io(rs)) == NULL);
00525 dbg_err_if((head = response_get_header(rs)) == NULL);
00526
00527
00528 response_set_status(rs, HTTP_STATUS_BAD_REQUEST);
00529
00530
00531 fqn = request_get_resolved_filename(rq);
00532
00533
00534 crit_err_if(cgi_exec(rq, rs, &child, &cgi_stdin, &cgi_stdout));
00535
00536
00537 response_disable_caching(rs);
00538
00539
00540 if(request_get_method(rq) == HM_POST &&
00541 (clen = request_get_content_length(rq)) > 0)
00542 {
00543
00544 crit_err_sif(io_fd_create(cgi_stdin, O_WRONLY, &cgi_out));
00545
00546
00547
00548
00549
00550
00551
00552
00553 crit_if(io_copy(cgi_out, request_io(rq), clen) < 0);
00554
00555 io_free(cgi_out); cgi_out = NULL;
00556 close(cgi_stdin); cgi_stdin = -1;
00557 }
00558
00559
00560 crit_err_sif(io_fd_create(cgi_stdout, O_RDONLY, &cgi_in));
00561
00562
00563 crit_err_if((filename = strrchr(fqn, '/')) == NULL);
00564 filename++;
00565
00566
00567 if(strncmp(filename, "nph-", 4))
00568 {
00569
00570 dbg_err_if(response_filter_create(rq, rs, NULL, &filter));
00571 io_codec_add_tail(out, filter);
00572 filter = NULL;
00573
00574
00575 crit_err_if(header_load_ex(head, cgi_in, HLM_OVERRIDE));
00576
00577
00578 if((field = header_get_field(head, "Status")) != NULL &&
00579 field_get_value(field))
00580 {
00581 response_set_status(rs, atoi(field_get_value(field)));
00582 } else {
00583 if(header_get_field(head, "Location"))
00584 response_set_status(rs, HTTP_STATUS_MOVED_TEMPORARILY);
00585 else
00586 response_set_status(rs, HTTP_STATUS_OK);
00587 }
00588 } else
00589 response_set_status(rs, HTTP_STATUS_OK);
00590
00591
00592 while((n = io_read(cgi_in, buf, sizeof(buf))) > 0)
00593 {
00594 if(io_write(out, buf, n) < 0)
00595 break;
00596 tot += n;
00597 }
00598
00599
00600
00601
00602 if(tot == 0)
00603 io_write(out, "\n", 1);
00604
00605 if(cgi_in)
00606 io_free(cgi_in);
00607 if(cgi_out)
00608 io_free(cgi_out);
00609
00610 close(cgi_stdin);
00611 close(cgi_stdout);
00612
00613
00614 waitpid(child, &status, 0);
00615 if(WIFEXITED(status) && WEXITSTATUS(status))
00616 warn("cgi exited with [%d]", WEXITSTATUS(status));
00617
00618 return 0;
00619 err:
00620 if(cgi_out)
00621 io_free(cgi_out);
00622 if(cgi_in)
00623 io_free(cgi_in);
00624 if(cgi_stdin != -1)
00625 close(cgi_stdin);
00626 if(cgi_stdout != -1)
00627 close(cgi_stdout);
00628 return ~0;
00629 }
00630
00631 static int cgi_init(void)
00632 {
00633 return 0;
00634 }
00635
00636 static void cgi_term(void)
00637 {
00638 return;
00639 }
00640
00641 supplier_t sup_cgi = {
00642 "cgi supplier",
00643 cgi_init,
00644 cgi_term,
00645 cgi_is_valid_uri,
00646 cgi_serve
00647 };
00648