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