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