sup_cgi.c

00001 /*
00002  * Copyright (c) 2005-2012 by KoanLogic s.r.l. <http://www.koanlogic.com>
00003  * All rights reserved.
00004  *
00005  * This file is part of KLone, and as such it is subject to the license stated
00006  * in the LICENSE file which you have received as part of this distribution.
00007  *
00008  * $Id: sup_cgi.c,v 1.12 2008/10/27 21:28:04 tat Exp $
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 /* holds environment variables passed to the cgi */
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     vhost_t *vhost;
00035 
00036     dbg_err_if(h == NULL);
00037     dbg_err_if(rq == NULL);
00038 
00039     dbg_err_if((vhost = http_get_vhost(h, rq)) == NULL);
00040 
00041     *pc = vhost->config;
00042 
00043     return 0;
00044 err:
00045     return ~0;
00046 }
00047 
00048 
00049 static int cgi_script(http_t *h, request_t *rq, const char *fqn)
00050 {
00051     u_config_t *config, *sub, *base;
00052     const char *dir;
00053     int i, t;
00054 
00055     if(fqn == NULL)
00056         return 0;
00057 
00058     /* get cgi. config subtree */
00059     dbg_err_if(cgi_get_config(h, rq, &base));
00060 
00061     /* get cgi. config subtree */
00062     nop_err_if(u_config_get_subkey(base, "cgi", &config));
00063 
00064     /* for each script_di config item */
00065     for(i = 0; !u_config_get_subkey_nth(config, "script_alias", i, &sub); ++i)
00066     {
00067         if((dir = u_config_get_value(sub)) == NULL)
00068             continue; /* empty key */
00069 
00070         /* find the dir part of the "alias dir" value */
00071         for(t = strlen(dir) - 1; t > 0; --t)
00072             if(dir[t] == ' ' || dir[t] == '\t')
00073                 break;
00074 
00075         if(t == 0)
00076             continue; /* malformed value */
00077 
00078         /* skip the blank */
00079         dir += ++t;
00080 
00081         /* the first part of fqn must be equal to p and the file must be +x */
00082         if(!strncmp(fqn, dir, strlen(dir)) && !access(fqn, X_OK))
00083             return 1; /* ok, fqn in in the dir_alias dir or in a subdir */
00084     }
00085 
00086 err:
00087     return 0;
00088 }
00089 
00090 /* returns 1 if the file extension in one of those handled by cgi programs */
00091 static int cgi_ext(http_t *h, request_t *rq, const char *fqn, 
00092         const char **phandler)
00093 {
00094     u_config_t *config, *base;
00095     char buf[U_FILENAME_MAX];
00096     const char *handler, *ext = NULL;
00097 
00098     if(fqn == NULL)
00099         return 0;
00100 
00101     for(ext = NULL; *fqn; ++fqn) 
00102         if(*fqn == '.')
00103             ext = fqn;
00104 
00105     if(ext == NULL)
00106         return 0; /* file with no extension */
00107 
00108     ext++; /* skip '.' */
00109 
00110     /* get cgi. config subtree */
00111     dbg_err_if(cgi_get_config(h, rq, &base));
00112 
00113     /* get cgi. config subtree */
00114     nop_err_if(u_config_get_subkey(base, "cgi", &config));
00115 
00116     dbg_err_if(u_snprintf(buf, sizeof(buf), "%s.handler", ext));
00117 
00118     /* check for cgi extension handler */
00119     handler = u_config_get_subkey_value(config, buf);
00120 
00121     if(handler)
00122     {
00123         if(phandler)
00124             *phandler = handler;
00125         return 1;
00126     }
00127 
00128 err:
00129     return 0;
00130 }
00131 
00132 static int cgi_setenv(cgi_env_t *env, const char *name, const char *value)
00133 {
00134     enum { CHUNK = 32 };
00135     char *keyval = NULL, **nenv = NULL;
00136     int i, nl, vl;
00137 
00138     dbg_return_if(!env || !name || !value, ~0);
00139 
00140     if((nl = strlen(name)) == 0)
00141         return ~0;
00142 
00143     vl = strlen(value);
00144 
00145     /* alloc or realloc the array */
00146     if(env->size == 0 || env->size == env->count)
00147     {
00148         env->size += CHUNK;
00149         if(env->env == NULL)
00150             nenv = u_zalloc(env->size * sizeof(char*));
00151         else {
00152             nenv = u_realloc(env->env, env->size * sizeof(char*));
00153         }
00154         dbg_err_if(nenv == NULL);
00155         /* zero-out new elems */
00156         for(i = env->count; i < env->size; ++i)
00157             nenv[i] = NULL;
00158         env->env = nenv;
00159     }
00160 
00161     keyval = u_malloc(nl + vl + 2);
00162     dbg_err_if(keyval == NULL);
00163 
00164     sprintf(keyval, "%s=%s", name, value);
00165 
00166     env->env[env->count++] = keyval;
00167 
00168     return 0;
00169 err:
00170     U_FREE(keyval);
00171     U_FREE(nenv);
00172     return ~0;
00173 }
00174 
00175 static int cgi_is_valid_uri(http_t *h, request_t *rq, const char *uri, 
00176         size_t len, void **handle, time_t *mtime)
00177 {
00178     struct stat st; 
00179     char fqn[U_FILENAME_MAX];
00180 
00181     dbg_return_if (uri == NULL, 0);
00182     dbg_return_if (mtime == NULL, 0);
00183     dbg_return_if (len + 1 > U_FILENAME_MAX, 0);
00184 
00185     memcpy(fqn, uri, len);
00186     fqn[len] = '\0';
00187 
00188     /* fqn must be already normalized */
00189     if(strstr(fqn, ".."))
00190         return 0; 
00191     
00192     if(stat(fqn, &st) == 0 && S_ISREG(st.st_mode))
00193     {
00194         /* if it's not a cgi given its extension of uri then exit */
00195         if(!cgi_ext(h, rq, fqn, NULL) && !cgi_script(h, rq, fqn))
00196             return 0;
00197 
00198         *mtime = st.st_mtime;
00199         *handle = NULL;
00200         return 1;
00201     } else
00202         return 0;
00203 }
00204 
00205 static int cgi_setenv_addr(cgi_env_t *env, const char *addr, 
00206         const char *label_addr, const char *label_port)
00207 {
00208     char buf[128];
00209     
00210     if (u_addr_get_ip(addr, buf, sizeof buf) != NULL)
00211         dbg_err_if (cgi_setenv(env, label_addr, buf));
00212 
00213     if (u_addr_get_port(addr, buf, sizeof buf) != NULL)
00214         dbg_err_if (cgi_setenv(env, label_port, buf));
00215 
00216     return 0;
00217 err:
00218     return ~0;
00219 }
00220 
00221 static int cgi_setenv_ctype(cgi_env_t *env, request_t *rq)
00222 {
00223     const char *ct;
00224 
00225     if((ct = request_get_field_value(rq, "Content-type")) != NULL)
00226         dbg_err_if(cgi_setenv(env, "CONTENT_TYPE", ct));
00227 
00228     return 0;
00229 err:
00230     return ~0;
00231 }
00232 
00233 static int cgi_setenv_clen(cgi_env_t *env, request_t *rq)
00234 {
00235     char buf[32];
00236     ssize_t len;
00237 
00238     if((len = request_get_content_length(rq)) > 0)
00239     {
00240         dbg_err_if(u_snprintf(buf, sizeof(buf), "%ld", (long int) len));
00241         dbg_err_if(cgi_setenv(env, "CONTENT_LENGTH", buf));
00242     }
00243 
00244     return 0;
00245 err:
00246     return ~0;
00247 }
00248 
00249 static int cgi_set_blocking(int fd)
00250 {
00251     int flags;
00252 
00253     warn_err_sif((flags = fcntl(fd, F_GETFL)) < 0);
00254 
00255     nop_err_if(fcntl(fd, F_SETFL, flags & (~O_NONBLOCK)) < 0);
00256 
00257     return 0;
00258 err:
00259     return ~0;
00260 }
00261 
00262 static int cgi_makeenv(request_t *rq, response_t *rs, cgi_env_t *env)
00263 {
00264     const char *addr, *cstr;
00265     header_t *h;
00266     field_t *field;
00267     char *p, buf[1024];
00268     unsigned int i;
00269 
00270     u_unused_args(rs);
00271 
00272     dbg_err_if(cgi_setenv(env, "SERVER_SOFTWARE", "klone/" KLONE_VERSION));
00273     dbg_err_if(cgi_setenv(env, "SERVER_PROTOCOL", "HTTP/1.0"));
00274     dbg_err_if(cgi_setenv(env, "GATEWAY_INTERFACE", "CGI/1.1"));
00275     dbg_err_if(cgi_setenv(env, "REDIRECT_STATUS", "200"));
00276 
00277     /* klone server address */
00278     if ((addr = request_get_addr(rq)) != NULL)
00279     {
00280         dbg_err_if(cgi_setenv_addr(env, addr, "SERVER_ADDR", "SERVER_PORT"));
00281 
00282         if ((u_addr_get_ip(addr, buf, sizeof(buf))) != NULL)
00283             dbg_err_if(cgi_setenv(env, "SERVER_NAME", buf));
00284     }
00285 
00286     /* client address */
00287     if ((addr = request_get_peer_addr(rq)) != NULL)
00288         dbg_err_if(cgi_setenv_addr(env, addr, "REMOTE_ADDR", "REMOTE_PORT"));
00289 
00290     /* method */
00291     switch(request_get_method(rq))
00292     {
00293     case HM_GET:    cstr = "GET"; break;
00294     case HM_HEAD:   cstr = "HEAD"; break;
00295     case HM_POST:   cstr = "POST"; break;
00296     default:        cstr = "UNKNOWN"; break;
00297     }
00298     dbg_err_if(cgi_setenv(env, "REQUEST_METHOD", cstr));
00299 
00300     if(io_is_secure(request_io(rq)))
00301         dbg_err_if(cgi_setenv(env, "HTTPS", "on"));
00302 
00303     if((cstr = request_get_path_info(rq)) != NULL)
00304         dbg_err_if(cgi_setenv(env, "PATH_INFO", cstr));
00305 
00306     if((cstr = request_get_resolved_path_info(rq)) != NULL)
00307         dbg_err_if(cgi_setenv(env, "PATH_TRANSLATED", cstr));
00308 
00309     if((cstr = request_get_query_string(rq)) != NULL)
00310         dbg_err_if(cgi_setenv(env, "QUERY_STRING", cstr));
00311 
00312     /* content length */
00313     dbg_err_if(cgi_setenv_clen(env, rq));
00314 
00315     /* content type*/
00316     dbg_err_if(cgi_setenv_ctype(env, rq));
00317 
00318     if((cstr = request_get_filename(rq)) != NULL)
00319         dbg_err_if(cgi_setenv(env, "SCRIPT_NAME", cstr));
00320 
00321     if((cstr = request_get_uri(rq)) != NULL)
00322         dbg_err_if(cgi_setenv(env, "REQUEST_URI", cstr));
00323 
00324     if((cstr = request_get_resolved_filename(rq)) != NULL)
00325         dbg_err_if(cgi_setenv(env, "SCRIPT_FILENAME", cstr));
00326 
00327     if((cstr = getenv("SYSTEMROOT")) != NULL)
00328         dbg_err_if(cgi_setenv(env, "SYSTEMROOT", cstr));
00329 
00330     dbg_err_if((h = request_get_header(rq)) == NULL);
00331 
00332     /* export all client request headers prefixing them with HTTP_ */
00333     for(i = 0; i < header_field_count(h); ++i)
00334     {
00335         field = header_get_fieldn(h, i);
00336         dbg_err_if(field == NULL);
00337 
00338         dbg_err_if(u_snprintf(buf, sizeof(buf), "HTTP_%s", 
00339                     field_get_name(field)));
00340 
00341         /* convert the field name to uppercase and '-' to '_' */
00342         for(p = buf; *p && *p != ':'; ++p)
00343         {
00344             if(*p == '-')
00345                 *p = '_';
00346             else
00347                 *p = toupper(*p);
00348         }
00349 
00350         if(field_get_value(field))
00351             dbg_err_if(cgi_setenv(env, buf, field_get_value(field)));
00352         else
00353             dbg_err_if(cgi_setenv(env, buf, ""));
00354     }
00355 
00356     return 0;
00357 err:
00358     return ~0;
00359 }
00360 
00361 #define close_pipe(fd)                      \
00362     do {                                    \
00363         if(fd[0] != -1) close(fd[0]);       \
00364         if(fd[1] != -1) close(fd[1]);       \
00365     } while(0); 
00366 
00367 static int cgi_exec(request_t *rq, response_t *rs, pid_t *pchild, 
00368         int *pcgi_stdin, int *pcgi_stdout)
00369 {
00370     enum { RD_END /* read end point */, WR_END /* write end point */};
00371     int cgi_stdin[2] = { -1, -1 };
00372     int cgi_stdout[2] = { -1, -1 };
00373     cgi_env_t cgi_env = { NULL, 0, 0 };
00374     http_t *h;
00375     const char *argv[] = { NULL, NULL, NULL };
00376     const char *cgi_file, *handler;
00377     char *p, *cgi_path = NULL;
00378     pid_t child;
00379     int fd;
00380 
00381     dbg_err_if((h = request_get_http(rq)) == NULL);
00382 
00383     /* create a pair of parent<->child IPC channels */
00384     dbg_err_if(pipe(cgi_stdin) < 0);
00385     dbg_err_if(pipe(cgi_stdout) < 0);
00386 
00387     crit_err_if((child = fork()) < 0);
00388 
00389     if(child == 0)
00390     {   /* child */
00391 
00392         /* close one end of both channels */
00393         close(cgi_stdin[WR_END]);
00394         close(cgi_stdout[RD_END]);
00395 
00396         /* setup cgi stdout to point to the write end of the cgi_stdout pipe */
00397         close(STDOUT_FILENO);
00398         crit_err_if(dup2(cgi_stdout[WR_END], STDOUT_FILENO) < 0);
00399         close(cgi_stdout[WR_END]);
00400 
00401         /* setup cgi stdin to point to the read end of the cgi_stdin pipe */
00402         close(STDIN_FILENO);
00403         crit_err_if(dup2(cgi_stdin[RD_END], STDIN_FILENO) < 0);
00404         close(cgi_stdin[RD_END]);
00405 
00406         /* ignore cgi stderr */
00407         fd = open("/dev/null", O_WRONLY);
00408         dbg_err_if(fd < 0);
00409         crit_err_if(dup2(fd, STDERR_FILENO) < 0);
00410         close(fd);
00411 
00412         /* all standard descriptor must be blocking */
00413         cgi_set_blocking(STDOUT_FILENO);
00414         cgi_set_blocking(STDIN_FILENO);
00415         cgi_set_blocking(STDERR_FILENO);
00416 
00417         /* close any other open fd */
00418         for(fd = 3; fd < 255; ++fd) 
00419             close(fd);
00420 
00421         /* extract path name from cgi_file */
00422         dbg_err_if((cgi_file = request_get_resolved_filename(rq)) == NULL);
00423 
00424         cgi_path = u_strdup(cgi_file);
00425         dbg_err_if(cgi_path == NULL);
00426 
00427         /* cut out filename part */
00428         dbg_err_if((p = strrchr(cgi_path, '/')) == NULL);
00429         ++p; *p = 0;
00430 
00431         crit_err_sifm(chdir(cgi_path) < 0, "unable to chdir to %s", cgi_path);
00432 
00433         U_FREE(cgi_path);
00434 
00435         /* make the CGI environment vars array */
00436         crit_err_sif(cgi_makeenv(rq, rs, &cgi_env));
00437 
00438         /* the handler may be the path of the handler or "exec" that means that
00439          * the script must be run as is */
00440         if(!cgi_ext(h, rq, cgi_file, &handler) || !strcasecmp(handler, "exec"))
00441         {
00442             /* setup cgi argv (ISINDEX command line handling is not impl) */
00443             argv[0] = cgi_file;
00444 
00445         } else {
00446             /* run the handler of this file extension */
00447             argv[0] = handler;
00448             argv[1] = cgi_file;
00449         }
00450 
00451         /* run the cgi (never returns) */
00452         crit_err_sif(execve(argv[0], argv, cgi_env.env));
00453 
00454         /* never reached */
00455 
00456     } else if(child > 0) {
00457         /* parent */
00458 
00459         /* close one end of both channels */
00460         close(cgi_stdin[RD_END]);
00461         close(cgi_stdout[WR_END]);
00462 
00463         /* return cgi read/write descriptors to the parent */
00464         *pcgi_stdin = cgi_stdin[WR_END];
00465         *pcgi_stdout = cgi_stdout[RD_END];
00466         *pchild = child;
00467 
00468         return 0;
00469 
00470     } else {
00471         warn_err("fork error");
00472     }
00473 
00474 err:
00475     if(child == 0)
00476         _exit(1); /* children exit here on error */
00477     close_pipe(cgi_stdin);
00478     close_pipe(cgi_stdout);
00479     return ~0;
00480 }
00481 
00482 static int cgi_serve(request_t *rq, response_t *rs)
00483 {
00484     codec_t *filter = NULL;
00485     header_t *head = NULL;
00486     field_t *field = NULL;
00487     const char *fqn, *filename;
00488     char buf[4096];
00489     io_t *out = NULL, *cgi_in = NULL, *cgi_out = NULL;
00490     ssize_t n, tot = 0, clen;
00491     int cgi_stdin = -1, cgi_stdout = -1, status;
00492     pid_t child;
00493 
00494     dbg_err_if (rq == NULL);
00495     dbg_err_if (rs == NULL);
00496 
00497     /* shortcuts */
00498     dbg_err_if((out = response_io(rs)) == NULL);
00499     dbg_err_if((head = response_get_header(rs)) == NULL);
00500 
00501     /* if something goes wrong return a "bad request" */
00502     response_set_status(rs, HTTP_STATUS_BAD_REQUEST); 
00503 
00504     /* script file name */
00505     fqn = request_get_resolved_filename(rq);
00506 
00507     /* run the CGI and return its stdin and stdout descriptor */
00508     crit_err_if(cgi_exec(rq, rs, &child, &cgi_stdin, &cgi_stdout));
00509 
00510     /* by default disable caching */
00511     response_disable_caching(rs);
00512 
00513     /* copy any POST input data to the CGI */
00514     if(request_get_method(rq) == HM_POST && 
00515             (clen = request_get_content_length(rq)) > 0)
00516     {
00517         /* build an io_t object to read cgi output */
00518         crit_err_sif(io_fd_create(cgi_stdin, O_WRONLY, &cgi_out));
00519 
00520         /* FIXME 
00521            if the cgi does not read from stdin (and POSTed data is big) 
00522            we could be block here waiting for the buffer to drain 
00523          */
00524         
00525         /* send POSTed data to the cgi (the script may not read it so we don't 
00526          * complain on broken pipe error) */
00527         crit_if(io_copy(cgi_out, request_io(rq), clen) < 0);
00528 
00529         io_free(cgi_out); cgi_out = NULL;
00530         close(cgi_stdin); cgi_stdin = -1;
00531     }
00532 
00533     /* build an io_t object to read cgi output */
00534     crit_err_sif(io_fd_create(cgi_stdout, O_RDONLY, &cgi_in));
00535 
00536     /* extract filename part of the fqn */
00537     crit_err_if((filename = strrchr(fqn, '/')) == NULL);
00538     filename++;
00539 
00540     /* header of cgis whose name start with nhp- must not be parsed */
00541     if(strncmp(filename, "nph-", 4))
00542     {
00543         /* create a response filter (used to automatically print the header) */
00544         dbg_err_if(response_filter_create(rq, rs, NULL, &filter));
00545         io_codec_add_tail(out, filter);
00546         filter = NULL; /* io_t owns it */
00547 
00548         /* merge cgi header with response headers */
00549         crit_err_if(header_load_ex(head, cgi_in, HLM_OVERRIDE));
00550 
00551         /* set the response code */
00552         if((field = header_get_field(head, "Status")) != NULL && 
00553                 field_get_value(field))
00554         {
00555             response_set_status(rs, atoi(field_get_value(field)));
00556         } else {
00557             if(header_get_field(head, "Location"))
00558                 response_set_status(rs, HTTP_STATUS_MOVED_TEMPORARILY);
00559             else
00560                 response_set_status(rs, HTTP_STATUS_OK); 
00561         }
00562     } else
00563         response_set_status(rs, HTTP_STATUS_OK); 
00564 
00565     /* write cgi output to the client */
00566     while((n = io_read(cgi_in, buf, sizeof(buf))) > 0)
00567     {
00568         if(io_write(out, buf, n) < 0)
00569             break;
00570         tot += n;
00571     }
00572 
00573     /* if nothing has been printed by the script; write a dummy byte so 
00574      * the io_t calls the filter function that, in turn, will print out the 
00575      * HTTP header (rsfilter will handle it) */
00576     if(tot == 0)
00577         io_write(out, "\n", 1);
00578 
00579     if(cgi_in)
00580         io_free(cgi_in); 
00581     if(cgi_out)
00582         io_free(cgi_out); 
00583 
00584     close(cgi_stdin);
00585     close(cgi_stdout);
00586 
00587     /* wait for the child to finish (FIXME add a max timeout) */
00588     waitpid(child, &status, 0);
00589     if(WIFEXITED(status) && WEXITSTATUS(status))
00590         warn("cgi exited with [%d]", WEXITSTATUS(status));
00591 
00592     return 0;
00593 err:
00594     if(cgi_out)
00595         io_free(cgi_out);
00596     if(cgi_in)
00597         io_free(cgi_in);
00598     if(cgi_stdin != -1)
00599         close(cgi_stdin);
00600     if(cgi_stdout != -1)
00601         close(cgi_stdout);
00602     return ~0;
00603 }
00604 
00605 static int cgi_init(void)
00606 {
00607     return 0;
00608 }
00609 
00610 static void cgi_term(void)
00611 {
00612     return;
00613 }
00614 
00615 supplier_t sup_cgi = {
00616     "cgi supplier",
00617     cgi_init,
00618     cgi_term,
00619     cgi_is_valid_uri,
00620     cgi_serve
00621 };
00622 

←Products
Copyright © 2005-2012 - KoanLogic S.r.l. - All rights reserved