#include <errno.h>
#include <ctype.h>
#include <signal.h>
+#include <sys/poll.h>
+#include <sys/wait.h>
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <utils.h>
#include <log.h>
+#include <req.h>
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) {
+ if(errno != EPIPE)
+ 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;
+ int (*execfun)(const char *, char *const []);
pipe(inp);
pipe(outp);
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_ADDRESS"))
+ putenv(sprintf2("SERVER_ADDR=%s", getenv("REQ_X_ASH_SERVER_ADDRESS")));
+ 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_PORT"))
+ putenv(sprintf2("REMOTE_PORT=%s", getenv("REQ_X_ASH_PORT")));
+ 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"))
* This is (understandably) missing from the CGI
* specification, but PHP seems to require it.
*/
- putenv(sprintf2("SCRIPT_FILENAME=%s", file));
- if(inpath)
- execlp(prog, prog, file, NULL);
- else
- execl(prog, prog, file, NULL);
+ execfun = inpath?execvp:execv;
+ if(file != NULL)
+ putenv(sprintf2("SCRIPT_FILENAME=%s", absolutify(file)));
+ execfun(prog[0], prog);
exit(127);
}
close(inp[0]);
close(outp[1]);
*infd = inp[1];
*outfd = outp[0];
+ return(pid);
}
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;
return(NULL);
}
+static char *defstatus(int code)
+{
+ if(code == 200)
+ return("OK");
+ else if(code == 201)
+ return("Created");
+ else if(code == 202)
+ return("Accepted");
+ else if(code == 204)
+ return("No Content");
+ else if(code == 300)
+ return("Multiple Choices");
+ else if(code == 301)
+ return("Moved Permanently");
+ else if(code == 302)
+ return("Found");
+ else if(code == 303)
+ return("See Other");
+ else if(code == 304)
+ return("Not Modified");
+ else if(code == 307)
+ return("Moved Temporarily");
+ else if(code == 400)
+ return("Bad Request");
+ else if(code == 401)
+ return("Unauthorized");
+ else if(code == 403)
+ return("Forbidden");
+ else if(code == 404)
+ return("Not Found");
+ else if(code == 500)
+ return("Internal Server Error");
+ else if(code == 501)
+ return("Not Implemented");
+ else if(code == 503)
+ return("Service Unavailable");
+ else
+ return("Unknown status");
+}
+
static void sendstatus(char **headers, FILE *out)
{
char **hp;
}
}
if(status) {
- fprintf(out, "HTTP/1.1 %s\r\n", status);
+ if(strchr(status, ' '))
+ fprintf(out, "HTTP/1.1 %s\n", status);
+ else
+ 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");
}
}
{
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] [-P PROGRAM ARGS... ;] METHOD URL REST");
}
int main(int argc, char **argv, char **envp)
{
int c;
- char *file, *prog;
- int inpath;
+ char *file, *sp;
+ struct charvbuf prog;
+ int inpath, addfile, cd;
int infd, outfd;
FILE *in, *out;
char **headers;
+ pid_t child;
+ int estat;
environ = envp;
signal(SIGPIPE, SIG_IGN);
- prog = NULL;
+ bufinit(prog);
inpath = 0;
- while((c = getopt(argc, argv, "p:")) >= 0) {
+ addfile = 1;
+ cd = 0;
+ while((c = getopt(argc, argv, "cp:P:")) >= 0) {
switch(c) {
+ case 'c':
+ cd = 1;
+ break;
case 'p':
- prog = optarg;
+ bufadd(prog, optarg);
+ inpath = 1;
+ break;
+ case 'P':
+ prog.d = 0;
+ bufadd(prog, optarg);
+ while(1) {
+ if(optind >= argc) {
+ flog(LOG_ERR, "callcgi: unterminated argument list for -P");
+ exit(1);
+ }
+ if(!strcmp(argv[optind], ";")) {
+ optind++;
+ break;
+ }
+ bufadd(prog, argv[optind++]);
+ }
+ if(prog.d == 0) {
+ flog(LOG_ERR, "callcgi: -P option needs at least a program name");
+ exit(1);
+ }
inpath = 1;
+ addfile = 0;
break;
default:
usage();
usage();
exit(1);
}
- if((file = getenv("REQ_X_ASH_FILE")) == NULL) {
+ if(((file = getenv("REQ_X_ASH_FILE")) == NULL) && (prog.d == 0)) {
flog(LOG_ERR, "callcgi: needs to be called with the X-Ash-File header");
exit(1);
}
- if(prog == NULL)
- prog = file;
- forkchild(inpath, prog, file, argv[optind], argv[optind + 1], argv[optind + 2], &infd, &outfd);
+
+ 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.d == 0)
+ bufadd(prog, file);
+ if(addfile && (file != NULL))
+ bufadd(prog, file);
+ bufadd(prog, NULL);
+ child = forkchild(inpath, prog.b, 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);
+ fclose(out);
+ if(waitpid(child, &estat, 0) == child) {
+ if(WCOREDUMP(estat))
+ flog(LOG_WARNING, "CGI handler `%s' dumped core", prog.b[0]);
+ if(WIFEXITED(estat) && !WEXITSTATUS(estat))
+ return(0);
+ else
+ return(1);
+ }
+ flog(LOG_WARNING, "could not wait for CGI handler: %s", strerror(errno));
+ return(1);
}