simple_httpd.c revision 94130
143939Sabial/*-
246493Sabial * Simple_HTTPd v1.1 - a very small, barebones HTTP server
343939Sabial *
443939Sabial * Copyright (c) 1998-1999 Marc Nicholas <marc@netstor.com>
543939Sabial * All rights reserved.
643939Sabial *
746493Sabial * Major rewrite by William Lloyd <wlloyd@slap.net>
846493Sabial *
943939Sabial * Redistribution and use in source and binary forms, with or without
1043939Sabial * modification, are permitted provided that the following conditions
1143939Sabial * are met:
1243939Sabial * 1. Redistributions of source code must retain the above copyright
1343939Sabial *    notice, this list of conditions and the following disclaimer.
1443939Sabial * 2. Redistributions in binary form must reproduce the above copyright
1543939Sabial *    notice, this list of conditions and the following disclaimer in the
1643939Sabial *    documentation and/or other materials provided with the distribution.
1743939Sabial *
1843939Sabial * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1943939Sabial * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2043939Sabial * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2143939Sabial * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2243939Sabial * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2343939Sabial * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2443939Sabial * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2543939Sabial * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2643939Sabial * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2743939Sabial * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2843939Sabial * SUCH DAMAGE.
2943939Sabial *
3050479Speter * $FreeBSD: head/release/picobsd/tinyware/simple_httpd/simple_httpd.c 94130 2002-04-07 17:07:37Z asmodai $
3138589Sabial */
3238589Sabial
3338589Sabial#include <sys/stat.h>
3438589Sabial#include <sys/time.h>
3538589Sabial#include <sys/types.h>
3638589Sabial#include <sys/socket.h>
3746493Sabial#include <sys/wait.h>
3838589Sabial#include <netinet/in.h>
3938589Sabial#include <arpa/inet.h>
4046493Sabial
4146493Sabial#include <fcntl.h>
4238589Sabial#include <netdb.h>
4346493Sabial#include <signal.h>
4446493Sabial#include <stdio.h>
4546493Sabial#include <stdlib.h>
4638589Sabial#include <string.h>
4746493Sabial#include <time.h>
4846493Sabial#include <unistd.h>
4938589Sabial
5046493Sabialint             http_port = 80;
5146493Sabialint             daemonize = 1;
5246493Sabialint             verbose = 0;
5338589Sabialint             http_sock, con_sock;
5446493Sabial
5546493Sabialchar            fetch_mode[100];
5646493Sabialchar            homedir[100];
5746493Sabialchar            logfile[80];
5838589Sabialchar           *adate();
5946493Sabial
6038589Sabialstruct hostent *hst;
6146493Sabialstruct sockaddr_in source;
6238589Sabial
6346493Sabial/* HTTP basics */
6446493Sabialstatic char httpd_server_ident[] = "Server: FreeBSD/PicoBSD simple_httpd 1.1\r";
6546493Sabial
6646493Sabialstatic char http_200[] = "HTTP/1.0 200 OK\r";
6746493Sabial
6846493Sabial/* Two parts, HTTP Header and then HTML */
6946493Sabialstatic char *http_404[2] =
7046493Sabial    {"HTTP/1.0 404 Not found\r\n",
7146493Sabial"<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 404</H1>\
7246493SabialNot found - file doesn't exist or you do not have permission.\n</BODY></HTML>\r\n"
7346493Sabial};
7446493Sabial
7546493Sabialstatic char *http_405[2] =
7646493Sabial    {"HTTP/1.0 405 Method Not allowed\r\nAllow: GET,HEAD\r\n",
7746493Sabial"<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 405</H1>\
7846493SabialThis server only supports GET and HEAD requests.\n</BODY></HTML>\r\n"
7946493Sabial};
8046493Sabial
8146493Sabial/*
8246493Sabial * Only called on initial invocation
8346493Sabial */
8438589Sabialvoid
8538589Sabialinit_servconnection(void)
8638589Sabial{
8738589Sabial	struct sockaddr_in server;
8838589Sabial
8938589Sabial	/* Create a socket */
9038589Sabial	http_sock = socket(AF_INET, SOCK_STREAM, 0);
9138589Sabial	if (http_sock < 0) {
9238589Sabial		perror("socket");
9338589Sabial		exit(1);
9438589Sabial	}
9538589Sabial	server.sin_family = AF_INET;
9638589Sabial	server.sin_port = htons(http_port);
9738589Sabial	server.sin_addr.s_addr = INADDR_ANY;
9838589Sabial	if (bind(http_sock, (struct sockaddr *) & server, sizeof(server)) < 0) {
9938589Sabial		perror("bind socket");
10038589Sabial		exit(1);
10138589Sabial	}
10294130Sasmodai        if (verbose) printf("simple_httpd:%d\n",http_port);
10338589Sabial}
10438589Sabial
10546493Sabial/*
10646493Sabial * Wait here until we see an incoming http request
10746493Sabial */
10846493Sabialwait_connection(void)
10938589Sabial{
11038589Sabial	int lg;
11138589Sabial
11238589Sabial	lg = sizeof(struct sockaddr_in);
11338589Sabial
11438589Sabial	con_sock = accept(http_sock, (struct sockaddr *) & source, &lg);
11538589Sabial	if (con_sock <= 0) {
11638589Sabial		perror("accept");
11738589Sabial		exit(1);
11838589Sabial	}
11938589Sabial}
12038589Sabial
12146493Sabial/*
12246493Sabial * Print timestamp for HTTP HEAD and GET
12346493Sabial */
12446493Sabialhttp_date()
12538589Sabial{
12638589Sabial	time_t	tl;
12738589Sabial	char	buff[50];
12838589Sabial
12938589Sabial	tl = time(NULL);
13038589Sabial	strftime(buff, 50, "Date: %a, %d %h %Y %H:%M:%S %Z\r\n", gmtime(&tl));
13138589Sabial	write(con_sock, buff, strlen(buff));
13246493Sabial	//return(buff);
13338589Sabial}
13438589Sabial
13546493Sabial/*
13646493Sabial * Send data to the open socket
13746493Sabial */
13846493Sabialhttp_output(char *html)
13946493Sabial{
14046493Sabial        write(con_sock, html, strlen(html));
14146493Sabial        write(con_sock, "\r\n", 2);
14246493Sabial}
14338589Sabial
14438589Sabial
14546493Sabial/*
14646493Sabial * Create and write the log information to file
14746493Sabial * Log file format is one line per entry
14846493Sabial */
14946493Sabiallog_line(char *req)
15038589Sabial{
15146493Sabial        char            log_buff[256];
15246493Sabial        char            msg[1024];
15346493Sabial	char            env_host[80], env_addr[80];
15446493Sabial	long            addr;
15546493Sabial	FILE           *log;
15638589Sabial
15746493Sabial	strcpy(log_buff,inet_ntoa(source.sin_addr));
15846493Sabial	sprintf(env_addr, "REMOTE_ADDR=%s",log_buff);
15946493Sabial
16046493Sabial        addr=inet_addr(log_buff);
16146493Sabial
16246493Sabial        strcpy(msg,adate());
16346493Sabial        strcat(msg,"    ");
16446493Sabial        hst=gethostbyaddr((char*) &addr, 4, AF_INET);
16546493Sabial
16646493Sabial	/* If DNS hostname exists */
16746493Sabial        if (hst) {
16846493Sabial	  strcat(msg,hst->h_name);
16946493Sabial	  sprintf(env_host, "REMOTE_HOST=%s",hst->h_name);
17038589Sabial	}
17146493Sabial        strcat(msg," (");
17246493Sabial        strcat(msg,log_buff);
17346493Sabial        strcat(msg,")   ");
17446493Sabial        strcat(msg,req);
17546493Sabial
17646493Sabial	if (daemonize) {
17746493Sabial	  log=fopen(logfile,"a");
17846493Sabial	  fprintf(log,"%s\n",msg);
17946493Sabial	  fclose(log);
18046493Sabial	} else
18146493Sabial	  printf("%s\n",msg);
18246493Sabial
18346493Sabial	/* This is for CGI scripts */
18446493Sabial	putenv(env_addr);
18546493Sabial	putenv(env_host);
18638589Sabial}
18738589Sabial
18846493Sabial/*
18946493Sabial * We have a connection.  Identify what type of request GET, HEAD, CGI, etc
19046493Sabial * and do what needs to be done
19146493Sabial */
19246493Sabialhttp_request()
19338589Sabial{
19446493Sabial	int             fd, lg, ld, i;
19546493Sabial	int             cmd = 0;
19646493Sabial	int             http1 = 0;
19746493Sabial	char           *p, *par;
19838589Sabial	char           *filename, *c;
19946493Sabial	struct stat     file_status;
20038589Sabial	char            req[1024];
20138589Sabial        char            msg[1024];
20246493Sabial	char            buff[8192];
20338589Sabial
20438589Sabial	lg = read(con_sock, req, 1024);
20538589Sabial
20638589Sabial        if (p=strstr(req,"\n")) *p=0;
20738589Sabial        if (p=strstr(req,"\r")) *p=0;
20838589Sabial
20946493Sabial	log_line(req);
21038589Sabial
21146493Sabial	c = strtok(req, " ");
21238589Sabial
21346493Sabial	/* Error msg if request is nothing */
21446493Sabial	if (c == NULL) {
21546493Sabial	  http_output(http_404[0]);
21646493Sabial	  http_output(http_404[1]);
21746493Sabial	  goto end_request;
21846493Sabial	}
21938589Sabial
22046493Sabial	if (strncmp(c, "GET", 3) == 0) cmd = 1;
22146493Sabial	if (strncmp(c, "HEAD", 4) == 0) cmd = 2;
22238589Sabial
22346493Sabial	/* Do error msg for any other type of request */
22446493Sabial	if (cmd == 0) {
22546493Sabial	  http_output(http_405[0]);
22646493Sabial	  http_output(http_405[1]);
22746493Sabial	  goto end_request;
22838589Sabial	}
22938589Sabial
23038589Sabial	filename = strtok(NULL, " ");
23138589Sabial
23238589Sabial	c = strtok(NULL, " ");
23346493Sabial	if (fetch_mode[0] != NULL) strcpy(filename,fetch_mode);
23438589Sabial	if (filename == NULL ||
23538589Sabial            strlen(filename)==1) filename="/index.html";
23638589Sabial
23746493Sabial	while (filename[0]== '/') filename++;
23846493Sabial
23946493Sabial        /* CGI handling.  Untested */
24038589Sabial        if (!strncmp(filename,"cgi-bin/",8))
24138589Sabial           {
24238589Sabial           par=0;
24338589Sabial           if (par=strstr(filename,"?"))
24438589Sabial              {
24538589Sabial               *par=0;
24638589Sabial                par++;
24738589Sabial              }
24838589Sabial           if (access(filename,X_OK)) goto conti;
24946493Sabial           stat (filename,&file_status);
25046493Sabial           if (setuid(file_status.st_uid)) return(0);
25146493Sabial           if (seteuid(file_status.st_uid)) return(0);
25238589Sabial           if (!fork())
25338589Sabial              {
25438589Sabial               close(1);
25538589Sabial               dup(con_sock);
25646493Sabial               //printf("HTTP/1.0 200 OK\nContent-type: text/html\n\n\n");
25746493Sabial	       printf("HTTP/1.0 200 OK\r\n");
25846493Sabial               /* Plug in environment variable, others in log_line */
25946493Sabial	       putenv("SERVER_SOFTWARE=FreeBSD/PicoBSD");
26046493Sabial
26179452Sbrian	       execlp (filename,filename,par,(char *)0);
26238589Sabial              }
26338589Sabial            wait(&i);
26438589Sabial            return(0);
26538589Sabial            }
26638589Sabial        conti:
26738589Sabial	if (filename == NULL) {
26846493Sabial	  http_output(http_405[0]);
26946493Sabial	  http_output(http_405[1]);
27046493Sabial	  goto end_request;
27138589Sabial	}
27246493Sabial	/* End of CGI handling */
27346493Sabial
27446493Sabial	/* Reject any request with '..' in it, bad hacker */
27538589Sabial	c = filename;
27638589Sabial	while (*c != '\0')
27746493Sabial	  if (c[0] == '.' && c[1] == '.') {
27846493Sabial	    http_output(http_404[0]);
27946493Sabial	    http_output(http_404[1]);
28046493Sabial	    goto end_request;
28146493Sabial	  } else
28246493Sabial	    c++;
28346493Sabial
28446493Sabial	/* Open filename */
28538589Sabial	fd = open(filename, O_RDONLY);
28638589Sabial	if (fd < 0) {
28746493Sabial	        http_output(http_404[0]);
28846493Sabial	        http_output(http_404[1]);
28946493Sabial		goto end_request;
29038589Sabial	}
29146493Sabial
29246493Sabial	/* Get file status information */
29346493Sabial	if (fstat(fd, &file_status) < 0) {
29446493Sabial	  http_output(http_404[0]);
29546493Sabial	  http_output(http_404[1]);
29646493Sabial	  goto end_request;
29738589Sabial	}
29846493Sabial
29946493Sabial	/* Is it a regular file? */
30046493Sabial	if (!S_ISREG(file_status.st_mode)) {
30146493Sabial	  http_output(http_404[0]);
30246493Sabial	  http_output(http_404[1]);
30346493Sabial	  goto end_request;
30446493Sabial	}
30538589Sabial
30646493Sabial	/* Past this point we are serving either a GET or HEAD */
30746493Sabial	/* Print all the header info */
30846493Sabial	http_output(http_200);
30946493Sabial	http_output(httpd_server_ident);
31046493Sabial	http_date();
31138589Sabial
31246493Sabial	sprintf(buff, "Content-length: %d\r\n", file_status.st_size);
31338589Sabial
31446493Sabial	if (strstr(filename,".txt")) {
31546493Sabial	  strcpy(buff,"Content-type: text/plain\r\n");
31646493Sabial	} else if (strstr(filename,".html") || strstr(filename,".htm")) {
31746493Sabial	    strcpy(buff,"Content-type: text/html\r\n");
31846493Sabial	} else if (strstr(filename,".gif")) {
31946493Sabial	  strcpy(buff,"Content-type: image/gif\r\n");
32046493Sabial	} else if (strstr(filename,".jpg")) {
32146493Sabial	  strcpy(buff,"Content-type: image/jpeg\r\n");
32246493Sabial	} else {
32346493Sabial	  /* Take a guess at content if we don't have something already */
32446493Sabial	  strcpy(buff,"Content-type: ");
32546493Sabial	  strcat(buff,strstr(filename,".")+1);
32646493Sabial	  strcat(buff,"\r\n");
32746493Sabial	}
32846493Sabial	write(con_sock, buff, strlen(buff));
32946493Sabial
33046493Sabial	strftime(buff, 50, "Last-Modified: %a, %d %h %Y %H:%M:%S %Z\r\n\r\n", gmtime(&file_status.st_mtime));
33146493Sabial	write(con_sock, buff, strlen(buff));
33238589Sabial
33346493Sabial	/* Send data only if GET request */
33438589Sabial	if (cmd == 1) {
33546493Sabial	  while (lg = read(fd, buff, 8192))
33646493Sabial	    write(con_sock, buff, lg);
33738589Sabial	}
33838589Sabial
33946493Sabialend_request:
34038589Sabial	close(fd);
34138589Sabial	close(con_sock);
34238589Sabial
34338589Sabial}
34438589Sabial
34546493Sabial/*
34646493Sabial * Simple httpd server for use in PicoBSD or other embedded application.
34746493Sabial * Should satisfy simple httpd needs.  For more demanding situations
34846493Sabial * apache is probably a better (but much larger) choice.
34946493Sabial */
35046493Sabialmain(int argc, char *argv[])
35138589Sabial{
35246493Sabial        extern char *optarg;
35346493Sabial        extern int optind;
35446493Sabial        int bflag, ch, fd, ld;
35546493Sabial        int             lg;
35646493Sabial	int             httpd_group = 65534;
35746493Sabial        pid_t server_pid;
35846493Sabial
35946493Sabial	/* Default for html directory */
36038589Sabial	strcpy (homedir,getenv("HOME"));
36138589Sabial        if (!geteuid()) strcpy (homedir,"/httphome");
36238589Sabial           else         strcat (homedir,"/httphome");
36338589Sabial
36446493Sabial	/* Defaults for log file */
36546493Sabial	if (geteuid()) {
36646493Sabial	    strcpy(logfile,getenv("HOME"));
36746493Sabial	    strcat(logfile,"/");
36846493Sabial	    strcat(logfile,"jhttp.log");
36946493Sabial	} else
37046493Sabial	  strcpy(logfile,"/var/log/jhttpd.log");
37138589Sabial
37246493Sabial	/* Parse command line arguments */
37346493Sabial	while ((ch = getopt(argc, argv, "d:f:g:l:p:vDh")) != -1)
37446493Sabial	  switch (ch) {
37546493Sabial	  case 'd':
37646493Sabial	    strcpy(homedir,optarg);
37746493Sabial	    break;
37846493Sabial	  case 'f':
37946493Sabial	    daemonize = 0;
38046493Sabial	    verbose = 1;
38146493Sabial	    strcpy(fetch_mode,optarg);
38246493Sabial	    break;
38346493Sabial	  case 'g':
38446493Sabial	    httpd_group = atoi(optarg);
38546493Sabial	    break;
38646493Sabial	  case 'l':
38746493Sabial	    strcpy(logfile,optarg);
38846493Sabial	    break;
38946493Sabial	  case 'p':
39046493Sabial	    http_port = atoi(optarg);
39146493Sabial	    break;
39246493Sabial	  case 'v':
39346493Sabial	    verbose = 1;
39446493Sabial	    break;
39546493Sabial	  case 'D':
39646493Sabial	    daemonize = 0;
39746493Sabial	    break;
39846493Sabial	  case '?':
39946493Sabial	  case 'h':
40046493Sabial	  default:
40146493Sabial	    printf("usage: simple_httpd [[-d directory][-g grpid][-l logfile][-p port][-vD]]\n");
40246493Sabial	    exit(1);
40346493Sabial	    /* NOTREACHED */
40446493Sabial	  }
40546493Sabial
40646493Sabial	/* Not running as root and no port supplied, assume 1080 */
40746493Sabial        if ((http_port == 80) && geteuid()) {
40846493Sabial	  http_port = 1080;
40946493Sabial	}
41046493Sabial
41146493Sabial	/* Do we really have rights in the html directory? */
41246493Sabial	if (fetch_mode[0] == NULL) {
41346493Sabial	  if (chdir(homedir)) {
41446493Sabial	    perror("chdir");
41546493Sabial	    puts(homedir);
41646493Sabial	    exit(1);
41746493Sabial	  }
41846493Sabial	}
41946493Sabial
42046493Sabial	/* Create log file if it doesn't exit */
42146493Sabial	if ((access(logfile,W_OK)) && daemonize) {
42246493Sabial	  ld = open (logfile,O_WRONLY);
42346493Sabial	  chmod (logfile,00600);
42446493Sabial	  close(ld);
42546493Sabial	}
42646493Sabial
42738589Sabial        init_servconnection();
42838589Sabial
42946493Sabial        if (verbose) {
43046493Sabial	  printf("Server started with options \n");
43146493Sabial	  printf("port: %d\n",http_port);
43246493Sabial	  if (fetch_mode[0] == NULL) printf("html home: %s\n",homedir);
43346493Sabial	  if (daemonize) printf("logfile: %s\n",logfile);
43446493Sabial	}
43538589Sabial
43646493Sabial	/* httpd is spawned */
43746493Sabial        if (daemonize) {
43846493Sabial	  if (server_pid = fork()) {
43946493Sabial	    wait3(0,WNOHANG,0);
44046493Sabial	    if (verbose) printf("pid: %d\n",server_pid);
44146493Sabial	    exit(0);
44246493Sabial	  }
44346493Sabial	  wait3(0,WNOHANG,0);
44446493Sabial	}
44538589Sabial
44646493Sabial	if (fetch_mode[0] == NULL) setpgrp(0,httpd_group);
44746493Sabial
44846493Sabial	/* How many connections do you want?
44946493Sabial	 * Keep this lower than the available number of processes
45046493Sabial	 */
45146493Sabial        if (listen(http_sock,15) < 0) exit(1);
45246493Sabial
45338589Sabial        label:
45446493Sabial	wait_connection();
45546493Sabial
45646493Sabial	if (fork()) {
45746493Sabial	  wait3(0,WNOHANG,0);
45846493Sabial	  close(con_sock);
45946493Sabial	  goto label;
46046493Sabial	}
46146493Sabial
46246493Sabial	http_request();
46346493Sabial
46446493Sabial	wait3(0,WNOHANG,0);
46546493Sabial	exit(0);
46638589Sabial}
46738589Sabial
46838589Sabial
46938589Sabialchar *adate()
47038589Sabial{
47138589Sabial        static char out[50];
47238589Sabial        long now;
47338589Sabial        struct tm *t;
47438589Sabial        time(&now);
47538589Sabial        t = localtime(&now);
47643939Sabial        sprintf(out, "%02d:%02d:%02d %02d/%02d/%02d",
47743939Sabial                     t->tm_hour, t->tm_min, t->tm_sec,
47843939Sabial                     t->tm_mday, t->tm_mon+1, t->tm_year );
47938589Sabial        return out;
48038589Sabial}
481