From 44741b5aa7dec45f5a55515334a0dc37426ac861 Mon Sep 17 00:00:00 2001 From: abial Date: Wed, 5 May 1999 07:22:34 +0000 Subject: [PATCH] Simple_HTTPd on steroids - major rework of the original program, by William Lloyd. New features include: * many additional command line options * "fetch" mode * less bugs :-) * better README. Submitted by: William Lloyd Reviewed by: abial --- .../picobsd/tinyware/simple_httpd/Makefile | 5 +- release/picobsd/tinyware/simple_httpd/README | 166 +++++- .../tinyware/simple_httpd/simple_httpd.c | 512 +++++++++++------- 3 files changed, 468 insertions(+), 215 deletions(-) diff --git a/release/picobsd/tinyware/simple_httpd/Makefile b/release/picobsd/tinyware/simple_httpd/Makefile index 1c36b52bc797..626f1c18ca49 100644 --- a/release/picobsd/tinyware/simple_httpd/Makefile +++ b/release/picobsd/tinyware/simple_httpd/Makefile @@ -1,10 +1,7 @@ -# $Id: Makefile,v 1.2.2.1 1999/02/05 12:21:41 abial Exp $ +# $Id: Makefile,v 1.3 1999/02/12 20:36:13 abial Exp $ # PROG=simple_httpd SRCS= simple_httpd.c -CFLAGS+=-g NOMAN=yes .include - - diff --git a/release/picobsd/tinyware/simple_httpd/README b/release/picobsd/tinyware/simple_httpd/README index a73d2bf99f89..eafeb26d637a 100644 --- a/release/picobsd/tinyware/simple_httpd/README +++ b/release/picobsd/tinyware/simple_httpd/README @@ -1,23 +1,167 @@ -This is a small HTTP server. This version is under BSD license. -It's very simple, yet sufficient for serving basic Web contents, -including ability to run CGI scripts. +Simple_httpd - A small and free Web server + +"Simple_httpd is like /usr/bin/mail is to mail clients, no frills." + +This HTTP server can be used in any FreeBSD/PicoBSD application. + +It has been tested under FreeBSD 2.2.x, 3.x and 4.x. It might work +on other OS systems, but it's for FreeBSD primarily. + +The main advantage to Simple_httpd is that it is very small. +The 25K binary can satisfy most needs in a small or embedded +appplication. If you want a full featured server see +/usr/ports/www/apache* or http://www.apache.org + +Simple_httpd is released under a BSD style copyright that unlike +GPL is embedded developer friendly. + +The server is designed to be run in one of two modes. The standard +mode is a httpd server running in the background serving up a directory +of html,gif,cgi whatever. Your traditional www server. + +The "fetch" mode supports file transfer over httpd. This +is best thought of as mate for fetch(1). This feature can be +usefull to transfer a file from one host to another. + +Simple_httpd has the ability to run CGI scripts. All CGI +scripts must be located in ${DOCUMENT_ROOT}/cgi-bin. The +server currently only sets 3 enviroment variables before calling +the script. + +CGI Enviroment variables are below: + +SERVER_SOFTWARE = FreeBSD/PicoBSD +REMOTE_HOST = client.canada_lower_taxes.com +REMOTE_ADDR = 200.122.13.108 + +In most target applications for this server the extra DNS traffic from +the remote_addr lookup will likely be on the local lan anyway and not +on the other side of the internet. You can turn it off yourself in +the code if you want to speed the whole process up. Be sure to turn +it off for the logfile also. How to use it? ============== -Compile, and run: +Compile with make, run as follows - simple_httpd +usage: simple_httpd [-vD] + [-d directory] + [-g grpid] + [-l logfile] + [-p port] +or +usage: simple_httpd [-p port] -f filename -If you're root, the document directory will be /httphome. If you're -not, then first of all you need to use the option (>1023). -Then, the document root will be ${HOME}/httphome. +-v +Run the server verbose. Show the program options that will be used for this +process. Will only show information during startup, no messages will +be displayed while serving requests. In other words you can still +daemonize without fear of output on stdout. -Log messages will be written out to /var/log/jhttpd.log . +-D +Do not daemonize. The server will not run in the background. It will +stay attached to the tty. This is usefull for debugging. In this +mode no log file is created. Logging info is to stdout. + +This option is automatically selected if fetch option is selected. + +-d directory +The html document directory, if nothing is provided the default is +/httphome if UID is root, otherwise document root is ${HOME}/public_html + +-l logfile +Set the logfile to use. Log messages will be written to /var/log/jhttpd.log +if you are root and ${HOME}/jhttpd.log otherwise. If you don't want a +log file try "-l /dev/null" + +-p port +Set the port httpd server will listen to. Default is port 80 if +you are root and 1080 if you are not. + +-f filename +This is the only option needed to use the "fetch" feature. The file +specified will be the ONLY file served to ANY GET request from a browser +or fetch(1). + +Example +======= + +Standard Mode: +-------------- +If you have the FreeBSD handbook installed on your machine and would +like to serve it up over http for a quick look you could do this + +simple_httpd -d /usr/share/doc/handbook -l /usr/tmp/jlog.txt -p 1088 -v + +Any browser would be able to look at the handbook with +http://whatever_host/handbook.html:1088 + +I'm using 1088 as the port since I already have apache running on port 80 +and port 1080 on my host. + +Please note, the handbook is not installed by default in FreeBSD 3.x +It must be installed from the ports collection first if you want to +try this. + +Another simple example is to browse your local ports collection: + +cd /usr/ports +make readmes #wait about 1 hour! +simple_httpd -p 1080 -v -d /usr/ports + +Then point your browser at http://whatever_host/README.html + +Fetch Mode: +-------------- +This is designed to be used in conjunction with fetch(3). It allows +for easy transfer of files from one host to another without messy +authentication or pathnames required with ftp. The file to be +served up must be readable by the user running simple_httpd. +This is not a magic way to avoid permissions and read files. + +The daemon will only serve up ONE file. The file specified will +be returned for every GET request regardless of what the browser +asks for. This allows for on the fly naming. + +sender# simple_httpd -f /usr/tmp/big_file.tgz +receiver# fetch http://sender.com/Industrial_Secrets.tgz + +big_file.tgz was transferred from one machine to another and renamed +Industrial_Secrets.tgz at the same time. + +Tunneling over other TCP ports. Choose something that firewall +will probably pass. See /etc/services. + +sender# simple_httpd -p 53 -f /usr/tmp/big_file.tgz +receiver# fetch http://sender.com:53/Industrial_Secrets.tgz + +To Do +===== + +Simple authentication would be very usefull[understatment]. +/etc/passwd or PAM would be nice. + +I think a netmask option would be good. Most internet appliances +probably want to restrict traffic to local ethernet anyway. +ie: Allow anything from my class C. + +The server always has 1 zombie process hanging around when it +runs as a daemon. Should fix so that it doesn't happen. + +Anything to make it faster! + +Man page + +If anyone has any improvements or ways to easily implement something +please let me know. If you make some neat embedded +device with PicoBSD I want to know too! Credits ======= -This program was contributed by Marc Nicholas +This program was originally contributed by Marc Nicholas -$Id: README,v 1.2.2.1 1999/02/05 12:21:41 abial Exp $ +Major rewrite by William Lloyd + +$Id:$ diff --git a/release/picobsd/tinyware/simple_httpd/simple_httpd.c b/release/picobsd/tinyware/simple_httpd/simple_httpd.c index 8e7c55ba3284..ddb593baebfd 100644 --- a/release/picobsd/tinyware/simple_httpd/simple_httpd.c +++ b/release/picobsd/tinyware/simple_httpd/simple_httpd.c @@ -1,9 +1,11 @@ /*- - * SimpleHTTPd v1.0 - a very small, barebones HTTP server + * Simple_HTTPd v1.1 - a very small, barebones HTTP server * * Copyright (c) 1998-1999 Marc Nicholas * All rights reserved. * + * Major rewrite by William Lloyd + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: @@ -25,32 +27,60 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: simple_httpd.c,v 1.2.2.1 1999/02/05 12:21:41 abial Exp $ + * $Id:$ */ -#include -#include -#include #include #include #include -#include #include +#include #include #include -#include + #include -#include +#include #include -#include +#include +#include +#include +#include +#include -int http_sock, con_sock; int http_port = 80; -struct sockaddr_in source; -char homedir[100]; -char *adate(); -struct hostent *hst; +int daemonize = 1; +int verbose = 0; +int http_sock, con_sock; +char fetch_mode[100]; +char homedir[100]; +char logfile[80]; +char *adate(); + +struct hostent *hst; +struct sockaddr_in source; + +/* HTTP basics */ +static char httpd_server_ident[] = "Server: FreeBSD/PicoBSD simple_httpd 1.1\r"; + +static char http_200[] = "HTTP/1.0 200 OK\r"; + +/* Two parts, HTTP Header and then HTML */ +static char *http_404[2] = + {"HTTP/1.0 404 Not found\r\n", +"Error

