aboutsummaryrefslogtreecommitdiffstats
path: root/cgi.c
diff options
context:
space:
mode:
Diffstat (limited to 'cgi.c')
-rw-r--r--cgi.c257
1 files changed, 257 insertions, 0 deletions
diff --git a/cgi.c b/cgi.c
new file mode 100644
index 0000000..809117a
--- /dev/null
+++ b/cgi.c
@@ -0,0 +1,257 @@
+/*
+ * started writing (limited) CGI support for webfsd
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "httpd.h"
+
+/* ---------------------------------------------------------------------- */
+
+extern char **environ;
+
+static char *env_wlist[] = {
+ "PATH", "HOME",
+ NULL
+};
+
+static void env_add(struct strlist **list, char *name, char *value)
+{
+ char *line;
+
+ line = malloc(strlen(name) + strlen(value) + 2);
+ sprintf(line,"%s=%s",name,value);
+ if (debug)
+ fprintf(stderr,"cgi: env %s\n",line);
+ list_add(list,line,1);
+}
+
+static char**
+env_convert(struct strlist *list)
+{
+ struct strlist *elem;
+ char **env;
+ int i;
+
+ for (i = 2, elem = list; NULL != elem; elem = elem->next)
+ i++;
+ env = malloc(sizeof(char*)*i);
+ for (i = 0, elem = list; NULL != elem; elem = elem->next)
+ env[i++] = elem->line;
+ env[i++] = NULL;
+ return env;
+}
+
+static void env_copy(struct strlist **list)
+{
+ int i,j,l;
+
+ for (i = 0; environ[i] != NULL; i++) {
+ for (j = 0; env_wlist[j] != NULL; j++) {
+ l = strlen(env_wlist[j]);
+ if (0 == strncmp(environ[i],env_wlist[j],l) &&
+ environ[i][l] == '=') {
+ env_add(list,env_wlist[j],environ[i]+l+1);
+ break;
+ }
+ }
+ }
+}
+
+/* ---------------------------------------------------------------------- */
+
+void
+cgi_request(struct REQUEST *req)
+{
+ struct sockaddr_storage addr;
+ struct strlist *env = NULL, *item;
+ char host[65],serv[9];
+ char filename[1024], *h, *argv[2], envname[128];
+ int pid,p[2],i,length;
+
+ if (debug)
+ fprintf(stderr,"%03d: is cgi request\n",req->fd);
+ if (-1 == pipe(p)) {
+ mkerror(req,500,0);
+ return;
+ }
+ pid = fork();
+ switch (pid) {
+ case -1:
+ /* error */
+ if (debug)
+ perror("fork");
+ mkerror(req,500,0);
+ return;
+ case 0:
+ break;
+ default:
+ /* parent - webfsd */
+ close(p[1]);
+ req->cgipid = pid;
+ req->cgipipe = p[0];
+ req->state = STATE_CGI_HEADER;
+ close_on_exec(req->cgipipe);
+ fcntl(req->cgipipe,F_SETFL,O_NONBLOCK);
+ return;
+ }
+
+ /* -------- below is the child process (cgi) code -------- */
+
+ /* lookup local socket (before it gets closed) */
+ length = sizeof(addr);
+ getsockname(req->fd,(struct sockaddr*)&addr,&length);
+ getnameinfo((struct sockaddr*)&addr,length,host,64,serv,8,
+ NI_NUMERICHOST | NI_NUMERICSERV);
+
+ /* setup file descriptors */
+ dup2(p[1],1); /* pipe -> stdout */
+ if (have_tty) {
+ int devnull = open("/dev/null",O_RDWR);
+ dup2(devnull,0); /* stdin */
+ dup2(devnull,2); /* stderr */
+ close(devnull);
+ } else {
+ /* nothing -- already attached to /dev/null */
+ }
+ close_on_exec(p[0]);
+ close_on_exec(p[1]);
+
+ /* setup environment */
+ env_copy(&env);
+
+ env_add(&env,"DOCUMENT_ROOT",doc_root);
+ env_add(&env,"GATEWAY_INTERFACE","CGI/1.1");
+ env_add(&env,"QUERY_STRING",req->query);
+ env_add(&env,"REQUEST_URI",req->uri);
+ env_add(&env,"REMOTE_ADDR",req->peerhost);
+ env_add(&env,"REMOTE_PORT",req->peerserv);
+ env_add(&env,"REQUEST_METHOD",req->type);
+ env_add(&env,"SERVER_ADMIN","root@localhost");
+ env_add(&env,"SERVER_NAME",server_host);
+ env_add(&env,"SERVER_PROTOCOL","HTTP/1.1");
+ env_add(&env,"SERVER_SOFTWARE",server_name);
+ env_add(&env,"SERVER_ADDR",host);
+ env_add(&env,"SERVER_PORT",serv);
+
+ for (item = req->header; NULL != item; item = item->next) {
+ strcpy(envname,"HTTP_");
+ if (1 != sscanf(item->line,"%120[-A-Za-z]: %n",envname+5,&length))
+ continue;
+ for (i = 0; envname[i]; i++) {
+ if (isalpha(envname[i]))
+ envname[i] = toupper(envname[i]);
+ if ('-' == envname[i])
+ envname[i] = '_';
+ }
+ env_add(&env,envname,item->line+length);
+ }
+
+ h = req->path + strlen(cgipath);
+ h = strchr(h,'/');
+ if (h) {
+ env_add(&env,"PATH_INFO",h);
+ *h = 0;
+ } else {
+ env_add(&env,"PATH_INFO","");
+ }
+ env_add(&env,"SCRIPT_NAME",req->path);
+ snprintf(filename,sizeof(filename)-1,"%s%s",doc_root,req->path);
+ env_add(&env,"SCRIPT_FILENAME",filename);
+
+ /* start cgi app */
+ argv[0] = filename;
+ argv[1] = NULL;
+ execve(filename,argv,env_convert(env));
+
+ /* exec failed ... */
+ printf("Content-Type: text/plain\n"
+ "\n"
+ "execve %s: %s\n",
+ filename,strerror(errno));
+ exit(1);
+}
+
+/* ---------------------------------------------------------------------- */
+
+void
+cgi_read_header(struct REQUEST *req)
+{
+ struct strlist *list = NULL;
+ char *h,*next,*status = NULL;
+ int rc;
+
+ restart:
+ rc = read(req->cgipipe, req->cgibuf+req->cgilen, MAX_HEADER-req->cgilen);
+ switch (rc) {
+ case -1:
+ if (errno == EAGAIN)
+ return;
+ if (errno == EINTR)
+ goto restart;
+ /* fall through */
+ case 0:
+ mkerror(req,500,0);
+ return;
+ default:
+ req->cgilen += rc;
+ req->cgibuf[req->cgilen] = 0;
+ }
+
+ /* header complete ?? */
+ if (NULL != (h = strstr(req->cgibuf,"\r\n\r\n")) ||
+ NULL != (h = strstr(req->cgibuf,"\n\n"))) {
+
+ /* parse cgi header */
+ for (h = req->cgibuf;; h = next) {
+ next = strstr(h,"\n");
+ next[0] = 0;
+ if (next[-1] == '\r')
+ next[-1] = 0;
+ next++;
+
+ if (0 == strlen(h))
+ break;
+ if (debug)
+ fprintf(stderr,"%03d: cgi: hdr %s\n",req->fd,h);
+ if (0 == strncasecmp(h,"Status: ",8)) {
+ status = h+8;
+ if (debug)
+ fprintf(stderr,"%03d: cgi: status %s\n",req->fd,status);
+ continue;
+ }
+ if (0 == strncasecmp(h,"Server:",7) ||
+ 0 == strncasecmp(h,"Connection:",11) ||
+ 0 == strncasecmp(h,"Accept-Ranges:",14) ||
+ 0 == strncasecmp(h,"Date:",5))
+ /* webfsd adds them -- filter out */
+ continue;
+ list_add(&list,h,0);
+ }
+ mkcgi(req, status ? status : "200 OK", list);
+ list_free(&list);
+ req->cgipos = next - req->cgibuf;
+ if (debug)
+ fprintf(stderr,"%03d: cgi: pos=%d len=%d\n",req->fd,
+ req->cgipos, req->cgilen);
+ return;
+ }
+
+ if (req->cgilen == MAX_HEADER) {
+ mkerror(req,400,0);
+ return;
+ }
+ return;
+}