simple_httpd.c revision 133836
10SN/A/*-
21410Sihse * Simple_HTTPd v1.1 - a very small, barebones HTTP server
30SN/A *
40SN/A * Copyright (c) 1998-1999 Marc Nicholas <marc@netstor.com>
50SN/A * All rights reserved.
60SN/A *
7180SN/A * Major rewrite by William Lloyd <wlloyd@slap.net>
80SN/A *
9180SN/A * Redistribution and use in source and binary forms, with or without
100SN/A * modification, are permitted provided that the following conditions
110SN/A * are met:
120SN/A * 1. Redistributions of source code must retain the above copyright
130SN/A *    notice, this list of conditions and the following disclaimer.
140SN/A * 2. Redistributions in binary form must reproduce the above copyright
150SN/A *    notice, this list of conditions and the following disclaimer in the
160SN/A *    documentation and/or other materials provided with the distribution.
170SN/A *
180SN/A * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
190SN/A * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
200SN/A * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21180SN/A * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22180SN/A * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23180SN/A * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
240SN/A * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
250SN/A * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
261410Sihse * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
271410Sihse * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
281410Sihse * SUCH DAMAGE.
291410Sihse *
301410Sihse * $FreeBSD: head/release/picobsd/tinyware/simple_httpd/simple_httpd.c 133836 2004-08-16 09:38:34Z dwmalone $
311410Sihse */
32910SN/A
33910SN/A#include <sys/stat.h>
341410Sihse#include <sys/time.h>
355SN/A#include <sys/types.h>
36910SN/A#include <sys/socket.h>
371410Sihse#include <sys/wait.h>
38910SN/A#include <netinet/in.h>
39338SN/A#include <arpa/inet.h>
401426Sihse
411410Sihse#include <fcntl.h>
421426Sihse#include <netdb.h>
431410Sihse#include <signal.h>
441410Sihse#include <stdio.h>
451426Sihse#include <stdlib.h>
461426Sihse#include <string.h>
47311SN/A#include <time.h>
481426Sihse#include <unistd.h>
491426Sihse
501426Sihseint             http_port = 80;
511410Sihseint             daemonize = 1;
521426Sihseint             verbose = 0;
531651Sihseint             http_sock, con_sock;
541651Sihse
551120SN/Aconst char     *fetch_mode = NULL;
561410Sihsechar            homedir[100];
571410Sihsechar            logfile[80];
581410Sihsechar           *adate(void);
591426Sihsevoid            init_servconnection(void);
601426Sihsevoid		http_date(void);
611426Sihsevoid            http_output(const char *html);
621426Sihsevoid            http_request(void);
631426Sihsevoid            log_line(char *req);
641426Sihsevoid            wait_connection(void);
651426Sihse
661426Sihsestruct hostent *hst;
671426Sihsestruct sockaddr_in source;
681426Sihse
691426Sihse/* HTTP basics */
701426Sihsestatic char httpd_server_ident[] = "Server: FreeBSD/PicoBSD simple_httpd 1.1\r";
711426Sihse
721426Sihsestatic char http_200[] = "HTTP/1.0 200 OK\r";
731426Sihse
741426Sihseconst char *default_mime_type = "application/octet-stream";
751426Sihse
761426Sihseconst char *mime_type[][2] = {
771426Sihse    { "txt",      "text/plain"            },
781426Sihse    { "htm",      "text/html"             },
791426Sihse    { "html",     "text/html"             },
801426Sihse    { "gif",      "image/gif"             },
811426Sihse    { "jpg",      "image/jpeg"            },
821426Sihse    { "mp3",      "audio/mpeg"            }
831426Sihse};
841426Sihse
851426Sihseconst int mime_type_max = sizeof(mime_type) / sizeof(mime_type[0]) - 1;
861426Sihse
871426Sihse/* Two parts, HTTP Header and then HTML */
881426Sihsestatic const char *http_404[2] =
891426Sihse    {"HTTP/1.0 404 Not found\r\n",
901426Sihse"<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 404</H1>\
911426SihseNot found - file doesn't exist or you do not have permission.\n</BODY></HTML>\r\n"
921426Sihse};
931426Sihse
941426Sihsestatic const char *http_405[2] =
951426Sihse    {"HTTP/1.0 405 Method Not allowed\r\nAllow: GET,HEAD\r\n",
961426Sihse"<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 405</H1>\
971426SihseThis server only supports GET and HEAD requests.\n</BODY></HTML>\r\n"
981426Sihse};
991426Sihse
1001426Sihse/*
1011426Sihse * Only called on initial invocation
1021426Sihse */
1031426Sihsevoid
1041426Sihseinit_servconnection(void)
1051426Sihse{
1061426Sihse	struct sockaddr_in server;
1071426Sihse
1081426Sihse	/* Create a socket */
1091426Sihse	http_sock = socket(AF_INET, SOCK_STREAM, 0);
1101426Sihse	if (http_sock < 0) {
1111426Sihse		perror("socket");
1121426Sihse		exit(1);
1131426Sihse	}
1141426Sihse	server.sin_family = AF_INET;
1151426Sihse	server.sin_port = htons(http_port);
1161426Sihse	server.sin_addr.s_addr = INADDR_ANY;
1171426Sihse	if (bind(http_sock, (struct sockaddr *) & server, sizeof(server)) < 0) {
1181426Sihse		perror("bind socket");
1191426Sihse		exit(1);
1201426Sihse	}
1211426Sihse        if (verbose) printf("simple_httpd:%d\n",http_port);
1221426Sihse}
1231426Sihse
1241410Sihse/*
1251120SN/A * Wait here until we see an incoming http request
1261426Sihse */
1271426Sihsevoid
1281426Sihsewait_connection(void)
1291426Sihse{
1301426Sihse	socklen_t lg;
1311426Sihse
1321426Sihse	lg = sizeof(struct sockaddr_in);
1331410Sihse
1341426Sihse	con_sock = accept(http_sock, (struct sockaddr *) & source, &lg);
1351426Sihse	if (con_sock <= 0) {
1361426Sihse		perror("accept");
13727SN/A		exit(1);
1381410Sihse	}
1391410Sihse}
1401701Sihse
1411701Sihse/*
1421701Sihse * Print timestamp for HTTP HEAD and GET
1431410Sihse */
1441410Sihsevoid
1451410Sihsehttp_date(void)
1461410Sihse{
1471410Sihse	time_t	tl;
1481410Sihse	char	buff[50];
1491410Sihse
1501426Sihse	tl = time(NULL);
1511426Sihse	strftime(buff, 50, "Date: %a, %d %h %Y %H:%M:%S %Z\r\n", gmtime(&tl));
1521410Sihse	write(con_sock, buff, strlen(buff));
1531410Sihse	/* return(buff); */
1541410Sihse}
1551156SN/A
1561120SN/A/*
1571936Sihse * Send data to the open socket
1581936Sihse */
1591936Sihsevoid
1601426Sihsehttp_output(const char *html)
1611426Sihse{
1621426Sihse        write(con_sock, html, strlen(html));
1631426Sihse        write(con_sock, "\r\n", 2);
1641410Sihse}
16527SN/A
1661426Sihse
1671426Sihse/*
1681426Sihse * Create and write the log information to file
1691426Sihse * Log file format is one line per entry
1701426Sihse */
1711426Sihsevoid
1721651Sihselog_line(char *req)
1731651Sihse{
1741915Sihse        char            log_buff[256];
1751651Sihse        char            msg[1024];
1761651Sihse	char            env_host[80], env_addr[80];
1771651Sihse	long            addr;
1781651Sihse	FILE           *log;
1791426Sihse
1801426Sihse	strcpy(log_buff,inet_ntoa(source.sin_addr));
1811426Sihse	sprintf(env_addr, "REMOTE_ADDR=%s",log_buff);
1821426Sihse
1831651Sihse        addr=inet_addr(log_buff);
1841426Sihse
1851410Sihse        strcpy(msg,adate());
1861410Sihse        strcat(msg,"    ");
1871410Sihse        hst=gethostbyaddr((char*) &addr, 4, AF_INET);
1881651Sihse
1891410Sihse	/* If DNS hostname exists */
1901426Sihse        if (hst) {
1911651Sihse	  strcat(msg,hst->h_name);
1921651Sihse	  sprintf(env_host, "REMOTE_HOST=%s",hst->h_name);
1931651Sihse	}
1941651Sihse        strcat(msg," (");
1951651Sihse        strcat(msg,log_buff);
1961651Sihse        strcat(msg,")   ");
1971651Sihse        strcat(msg,req);
1981651Sihse
1991651Sihse	if (daemonize) {
2001651Sihse	  log=fopen(logfile,"a");
2011651Sihse	  fprintf(log,"%s\n",msg);
2021651Sihse	  fclose(log);
2031651Sihse	} else
2041651Sihse	  printf("%s\n",msg);
2051410Sihse
2061410Sihse	/* This is for CGI scripts */
2071410Sihse	putenv(env_addr);
2081410Sihse	putenv(env_host);
2090SN/A}
2101426Sihse
2111410Sihse/*
2121410Sihse * We have a connection.  Identify what type of request GET, HEAD, CGI, etc
2131410Sihse * and do what needs to be done
2141410Sihse */
2151410Sihsevoid
2161410Sihsehttp_request(void)
2171410Sihse{
2181410Sihse	int             fd, lg, i;
2191410Sihse	int             cmd = 0;
2201426Sihse	char           *p, *par;
2211426Sihse	const char     *filename, *c, *ext, *type;
2221426Sihse	struct stat     file_status;
2231426Sihse	char            req[1024];
2241426Sihse	char            buff[8192];
2251426Sihse
2261426Sihse	lg = read(con_sock, req, 1024);
2271426Sihse
2281651Sihse        if ((p=strstr(req,"\n"))) *p=0;
2291651Sihse        if ((p=strstr(req,"\r"))) *p=0;
2301651Sihse
2311410Sihse	log_line(req);
2321862Serikj
2331862Serikj	c = strtok(req, " ");
2341410Sihse
2351410Sihse	/* Error msg if request is nothing */
2361410Sihse	if (c == NULL) {
2371410Sihse	  http_output(http_404[0]);
2381410Sihse	  http_output(http_404[1]);
2391410Sihse	  goto end_request;
2401426Sihse	}
2411426Sihse
2421426Sihse	if (strncmp(c, "GET", 3) == 0) cmd = 1;
2431426Sihse	if (strncmp(c, "HEAD", 4) == 0) cmd = 2;
2441426Sihse
2451426Sihse	/* Do error msg for any other type of request */
2461426Sihse	if (cmd == 0) {
2471426Sihse	  http_output(http_405[0]);
2481426Sihse	  http_output(http_405[1]);
2491426Sihse	  goto end_request;
2501426Sihse	}
2511426Sihse
2521426Sihse	filename = strtok(NULL, " ");
2531327SN/A
2541628Sihse	c = strtok(NULL, " ");
2551628Sihse	if (fetch_mode != NULL) filename=fetch_mode;
2561628Sihse	if (filename == NULL ||
2571410Sihse            strlen(filename)==1) filename="/index.html";
2581410Sihse
2591410Sihse	while (filename[0]== '/') filename++;
2601410Sihse
2611410Sihse        /* CGI handling.  Untested */
2621410Sihse        if (!strncmp(filename,"cgi-bin/",8))
2631410Sihse           {
2641410Sihse           par=0;
2651410Sihse           if ((par=strstr(filename,"?")))
2661426Sihse              {
2671426Sihse               *par=0;
2681426Sihse                par++;
2691410Sihse              }
2701651Sihse           if (access(filename,X_OK)) goto conti;
2711426Sihse           stat (filename,&file_status);
2721426Sihse           if (setuid(file_status.st_uid)) return;
2731410Sihse           if (seteuid(file_status.st_uid)) return;
2741410Sihse           if (!fork())
2751410Sihse              {
2761410Sihse               close(1);
2771410Sihse               dup(con_sock);
2780SN/A               /*printf("HTTP/1.0 200 OK\nContent-type: text/html\n\n\n");*/
2791426Sihse	       printf("HTTP/1.0 200 OK\r\n");
2801426Sihse               /* Plug in environment variable, others in log_line */
2811426Sihse	       putenv("SERVER_SOFTWARE=FreeBSD/PicoBSD");
2821623Sihse
2831862Serikj	       execlp (filename,filename,par,(char *)0);
2841426Sihse              }
2851426Sihse            wait(&i);
2861862Serikj            return;
2871426Sihse            }
2881426Sihse        conti:
2891426Sihse	if (filename == NULL) {
2901426Sihse	  http_output(http_405[0]);
2911426Sihse	  http_output(http_405[1]);
2921426Sihse	  goto end_request;
2931426Sihse	}
2941426Sihse	/* End of CGI handling */
2951862Serikj
2961623Sihse	/* Reject any request with '..' in it, bad hacker */
2971862Serikj	c = filename;
2981862Serikj	while (*c != '\0')
2991862Serikj	  if (c[0] == '.' && c[1] == '.') {
3001862Serikj	    http_output(http_404[0]);
3011623Sihse	    http_output(http_404[1]);
3021623Sihse	    goto end_request;
3031623Sihse	  } else
3041426Sihse	    c++;
3051426Sihse
3061426Sihse	/* Open filename */
3071426Sihse	fd = open(filename, O_RDONLY);
3081862Serikj	if (fd < 0) {
3091410Sihse	        http_output(http_404[0]);
3101410Sihse	        http_output(http_404[1]);
3111623Sihse		goto end_request;
3121745Sihse	}
3131745Sihse
3141745Sihse	/* Get file status information */
3151745Sihse	if (fstat(fd, &file_status) < 0) {
3161745Sihse	  http_output(http_404[0]);
3171623Sihse	  http_output(http_404[1]);
3181623Sihse	  goto end_request2;
3191651Sihse	}
3201651Sihse
3211900Sihse	/* Is it a regular file? */
3221745Sihse	if (!S_ISREG(file_status.st_mode)) {
3231651Sihse	  http_output(http_404[0]);
3241651Sihse	  http_output(http_404[1]);
3251926Serikj	  goto end_request2;
3261745Sihse	}
3271745Sihse
3281651Sihse	/* Past this point we are serving either a GET or HEAD */
3291623Sihse	/* Print all the header info */
3301410Sihse	http_output(http_200);
331	http_output(httpd_server_ident);
332	http_date();
333
334	sprintf(buff, "Content-length: %lld\r\n", file_status.st_size);
335	write(con_sock, buff, strlen(buff));
336
337	strcpy(buff, "Content-type: ");
338	type = default_mime_type;
339	if ((ext = strrchr(filename, '.')) != NULL) {
340	  for (i = mime_type_max; i >= 0; i--)
341	    if (strcmp(ext + 1, mime_type[i][0]) == 0) {
342	      type = mime_type[i][1];
343	      break;
344	    }
345	}
346	strcat(buff, type);
347	http_output(buff);
348
349	strftime(buff, 50, "Last-Modified: %a, %d %h %Y %H:%M:%S %Z\r\n\r\n", gmtime(&file_status.st_mtime));
350	write(con_sock, buff, strlen(buff));
351
352	/* Send data only if GET request */
353	if (cmd == 1) {
354	  while ((lg = read(fd, buff, 8192)) > 0)
355	    write(con_sock, buff, lg);
356	}
357
358end_request2:
359	close(fd);
360end_request:
361	close(con_sock);
362
363}
364
365/*
366 * Simple httpd server for use in PicoBSD or other embedded application.
367 * Should satisfy simple httpd needs.  For more demanding situations
368 * apache is probably a better (but much larger) choice.
369 */
370int
371main(int argc, char *argv[])
372{
373        int ch, ld;
374	int             httpd_group = 65534;
375        pid_t server_pid;
376
377	/* Default for html directory */
378	strcpy (homedir,getenv("HOME"));
379        if (!geteuid()) strcpy (homedir,"/httphome");
380           else         strcat (homedir,"/httphome");
381
382	/* Defaults for log file */
383	if (geteuid()) {
384	    strcpy(logfile,getenv("HOME"));
385	    strcat(logfile,"/");
386	    strcat(logfile,"jhttp.log");
387	} else
388	  strcpy(logfile,"/var/log/jhttpd.log");
389
390	/* Parse command line arguments */
391	while ((ch = getopt(argc, argv, "d:f:g:l:p:vDh")) != -1)
392	  switch (ch) {
393	  case 'd':
394	    strcpy(homedir,optarg);
395	    break;
396	  case 'f':
397	    daemonize = 0;
398	    verbose = 1;
399	    fetch_mode = optarg;
400	    break;
401	  case 'g':
402	    httpd_group = atoi(optarg);
403	    break;
404	  case 'l':
405	    strcpy(logfile,optarg);
406	    break;
407	  case 'p':
408	    http_port = atoi(optarg);
409	    break;
410	  case 'v':
411	    verbose = 1;
412	    break;
413	  case 'D':
414	    daemonize = 0;
415	    break;
416	  case '?':
417	  case 'h':
418	  default:
419	    printf("usage: simple_httpd [[-d directory][-g grpid][-l logfile][-p port][-vD]]\n");
420	    exit(1);
421	    /* NOTREACHED */
422	  }
423
424	/* Not running as root and no port supplied, assume 1080 */
425        if ((http_port == 80) && geteuid()) {
426	  http_port = 1080;
427	}
428
429	/* Do we really have rights in the html directory? */
430	if (fetch_mode == NULL) {
431	  if (chdir(homedir)) {
432	    perror("chdir");
433	    puts(homedir);
434	    exit(1);
435	  }
436	}
437
438	/* Create log file if it doesn't exit */
439	if ((access(logfile,W_OK)) && daemonize) {
440	  ld = open (logfile,O_WRONLY);
441	  chmod (logfile,00600);
442	  close(ld);
443	}
444
445        init_servconnection();
446
447        if (verbose) {
448	  printf("Server started with options \n");
449	  printf("port: %d\n",http_port);
450	  if (fetch_mode == NULL) printf("html home: %s\n",homedir);
451	  if (daemonize) printf("logfile: %s\n",logfile);
452	}
453
454	/* httpd is spawned */
455        if (daemonize) {
456	  if ((server_pid = fork()) != 0) {
457	    wait3(0,WNOHANG,0);
458	    if (verbose) printf("pid: %d\n",server_pid);
459	    exit(0);
460	  }
461	  wait3(0,WNOHANG,0);
462	}
463
464	if (fetch_mode == NULL) setpgrp(0,httpd_group);
465
466	/* How many connections do you want?
467	 * Keep this lower than the available number of processes
468	 */
469        if (listen(http_sock,15) < 0) exit(1);
470
471        label:
472	wait_connection();
473
474	if (fork()) {
475	  wait3(0,WNOHANG,0);
476	  close(con_sock);
477	  goto label;
478	}
479
480	http_request();
481
482	wait3(0,WNOHANG,0);
483	exit(0);
484}
485
486
487char *
488adate(void)
489{
490        static char out[50];
491        time_t now;
492        struct tm *t;
493        time(&now);
494        t = localtime(&now);
495        sprintf(out, "%02d:%02d:%02d %02d/%02d/%02d",
496                     t->tm_hour, t->tm_min, t->tm_sec,
497                     t->tm_mday, t->tm_mon+1, t->tm_year );
498        return out;
499}
500