Error 404

\ +Not found - file doesn't exist or you do not have permission.\n\r\n" +}; + +static char *http_405[2] = + {"HTTP/1.0 405 Method Not allowed\r\nAllow: GET,HEAD\r\n", +"Error

Error 405

\ +This server only supports GET and HEAD requests.\n\r\n" +}; + +/* + * Only called on initial invocation + */ void init_servconnection(void) { @@ -69,10 +99,13 @@ init_servconnection(void) perror("bind socket"); exit(1); } - printf("simpleHTTPd running on %d port\n",http_port); + if (verbose) printf("simple_httpd\n",http_port); } -attenteconnection(void) +/* + * Wait here until we see an incoming http request + */ +wait_connection(void) { int lg; @@ -85,7 +118,10 @@ attenteconnection(void) } } -outdate() +/* + * Print timestamp for HTTP HEAD and GET + */ +http_date() { time_t tl; char buff[50]; @@ -93,110 +129,114 @@ outdate() tl = time(NULL); strftime(buff, 50, "Date: %a, %d %h %Y %H:%M:%S %Z\r\n", gmtime(&tl)); write(con_sock, buff, strlen(buff)); + //return(buff); } -char *rep_err_nget[2] = {"Error

Error 405

\ -This server is supports only GET and HEAD requests\n\r\n", -"HTTP/1.0 405 Method Not allowed\r\nAllow: GET,HEAD\r\nServer: jhttpd\r\n"}; - -char *rep_err_acc[2] = {"Error

