Main Page | Modules | File List | 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.10 2008/04/25 18:59:08 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, 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         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     /* klone server address */
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     /* client address */
00312     if((addr = request_get_peer_addr(rq)) != NULL) 
00313         dbg_err_if(cgi_setenv_addr(env, addr, "REMOTE_ADDR", "REMOTE_PORT"));
00314 
00315     /* method */
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     /* content length */
00339     dbg_err_if(cgi_setenv_clen(env, rq));
00340 
00341     /* content type*/
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     /* export all client request headers prefixing them with HTTP_ */
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         /* convert the field name to uppercase and '-' to '_' */
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 /* read end point */, WR_END /* write end point */};
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     /* create a pair of parent<->child IPC channels */
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     {   /* child */
00417 
00418         /* close one end of both channels */
00419         close(cgi_stdin[WR_END]);
00420         close(cgi_stdout[RD_END]);
00421 
00422         /* setup cgi stdout to point to the write end of the cgi_stdout pipe */
00423         close(STDOUT_FILENO);
00424         crit_err_if(dup2(cgi_stdout[WR_END], STDOUT_FILENO) < 0);
00425         close(cgi_stdout[WR_END]);
00426 
00427         /* setup cgi stdin to point to the read end of the cgi_stdin pipe */
00428         close(STDIN_FILENO);
00429         crit_err_if(dup2(cgi_stdin[RD_END], STDIN_FILENO) < 0);
00430         close(cgi_stdin[RD_END]);
00431 
00432         /* ignore cgi stderr */
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         /* all standard descriptor must be blocking */
00439         cgi_set_blocking(STDOUT_FILENO);
00440         cgi_set_blocking(STDIN_FILENO);
00441         cgi_set_blocking(STDERR_FILENO);
00442 
00443         /* close any other open fd */
00444         for(fd = 3; fd < 255; ++fd) 
00445             close(fd);
00446 
00447         /* extract path name from cgi_file */
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         /* cut out filename part */
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         /* make the CGI environment vars array */
00462         crit_err_sif(cgi_makeenv(rq, rs, &cgi_env));
00463 
00464         /* the handler may be the path of the handler or "exec" that means that
00465          * the script must be run as is */
00466         if(!cgi_ext(h, rq, cgi_file, &handler) || !strcasecmp(handler, "exec"))
00467         {
00468             /* setup cgi argv (ISINDEX command line handling is not impl) */
00469             argv[0] = cgi_file;
00470 
00471         } else {
00472             /* run the handler of this file extension */
00473             argv[0] = handler;
00474             argv[1] = cgi_file;
00475         }
00476 
00477         /* run the cgi (never returns) */
00478         crit_err_sif(execve(argv[0], argv, cgi_env.env));
00479 
00480         /* never reached */
00481 
00482     } else if(child > 0) {
00483         /* parent */
00484 
00485         /* close one end of both channels */
00486         close(cgi_stdin[RD_END]);
00487         close(cgi_stdout[WR_END]);
00488 
00489         /* return cgi read/write descriptors to the parent */
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); /* children exit here on error */
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     /* shortcuts */
00524     dbg_err_if((out = response_io(rs)) == NULL);
00525     dbg_err_if((head = response_get_header(rs)) == NULL);
00526 
00527     /* if something goes wrong return a "bad request" */
00528     response_set_status(rs, HTTP_STATUS_BAD_REQUEST); 
00529 
00530     /* script file name */
00531     fqn = request_get_resolved_filename(rq);
00532 
00533     /* run the CGI and return its stdin and stdout descriptor */
00534     crit_err_if(cgi_exec(rq, rs, &child, &cgi_stdin, &cgi_stdout));
00535 
00536     /* by default disable caching */
00537     response_disable_caching(rs);
00538 
00539     /* copy any POST input data to the CGI */
00540     if(request_get_method(rq) == HM_POST && 
00541             (clen = request_get_content_length(rq)) > 0)
00542     {
00543         /* build an io_t object to read cgi output */
00544         crit_err_sif(io_fd_create(cgi_stdin, O_WRONLY, &cgi_out));
00545 
00546         /* FIXME 
00547            if the cgi does not read from stdin (and POSTed data is big) 
00548            we could be block here waiting for the buffer to drain 
00549          */
00550         
00551         /* send POSTed data to the cgi (the script may not read it so we don't 
00552          * complain on broken pipe error) */
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     /* build an io_t object to read cgi output */
00560     crit_err_sif(io_fd_create(cgi_stdout, O_RDONLY, &cgi_in));
00561 
00562     /* extract filename part of the fqn */
00563     crit_err_if((filename = strrchr(fqn, '/')) == NULL);
00564     filename++;
00565 
00566     /* header of cgis whose name start with nhp- must not be parsed */
00567     if(strncmp(filename, "nph-", 4))
00568     {
00569         /* create a response filter (used to automatically print the header) */
00570         dbg_err_if(response_filter_create(rq, rs, NULL, &filter));
00571         io_codec_add_tail(out, filter);
00572         filter = NULL; /* io_t owns it */
00573 
00574         /* merge cgi header with response headers */
00575         crit_err_if(header_load_ex(head, cgi_in, HLM_OVERRIDE));
00576 
00577         /* set the response code */
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     /* write cgi output to the client */
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     /* if nothing has been printed by the script; write a dummy byte so 
00600      * the io_t calls the filter function that, in turn, will print out the 
00601      * HTTP header (rsfilter will handle it) */
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     /* wait for the child to finish (FIXME add a max timeout) */
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