You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

258 lines
6.0 KiB
C

/*
* 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;
}