Error 404

\ -Not found - file doesn't exist or is read protected\n\r\n", -"HTTP/1.0 404 Not found\r\nServer: jhttpd\r\n"}; - -outerror(char **rep, int http1) /* Выдыча ошибки клиенту в html- виде */ +/* + * Send data to the open socket + */ +http_output(char *html) { - - if (http1) { - write(con_sock, rep[1], strlen(rep[1])); - outdate(); - write(con_sock, "\r\n", 2); - } - write(con_sock, rep[0], strlen(rep[0])); + write(con_sock, html, strlen(html)); + write(con_sock, "\r\n", 2); } -char rep_head[] = "HTTP/1.0 200 OK\r\nServer: simpleHTTPD\r\n"; -traite_req() +/* + * Create and write the log information to file + * Log file format is one line per entry + */ +log_line(char *req) { - char buff[8192]; - int fd, lg, cmd, http1, i; - char *filename, *c; - struct stat statres; - char req[1024]; - char logfile[80]; + char log_buff[256]; char msg[1024]; - char *p, - *par; - long addr; - FILE *log; + char env_host[80], env_addr[80]; + long addr; + FILE *log; + + strcpy(log_buff,inet_ntoa(source.sin_addr)); + sprintf(env_addr, "REMOTE_ADDR=%s",log_buff); + + addr=inet_addr(log_buff); + + strcpy(msg,adate()); + strcat(msg," "); + hst=gethostbyaddr((char*) &addr, 4, AF_INET); + + /* If DNS hostname exists */ + if (hst) { + strcat(msg,hst->h_name); + sprintf(env_host, "REMOTE_HOST=%s",hst->h_name); + } + strcat(msg," ("); + strcat(msg,log_buff); + strcat(msg,") "); + strcat(msg,req); + + if (daemonize) { + log=fopen(logfile,"a"); + fprintf(log,"%s\n",msg); + fclose(log); + } else + printf("%s\n",msg); + + /* This is for CGI scripts */ + putenv(env_addr); + putenv(env_host); +} + +/* + * We have a connection. Identify what type of request GET, HEAD, CGI, etc + * and do what needs to be done + */ +http_request() +{ + int fd, lg, ld, i; + int cmd = 0; + int http1 = 0; + char *p, *par; + char *filename, *c; + struct stat file_status; + char req[1024]; + char msg[1024]; + char buff[8192]; lg = read(con_sock, req, 1024); if (p=strstr(req,"\n")) *p=0; if (p=strstr(req,"\r")) *p=0; - if (geteuid()) - { - strcpy(logfile,getenv("HOME")); - strcat(logfile,"/"); - strcat(logfile,"jhttp.log"); - } - else strcpy(logfile,"/var/log/jhttpd.log"); - - if ( access(logfile,W_OK)) - { - lg=creat (logfile,O_WRONLY); - chmod (logfile,00600); - close(lg); - } - - strcpy(buff,inet_ntoa(source.sin_addr)); - - addr=inet_addr(buff); - - strcpy(msg,adate()); - strcat(msg," "); - hst=gethostbyaddr((char*) &addr, 4, AF_INET); - if (hst) strcat(msg,hst->h_name); - strcat(msg," ("); - strcat(msg,buff); - strcat(msg,") "); - strcat(msg,req); - - log=fopen(logfile,"a"); - fprintf(log,"%s\n",msg); - fclose(log); + log_line(req); c = strtok(req, " "); + + /* Error msg if request is nothing */ if (c == NULL) { - outerror(rep_err_nget, 0); - goto error; + http_output(http_404[0]); + http_output(http_404[1]); + goto end_request; } - cmd = 0; - if (strncmp(c, "GET", 3) == 0) - cmd = 1; - if (strncmp(c, "HEAD", 4) == 0) { - cmd = 2; + + if (strncmp(c, "GET", 3) == 0) cmd = 1; + if (strncmp(c, "HEAD", 4) == 0) cmd = 2; + + /* Do error msg for any other type of request */ + if (cmd == 0) { + http_output(http_405[0]); + http_output(http_405[1]); + goto end_request; } filename = strtok(NULL, " "); - http1 = 0; c = strtok(NULL, " "); - if (c != NULL && strncmp(c, "HTTP", 4) == 0) - http1 = 1; - - if (cmd == 0) { - outerror(rep_err_nget, http1); - goto error; - } - + if (fetch_mode[0] != NULL) strcpy(filename,fetch_mode); if (filename == NULL || strlen(filename)==1) filename="/index.html"; - while (filename[0]== '/') filename++; - - /**/ + while (filename[0]== '/') filename++; + + /* CGI handling. Untested */ if (!strncmp(filename,"cgi-bin/",8)) { par=0; @@ -206,152 +246,224 @@ traite_req() par++; } if (access(filename,X_OK)) goto conti; - stat (filename,&statres); - if (setuid(statres.st_uid)) return(0); - if (seteuid(statres.st_uid)) return(0); + stat (filename,&file_status); + if (setuid(file_status.st_uid)) return(0); + if (seteuid(file_status.st_uid)) return(0); if (!fork()) { close(1); dup(con_sock); - printf("HTTP/1.0 200 OK\nContent-type: text/html\n\n\n"); - execlp (filename,filename,par,0); + //printf("HTTP/1.0 200 OK\nContent-type: text/html\n\n\n"); + printf("HTTP/1.0 200 OK\r\n"); + /* Plug in environment variable, others in log_line */ + putenv("SERVER_SOFTWARE=FreeBSD/PicoBSD"); + + execlp (filename,filename,par,0); } wait(&i); return(0); } conti: if (filename == NULL) { - outerror(rep_err_acc, http1); - goto error; + http_output(http_405[0]); + http_output(http_405[1]); + goto end_request; } - /* interdit les .. dans le path */ + /* End of CGI handling */ + + /* Reject any request with '..' in it, bad hacker */ c = filename; while (*c != '\0') - if (c[0] == '.' && c[1] == '.') { - outerror(rep_err_acc, http1); - goto error; - } else - c++; - + if (c[0] == '.' && c[1] == '.') { + http_output(http_404[0]); + http_output(http_404[1]); + goto end_request; + } else + c++; + + /* Open filename */ fd = open(filename, O_RDONLY); if (fd < 0) { - outerror(rep_err_acc, http1); - goto error; + http_output(http_404[0]); + http_output(http_404[1]); + goto end_request; } - if (fstat(fd, &statres) < 0) { - outerror(rep_err_acc, http1); - goto error; + + /* Get file status information */ + if (fstat(fd, &file_status) < 0) { + http_output(http_404[0]); + http_output(http_404[1]); + goto end_request; + } + + /* Is it a regular file? */ + if (!S_ISREG(file_status.st_mode)) { + http_output(http_404[0]); + http_output(http_404[1]); + goto end_request; } - if (!S_ISREG(statres.st_mode)) - { - outerror(rep_err_acc, http1); - goto error; - } - if (http1) { - char buff[50]; - time_t tl; - write(con_sock, rep_head, strlen(rep_head)); - sprintf(buff, "Content-length: %d\r\n", statres.st_size); - write(con_sock, buff, strlen(buff)); - outdate(); + /* Past this point we are serving either a GET or HEAD */ + /* Print all the header info */ + http_output(http_200); + http_output(httpd_server_ident); + http_date(); - if (strstr(filename,".")) - { - strcpy(buff,"Content-type: "); - strcat(buff,strstr(filename,".")+1); - strcat(buff,"\r\n"); - write(con_sock,buff,strlen(buff)); - } + sprintf(buff, "Content-length: %d\r\n", file_status.st_size); - if (strstr(filename,".txt")) - { - strcpy(buff,"Content-type: text/plain\r\n"); - write(con_sock, buff, strlen(buff)); - } - - if (strstr(filename,".html") || - strstr(filename,".htm")) - { - strcpy(buff,"Content-type: text/html\r\n"); - write(con_sock, buff, strlen(buff)); - } - - if (strstr(filename,".gif")) - { - strcpy(buff,"Content-type: image/gif\r\n"); - write(con_sock, buff, strlen(buff)); - } - - if (strstr(filename,".jpg")) - { - strcpy(buff,"Content-type: image/jpeg\r\n"); - write(con_sock, buff, strlen(buff)); - } - - strftime(buff, 50, "Last-Modified: %a, %d %h %Y %H:%M:%S %Z\r\n\r\n", gmtime(&statres.st_mtime)); - write(con_sock, buff, strlen(buff)); + if (strstr(filename,".txt")) { + strcpy(buff,"Content-type: text/plain\r\n"); + } else if (strstr(filename,".html") || strstr(filename,".htm")) { + strcpy(buff,"Content-type: text/html\r\n"); + } else if (strstr(filename,".gif")) { + strcpy(buff,"Content-type: image/gif\r\n"); + } else if (strstr(filename,".jpg")) { + strcpy(buff,"Content-type: image/jpeg\r\n"); + } else { + /* Take a guess at content if we don't have something already */ + strcpy(buff,"Content-type: "); + strcat(buff,strstr(filename,".")+1); + strcat(buff,"\r\n"); } + write(con_sock, buff, strlen(buff)); + + strftime(buff, 50, "Last-Modified: %a, %d %h %Y %H:%M:%S %Z\r\n\r\n", gmtime(&file_status.st_mtime)); + write(con_sock, buff, strlen(buff)); + + /* Send data only if GET request */ if (cmd == 1) { - while (lg = read(fd, buff, 8192)) - write(con_sock, buff, lg); + while (lg = read(fd, buff, 8192)) + write(con_sock, buff, lg); } -error: +end_request: close(fd); close(con_sock); } - -main(int argc, char **argv) +/* + * Simple httpd server for use in PicoBSD or other embedded application. + * Should satisfy simple httpd needs. For more demanding situations + * apache is probably a better (but much larger) choice. + */ +main(int argc, char *argv[]) { - int lg; - char hello[100]; - - if (argc<2 && geteuid()) - { - printf("Usage: simple_htppd \n"); - exit(1); - } - - if (argc>=2) http_port = atoi(argv[1]); - + extern char *optarg; + extern int optind; + int bflag, ch, fd, ld; + int lg; + int httpd_group = 65534; + pid_t server_pid; + + /* Default for html directory */ strcpy (homedir,getenv("HOME")); if (!geteuid()) strcpy (homedir,"/httphome"); else strcat (homedir,"/httphome"); - strcpy(hello,homedir); - strcat(hello,"/0hello.html"); + /* Defaults for log file */ + if (geteuid()) { + strcpy(logfile,getenv("HOME")); + strcat(logfile,"/"); + strcat(logfile,"jhttp.log"); + } else + strcpy(logfile,"/var/log/jhttpd.log"); + + /* Parse command line arguments */ + while ((ch = getopt(argc, argv, "d:f:g:l:p:vDh")) != -1) + switch (ch) { + case 'd': + strcpy(homedir,optarg); + break; + case 'f': + daemonize = 0; + verbose = 1; + strcpy(fetch_mode,optarg); + break; + case 'g': + httpd_group = atoi(optarg); + break; + case 'l': + strcpy(logfile,optarg); + break; + case 'p': + http_port = atoi(optarg); + break; + case 'v': + verbose = 1; + break; + case 'D': + daemonize = 0; + break; + case '?': + case 'h': + default: + printf("usage: simple_httpd [[-d directory][-g grpid][-l logfile][-p port][-vD]]\n"); + exit(1); + /* NOTREACHED */ + } + + /* Not running as root and no port supplied, assume 1080 */ + if ((http_port == 80) && geteuid()) { + http_port = 1080; + } + + /* Do we really have rights in the html directory? */ + if (fetch_mode[0] == NULL) { + if (chdir(homedir)) { + perror("chdir"); + puts(homedir); + exit(1); + } + } + + /* Create log file if it doesn't exit */ + if ((access(logfile,W_OK)) && daemonize) { + ld = open (logfile,O_WRONLY); + chmod (logfile,00600); + close(ld); + } - if (chdir(homedir)) - { - perror("chdir"); - puts(homedir); - exit(1); - } init_servconnection(); - - if (fork()) exit(0); - setpgrp(0,65534); - signal(SIGQUIT, SIG_IGN); - signal(SIGHUP, SIG_IGN); + if (verbose) { + printf("Server started with options \n"); + printf("port: %d\n",http_port); + if (fetch_mode[0] == NULL) printf("html home: %s\n",homedir); + if (daemonize) printf("logfile: %s\n",logfile); + } - if (listen(http_sock,100) < 0) exit(1); + /* httpd is spawned */ + if (daemonize) { + if (server_pid = fork()) { + wait3(0,WNOHANG,0); + if (verbose) printf("pid: %d\n",server_pid); + exit(0); + } + wait3(0,WNOHANG,0); + } + + if (fetch_mode[0] == NULL) setpgrp(0,httpd_group); + + /* How many connections do you want? + * Keep this lower than the available number of processes + */ + if (listen(http_sock,15) < 0) exit(1); label: - attenteconnection(); - if (fork()) - { - close(con_sock); - goto label; - } - alarm(1800); - traite_req(); - exit(0); -} + wait_connection(); + + if (fork()) { + wait3(0,WNOHANG,0); + close(con_sock); + goto label; + } + http_request(); + + wait3(0,WNOHANG,0); + exit(0); +} char *adate()