/* Simple httpd for Amiga */ #include "httpd.h" #include /* Default server root if current directory or -d command-line option * doesn't contain conf/httpd.conf. */ #define DEF_SERVROOT "AmiTCP:httpd/" /* The same thing for DocumentRoot (except it doesn't have a command-line * option. */ #define DEF_DOCROOT "AmiTCP:httpd/htdocs/" /* And for DirectoryIndex. */ #define DEF_DIRINDEX "index.html" /* What port to use by default (usually 80). */ #define SERVER_PORT 80 /* Define this to cause httpd to just return "Service unavailable" messages */ //#define SERVICE_UNAVAILABLE char *docroot, *servroot, *userdir, *dirindex; char hostname[256]; Alias *aliases, *script_aliases; DocType *mime_types; mFILE *global_log, *global_errlog; /* For keeping track of subprocesses. We never spawn more than MAXPROCS * processes; each slot in proclist[] is either a TCB address (indicating * an active subprocess) or NULL (indicating an empty slot). socklist[] * holds the socket number passed to each subprocess. */ #define MAXPROCS 32 struct Task *proclist[MAXPROCS]; int socklist[MAXPROCS]; /* Translate an HTTP path into a local filename. Return by side effect * whether the file is a CGI program, or whether a "redirect" to a new * URL (returned in place of the path) should be output. */ // Stack: <256 char *translate_path(char *path, int *is_cgi, int *is_redirect) { char *newpath, *s, c; Alias *a; struct stat st; newpath = salloc(strlen(path)+1); strcpy(newpath, path); path = newpath; *is_cgi = *is_redirect = 0; while ((s = strchr(path, '%')) && s-path < strlen(path)-2) { c = s[1]; if (c > '9') c -= 7; *s = (c-'0') << 4; c = s[2]; if (c > '9') c -= 7; *s |= (c-'0'); strcpy(s+1, s+3); } for (a = script_aliases; a; a = a->next) { if (!strncmp(a->http_path, path, strlen(a->http_path))) { *is_cgi = 1; break; } } if (!a) { for (a = aliases; a; a = a->next) { if (!strncmp(a->http_path, path, strlen(a->http_path))) break; } } if (a) { newpath = salloc(strlen(a->real_path) + strlen(path) - strlen(a->http_path) + 1); sprintf(newpath, "%s%s", a->real_path, path + strlen(a->http_path)); } else { newpath = salloc(strlen(docroot) + strlen(path)); sprintf(newpath, "%s%s", docroot, path+1); } if (path[strlen(path)-1] == '/') { newpath = srealloc(newpath, strlen(newpath) + strlen(dirindex) + 1); strcat(newpath, dirindex); } else if (!strcmp(path+strlen(path)-4, ".cgi")) { *is_cgi = 1; } if (stat(newpath, &st) >= 0 && S_ISDIR(st.st_mode)) { *is_redirect = 0; newpath = srealloc(newpath, strlen(path) + strlen(hostname) + 9); sprintf(newpath, "http://%s%s/", hostname, path); *is_redirect = 1; } free(path); /* free our copy */ return newpath; } /* Returns 1 if 'type' represents a binary file type, 0 otherwise. */ // Stack: 0 static int is_binary(char *type) { char *s; int binary; type = strdup(type); if (!type) { outerr("Out of memory"); exit(40); } if (!(s = strchr(type, '/'))) { outerr("is_binary(): unknown type '%s'", type); free(type); return 1; } *s = 0; if (!strcmp(type, "text")) binary = 0; else binary = 1; free(type); return binary; } /* Returns size of file sent. Also returns status code by side effect. */ // Stack: 4k + <256 static int send_file(char *file, int sock, int *status_ret, struct tm *if_mod_since, int header_only) { int is_cgi, is_redirect, not_modified = 0; char *newfile; struct stat st; time_t t; struct tm *tm; char buf[4096]; char buf1[64], buf2[64]; DocType *dt; char *s, *type = NULL, *query; size_t size = 0; mFILE *f; if (query = strchr(file, '?')) *query++ = 0; newfile = translate_path(file, &is_cgi, &is_redirect); if (is_redirect) { sockprintf(sock, "\ HTTP/1.0 301 Moved\n\ Content-Type: text/html\n\ Location: %s\n\ \n\ 301 Moved\n\

