]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - release/picobsd/tinyware/simple_httpd/simple_httpd.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.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$
31  */
32
33 #include <sys/stat.h>
34 #include <sys/time.h>
35 #include <sys/types.h>
36 #include <sys/socket.h>
37 #include <sys/wait.h>
38 #include <netinet/in.h>
39 #include <arpa/inet.h>
40
41 #include <fcntl.h>
42 #include <netdb.h>
43 #include <signal.h>
44 #include <stdio.h>
45 #include <stdint.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 const char     *fetch_mode = NULL;
57 char            homedir[100];
58 char            logfile[80];
59 char           *adate(void);
60 void            init_servconnection(void);
61 void            http_date(void);
62 void            http_output(const char *html);
63 void            http_request(void);
64 void            log_line(char *req);
65 void            wait_connection(void);
66
67 struct hostent *hst;
68 struct sockaddr_in source;
69
70 /* HTTP basics */
71 static char httpd_server_ident[] = "Server: FreeBSD/PicoBSD simple_httpd 1.1\r";
72
73 static char http_200[] = "HTTP/1.0 200 OK\r";
74
75 const char *default_mime_type = "application/octet-stream";
76
77 const char *mime_type[][2] = {
78     { "txt",      "text/plain"            },
79     { "htm",      "text/html"             },
80     { "html",     "text/html"             },
81     { "gif",      "image/gif"             },
82     { "jpg",      "image/jpeg"            },
83     { "mp3",      "audio/mpeg"            }
84 };
85
86 const int mime_type_max = sizeof(mime_type) / sizeof(mime_type[0]) - 1;
87
88 /* Two parts, HTTP Header and then HTML */
89 static const char *http_404[2] = 
90     {"HTTP/1.0 404 Not found\r\n", 
91 "<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 404</H1>\
92 Not found - file doesn't exist or you do not have permission.\n</BODY></HTML>\r\n"
93 };
94
95 static const char *http_405[2] = 
96     {"HTTP/1.0 405 Method Not allowed\r\nAllow: GET,HEAD\r\n",
97 "<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 405</H1>\
98 This server only supports GET and HEAD requests.\n</BODY></HTML>\r\n"
99 };
100
101 /*
102  * Only called on initial invocation
103  */
104 void
105 init_servconnection(void)
106 {
107         struct sockaddr_in server;
108
109         /* Create a socket */
110         http_sock = socket(AF_INET, SOCK_STREAM, 0);
111         if (http_sock < 0) {
112                 perror("socket");
113                 exit(1);
114         }
115         server.sin_family = AF_INET;
116         server.sin_port = htons(http_port);
117         server.sin_addr.s_addr = INADDR_ANY;
118         if (bind(http_sock, (struct sockaddr *) & server, sizeof(server)) < 0) {
119                 perror("bind socket");
120                 exit(1);
121         }
122         if (verbose) printf("simple_httpd:%d\n",http_port);
123 }
124
125 /*
126  * Wait here until we see an incoming http request
127  */
128 void
129 wait_connection(void)
130 {
131         socklen_t lg;
132
133         lg = sizeof(struct sockaddr_in);
134
135         con_sock = accept(http_sock, (struct sockaddr *) & source, &lg);
136         if (con_sock <= 0) {
137                 perror("accept");
138                 exit(1);
139         }
140 }
141
142 /*
143  * Print timestamp for HTTP HEAD and GET
144  */
145 void
146 http_date(void)
147 {
148         time_t  tl;
149         char    buff[50];
150
151         tl = time(NULL);
152         strftime(buff, 50, "Date: %a, %d %h %Y %H:%M:%S %Z\r\n", gmtime(&tl));
153         write(con_sock, buff, strlen(buff));
154         /* return(buff); */
155 }
156
157 /*
158  * Send data to the open socket
159  */
160 void
161 http_output(const char *html)
162 {
163         write(con_sock, html, strlen(html));
164         write(con_sock, "\r\n", 2);
165 }
166
167
168 /*
169  * Create and write the log information to file
170  * Log file format is one line per entry
171  */
172 void
173 log_line(char *req)
174 {
175         char            log_buff[256];
176         char            msg[1024];
177         char            env_host[80], env_addr[80];
178         long            addr;
179         FILE           *log;
180
181         strcpy(log_buff,inet_ntoa(source.sin_addr));
182         sprintf(env_addr, "REMOTE_ADDR=%s",log_buff);
183
184         addr=inet_addr(log_buff);
185         
186         strcpy(msg,adate());
187         strcat(msg,"    ");                 
188         hst=gethostbyaddr((char*) &addr, 4, AF_INET);
189
190         /* If DNS hostname exists */
191         if (hst) {
192           strcat(msg,hst->h_name);
193           sprintf(env_host, "REMOTE_HOST=%s",hst->h_name);
194         }
195         strcat(msg," (");
196         strcat(msg,log_buff);
197         strcat(msg,")   ");
198         strcat(msg,req);
199
200         if (daemonize) {
201           log=fopen(logfile,"a");
202           fprintf(log,"%s\n",msg);
203           fclose(log);
204         } else
205           printf("%s\n",msg);
206
207         /* This is for CGI scripts */
208         putenv(env_addr);
209         putenv(env_host);
210 }
211
212 /*
213  * We have a connection.  Identify what type of request GET, HEAD, CGI, etc 
214  * and do what needs to be done
215  */
216 void
217 http_request(void)
218 {
219         int             fd, lg, i; 
220         int             cmd = 0;
221         char           *p, *par;
222         const char     *filename, *c, *ext, *type;
223         struct stat     file_status;
224         char            req[1024];
225         char            buff[8192];
226
227         lg = read(con_sock, req, 1024);
228
229         if ((p=strstr(req,"\n"))) *p=0;
230         if ((p=strstr(req,"\r"))) *p=0;
231
232         log_line(req);
233
234         c = strtok(req, " ");
235
236         /* Error msg if request is nothing */
237         if (c == NULL) {
238           http_output(http_404[0]);
239           http_output(http_404[1]);
240           goto end_request;
241         }
242
243         if (strncmp(c, "GET", 3) == 0) cmd = 1;
244         if (strncmp(c, "HEAD", 4) == 0) cmd = 2;
245
246         /* Do error msg for any other type of request */
247         if (cmd == 0) {         
248           http_output(http_405[0]);
249           http_output(http_405[1]);
250           goto end_request;
251         }
252
253         filename = strtok(NULL, " ");
254
255         c = strtok(NULL, " ");
256         if (fetch_mode != NULL) filename=fetch_mode; 
257         if (filename == NULL || 
258             strlen(filename)==1) filename="/index.html"; 
259
260         while (filename[0]== '/') filename++;        
261         
262         /* CGI handling.  Untested */
263         if (!strncmp(filename,"cgi-bin/",8))           
264            {
265            par=0;
266            if ((par=strstr(filename,"?")))                        
267               {
268                *par=0;            
269                 par++;      
270               } 
271            if (access(filename,X_OK)) goto conti;
272            stat (filename,&file_status);
273            if (setuid(file_status.st_uid)) return;
274            if (seteuid(file_status.st_uid)) return;
275            if (!fork())
276               {
277                close(1);
278                dup(con_sock);
279                /*printf("HTTP/1.0 200 OK\nContent-type: text/html\n\n\n");*/
280                printf("HTTP/1.0 200 OK\r\n");
281                /* Plug in environment variable, others in log_line */
282                setenv("SERVER_SOFTWARE", "FreeBSD/PicoBSD", 1);
283
284                execlp (filename,filename,par,(char *)0);
285               } 
286             wait(&i);
287             return;
288             }
289         conti:
290         if (filename == NULL) {
291           http_output(http_405[0]);
292           http_output(http_405[1]);
293           goto end_request;
294         }
295         /* End of CGI handling */
296         
297         /* Reject any request with '..' in it, bad hacker */
298         c = filename;
299         while (*c != '\0')
300           if (c[0] == '.' && c[1] == '.') {
301             http_output(http_404[0]);
302             http_output(http_404[1]); 
303             goto end_request;
304           } else
305             c++;
306         
307         /* Open filename */
308         fd = open(filename, O_RDONLY);
309         if (fd < 0) {
310                 http_output(http_404[0]);
311                 http_output(http_404[1]);
312                 goto end_request;
313         }
314
315         /* Get file status information */
316         if (fstat(fd, &file_status) < 0) {
317           http_output(http_404[0]);
318           http_output(http_404[1]);
319           goto end_request2;
320         }
321
322         /* Is it a regular file? */
323         if (!S_ISREG(file_status.st_mode)) {
324           http_output(http_404[0]);
325           http_output(http_404[1]);
326           goto end_request2;
327         }
328      
329         /* Past this point we are serving either a GET or HEAD */
330         /* Print all the header info */
331         http_output(http_200);
332         http_output(httpd_server_ident);
333         http_date();
334
335         sprintf(buff, "Content-length: %jd\r\n", (intmax_t)file_status.st_size);
336         write(con_sock, buff, strlen(buff));
337
338         strcpy(buff, "Content-type: ");
339         type = default_mime_type;
340         if ((ext = strrchr(filename, '.')) != NULL) {
341           for (i = mime_type_max; i >= 0; i--)
342             if (strcmp(ext + 1, mime_type[i][0]) == 0) {
343               type = mime_type[i][1];
344               break;
345             }
346         }
347         strcat(buff, type);
348         http_output(buff);
349         
350         strftime(buff, 50, "Last-Modified: %a, %d %h %Y %H:%M:%S %Z\r\n\r\n", gmtime(&file_status.st_mtime));
351         write(con_sock, buff, strlen(buff));
352
353         /* Send data only if GET request */
354         if (cmd == 1) {
355           while ((lg = read(fd, buff, 8192)) > 0)
356             write(con_sock, buff, lg);
357         } 
358
359 end_request2:
360         close(fd);
361 end_request:
362         close(con_sock);
363
364 }
365
366 /*
367  * Simple httpd server for use in PicoBSD or other embedded application. 
368  * Should satisfy simple httpd needs.  For more demanding situations
369  * apache is probably a better (but much larger) choice.
370  */
371 int
372 main(int argc, char *argv[])
373 {
374         int ch, ld;
375         int             httpd_group = 65534;
376         pid_t server_pid;
377   
378         /* Default for html directory */
379         strcpy (homedir,getenv("HOME"));
380         if (!geteuid()) strcpy (homedir,"/httphome");
381            else         strcat (homedir,"/httphome");
382
383         /* Defaults for log file */
384         if (geteuid()) {
385             strcpy(logfile,getenv("HOME"));
386             strcat(logfile,"/");
387             strcat(logfile,"jhttp.log");
388         } else 
389           strcpy(logfile,"/var/log/jhttpd.log");
390
391         /* Parse command line arguments */
392         while ((ch = getopt(argc, argv, "d:f:g:l:p:vDh")) != -1)
393           switch (ch) {
394           case 'd':
395             strcpy(homedir,optarg);
396             break;        
397           case 'f':
398             daemonize = 0;
399             verbose = 1;
400             fetch_mode = optarg;
401             break;
402           case 'g':
403             httpd_group = atoi(optarg);
404             break;
405           case 'l':
406             strcpy(logfile,optarg);
407             break;
408           case 'p':
409             http_port = atoi(optarg);
410             break;
411           case 'v':
412             verbose = 1;
413             break;
414           case 'D':
415             daemonize = 0;
416             break;
417           case '?':
418           case 'h':
419           default:
420             printf("usage: simple_httpd [[-d directory][-g grpid][-l logfile][-p port][-vD]]\n");
421             exit(1);
422             /* NOTREACHED */
423           }                           
424
425         /* Not running as root and no port supplied, assume 1080 */
426         if ((http_port == 80) && geteuid()) {
427           http_port = 1080;
428         }
429
430         /* Do we really have rights in the html directory? */
431         if (fetch_mode == NULL) {
432           if (chdir(homedir)) {
433             perror("chdir");
434             puts(homedir);
435             exit(1);
436           }
437         }
438
439         /* Create log file if it doesn't exit */
440         if ((access(logfile,W_OK)) && daemonize) { 
441           ld = open (logfile,O_WRONLY);         
442           chmod (logfile,00600);
443           close(ld);
444         }
445
446         init_servconnection();                  
447
448         if (verbose) {
449           printf("Server started with options \n"); 
450           printf("port: %d\n",http_port);
451           if (fetch_mode == NULL) printf("html home: %s\n",homedir);
452           if (daemonize) printf("logfile: %s\n",logfile);
453         }
454
455         /* httpd is spawned */
456         if (daemonize) {
457           if ((server_pid = fork()) != 0) {
458             wait3(0,WNOHANG,0);
459             if (verbose) printf("pid: %d\n",server_pid);
460             exit(0);
461           }
462           wait3(0,WNOHANG,0);
463         }
464
465         if (fetch_mode == NULL) setpgrp(0,httpd_group);
466
467         /* How many connections do you want? 
468          * Keep this lower than the available number of processes
469          */
470         if (listen(http_sock,15) < 0) exit(1);
471
472         label:  
473         wait_connection();
474     
475         if (fork()) {
476           wait3(0,WNOHANG,0);
477           close(con_sock);
478           goto label;
479         }
480
481         http_request();
482
483         wait3(0,WNOHANG,0);
484         exit(0);
485 }
486
487
488 char *
489 adate(void)
490 {
491         static char out[50];
492         time_t now;
493         struct tm *t;
494         time(&now);
495         t = localtime(&now);
496         sprintf(out, "%02d:%02d:%02d %02d/%02d/%02d",
497                      t->tm_hour, t->tm_min, t->tm_sec,
498                      t->tm_mday, t->tm_mon+1, t->tm_year );
499         return out;
500 }