Article 2675 of comp.sources.unix:
Path: nntpd.lkg.dec.com!crl.dec.com!crl.dec.com!bloom-beacon.mit.edu!newsfeed.internetmci.com!uunet!in1.uu.net!news1.digital.com!vixie!vixie!not-for-mail
From: Thierry.Leconte@irisa.fr (Thierry Leconte)
Newsgroups: comp.sources.unix
Subject: v29i019: homehttpd2 - a very small and very simple http server (V2), Part01/01
Date: 2 Aug 1995 11:34:57 -0700
Organization: Vixie Enterprises
Lines: 529
Sender: vixie@vix.com
Approved: vixie@gw.home.vix.com
Message-ID: <1.807388478.3279@gw.home.vix.com>
NNTP-Posting-Host: gw.home.vix.com

Submitted-By: Thierry.Leconte@irisa.fr (Thierry Leconte)
Posting-Number: Volume 29, Issue 19
Archive-Name: homehttpd2/part01

	Here is the new and last version of homehttpd.

	homehttp is a very small and very simple http server.
	It answers only to GET and HEAD requests.

	I wrote it just for the fun but could help people who want to
	run a server just for some home pages.

	It works on SunOS 4.1.1 but must be easly portable to other platforms.

	The new version support HEAD requests, is more HTTP/1.0 complient 
	and correct a security problem.

		Thierry.Leconte@irisa.fr (Thierry Leconte)

#!/bin/sh
# This is a shell archive (produced by GNU shar 4.0).
# To extract the files from this archive, save it to some FILE, remove
# everything before the `!/bin/sh' line above, then type `sh FILE'.
#
#
# Existing files will *not* be overwritten unless `-c' is specified.
#
# This shar contains:
# length mode       name
# ------ ---------- ------------------------------------------
#   6861 -rw-r----- homehttpd/homehttpd.c
#   1550 -rw-r----- homehttpd/homehttpd.1
#   1798 -rw-r----- homehttpd/README
#
touch -am 1231235999 $$.touch >/dev/null 2>&1
if test ! -f 1231235999 && test -f $$.touch; then
  shar_touch=touch
else
  shar_touch=:
  echo 'WARNING: not restoring timestamps'
fi
rm -f 1231235999 $$.touch
#
# ============= homehttpd/homehttpd.c ==============
if test ! -d 'homehttpd'; then
  echo 'x - creating directory homehttpd'
  mkdir 'homehttpd'
fi
if test -f 'homehttpd/homehttpd.c' && test X"$1" != X"-c"; then
  echo 'x - skipping homehttpd/homehttpd.c (File already exists)'