301 Moved

\n\ This document has moved here.\n", newfile, newfile); if (*status_ret) *status_ret = 301; return 0; } if (stat(newfile, &st) < 0) { sockprintf(sock, "\ HTTP/1.0 404 Not Found\n\ Content-Type: text/html\n\ \n\ 404 Not Found\n\

404 Not Found

\n\ The document %s was not found on this server.\n", file); if (status_ret) *status_ret = 404; return 0; } /*** CGI handling ***/ if (is_cgi) { mFILE *pipe; if (query) { sprintf(buf, "set QUERY_STRING \"%s\"\n%s", query, newfile); pipe = Popen(buf, "r"); } else { pipe = Popen(newfile, "r"); } if (!pipe) { sockprintf(sock, "\ HTTP/1.0 500 Server Error\n\ Content-Type: text/html\n\ \n\ 500 Server Error\n\

500 Server Error

\n\ The server encountered an internal error and was unable to complete your\n\ request.

\n\ Error: couldn't open pipe"); outerr("couldn't open pipe for %s", newfile); if (status_ret) *status_ret = 500; return 0; } while ((s = Fgets2(buf, sizeof(buf), pipe)) && *buf) { char *cmd = strtok(buf, " \t"), *s; if (!stricmp(cmd, "Content-Type:")) { if (!(s = strtok(NULL, " \t"))) goto cgi_header_err; type = strdup(s); } else { cgi_header_err: Pclose(pipe); sockprintf(sock, "\ HTTP/1.0 500 Server Error\n\ Content-Type: text/html\n\ \n\ 500 Server Error\n\

500 Server Error

\n\ The server encountered an internal error and was unable to complete your\n\ request.

\n\ Error: malformed header from script"); outerr("malformed header from CGI-bin %s", newfile); if (status_ret) *status_ret = 500; return 0; } } if (!s || !type) { Pclose(pipe); sockprintf(sock, "\ HTTP/1.0 500 Server Error\n\ Content-Type: text/html\n\ \n\ 500 Server Error\n\

500 Server Error

\n\ The server encountered an internal error and was unable to complete your\n\ request.

\n\ Error: malformed header from script"); outerr("malformed header from CGI-bin %s", newfile); if (status_ret) *status_ret = 500; return 0; } t = time(NULL); tm = gmtime(&t); strftime(buf2, sizeof(buf2), "%a, %d %b %Y %H:%M:%S GMT", tm); sockprintf(sock, "\ HTTP/1.0 200 OK\n\ Date: %s\n\ Content-Type: %s\n\n", buf2, type); if (status_ret) *status_ret = 200; if (!header_only) { if (is_binary(type)) { long len; while (len = Fread(buf, 1, sizeof(buf), pipe)) { send(sock, buf, len, 0); size += len; } } else { while (Fgets2(buf, sizeof(buf), pipe)) { sockprintf(sock, "%s\r\n", buf); size += strlen(buf)+1; } } } Pclose(pipe); return size; } /*** end of CGI handling ***/ tm = gmtime(&st.st_mtime); if (if_mod_since && timecmp(tm, if_mod_since) <= 0) not_modified = 1; strftime(buf1, sizeof(buf1), "%a, %d %b %Y %H:%M:%S GMT", tm); t = time(NULL); tm = gmtime(&t); if (if_mod_since && timecmp(tm, if_mod_since) < 0) not_modified = 0; /* time after "now" -> invalid */ strftime(buf2, sizeof(buf2), "%a, %d %b %Y %H:%M:%S GMT", tm); s = strrchr(newfile, '.'); if (s) ++s; else s = ""; type = "text/plain"; for (dt = mime_types; dt; dt = dt->next) { char **ext = dt->ext; int i = dt->numext; while (i--) { if (!strcmp(s, *ext)) break; ++ext; } if (i >= 0) { type = dt->type; break; } } if (not_modified) { sockprintf(sock, "\ HTTP/1.0 304 Not Modified\n\ Last-Modified: %s\n\ Date: %s\n\ Content-Type: %s\n\ Content-Length: %d\n\n", buf1, buf2, type, st.st_size); if (status_ret) *status_ret = 304; } else { sockprintf(sock, "\ HTTP/1.0 200 OK\n\ Last-Modified: %s\n\ Date: %s\n\ Content-Type: %s\n\ Content-Length: %d\n\n", buf1, buf2, type, st.st_size); if (status_ret) *status_ret = 200; } if (header_only || not_modified) return 0; if (!(f = Fopen(newfile, "r"))) { sockprintf(sock, "\ 404 Not Found\n\

404 Not Found

\n\ The document \"%s\" was not found on this server.\n", file); if (status_ret) *status_ret = 404; return 0; } if (is_binary(type)) { long len; while (len = Fread(buf, 1, sizeof(buf), f)) send(sock, buf, len, 0); } else { while (Fgets2(buf, sizeof(buf), f)) sockprintf(sock, "%s\r\n", buf); } size = Ftell(f); Fclose(f); return size; } /**************/ struct procdata { long sock_id; struct sockaddr_in *sin; long protocol; int mainsock; }; void request_entry(struct procdata *pd) { int sock, procnum; void do_request(int, struct sockaddr_in *); taskData *td = malloc(sizeof(*td)); struct Task *tcb = FindTask(NULL); if (!td) { Fputs("out of memory", global_errlog); return; } tcb->tc_UserData = (void *)td; errlog = Fdup(global_errlog); if (!errlog) { Fputs("can't duplicate error log handle\n", global_errlog); return; } logfile = Fdup(global_log); if (!logfile) { Fclose(errlog); Fputs("can't duplicate access log handle\n", global_errlog); return; } if (!(OpenSocketLibrary())) { outerr("can't open bsdsocket.library"); Fclose(logfile); Fclose(errlog); return; } if ((sock=ObtainSocket(pd->sock_id,AF_INET,SOCK_STREAM,pd->protocol)) < 0) { CloseSocketLibrary(); Fclose(logfile); Fclose(errlog); return; } for (procnum = 0; procnum < MAXPROCS && proclist[procnum] != NULL; ++procnum); if (procnum >= MAXPROCS) { sockprintf(sock, "\ HTTP/1.0 503 Service Temporarily Unavailable\n\ Content-type: text/html\n\ \n\ 503 Service Temporarily Unavailable\n\

503 Service Temporarily Unavailable

\n\ This service is temporarily overloaded; please try again later.\n"); shutdown(sock, 2); close(sock); CloseSocketLibrary(); Fclose(logfile); Fclose(errlog); return; } proclist[procnum] = FindTask(NULL); socklist[procnum] = pd->mainsock; if (!setjmp(td->exit_ptr)) do_request(sock, pd->sin); shutdown(sock, 2); close(sock); free(pd); CloseSocketLibrary(); Fclose(logfile); Fclose(errlog); free(td); proclist[procnum] = NULL; } void request_exit(int rc) { struct Task *me = FindTask(NULL); longjmp(((taskData *)me->tc_UserData)->exit_ptr, 1); } /* Stack: 4.5k */ void do_request(int sock, struct sockaddr_in *sin) { char inbuf[256], inbuf0[256]; char *cmd, *path; time_t t; struct tm *if_mod_since = NULL; char *str; int status; size_t size = -1; u_long remote_ip; struct hostent *he; char buf[4096]; if (!sgets2(inbuf, sizeof(inbuf), sock)) return; strcpy(inbuf0, inbuf); cmd = strtok(inbuf, " \t"); path = strtok(NULL, " \t"); if (cmd && !strcmp(cmd, "XSTAT")) { int nprocs = 0, i; sockprintf(sock, "\ HTTP/1.0 200 OK\n\ Content-Type: text/plain\n\n"); for (i = 0; i < MAXPROCS; ++i) if (proclist[i] != NULL) ++nprocs; sockprintf(sock, "Processes (active/max): %d/%d\n", nprocs, MAXPROCS); return; } if (!cmd || !path) { sockprintf(sock, "\ HTTP/1.0 400 Syntax Error\n\ Content-Type: text/html\n\ \n\ 400 Syntax Error\n\

400 Bad Request

\n\ No %s was specified in the request.\n", cmd ? "path" : "command"); status = 400; return; } while (sgets2(inbuf, sizeof(inbuf), sock) && *inbuf) { str = strchr(inbuf, ':'); if (!str) continue; *str++ = 0; while (isspace(*str)) ++str; if (!strcmp(inbuf, "If-Modified-Since")) if_mod_since = parse_date(str); } if (!strcmp(cmd, "GET")) { size = send_file(path, sock, &status, if_mod_since, 0); } else if (!strcmp(cmd, "HEAD")) { size = send_file(path, sock, &status, NULL, 1); } else { sockprintf(sock, "\ HTTP/1.0 400 Syntax Error\n\ Content-Type: text/html\n\ \n\ 400 Syntax Error\n\

400 Bad Request

\n\ The command \"%s\" is not recognized as a valid command.\n", cmd); status = 400; } if (if_mod_since) free(if_mod_since); time(&t); str = ctime(&t); str[strlen(str)-1] = 0; if (he = gethostbyaddr((caddr_t)&sin->sin_addr.s_addr, sizeof(u_long), AF_INET)) { Fputs(he->h_name, logfile); } else { remote_ip = ntohl(sin->sin_addr.s_addr); sprintf(buf, "%d.%d.%d.%d", (remote_ip >> 24) & 0xFF, (remote_ip >> 16) & 0xFF, (remote_ip >> 8) & 0xFF, remote_ip & 0xFF); Fputs(buf, logfile); } if (status != 200) sprintf(buf, " - - [%s] \"%s\" %d -\n", str, inbuf0, status); else sprintf(buf, " - - [%s] \"%s\" %d %d\n", str, inbuf0, status, size); Fputs(buf, logfile); Fflush(logfile); } /**************/ static int got_int = 0; static struct Task *main_task; static void brk(int sig_unused) { if (FindTask(NULL) == main_task) got_int = 1; else exit(20); signal(SIGINT, brk); } int main(int ac, char **av) { static int port = SERVER_PORT; option optlist[] = { { "d", OPT_STRING, &servroot }, { "p", OPT_INT, &port }, { NULL } }; int sock, listen_sock, proc; struct protoent *pe; struct sockaddr_in sin; long size; struct procdata *pd; void *oldWindowPtr; int retcode; extern long fork_stacksize; int on = 1; main_task = FindTask(NULL); if (!(main_task->tc_UserData = malloc(sizeof(taskData)))) { fprintf(stderr, "No memory!"); exit(40); } ((taskData *)main_task->tc_UserData)->socketBase = SocketBase; for (proc = 0; proc < MAXPROCS; ++proc) socklist[proc] = -1; fork_stacksize = 24576; ac = do_options(ac, av, optlist); if (servroot) chdir(servroot); else servroot = DEF_SERVROOT; docroot = DEF_DOCROOT; dirindex = DEF_DIRINDEX; if (!readconf()) { chdir(DEF_SERVROOT); if (!readconf()) { fprintf(stderr, "Couldn't find configuration file conf/httpd.conf"); return 20; } } chdir(servroot); #ifndef SERVICE_UNAVAILABLE if (!(global_errlog = Fopen("logs/error_log", "a"))) { fprintf(stderr, "couldn't open error log: %s", strerror(errno)); return 20; } ((taskData *)main_task->tc_UserData)->error_log = global_errlog; if (!(global_log = Fopen("logs/access_log", "a"))) { outerr("couldn't open access log: %s", strerror(errno)); Fclose(global_errlog); return 20; } #endif if (gethostname(hostname, sizeof(hostname)) != 0) { outerr("couldn't determine host name: %s", strerror(errno)); Fclose(global_log); Fclose(global_errlog); return 20; } pe = getprotobyname("tcp"); if (!pe) { outerr("getprotobyname() failed: %s", strerror(errno)); Fclose(global_log); Fclose(global_errlog); return 20; } bzero(&sin, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; sin.sin_port = htons(port); listen_sock = socket(AF_INET, SOCK_STREAM, pe->p_proto); if (listen_sock < 0) { outerr("unable to create socket: %s", strerror(errno)); Fclose(global_log); Fclose(global_errlog); return 20; } if (bind(listen_sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) { outerr("unable to bind socket: %s", strerror(errno)); close(listen_sock); Fclose(global_log); Fclose(global_errlog); return 20; } if (listen(listen_sock, 16) < 0) { outerr("unable to make socket listen: %s", strerror(errno)); shutdown(listen_sock, 2); close(listen_sock); Fclose(global_log); Fclose(global_errlog); return 20; } oldWindowPtr = ((struct Process *)main_task)->pr_WindowPtr; ((struct Process *)main_task)->pr_WindowPtr = NULL; signal(SIGINT, brk); while (!got_int) { size = sizeof(sin); if ((sock = accept(listen_sock, &sin, &size)) < 0) { chkabort(); if (got_int) { outerr("shutting down"); retcode = 0; break; } outerr("error accepting connection: %s", strerror(errno)); retcode = 20; break; } #ifdef SERVICE_UNAVAILABLE sockprintf(sock, "\ HTTP/1.0 503 Service Temporarily Unavailable\n\ Content-type: text/html\n\ \n\ 503 Service Temporarily Unavailable\n\

503 Service Temporarily Unavailable

\n\ This service is temporarily unavailable.\n\ \n\ --Andy Church\n"); shutdown(sock, 2); close(sock); continue; #endif pd = malloc(sizeof(*pd)); /* will be freed by subprocess */ if (!pd) { outerr("out of memory"); retcode = 40; break; } pd->mainsock = sock; pd->protocol = pe->p_proto; pd->sin = &sin; pd->sock_id = ReleaseSocket(sock, UNIQUE_ID); if (pd->sock_id == -1) { free(pd); outerr("unable to transfer socket %d", sock); } else { forkproc(request_entry, pd); } for (proc = 0; proc < MAXPROCS; ++proc) { if (proclist[proc] == NULL && socklist[proc] >= 0) { close(socklist[proc]); socklist[proc] = -1; } } /* Check for signals: * ^C to tell httpd to quit * ^D to send ^C to all currently running subprocesses */ chkabort(); if (got_int) { outerr("shutting down"); retcode = 0; } if (SetSignal(0,0) & SIGBREAKF_CTRL_D) { SetSignal(0, SIGBREAKF_CTRL_D); Forbid(); for (proc = 0; proc < MAXPROCS; ++proc) { if (proclist[proc]) Signal(proclist[proc], SIGBREAKF_CTRL_C); } Permit(); } } /* Check for leftover processes and tell them to quit */ Forbid(); for (proc = 0; proc < MAXPROCS; ++proc) { if (proclist[proc]) Signal(proclist[proc], SIGBREAKF_CTRL_C); } Permit(); shutdown(listen_sock, 2); close(listen_sock); Fclose(global_log); Fclose(global_errlog); /* Wait for processes to quit. A ^C here will force httpd to unload - * if any subprocesses are actually still running, this will probably * crash the system! */ got_int = 0; do { for (proc = 0; proc < MAXPROCS && proclist[proc] == NULL; ++proc); chkabort(); if (got_int) break; } while (proc < MAXPROCS); free(main_task->tc_UserData); ((struct Process *)main_task)->pr_WindowPtr = oldWindowPtr; return retcode; }