KLone APIs | Modules | Data Structures | File List | Data Fields | Globals

sup_cgi.c

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