else
  echo 'x - extracting homehttpd/homehttpd.c (text)'
  sed 's/^X//' << 'SHAR_EOF' > 'homehttpd/homehttpd.c' &&
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
X
/* Copyright (C) 1995, Thierry Leconte. All Rights Reserved. */
X
/*
X * Permission to use, copy, and distribute homehttpd in its entirety, for
X * non-commercial purposes, is hereby granted without fee, provided that
X * the copyright notice and the disclaimer of warranty appear in all copies.
*/
X
/*
X * Disclaimer of Warranty
X * 
X * These software programs are available to the user without any license fee or
X * royalty on an "as is" basis.  Thierry Leconte disclaims any and all
X * warranties, whether express, implied, or statuary, including any implied
X * warranties or merchantability or of fitness for a particular purpose.  In
X * no event shall the copyright-holder be liable for any incidental,
X * punitive, or consequential damages of any kind whatsoever arising from the
X * use of these programs.
X * 
X */
X
int             http_sock, con_sock;
int             http_port = 80;
struct sockaddr_in source;
char           *log_httpfile = NULL;
char           *homedir;
int             uid;
X
void
fermesock()
{
X	close(con_sock);
X	close(http_sock);
X
X	log_http("END on SIGNAL", "");
X	exit(0);
}
X
void
init_servconnection(void)
{
X	struct sockaddr_in server;
X
X	/* Creation socket */
X	http_sock = socket(AF_INET, SOCK_STREAM, 0);
X	if (http_sock < 0) {
X		perror("socket");
X		exit(1);
X	}
X	server.sin_family = AF_INET;
X	server.sin_port = htons(http_port);
X	server.sin_addr.s_addr = INADDR_ANY;
X	if (bind(http_sock, (struct sockaddr *) & server, sizeof(server)) < 0) {
X		perror("bind socket");
X		exit(1);
X	}
X	if (listen(http_sock, 5) < 0) {
X		perror("listen");
X		exit(1);
X	}
}
X
attenteconnection(void)
{
X	int             lg;
X
X	lg = sizeof(struct sockaddr_in);
X
X
X	con_sock = accept(http_sock, (struct sockaddr *) & source, &lg);
X	if (con_sock <= 0) {
X		perror("accept");
X		exit(1);
X	}
}
X
usage()
{
X	fprintf(stderr, "usage homehttpd [-l log_httpfile][-p port][-d homedir][-u uid]\n");
X	exit(1);
}
X
X
log_http(char *action, char *item)
{
X	int             fd;
X	char            logline[1024];
X	time_t          tl;
X
X	if (log_httpfile == NULL)
X		return;
X
X	tl = time(NULL);
X	strcpy(logline, ctime(&tl));
X	logline[strlen(logline) - 1] = ' ';
X	strcat(logline, inet_ntoa(source.sin_addr));
X	strcat(logline, " ");
X	strcat(logline, action);
X	strcat(logline, ":");
X	strcat(logline, item);
X	strcat(logline, "\n");
X
X
X	fd = open(log_httpfile, O_WRONLY | O_APPEND | O_CREAT, 0600);
X	if (fd < 0) {
X		perror("open log file");
X		exit(1);
X	}
X	write(fd, logline, strlen(logline));
X	close(fd);
}
X
outdate()
{
X	time_t          tl;
X	char            buff[50];
X
X	tl = time(NULL);
X	strftime(buff, 50, "Date: %a, %d %h %Y %H:%M:%S %Z\r\n", gmtime(&tl));
X	write(con_sock, buff, strlen(buff));
}
X
char           *rep_err_nget[2] = {"<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 405</H1>\
This server answer only to GETand HEAD  requests\n</BODY></HTML>\r\n",
"HTTP/1.0 405 Method Not allowed\r\nAllow: GET,HEAD\r\nServer: homehttpd\r\n"};
char           *rep_err_acc[2] = {"<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 404</H1>\
Not found - file doesn't exist or is read protected\n</BODY></HTML>\r\n",
"HTTP/1.0 404 Not found\r\nServer: homehttpd\r\n"};
X
outerror(char **rep, int http1)
{
X
X	if (http1) {
X		write(con_sock, rep[1], strlen(rep[1]));
X		outdate();
X		write(con_sock, "\r\n", 2);
X	}
X	write(con_sock, rep[0], strlen(rep[0]));
}
X
char            rep_head[] = "HTTP/1.0 200 OK\r\nServer: homehttpd\r\n";
X
traite_req()
{
X	char            buff[8192];
X	int             fd, lg, cmd, http1;
X	char           *filename, *c;
X	struct stat     statres;
X	char            req[1024];
X
X	lg = read(con_sock, req, 1024);
X
X	if (req[lg - 1] != '\n' || req[lg - 2] != '\r') {
X		outerror(rep_err_nget, 0);
X		log_http("error 405", req);
X		goto error;
X	}
X	req[lg - 2] = '\0';
X
X	c = strtok(req, " ");
X	if (c == NULL) {
X		outerror(rep_err_nget, 0);
X		log_http("error 405", req);
X		goto error;
X	}
X	cmd = 0;
X	if (strncmp(c, "GET", 3) == 0)
X		cmd = 1;
X	if (strncmp(c, "HEAD", 3) == 0)
X		cmd = 2;
X
X	filename = strtok(NULL, " ");
X
X	http1 = 0;
X	c = strtok(NULL, " ");
X	if (c != NULL && strncmp(c, "HTTP", 4) == 0)
X		http1 = 1;
X
X	if (cmd == 0) {
X		outerror(rep_err_nget, http1);
X		log_http("error 405", req);
X		goto error;
X	}
X	if (filename == NULL) {
X		outerror(rep_err_acc, http1);
X		log_http("error 404", filename);
X		goto error;
X	}
X	/* supprime les / en tete */
X	while (filename[0] == '/')
X		filename++;
X
X	if (filename == NULL) {
X		outerror(rep_err_acc, http1);
X		log_http("error 404", filename);
X		goto error;
X	}
X	/* interdit les .. dans le path */
X	c = filename;
X	while (*c != '\0')
X		if (c[0] == '.' && c[1] == '.') {
X			outerror(rep_err_acc, http1);
X			log_http("error 404", filename);
X			goto error;
X		} else
X			c++;
X
X	fd = open(filename, O_RDONLY);
X	if (fd < 0) {
X		outerror(rep_err_acc, http1);
X		log_http("error 404", filename);
X		goto error;
X	}
X	if (fstat(fd, &statres) < 0) {
X		outerror(rep_err_acc, http1);
X		log_http("error 404", filename);
X		goto error;
X	}
X	if (statres.st_uid != uid ||
X	    !S_ISREG(statres.st_mode) ||
X	    !(statres.st_mode & S_IROTH)) {
X		outerror(rep_err_acc, http1);
X		log_http("error 404", filename);
X		goto error;
X	}
X	if (http1) {
X		char            buff[50];
X		time_t          tl;
X
X		write(con_sock, rep_head, strlen(rep_head));
X		sprintf(buff, "Contents-lenght: %d\r\n", statres.st_size);
X		write(con_sock, buff, strlen(buff));
X		outdate();
X		strftime(buff, 50, "Last-Modified: %a, %d %h %Y %H:%M:%S %Z\r\n\r\n", gmtime(&statres.st_mtime));
X		write(con_sock, buff, strlen(buff));
X	}
X	if (cmd == 1) {
X		while (lg = read(fd, buff, 8192))
X			write(con_sock, buff, lg);
X		log_http("GET", filename);
X	} else
X		log_http("HEAD", filename);
X
error:
X	close(fd);
X	close(con_sock);
X
}
X
main(int argc, char **argv)
{
X	int             lg;
X
X	uid = getuid();
X
X	argc--;
X	argv++;
X	while (argc > 0) {
X		if (argv[0][0] != '-')
X			usage();
X		switch (argv[0][1]) {
X		case 'l':
X			log_httpfile = argv[1];
X			argv += 2;
X			argc -= 2;
X			break;
X		case 'p':
X			http_port = atoi(argv[1]);
X			argv += 2;
X			argc -= 2;
X			break;
X		case 'u':
X			uid = atoi(argv[1]);
X			argv += 2;
X			argc -= 2;
X			break;
X		case 'd':
X			homedir = argv[1];
X			argv += 2;
X			argc -= 2;
X			break;
X		default:
X			usage();
X		}
X	}
X
X	if (homedir == NULL)
X		homedir = getenv("HOME");
X
X	if (chdir(homedir) < 0) {
X		perror("chdir");
X		exit(1);
X	}
X	signal(SIGTERM, fermesock);
X	signal(SIGQUIT, fermesock);
X	signal(SIGINT, fermesock);
X
X	/* appelle par inetd ? */
X	con_sock = 0;
X	if (getpeername(con_sock, (struct sockaddr *) & source, &lg) == 0) {
X		traite_req();
X		return (0);
X	}
X	init_servconnection();
X
X	do {
X		attenteconnection();
X		traite_req();
X	} while (1);
X
}
SHAR_EOF
  $shar_touch -am 0802150195 'homehttpd/homehttpd.c' &&
  chmod 0640 'homehttpd/homehttpd.c' ||
  echo 'restore of homehttpd/homehttpd.c failed'
  shar_count="`wc -c < 'homehttpd/homehttpd.c'`"
  test 6861 -eq "$shar_count" ||
    echo "homehttpd/homehttpd.c: original size 6861, current size $shar_count"
