Made callcgi more Apache-compliant.
[ashd.git] / src / callcgi.c
index 1713a4f..10bb87c 100644 (file)
@@ -52,10 +52,21 @@ static void passdata(FILE *in, FILE *out)
     free(buf);
 }
 
-static void forkchild(char *prog, char *file, char *method, char *url, char *rest, int *infd, int *outfd)
+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)
 {
     int i;
-    char *qp, **env;
+    char *qp, **env, *name;
     int inp[2], outp[2];
     pid_t pid;
 
@@ -74,21 +85,28 @@ static void forkchild(char *prog, char *file, char *method, char *url, char *res
            close(i);
        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));
+       putenv(sprintf2("PATH_INFO=/%s", rest));
+       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)) &&
+          (url[strlen(url) - strlen(rest) - 1] == '/')) {
+           name = sprintf2("%.*s", (int)(strlen(url) - strlen(rest) - 1), url);
+       }
+       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_CONTENT_TYPE"))
@@ -103,8 +121,11 @@ static void forkchild(char *prog, char *file, char *method, char *url, char *res
         * This is (understandably) missing from the CGI
         * specification, but PHP seems to require it.
         */
-       putenv(sprintf2("SCRIPT_FILENAME=%s", file));
-       execlp(prog, prog, file, NULL);
+       putenv(sprintf2("SCRIPT_FILENAME=%s", absolutify(file)));
+       if(inpath)
+           execlp(prog, prog, file, NULL);
+       else
+           execl(prog, prog, file, NULL);
        exit(127);
     }
     close(inp[0]);
@@ -183,6 +204,46 @@ fail:
     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;
@@ -197,16 +258,20 @@ static void sendstatus(char **headers, FILE *out)
            **hp = 0;
        } else if(!strcasecmp(hp[0], "location")) {
            location = hp[1];
+           hp += 2;
        } else {
            hp += 2;
        }
     }
     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");
     }
 }
 
@@ -214,40 +279,64 @@ 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");
+}
+
 int main(int argc, char **argv, char **envp)
 {
-    char *file;
-    int in, out;
-    FILE *ins, *outs;
+    int c;
+    char *file, *prog;
+    int inpath;
+    int infd, outfd;
+    FILE *in, *out;
     char **headers;
     
     environ = envp;
     signal(SIGPIPE, SIG_IGN);
-    if(argc < 5) {
-       flog(LOG_ERR, "usage: callcgi PROGRAM METHOD URL REST");
+    
+    prog = NULL;
+    inpath = 0;
+    while((c = getopt(argc, argv, "p:")) >= 0) {
+       switch(c) {
+       case 'p':
+           prog = optarg;
+           inpath = 1;
+           break;
+       default:
+           usage();
+           exit(1);
+       }
+    }
+    
+    if(argc - optind < 3) {
+       usage();
        exit(1);
     }
     if((file = getenv("REQ_X_ASH_FILE")) == NULL) {
        flog(LOG_ERR, "callcgi: needs to be called with the X-Ash-File header");
        exit(1);
     }
-    forkchild(argv[1], file, argv[2], argv[3], argv[4], &in, &out);
-    ins = fdopen(in, "w");
-    passdata(stdin, ins);
-    fclose(ins);
-    outs = fdopen(out, "r");
-    if((headers = parseheaders(outs)) == NULL) {
+    if(prog == NULL)
+       prog = file;
+    forkchild(inpath, prog, file, argv[optind], argv[optind + 1], argv[optind + 2], &infd, &outfd);
+    in = fdopen(infd, "w");
+    passdata(stdin, in);
+    fclose(in);
+    out = fdopen(outfd, "r");
+    if((headers = parseheaders(out)) == NULL) {
        flog(LOG_WARNING, "CGI handler returned invalid headers");
        exit(1);
     }
     sendstatus(headers, stdout);
     sendheaders(headers, stdout);
-    printf("\r\n");
-    passdata(outs, stdout);
+    printf("\n");
+    passdata(out, stdout);
     return(0);
 }