Add the DragonFly cvs id and perform general cleanups on cvs/rcs/sccs ids. Most
[dragonfly.git] / release / picobsd / tinyware / simple_httpd / simple_httpd.c
1 /*-
2  * Simple_HTTPd v1.1 - a very small, barebones HTTP server
3  * 
4  * Copyright (c) 1998-1999 Marc Nicholas <marc@netstor.com>
5  * All rights reserved.
6  *
7  * Major rewrite by William Lloyd <wlloyd@slap.net>
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  *
30  * $FreeBSD: src/release/picobsd/tinyware/simple_httpd/simple_httpd.c,v 1.6 1999/08/28 01:33:59 peter Exp $
31  * $DragonFly: src/release/picobsd/tinyware/simple_httpd/Attic/simple_httpd.c,v 1.2 2003/06/17 04:27:20 dillon Exp $
32  */
33
34 #include <sys/stat.h>
35 #include <sys/time.h>
36 #include <sys/types.h>
37 #include <sys/socket.h>
38 #include <sys/wait.h>
39 #include <netinet/in.h>
40 #include <arpa/inet.h>
41
42 #include <fcntl.h>
43 #include <netdb.h>
44 #include <signal.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <time.h>
49 #include <unistd.h>
50
51 int             http_port = 80;
52 int             daemonize = 1;
53 int             verbose = 0;
54 int             http_sock, con_sock;
55
56 char            fetch_mode[100];
57 char            homedir[100];
58 char            logfile[80];
59 char           *adate();
60
61 struct hostent *hst;
62 struct sockaddr_in source;
63
64 /* HTTP basics */
65 static char httpd_server_ident[] = "Server: FreeBSD/PicoBSD simple_httpd 1.1\r";
66
67 static char http_200[] = "HTTP/1.0 200 OK\r";
68
69 /* Two parts, HTTP Header and then HTML */
70 static char *http_404[2] = 
71     {"HTTP/1.0 404 Not found\r\n", 
72 "<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 404</H1>\
73 Not found - file doesn't exist or you do not have permission.\n</BODY></HTML>\r\n"
74 };
75
76 static char *http_405[2] = 
77     {"HTTP/1.0 405 Method Not allowed\r\nAllow: GET,HEAD\r\n",
78 "<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 405</H1>\
79 This server only supports GET and HEAD requests.\n</BODY></HTML>\r\n"
80 };
81
82 /*
83  * Only called on initial invocation
84  */
85 void
86 init_servconnection(void)
87 {
88         struct sockaddr_in server;
89
90         /* Create a socket */
91         http_sock = socket(AF_INET, SOCK_STREAM, 0);
92         if (http_sock < 0) {
93                 perror("socket");
94                 exit(1);
95         }
96         server.sin_family = AF_INET;
97         server.sin_port = htons(http_port);
98         server.sin_addr.s_addr = INADDR_ANY;
99         if (bind(http_sock, (struct sockaddr *) & server, sizeof(server)) < 0) {
100                 perror("bind socket");
101                 exit(1);
102         }
103         if (verbose) printf("simple_httpd\n",http_port);
104 }
105
106 /*
107  * Wait here until we see an incoming http request
108  */
109 wait_connection(void)
110 {
111         int lg;
112
113         lg = sizeof(struct sockaddr_in);
114
115         con_sock = accept(http_sock, (struct sockaddr *) & source, &lg);
116         if (con_sock <= 0) {
117                 perror("accept");
118                 exit(1);
119         }
120 }
121
122 /*
123  * Print timestamp for HTTP HEAD and GET
124  */
125 http_date()
126 {
127         time_t  tl;
128         char    buff[50];
129
130         tl = time(NULL);
131         strftime(buff, 50, "Date: %a, %d %h %Y %H:%M:%S %Z\r\n", gmtime(&tl));
132         write(con_sock, buff, strlen(buff));
133         //return(buff);
134 }
135
136 /*
137  * Send data to the open socket
138  */
139 http_output(char *html)
140 {
141         write(con_sock, html, strlen(html));
142         write(con_sock, "\r\n", 2);
143 }
144
145
146 /*
147  * Create and write the log information to file
148  * Log file format is one line per entry
149  */
150 log_line(char *req)
151 {
152         char            log_buff[256];
153         char            msg[1024];
154         char            env_host[80], env_addr[80];
155         long            addr;
156         FILE           *log;
157
158         strcpy(log_buff,inet_ntoa(source.sin_addr));
159         sprintf(env_addr, "REMOTE_ADDR=%s",log_buff);
160
161         addr=inet_addr(log_buff);
162         
163         strcpy(msg,adate());
164         strcat(msg,"    ");                 
165         hst=gethostbyaddr((char*) &addr, 4, AF_INET);
166
167         /* If DNS hostname exists */
168         if (hst) {
169           strcat(msg,hst->h_name);
170           sprintf(env_host, "REMOTE_HOST=%s",hst->h_name);
171         }
172         strcat(msg," (");
173         strcat(msg,log_buff);
174         strcat(msg,")   ");
175         strcat(msg,req);
176
177         if (daemonize) {
178           log=fopen(logfile,"a");
179           fprintf(log,"%s\n",msg);
180           fclose(log);
181         } else
182           printf("%s\n",msg);
183
184         /* This is for CGI scripts */
185         putenv(env_addr);
186         putenv(env_host);
187 }
188
189 /*
190  * We have a connection.  Identify what type of request GET, HEAD, CGI, etc 
191  * and do what needs to be done
192  */
193 http_request()
194 {
195         int             fd, lg, ld, i; 
196         int             cmd = 0;
197         int             http1 = 0;
198         char           *p, *par;
199         char           *filename, *c;
200         struct stat     file_status;
201         char            req[1024];
202         char            msg[1024];
203         char            buff[8192];
204
205         lg = read(con_sock, req, 1024);
206
207         if (p=strstr(req,"\n")) *p=0;
208         if (p=strstr(req,"\r")) *p=0;
209
210         log_line(req);
211
212         c = strtok(req, " ");
213
214         /* Error msg if request is nothing */
215         if (c == NULL) {
216           http_output(http_404[0]);
217           http_output(http_404[1]);
218           goto end_request;
219         }
220
221         if (strncmp(c, "GET", 3) == 0) cmd = 1;
222         if (strncmp(c, "HEAD", 4) == 0) cmd = 2;
223
224         /* Do error msg for any other type of request */
225         if (cmd == 0) {         
226           http_output(http_405[0]);
227           http_output(http_405[1]);
228           goto end_request;
229         }
230
231         filename = strtok(NULL, " ");
232
233         c = strtok(NULL, " ");
234         if (fetch_mode[0] != NULL) strcpy(filename,fetch_mode); 
235         if (filename == NULL || 
236             strlen(filename)==1) filename="/index.html"; 
237
238         while (filename[0]== '/') filename++;        
239         
240         /* CGI handling.  Untested */
241         if (!strncmp(filename,"cgi-bin/",8))           
242            {
243            par=0;
244            if (par=strstr(filename,"?"))                        
245               {
246                *par=0;            
247                 par++;      
248               } 
249            if (access(filename,X_OK)) goto conti;
250            stat (filename,&file_status);
251            if (setuid(file_status.st_uid)) return(0);
252            if (seteuid(file_status.st_uid)) return(0);
253            if (!fork())
254               {
255                close(1);
256                dup(con_sock);
257                //printf("HTTP/1.0 200 OK\nContent-type: text/html\n\n\n");
258                printf("HTTP/1.0 200 OK\r\n");
259                /* Plug in environment variable, others in log_line */
260                putenv("SERVER_SOFTWARE=FreeBSD/PicoBSD");
261
262                execlp (filename,filename,par,0);
263               } 
264             wait(&i);
265             return(0);
266             }
267         conti:
268         if (filename == NULL) {
269           http_output(http_405[0]);
270           http_output(http_405[1]);
271           goto end_request;
272         }
273         /* End of CGI handling */
274         
275         /* Reject any request with '..' in it, bad hacker */
276         c = filename;
277         while (*c != '\0')
278           if (c[0] == '.' && c[1] == '.') {
279             http_output(http_404[0]);
280             http_output(http_404[1]); 
281             goto end_request;
282           } else
283             c++;
284         
285         /* Open filename */
286         fd = open(filename, O_RDONLY);
287         if (fd < 0) {
288                 http_output(http_404[0]);
289                 http_output(http_404[1]);
290                 goto end_request;
291         }
292
293         /* Get file status information */
294         if (fstat(fd, &file_status) < 0) {
295           http_output(http_404[0]);
296           http_output(http_404[1]);
297           goto end_request;
298         }
299
300         /* Is it a regular file? */
301         if (!S_ISREG(file_status.st_mode)) {
302           http_output(http_404[0]);
303           http_output(http_404[1]);
304           goto end_request;
305         }
306      
307         /* Past this point we are serving either a GET or HEAD */
308         /* Print all the header info */
309         http_output(http_200);
310         http_output(httpd_server_ident);
311         http_date();
312
313         sprintf(buff, "Content-length: %d\r\n", file_status.st_size);
314
315         if (strstr(filename,".txt")) {
316           strcpy(buff,"Content-type: text/plain\r\n");
317         } else if (strstr(filename,".html") || strstr(filename,".htm")) {
318             strcpy(buff,"Content-type: text/html\r\n");
319         } else if (strstr(filename,".gif")) {
320           strcpy(buff,"Content-type: image/gif\r\n");
321         } else if (strstr(filename,".jpg")) {
322           strcpy(buff,"Content-type: image/jpeg\r\n");
323         } else {
324           /* Take a guess at content if we don't have something already */
325           strcpy(buff,"Content-type: ");
326           strcat(buff,strstr(filename,".")+1);
327           strcat(buff,"\r\n");
328         }
329         write(con_sock, buff, strlen(buff));
330         
331         strftime(buff, 50, "Last-Modified: %a, %d %h %Y %H:%M:%S %Z\r\n\r\n", gmtime(&file_status.st_mtime));
332         write(con_sock, buff, strlen(buff));
333
334         /* Send data only if GET request */
335         if (cmd == 1) {
336           while (lg = read(fd, buff, 8192))
337             write(con_sock, buff, lg);
338         } 
339
340 end_request:
341         close(fd);
342         close(con_sock);
343
344 }
345
346 /*
347  * Simple httpd server for use in PicoBSD or other embedded application. 
348  * Should satisfy simple httpd needs.  For more demanding situations
349  * apache is probably a better (but much larger) choice.
350  */
351 main(int argc, char *argv[])
352 {
353         extern char *optarg;
354         extern int optind;
355         int bflag, ch, fd, ld;
356         int             lg;
357         int             httpd_group = 65534;
358         pid_t server_pid;
359   
360         /* Default for html directory */
361         strcpy (homedir,getenv("HOME"));
362         if (!geteuid()) strcpy (homedir,"/httphome");
363            else         strcat (homedir,"/httphome");
364
365         /* Defaults for log file */
366         if (geteuid()) {
367             strcpy(logfile,getenv("HOME"));
368             strcat(logfile,"/");
369             strcat(logfile,"jhttp.log");
370         } else 
371           strcpy(logfile,"/var/log/jhttpd.log");
372
373         /* Parse command line arguments */
374         while ((ch = getopt(argc, argv, "d:f:g:l:p:vDh")) != -1)
375           switch (ch) {
376           case 'd':
377             strcpy(homedir,optarg);
378             break;        
379           case 'f':
380             daemonize = 0;
381             verbose = 1;
382             strcpy(fetch_mode,optarg);
383             break;
384           case 'g':
385             httpd_group = atoi(optarg);
386             break;
387           case 'l':
388             strcpy(logfile,optarg);
389             break;
390           case 'p':
391             http_port = atoi(optarg);
392             break;
393           case 'v':
394             verbose = 1;
395             break;
396           case 'D':
397             daemonize = 0;
398             break;
399           case '?':
400           case 'h':
401           default:
402             printf("usage: simple_httpd [[-d directory][-g grpid][-l logfile][-p port][-vD]]\n");
403             exit(1);
404             /* NOTREACHED */
405           }                           
406
407         /* Not running as root and no port supplied, assume 1080 */
408         if ((http_port == 80) && geteuid()) {
409           http_port = 1080;
410         }
411
412         /* Do we really have rights in the html directory? */
413         if (fetch_mode[0] == NULL) {
414           if (chdir(homedir)) {
415             perror("chdir");
416             puts(homedir);
417             exit(1);
418           }
419         }
420
421         /* Create log file if it doesn't exit */
422         if ((access(logfile,W_OK)) && daemonize) { 
423           ld = open (logfile,O_WRONLY);         
424           chmod (logfile,00600);
425           close(ld);
426         }
427
428         init_servconnection();                  
429
430         if (verbose) {
431           printf("Server started with options \n"); 
432           printf("port: %d\n",http_port);
433           if (fetch_mode[0] == NULL) printf("html home: %s\n",homedir);
434           if (daemonize) printf("logfile: %s\n",logfile);
435         }
436
437         /* httpd is spawned */
438         if (daemonize) {
439           if (server_pid = fork()) {
440             wait3(0,WNOHANG,0);
441             if (verbose) printf("pid: %d\n",server_pid);
442             exit(0);
443           }
444           wait3(0,WNOHANG,0);
445         }
446
447         if (fetch_mode[0] == NULL) setpgrp(0,httpd_group);
448
449         /* How many connections do you want? 
450          * Keep this lower than the available number of processes
451          */
452         if (listen(http_sock,15) < 0) exit(1);
453
454         label:  
455         wait_connection();
456     
457         if (fork()) {
458           wait3(0,WNOHANG,0);
459           close(con_sock);
460           goto label;
461         }
462
463         http_request();
464
465         wait3(0,WNOHANG,0);
466         exit(0);
467 }
468
469
470 char *adate()
471 {
472         static char out[50];
473         long now;
474         struct tm *t;
475         time(&now);
476         t = localtime(&now);
477         sprintf(out, "%02d:%02d:%02d %02d/%02d/%02d",
478                      t->tm_hour, t->tm_min, t->tm_sec,
479                      t->tm_mday, t->tm_mon+1, t->tm_year );
480         return out;
481 }