simple_httpd.c revision 50479
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: head/release/picobsd/tinyware/simple_httpd/simple_httpd.c 50479 1999-08-28 01:35:59Z peter $
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
50int             http_port = 80;
51int             daemonize = 1;
52int             verbose = 0;
53int             http_sock, con_sock;
54
55char            fetch_mode[100];
56char            homedir[100];
57char            logfile[80];
58char           *adate();
59
60struct hostent *hst;
61struct sockaddr_in source;
62
63/* HTTP basics */
64static char httpd_server_ident[] = "Server: FreeBSD/PicoBSD simple_httpd 1.1\r";
65
66static char http_200[] = "HTTP/1.0 200 OK\r";
67
68/* Two parts, HTTP Header and then HTML */
69static 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>\
72Not found - file doesn't exist or you do not have permission.\n</BODY></HTML>\r\n"
73};
74
75static 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>\
78This server only supports GET and HEAD requests.\n</BODY></HTML>\r\n"
79};
80
81/*
82 * Only called on initial invocation
83 */
84void
85init_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 */
108wait_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 */
124http_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 */
138http_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 */
149log_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 */
192http_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
339end_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 */
350main(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
469char *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