X-Git-Url: http://www.dolda2000.com/gitweb/?p=ashd.git;a=blobdiff_plain;f=src%2Fcallcgi.c;h=3a28880b41ad03b99a4000dcf78e2ecdda9ed1f2;hp=dcd681948acccc7641d0dc83fa6e66079990028b;hb=18838a2eddc3ae7fdc8f3fefb8cd83c012328a5e;hpb=f812ea037b5c693977e83ace9ed7ac2515a4a6d0 diff --git a/src/callcgi.c b/src/callcgi.c index dcd6819..3a28880 100644 --- a/src/callcgi.c +++ b/src/callcgi.c @@ -23,41 +23,73 @@ #include #include #include +#include +#include #ifdef HAVE_CONFIG_H #include #endif #include #include +#include static char **environ; -static void passdata(FILE *in, FILE *out) +static int passdata(FILE *in, FILE *out) { int ret; - char *buf; + char buf[65536]; + struct pollfd pfds[2]; - buf = smalloc(65536); while(!feof(in)) { - ret = fread(buf, 1, 65536, in); - if(ferror(in)) { - flog(LOG_ERR, "sendfile: could not read input: %s", strerror(errno)); - break; + memset(pfds, 0, sizeof(struct pollfd) * 2); + pfds[0].fd = fileno(in); + pfds[0].events = POLLIN; + pfds[1].fd = fileno(out); + pfds[1].events = POLLHUP; + ret = poll(pfds, 2, -1); + if(ret < 0) { + if(errno != EINTR) { + flog(LOG_ERR, "callcgi: error in poll: %s", strerror(errno)); + return(1); + } } - if(fwrite(buf, 1, ret, out) != ret) { - flog(LOG_ERR, "sendfile: could not write output: %s", strerror(errno)); - break; + if(ret > 0) { + if(pfds[0].revents & (POLLIN | POLLERR | POLLHUP)) { + ret = fread(buf, 1, 65536, in); + if(ferror(in)) { + flog(LOG_ERR, "callcgi: could not read input: %s", strerror(errno)); + return(1); + } + if(fwrite(buf, 1, ret, out) != ret) { + flog(LOG_ERR, "callcgi: could not write output: %s", strerror(errno)); + return(1); + } + } + if(pfds[1].revents & POLLHUP) + return(1); } } - free(buf); + return(0); +} + +static char *absolutify(char *file) +{ + char cwd[1024]; + + if(*file != '/') { + getcwd(cwd, sizeof(cwd)); + return(sprintf2("%s/%s", cwd, file)); + } + return(sstrdup(file)); } -static void forkchild(int inpath, char *prog, char *file, char *method, char *url, char *rest, int *infd, int *outfd) +static pid_t forkchild(int inpath, char *prog, char *file, char *method, char *url, char *rest, int *infd, int *outfd) { - int i; - char *qp, **env; + char *qp, **env, *name; int inp[2], outp[2]; pid_t pid; + char *pi; pipe(inp); pipe(outp); @@ -66,31 +98,61 @@ static void forkchild(int inpath, char *prog, char *file, char *method, char *ur exit(1); } if(pid == 0) { - close(inp[1]); - close(outp[0]); dup2(inp[0], 0); dup2(outp[1], 1); - for(i = 3; i < FD_SETSIZE; i++) - close(i); + close(inp[0]); + close(inp[1]); + close(outp[0]); + close(outp[1]); if((qp = strchr(url, '?')) != NULL) *(qp++) = 0; - /* - * XXX: Currently missing: - * SERVER_NAME (Partially) - * SERVER_PORT - */ putenv(sprintf2("SERVER_SOFTWARE=ashd/%s", VERSION)); putenv("GATEWAY_INTERFACE=CGI/1.1"); if(getenv("HTTP_VERSION")) - putenv(sprintf2("SERVER_PROTOCOL=HTTP/%s", getenv("HTTP_VERSION"))); + putenv(sprintf2("SERVER_PROTOCOL=%s", getenv("HTTP_VERSION"))); putenv(sprintf2("REQUEST_METHOD=%s", method)); - putenv(sprintf2("PATH_INFO=%s", rest)); - putenv(sprintf2("SCRIPT_NAME=%s", url)); + name = url; + /* XXX: This is an ugly hack (I think), but though I can think + * of several alternatives, none seem to be better. */ + if(*rest && (strlen(url) >= strlen(rest)) && + !strcmp(rest, url + strlen(url) - strlen(rest))) { + name = sprintf2("%.*s", (int)(strlen(url) - strlen(rest)), url); + } + if((pi = unquoteurl(rest)) == NULL) + pi = rest; + if(!strcmp(name, "/")) { + /* + * Normal CGI behavior appears to be to always let + * PATH_INFO begin with a slash and never let SCRIPT_NAME + * end with one. That conflicts, however, with some + * behaviors, such as "mounting" CGI applications on a + * directory element of the URI space -- a handler + * responding to "/foo/" would not be able to tell that it + * is not called "/foo", which makes a large difference, + * not least in relation to URI reconstruction and + * redirections. A common practical case is CGI-handled + * index files in directories. Therefore, this only + * handles the nonconditional case of the root directory + * and leaves other decisions to the previous handler + * handing over the request to callcgi. It is unclear if + * there is a better way to handle the problem. + */ + name[0] = 0; + pi = sprintf2("/%s", pi); + } + putenv(sprintf2("PATH_INFO=%s", pi)); + putenv(sprintf2("SCRIPT_NAME=%s", name)); putenv(sprintf2("QUERY_STRING=%s", qp?qp:"")); if(getenv("REQ_HOST")) putenv(sprintf2("SERVER_NAME=%s", getenv("REQ_HOST"))); + if(getenv("REQ_X_ASH_SERVER_PORT")) + putenv(sprintf2("SERVER_PORT=%s", getenv("REQ_X_ASH_SERVER_PORT"))); + if(getenv("REQ_X_ASH_PROTOCOL") && !strcmp(getenv("REQ_X_ASH_PROTOCOL"), "https")) + putenv("HTTPS=on"); if(getenv("REQ_X_ASH_ADDRESS")) putenv(sprintf2("REMOTE_ADDR=%s", getenv("REQ_X_ASH_ADDRESS"))); + if(getenv("REQ_X_ASH_REMOTE_USER")) + putenv(sprintf2("REMOTE_USER=%s", getenv("REQ_X_ASH_REMOTE_USER"))); if(getenv("REQ_CONTENT_TYPE")) putenv(sprintf2("CONTENT_TYPE=%s", getenv("REQ_CONTENT_TYPE"))); if(getenv("REQ_CONTENT_LENGTH")) @@ -103,7 +165,7 @@ static void forkchild(int inpath, char *prog, char *file, char *method, char *ur * This is (understandably) missing from the CGI * specification, but PHP seems to require it. */ - putenv(sprintf2("SCRIPT_FILENAME=%s", file)); + putenv(sprintf2("SCRIPT_FILENAME=%s", absolutify(file))); if(inpath) execlp(prog, prog, file, NULL); else @@ -114,6 +176,7 @@ static void forkchild(int inpath, char *prog, char *file, char *method, char *ur close(outp[1]); *infd = inp[1]; *outfd = outp[0]; + return(pid); } static void trim(struct charbuf *buf) @@ -125,7 +188,7 @@ static void trim(struct charbuf *buf) for(p = buf->b + buf->d - 1; (p > buf->b) && isspace(*p); p--, buf->d--); } -static char **parseheaders(FILE *s) +static char **parsecgiheaders(FILE *s) { int c, state; struct charvbuf hbuf; @@ -247,13 +310,13 @@ static void sendstatus(char **headers, FILE *out) } if(status) { if(strchr(status, ' ')) - fprintf(out, "HTTP/1.1 %s\r\n", status); + fprintf(out, "HTTP/1.1 %s\n", status); else - fprintf(out, "HTTP/1.1 %i %s\r\n", atoi(status), defstatus(atoi(status))); + fprintf(out, "HTTP/1.1 %i %s\n", atoi(status), defstatus(atoi(status))); } else if(location) { - fprintf(out, "HTTP/1.1 303 See Other\r\n"); + fprintf(out, "HTTP/1.1 303 See Other\n"); } else { - fprintf(out, "HTTP/1.1 200 OK\r\n"); + fprintf(out, "HTTP/1.1 200 OK\n"); } } @@ -261,32 +324,38 @@ static void sendheaders(char **headers, FILE *out) { while(*headers) { if(**headers) - fprintf(out, "%s: %s\r\n", headers[0], headers[1]); + fprintf(out, "%s: %s\n", headers[0], headers[1]); headers += 2; } } static void usage(void) { - flog(LOG_ERR, "usage: callcgi [-p PROGRAM] METHOD URL REST"); + flog(LOG_ERR, "usage: callcgi [-c] [-p PROGRAM] METHOD URL REST"); } int main(int argc, char **argv, char **envp) { int c; - char *file, *prog; - int inpath; + char *file, *prog, *sp; + int inpath, cd; int infd, outfd; FILE *in, *out; char **headers; + pid_t child; + int estat; environ = envp; signal(SIGPIPE, SIG_IGN); prog = NULL; inpath = 0; - while((c = getopt(argc, argv, "p:")) >= 0) { + cd = 0; + while((c = getopt(argc, argv, "cp:")) >= 0) { switch(c) { + case 'c': + cd = 1; + break; case 'p': prog = optarg; inpath = 1; @@ -305,20 +374,45 @@ int main(int argc, char **argv, char **envp) flog(LOG_ERR, "callcgi: needs to be called with the X-Ash-File header"); exit(1); } + + if(cd) { + /* This behavior is encouraged by the CGI specification (RFC 3875, 7.2), + * but not strictly required, and I get the feeling it might break some + * relative paths here or there, so it's not the default for now. */ + if((sp = strrchr(file, '/')) != NULL) { + *sp = 0; + if(chdir(file)) { + *sp = '/'; + } else { + file = sp + 1; + } + } + } + if(prog == NULL) prog = file; - forkchild(inpath, prog, file, argv[optind], argv[optind + 1], argv[optind + 2], &infd, &outfd); + child = forkchild(inpath, prog, file, argv[optind], argv[optind + 1], argv[optind + 2], &infd, &outfd); in = fdopen(infd, "w"); - passdata(stdin, in); + passdata(stdin, in); /* Ignore errors, perhaps? */ fclose(in); out = fdopen(outfd, "r"); - if((headers = parseheaders(out)) == NULL) { + if((headers = parsecgiheaders(out)) == NULL) { flog(LOG_WARNING, "CGI handler returned invalid headers"); exit(1); } sendstatus(headers, stdout); sendheaders(headers, stdout); - printf("\r\n"); - passdata(out, stdout); - return(0); + printf("\n"); + if(passdata(out, stdout)) + kill(child, SIGINT); + if(waitpid(child, &estat, 0) == child) { + if(WCOREDUMP(estat)) + flog(LOG_WARNING, "CGI handler `%s' dumped core", prog); + if(WIFEXITED(estat) && !WEXITSTATUS(estat)) + return(0); + else + return(1); + } + flog(LOG_WARNING, "could not wait for CGI handler: %s", strerror(errno)); + return(1); }