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: stable/11/release/picobsd/tinyware/simple_httpd/simple_httpd.c 351466 2019-08-25 00:57:51Z eugen $
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
51static int	http_port = 80;
52static int	daemonize = 1;
53static int	verbose = 0;
54static int	http_sock, con_sock;
55
56static const char *fetch_mode = NULL;
57static char	homedir[100];
58static char	logfile[80];
59static char	*adate(void);
60static void	init_servconnection(void);
61static void	http_date(void);
62static void	http_output(const char *html);
63static void	http_request(void);
64static void	log_line(char *req);
65static void	wait_connection(void);
66
67static struct hostent *hst;
68static struct sockaddr_in source;
69
70/* HTTP basics */
71static char httpd_server_ident[] = "Server: FreeBSD/PicoBSD simple_httpd 1.1\r";
72
73static char http_200[] = "HTTP/1.0 200 OK\r";
74
75static const char *default_mime_type = "application/octet-stream";
76
77static 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
86static const int mime_type_max = sizeof(mime_type) / sizeof(mime_type[0]) - 1;
87
88/* Two parts, HTTP Header and then HTML */
89static 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>\
92Not found - file doesn't exist or you do not have permission.\n</BODY></HTML>\r\n"
93};
94
95static 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>\
98This server only supports GET and HEAD requests.\n</BODY></HTML>\r\n"
99};
100
101/*
102 * Only called on initial invocation
103 */
104static void
105init_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 */
128static void
129wait_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 */
145static void
146http_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 */
160static void
161http_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 */
172static void
173log_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 */
216static void
217http_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
359end_request2:
360	close(fd);
361end_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 */
371int
372main(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
489char *
490adate(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