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