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 <stdint.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <time.h>
49 #include <unistd.h>
50 
51 static int	http_port = 80;
52 static int	daemonize = 1;
53 static int	verbose = 0;
54 static int	http_sock, con_sock;
55 
56 static const char *fetch_mode = NULL;
57 static char	homedir[100];
58 static char	logfile[80];
59 static char	*adate(void);
60 static void	init_servconnection(void);
61 static void	http_date(void);
62 static void	http_output(const char *html);
63 static void	http_request(void);
64 static void	log_line(char *req);
65 static void	wait_connection(void);
66 
67 static struct hostent *hst;
68 static 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 static const char *default_mime_type = "application/octet-stream";
76 
77 static 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 static 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 static void
init_servconnection(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 static void
wait_connection(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 static void
http_date(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 static void
http_output(const char * html)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 static void
log_line(char * req)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 static void
http_request(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
main(int argc,char * argv[])372 main(int argc, char *argv[])
373 {
374 	int ch, ld;
375 	pid_t 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)
466 		setpgrp((pid_t)0, httpd_group);
467 
468 	/* How many connections do you want?
469 	 * Keep this lower than the available number of processes
470 	 */
471 	if (listen(http_sock,15) < 0) exit(1);
472 
473 	label:
474 	wait_connection();
475 
476 	if (fork()) {
477 	  wait3(0,WNOHANG,0);
478 	  close(con_sock);
479 	  goto label;
480 	}
481 
482 	http_request();
483 
484 	wait3(0,WNOHANG,0);
485 	exit(0);
486 }
487 
488 
489 char *
adate(void)490 adate(void)
491 {
492 	static char out[50];
493 	time_t now;
494 	struct tm *t;
495 	time(&now);
496 	t = localtime(&now);
497 	sprintf(out, "%02d:%02d:%02d %02d/%02d/%02d",
498 		     t->tm_hour, t->tm_min, t->tm_sec,
499 		     t->tm_mday, t->tm_mon+1, t->tm_year );
500 	return out;
501 }
502