fi
# ============= homehttpd/homehttpd.1 ==============
if test -f 'homehttpd/homehttpd.1' && test X"$1" != X"-c"; then
  echo 'x - skipping homehttpd/homehttpd.1 (File already exists)'
else
  echo 'x - extracting homehttpd/homehttpd.1 (text)'
  sed 's/^X//' << 'SHAR_EOF' > 'homehttpd/homehttpd.1' &&
X.PU
X.TH HOMEHTTPD 1 local
X.SH NAME
homehttpd a very small and simple http server
X.SH SYNOPSIS
X.ll +8
X.B homehttpd
X.RB [ \-d\ dirname " ]
X.RB [ \-l\ log_filename ]
X.RB [ \-p\ portnumber]
X.RB [ \-u\ uid ]
X.SH DESCRIPTION
X.I homehttpd 
is a very small and very simple http server.
X.PP
It answers only to GETand HEAD requests.
X.PP
I wrote it just for the fun but could help people who want to
run a server just for some home pages.
X
X.SH OPTIONS
X.TP
X.B \-d dirname
By default
X.I homehttpd
serves files in $HOME dir. The -d option allows to give an other home directory.
X.TP
X.B \-l log_filename
This option activate the use of a log of incoming requests.
X.TP
X.B \-p portnumber
Port numbers under 1024 are "reserved port".
As the default port for www is 80 , you must have root access to
launch
X.I homehttpd,
unless you use this option to change the listening port number.
(Don't forget in this case to give the good URL 
X.I http://machine:portnumber/file
).
X.TP
X.B \-u uid
By default,
X.I homehttp
serves only files with owner uid = uid of the homehttpd user.
You can change that by use of the -u command.
X
X
X.SH "ENVIRONMENT"
The environment variable
X.B $HOME
is used by default as the home directory if no -d option is used.
X
X.SH EXEMPLES
homehttpd -d $HOME/homepage -p 2000 -l http_log &
X.PP
Will run in the background a http server on port 2000 which will serve
html pages in the directory $HOME/homepage.
X.PP
The URL to give in this case is
X.I http://achinename:2000/home.html
for the
X.I $HOME/homepage/home.html
page.
X
X.SH AUTHOR
Thierry Leconte 1994
X
SHAR_EOF
  $shar_touch -am 0802145395 'homehttpd/homehttpd.1' &&
  chmod 0640 'homehttpd/homehttpd.1' ||
  echo 'restore of homehttpd/homehttpd.1 failed'
  shar_count="`wc -c < 'homehttpd/homehttpd.1'`"
  test 1550 -eq "$shar_count" ||
    echo "homehttpd/homehttpd.1: original size 1550, current size $shar_count"
fi
# ============= homehttpd/README ==============
if test -f 'homehttpd/README' && test X"$1" != X"-c"; then
  echo 'x - skipping homehttpd/README (File already exists)'
else
  echo 'x - extracting homehttpd/README (text)'
  sed 's/^X//' << 'SHAR_EOF' > 'homehttpd/README' &&
X
homehttp is a very small and very simple http server.
It answers only to GET and HEAD requests and serve only file with o+r rights
and without .. in the path.
X
I wrote it just for the fun but could help people who want to
run a server just for some home pages.
X
It works on SunOS 4.1.1 but must be easly portable to other platforms.
Just compile it by acc homehttpd.c -o homehttpd
X
usage :
X homehttpd [-l log_filename][-p port][-d homedir][-u uid]
X
By default it serves files in HOME dir but the -d option can change that.
X
Use  -l if you want a log of incomming requests.
X
Port numbers under 1024 are "reserved port".
As the default port for www is 80 , you must have root access to launch homehttpd 
unless you use the -p option to change the listening port number.
(Don't forget in this case to give a good URL like http://machine:portnumber/file).
X
By default, homehttp serves only files with owner uid = uid of the homehttpd user.
You can change that by use of the -u command.
X
/* Copyright (C) 1995, Thierry Leconte. All Rights Reserved. */
X
/*
X * Permission to use, copy, and distribute homehttpd in its entirety, for
X * non-commercial purposes, is hereby granted without fee, provided that
X * the copyright notice and the disclaimer of warranty appear in all copies.
*/
X
/*
X * Disclaimer of Warranty
X *
X * These software programs are available to the user without any license fee or
X * royalty on an "as is" basis.  Thierry Leconte disclaims any and all
X * warranties, whether express, implied, or statuary, including any implied
X * warranties or merchantability or of fitness for a particular purpose.  In
X * no event shall the copyright-holder be liable for any incidental,
X * punitive, or consequential damages of any kind whatsoever arising from the
X * use of these programs.
X *
X */
X
X
SHAR_EOF
  $shar_touch -am 0802150195 'homehttpd/README' &&
  chmod 0640 'homehttpd/README' ||
  echo 'restore of homehttpd/README failed'
  shar_count="`wc -c < 'homehttpd/README'`"
  test 1798 -eq "$shar_count" ||
    echo "homehttpd/README: original size 1798, current size $shar_count"
fi
exit 0


