Main Page | Modules | Data Structures | Directories | File List | Data Fields | Globals

sup_cgi.c

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