diff -urN boa-0.93.16.2/src/Changelog boa-0.93.17.1/src/Changelog
--- boa-0.93.16.2/src/Changelog	Wed Jun 30 13:44:23 1999
+++ boa-0.93.17.1/src/Changelog	Sat Jul 10 23:09:04 1999
@@ -1,3 +1,7 @@
+** Changes from 0.93.16.2 to 0.93.17.1
+ * New config file parser (supposed to be more maintainable) (LRD)
+ * Support for "|command" and ":host:port" syntax for logfiles (untested) (LRD)
+
 ** Changes for the 0.93 version **
  * Huge quantities of changes
  * keepalive Bugfix in 0.93.16.2 by Jon Nelson
diff -urN boa-0.93.16.2/src/Makefile.in boa-0.93.17.1/src/Makefile.in
--- boa-0.93.16.2/src/Makefile.in	Fri Dec 25 20:35:12 1998
+++ boa-0.93.17.1/src/Makefile.in	Sat Jul 10 22:13:55 1999
@@ -21,7 +21,7 @@
 
 SOURCES = alias.c boa.c cgi.c config.c get.c hash.c log.c \
 	queue.c read.c request.c response.c signals.c util.c \
-	cgi_header.c pipe.c mmap_cache.c
+	cgi_header.c pipe.c mmap_cache.c sublog.c
 	
 OBJS = y.tab.o lex.yy.o ${SOURCES:.c=.o} timestamp.o
 
diff -urN boa-0.93.16.2/src/boa.h boa-0.93.17.1/src/boa.h
--- boa-0.93.16.2/src/boa.h	Wed Jun 30 13:44:23 1999
+++ boa-0.93.17.1/src/boa.h	Sat Jul 10 22:45:46 1999
@@ -186,7 +186,10 @@
 /* timestamp.c */
 void timestamp(void);
 
+/* mmap_cache.c */
 struct mmap_entry *find_mmap(int data_fd, struct stat *s);
 void release_mmap(struct mmap_entry *e);
 
+/* sublog.c */
+int open_gen_fd(char *spec);
 #endif
diff -urN boa-0.93.16.2/src/boa_grammar.y boa-0.93.17.1/src/boa_grammar.y
--- boa-0.93.16.2/src/boa_grammar.y	Wed Jun 30 13:44:23 1999
+++ boa-0.93.17.1/src/boa_grammar.y	Sat Jul 10 22:40:31 1999
@@ -23,46 +23,35 @@
 #include <string.h>
 #include <stdio.h>
 #include <stdlib.h>
-#ifdef HAVE_UNISTD_H
 #include <unistd.h>
-#endif
-#include <sys/socket.h> /* for SO_MAXCONN */
-#include <pwd.h>		/* struct passwd */
-#include <grp.h>		/* struct group */
-#include "globals.h"
+/* #include "boa.h" */
+#include "parse.h"
 
 int yyerror(char * msg);
 
-void add_alias(char * fakename, char * realname, int script);
-void add_mime_type(char * extension, char * type);
+/* yydebug = 1; */
 
-struct passwd * passwdbuf;
-struct group * groupbuf;
-char mime_type[256];		/* global to inherit */
-char fakename[256];		/* ScriptAlias */
+#ifdef DEBUG
+#define DBG(x) x
+#else
+#define DBG(x)
+#endif
 
-/* yydebug = 1; */
+char *arg1hold;
+char mime_type[256];            /* global to inherit */
 
 %}
 
 %union {
     char *	sval;
     int		ival;
+    struct ccommand * cval;
 };
 
 /* boa.conf tokens */
-%token B_PORT B_BACKLOG B_USER B_GROUP
-%token B_SERVERADMIN B_SERVERROOT B_ERRORLOG B_ACCESSLOG
-%token B_CGILOG B_VERBOSECGILOGS
-%token B_SERVERNAME
-
-/* srm.conf tokens */
-%token B_DOCUMENTROOT B_USERDIR B_DIRECTORYINDEX
-%token B_DIRECTORYMAKER
-%token B_MIMETYPES B_DEFAULTTYPE B_ADDTYPE
-%token B_ALIAS B_SCRIPTALIAS B_REDIRECT 
-%token B_KEEPALIVEMAX B_KEEPALIVETIMEOUT
+%token <cval> STMT_NO_ARGS STMT_ONE_ARG STMT_TWO_ARGS
 
+/* mime.type tokens */
 %token <sval> MIMETYPE
 %token <sval> STRING
 %token <ival> INTEGER
@@ -79,186 +68,37 @@
 	;
 
 BoaConfigStmt:		
-			PortStmt
-	|		BackLogStmt
-	|		UserStmt	
-	|		GroupStmt
-	|		ServerAdminStmt
-	|		ServerRootStmt
-	|		ErrorLogStmt
-	|		AccessLogStmt
-	|		CgiLogStmt
-	|		VerboseCGILogsStmt
-	|		ServerNameStmt
-	|		DocumentRootStmt
-	|		UserDirStmt
-	|		DirectoryIndexStmt
-	|		DirectoryMakerStmt
-	|		KeepAliveMaxStmt
-	|		KeepAliveTimeoutStmt
-	|		DefaultTypeStmt
-	|		MimeTypesStmt
-	|		AddTypeStmt
-	|		AliasStmt
-	|		ScriptAliasStmt
-	|		RedirectStmt
-	;
-
-PortStmt:		B_PORT INTEGER
-		{ server_port = $2; }
-	;
-
-BackLogStmt:		B_BACKLOG INTEGER
-		{ backlog = $2; 
-		  if (backlog < 1 || backlog > SO_MAXCONN)
-		    backlog = SO_MAXCONN;
-		}
-	;
-
-UserStmt:		B_USER STRING
-		{ passwdbuf = getpwnam($2);
-		  if(!passwdbuf) {
-		    fprintf(stderr, "No such user: %s\n", $2);
-		    exit(1);
-		  }
-		  server_uid = passwdbuf->pw_uid; 
-		}
-	|		B_USER INTEGER
-		{ server_uid = $2; }
-	;
-
-GroupStmt:		B_GROUP STRING
-		{ groupbuf = getgrnam($2);
-		  if(!groupbuf) {
-		    fprintf(stderr, "No such group: %s\n", $2);
-		    exit(1);
-		  }
-		  server_gid = groupbuf->gr_gid;
-		}
-	|		B_GROUP INTEGER
-		{ server_gid = $2; }
-	;
-
-ServerAdminStmt:	B_SERVERADMIN STRING
-		{ if(server_admin)
-			free(server_admin);
-		  server_admin = strdup($2); 
-		}
-	;
-
-ServerRootStmt:		B_SERVERROOT STRING
-		{ if(server_root)
-			free(server_root);
-		  server_root = strdup($2); 
-		  if(chdir(server_root) == -1) {
-		      fprintf(stderr, "Could not chdir to ServerRoot.\n");
-		      exit(1);
-		  } 
-		}
+			StmtNoArgs
+	|		StmtOneArg
+	|		StmtTwoArgs
 	;
 
-ErrorLogStmt:		B_ERRORLOG STRING
-		{ if(error_log_name)
-			free(error_log_name);
-		  error_log_name = strdup($2); 
-		}
-	;
-
-AccessLogStmt:		B_ACCESSLOG STRING
-		{ if(access_log_name)
-			free(access_log_name);
-		  access_log_name = strdup($2); 
-		}
-	;
-	
-CgiLogStmt:			B_CGILOG STRING
-		{ if(cgi_log_name)
-			free(cgi_log_name);
-		  cgi_log_name = strdup($2);
-		}
-	;
-	
-VerboseCGILogsStmt:	B_VERBOSECGILOGS
-		{ 
-		  verbose_cgi_logs = 1;
+StmtNoArgs:		STMT_NO_ARGS
+		{ if ($1->action) {
+			DBG(printf("StmtNoArgs: %s\n",$1->name);)
+			$1->action(NULL,NULL,$1->object);
 		 }
-	;
-
-ServerNameStmt:		B_SERVERNAME STRING
-		{ if(server_name)
-			free(server_name);
-		  server_name = strdup($2); 
-		}
-	;
-
-DocumentRootStmt:	B_DOCUMENTROOT STRING
-		{ if(document_root)
-			free(document_root);
-		  document_root = strdup($2); 
-		}
-	;
-
-UserDirStmt:		B_USERDIR STRING
-		{ if(user_dir)
-			free(user_dir);
-		  user_dir = strdup($2); 
-		}
-	;
-
-DirectoryIndexStmt:	B_DIRECTORYINDEX STRING
-		{ if(directory_index)
-			free(directory_index);
-		  directory_index = strdup($2); 
-		}
-	;
-
-DirectoryMakerStmt: B_DIRECTORYMAKER STRING
-		{ if (dirmaker)
-			free(dirmaker);
-		  dirmaker = strdup($2);
 		}
 	;
 
-KeepAliveMaxStmt:		B_KEEPALIVEMAX INTEGER
-		{ ka_max = $2; }
-	;
-
-KeepAliveTimeoutStmt:		B_KEEPALIVETIMEOUT INTEGER
-		{ ka_timeout = $2; }
-	;
-
-MimeTypesStmt:	B_MIMETYPES STRING
-		{ if (mime_types)
-			free(mime_types);
-		  mime_types = strdup($2);
+StmtOneArg:		STMT_ONE_ARG STRING
+		{ if ($1->action) {
+			DBG(printf("StmtOneArg: %s %s\n",$1->name,$2);)
+			$1->action($2,NULL,$1->object);
+		 }
 		}
 	;
 
-DefaultTypeStmt:	B_DEFAULTTYPE STRING
-		{ if(default_type)
-			free(default_type);
-		  default_type = strdup($2); 
+StmtTwoArgs:		STMT_TWO_ARGS STRING
+		{ arg1hold = strdup($2); }
+			 STRING
+		{ if ($1->action) {
+			DBG(printf("StmtTwoArgs: '%s' '%s' '%s'\n",
+			            $1->name,arg1hold,$4);)
+			$1->action($4,arg1hold,$1->object);
+		  }
+		  free(arg1hold);
 		}
-	;
-
-AddTypeStmt:		B_ADDTYPE MimeTypeStmt
-	;
-
-AliasStmt:		B_ALIAS STRING
-		{ strcpy(fakename, $2); }
-			STRING
-		{ add_alias(fakename, $4, ALIAS); }
-
-ScriptAliasStmt:	B_SCRIPTALIAS STRING 
-		{ strcpy(fakename, $2); }
-			STRING
-		{ add_alias(fakename, $4, SCRIPTALIAS); }
-	;
-
-RedirectStmt:		B_REDIRECT STRING 
-		{ strcpy(fakename, $2); }
-			STRING
-		{ add_alias(fakename, $4, REDIRECT); }
 	;
 
 
diff -urN boa-0.93.16.2/src/boa_lexer.l boa-0.93.17.1/src/boa_lexer.l
--- boa-0.93.16.2/src/boa_lexer.l	Wed Jun 30 13:44:23 1999
+++ boa-0.93.17.1/src/boa_lexer.l	Sat Jul 10 22:41:57 1999
@@ -22,20 +22,30 @@
 
 #include "y.tab.h"
 #include <stdlib.h>
-#ifdef HAVE_UNISTD_H
 #include <unistd.h>
-#endif
+#include "parse.h"
+
+#define MAX_STR_CONST 1024
+#define qspush(c) \
+	if (string_buf_ptr < string_buf+MAX_STR_CONST) \
+		*string_buf_ptr++ = c; \
+	else \
+		yyerror("quoted string overflow");
 
 char *mime_types = NULL;
 
 static int file = 0;
 int lineno = 1;
+struct ccommand *k; 
+char string_buf[MAX_STR_CONST];
+char *string_buf_ptr;
 %}
 
-%a 4000
-%p 4000
-
 %s MIME
+/* Quoted string handling (almost) straight out of
+   the flex 2.5 man page, April 1995 */
+%x str
+
 %%
 
 [ \t]+		;
@@ -43,34 +53,67 @@
 
 <MIME>[^ \t\n]+\/[^ \t\n]+	{ yylval.sval = yytext; return MIMETYPE; }
 
-Port		{ BEGIN INITIAL; return B_PORT; }
-BackLog         { BEGIN INITIAL; return B_BACKLOG; }
-User		{ BEGIN INITIAL; return B_USER; }
-Group		{ BEGIN INITIAL; return B_GROUP; }
-ServerAdmin	{ BEGIN INITIAL; return B_SERVERADMIN; }
-ServerRoot	{ BEGIN INITIAL; return B_SERVERROOT; }
-ErrorLog	{ BEGIN INITIAL; return B_ERRORLOG; }
-AccessLog	{ BEGIN INITIAL; return B_ACCESSLOG; }
-CgiLog		{ BEGIN INITIAL; return B_CGILOG; }
-VerboseCGILogs	{ BEGIN INITIAL; return B_VERBOSECGILOGS; }
-ServerName	{ BEGIN INITIAL; return B_SERVERNAME; }
-Redirect	{ BEGIN INITIAL; return B_REDIRECT; }
-
-DocumentRoot	{ BEGIN INITIAL; return B_DOCUMENTROOT; }
-UserDir		{ BEGIN INITIAL; return B_USERDIR; }
-DirectoryIndex	{ BEGIN INITIAL; return B_DIRECTORYINDEX; }
-DirectoryMaker	{ BEGIN INITIAL; return B_DIRECTORYMAKER; }
-KeepAliveMax 	{ BEGIN INITIAL; return B_KEEPALIVEMAX; }
-KeepAliveTimeout { BEGIN INITIAL; return B_KEEPALIVETIMEOUT; }
-MimeTypes	{ BEGIN INITIAL; return B_MIMETYPES; }
-DefaultType	{ BEGIN INITIAL; return B_DEFAULTTYPE; }
-AddType		{ BEGIN MIME; return B_ADDTYPE; }
-ScriptAlias	{ BEGIN INITIAL; return B_SCRIPTALIAS; }
-Alias		{ BEGIN INITIAL; return B_ALIAS; }
+[^ \"\t\n]+	{ /* XXX could use better checks that we are in a state to
+		   * accept keywords; this version matches original behavior */
+		  if ((YYSTATE==INITIAL) && (k=lookup_keyword(yytext))) {
+		      yylval.cval=k;
+		      return (k->type);
+		  } else { yylval.sval = yytext; return STRING; }
+                }
+
+\"	{
+	string_buf_ptr = string_buf;
+	BEGIN(str);
+	}
+
+<str>{
+\"	{ /* saw closing quote - all done */
+	BEGIN(INITIAL);
+	*string_buf_ptr = '\0';
+	/* return string constant token type and value to parser */
+	yylval.sval = string_buf; return STRING;
+	}
+
+\n	{
+	/* error - unterminated string constant */
+	/* generate error message */
+	yyerror("unterminated string constant");
+	}
 
--?[0-9]+	{ yylval.ival = atoi(yytext); return INTEGER; }
-[^ \t\n]+	{ yylval.sval = yytext; return STRING; }
+\\[0-7]{1,3}  {
+	/* octal escape sequence */
+	int result;
 
+	(void) sscanf( yytext + 1, "%o", &result );
+
+	if ( result > 0xff )
+		{ /* error, constant is out-of-bounds */ }
+
+	qspush(result);
+	}
+
+\\[0-9]+  {
+	/* generate error - bad escape sequence; something
+	 * like '\48' or '\0777777'
+	 */
+	yyerror("bad escape sequence");
+	}
+
+\\n	qspush('\n');
+\\t	qspush('\t');
+\\r	qspush('\r');
+\\b	qspush('\b');
+\\f	qspush('\f');
+
+\\(.|\n)  *string_buf_ptr++ = yytext[1];
+
+[^\\\n\"]+  {
+	char *yptr = yytext;
+	while ( *yptr )
+		qspush(*yptr++);
+	}
+}
+	/* End of <str> dependence */
 \n		{ lineno++; }
 %%
 
@@ -86,17 +129,15 @@
 
     switch(file) {
       case 1:
-        /* if mime_types is not defined, fall through to done-reading case */
-        if ( mime_types ) {
-  	  yyin = fopen(mime_types, "r");
-  	  if(!yyin) {
+	printf("Using %s as mime.types file.\n", mime_types);
+	yyin = fopen(mime_types, "r");
+	if(!yyin) {
 	    fprintf(stderr, "Could not open mime.types file, \"%s\", "
 	    	"for reading\n", mime_types);
 	    exit(1);
 	}
 	BEGIN MIME;
 	return 0;
-        }
       default:
 	BEGIN INITIAL;
 	file = 0;		/* in case we reread config files */
diff -urN boa-0.93.16.2/src/config.c boa-0.93.17.1/src/config.c
--- boa-0.93.16.2/src/config.c	Wed Jun 30 13:44:23 1999
+++ boa-0.93.17.1/src/config.c	Sat Jul 10 22:50:55 1999
@@ -1,6 +1,6 @@
 /*
  *  Boa, an http server
- *  Copyright (C) 1995 Paul Phillips <psp@well.com>
+ *  Copyright (C) 1999 Larry Doolittle <ldoolitt@boa.org>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
@@ -20,11 +20,26 @@
 
 /* boa: config.c */
 
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include "y.tab.h"
+#include "parse.h"
 #include "boa.h"
-#include <netdb.h>
 
+extern FILE *yyin;
 int yyparse(void);				/* Better match the output of lex */
 
+#ifdef DEBUG
+#define DBG(x) x
+#else
+#define DBG(x)
+#endif
+
+/* these came from config.c */
 int server_port;
 uid_t server_uid;
 gid_t server_gid;
@@ -42,24 +57,168 @@
 int ka_timeout;
 int ka_max;
 
+/* These came from log.c */
+char *error_log_name;
+char *access_log_name;
+char *cgi_log_name;
+
+/* These come from boa_grammar.y */
+void add_alias(char * fakename, char * realname, int script);
+void add_mime_type(char * extension, char * type);
+
+/* These are new */
+void c_set_user  (char *v1, char *v2, void *t);
+void c_set_group (char *v1, char *v2, void *t);
+void c_set_string(char *v1, char *v2, void *t);
+void c_set_int   (char *v1, char *v2, void *t);
+void c_set_unity (char *v1, char *v2, void *t);
+void c_add_type  (char *v1, char *v2, void *t);
+void c_add_alias (char *v1, char *v2, void *t);
+
+/* Fakery to keep the value passed to action() a void *,
+   see usage in table and c_add_alias() below */
+int script_number   = SCRIPTALIAS;
+int redirect_number = REDIRECT;
+int alias_number    = ALIAS;
+
+/* Help keep the table below compact */
+#define S0A STMT_NO_ARGS
+#define S1A STMT_ONE_ARG
+#define S2A STMT_TWO_ARGS
+
+struct ccommand clist[] = {
+	{ "Port",             S1A, c_set_int,      &server_port },
+	{ "BackLog",          S1A, c_set_int,      &backlog },
+	{ "User",             S1A, c_set_user,     NULL },
+	{ "Group",            S1A, c_set_group,    NULL },
+	{ "ServerAdmin",      S1A, c_set_string,   &server_admin },
+	{ "ServerRoot",       S1A, c_set_string,   &server_root },
+	{ "ErrorLog",         S1A, c_set_string,   &error_log_name },
+	{ "AccessLog",        S1A, c_set_string,   &access_log_name },
+	{ "CgiLog",           S1A, c_set_string,   &cgi_log_name },
+	{ "VerboseCGILogs",   S0A, c_set_unity,    &verbose_cgi_logs },
+	{ "ServerName",       S1A, c_set_string,   &server_name },
+	{ "VirtualHost",      S0A, c_set_unity,    &virtualhost },
+	{ "DocumentRoot",     S1A, c_set_string,   &document_root },
+	{ "UserDir",          S1A, c_set_string,   &user_dir },
+	{ "DirectoryIndex",   S1A, c_set_string,   &directory_index },
+	{ "DirectoryMaker",   S1A, c_set_string,   &dirmaker },
+	{ "KeepAliveMax",     S1A, c_set_int,      &ka_max },
+	{ "KeepAliveTimeout", S1A, c_set_int,      &ka_timeout },
+	{ "MimeTypes",        S1A, c_set_string,   &mime_types },
+	{ "DefaultType",      S1A, c_set_string,   &default_type },
+	{ "AddType",          S2A, c_add_type,     NULL },
+	{ "ScriptAlias",      S2A, c_add_alias,    &script_number },
+	{ "Redirect",         S2A, c_add_alias,    &redirect_number },
+	{ "Alias",            S2A, c_add_alias,    &alias_number }
+};
+
+void c_set_user  (char *v1, char *v2, void *t)
+{
+	struct passwd * passwdbuf;
+	char *endptr;
+	int i;
+	DBG(printf("User %s = ", v1);)
+	i = strtol(v1, &endptr, 0);
+	if (*v1 != '\0' && *endptr == '\0') {
+		server_uid = i;
+	} else {
+		passwdbuf = getpwnam(v1);
+		if(!passwdbuf) {
+			fprintf(stderr, "No such user: %s\n", v1);
+			exit(1);
+		}
+		server_uid = passwdbuf->pw_uid;
+	}
+	DBG(printf("%d\n", server_uid);)
+}
+
+void c_set_group (char *v1, char *v2, void *t)
+{
+	struct group * groupbuf;
+	char *endptr;
+	int i;
+	DBG(printf("Group %s = ", v1);)
+	i = strtol(v1, &endptr, 0);
+	if (*v1 != '\0' && *endptr == '\0') {
+		server_gid = i;
+	} else {
+		groupbuf = getgrnam(v1);
+		if(!groupbuf) {
+			fprintf(stderr, "No such group: %s\n", v1);
+			exit(1);
+		}
+		server_gid = groupbuf->gr_gid;
+	}
+	DBG(printf("%d\n", server_gid);)
+}
+
+void c_set_string(char *v1, char *v2, void *t)
+{
+	char *s;
+	DBG(printf("Setting pointer %p to string %s ..", t, v1);)
+	if (t) {
+		s=*(char **)t;
+		if (s) free(s);
+		*(char **)t = strdup(v1);
+		DBG(printf("done.\n");)
+	} else {
+		DBG(printf("skipped.\n");)
+	}
+}
+
+void c_set_int   (char *v1, char *v2, void *t){
+	char *endptr;
+	int i;
+	DBG(printf("Setting pointer %p to integer string %s ..", t, v1);)
+	if (t) {
+		i=strtol(v1, &endptr, 0); /* Automatic base 10/16/8 switching */
+		if (*v1 != '\0' && *endptr == '\0') {
+			*(int *)t = i;
+			DBG(printf(" Integer converted as %d, done\n",i);)
+		} else {
+			/* XXX should tell line number to user */
+			fprintf(stderr, "Error: %s found where integer expected\n",v1);
+		}
+	} else {
+		DBG(printf("skipped.\n");)
+	}
+}
+
+void c_set_unity (char *v1, char *v2, void *t)
+{
+	DBG(printf ("Setting pointer %p to unity\n", t);)
+	if (t) *(int *)t = 1;
+}
+
+void c_add_type  (char *v1, char *v2, void *t)
+{
+	add_mime_type(v1,v2);
+}
+
+void c_add_alias (char *v1, char *v2, void *t)
+{
+	add_alias(v1, v2, *(int *)t);
+}
+
+struct ccommand *lookup_keyword(char *c)
+{
+	struct ccommand *p;
+	DBG(printf("Checking string '%s' against keyword list\n",c);)
+	for (p=clist; p<clist+(sizeof(clist)/sizeof(struct ccommand)); p++) {
+		if (strcmp(c,p->name)==0) return p;
+	}
+	return NULL;
+}
+
 /*
  * Name: read_config_files
- *
- * Description: Reads config files via yyparse, then makes sure that
+ * 
+ * Description: Reads config files via yyparse, then makes sure that 
  * all required variables were set properly.
  */
-
-void read_config_files()
+void read_config_files(void)
 {
-	char hostnamebuf[MAX_SITENAME_LENGTH + 1];
-	struct hostent *hostentbuf;
-
-	server_port = 0;
-	virtualhost = 0;
-	ka_timeout = 0;
-	ka_max = 0;
-	verbose_cgi_logs = 0;
-
 	yyin = fopen("boa.conf", "r");
 
 	if (!yyin) {
@@ -70,19 +229,8 @@
 		fputs("Error parsing config files, exiting\n", stderr);
 		exit(1);
 	}
-	if (!server_name) {
-#ifdef HAVE_GETHOSTNAME
-		gethostname(hostnamebuf, MAX_SITENAME_LENGTH);
-		hostentbuf = gethostbyname(hostnamebuf);
-		if (!hostentbuf)
-#endif
-		{
-			fputs("Cannot determine hostname. Set ServerName in boa.conf.\n",
-				  stderr);
-			exit(1);
-		}
-		server_name = strdup(hostentbuf->h_name);
+	if(chdir(server_root) == -1) {
+		fprintf(stderr, "Could not chdir to ServerRoot.\n");
+		exit(1);
 	}
-	init_ka_phrase();
-	alarm(60);
 }
diff -urN boa-0.93.16.2/src/defines.h boa-0.93.17.1/src/defines.h
--- boa-0.93.16.2/src/defines.h	Wed Jun 30 13:44:23 1999
+++ boa-0.93.17.1/src/defines.h	Sat Jul 10 23:00:16 1999
@@ -77,7 +77,7 @@
 #define COMMON_CGI_VARS				8
 
 #ifndef SERVER_VERSION
-#define SERVER_VERSION				"Boa/0.93.16.1"
+#define SERVER_VERSION				"Boa/0.93.17.1"
 #endif
 
 #define CGI_VERSION				"CGI/1.1"
diff -urN boa-0.93.16.2/src/log.c boa-0.93.17.1/src/log.c
--- boa-0.93.16.2/src/log.c	Wed Jun 30 13:44:23 1999
+++ boa-0.93.17.1/src/log.c	Sat Jul 10 22:44:40 1999
@@ -32,6 +32,15 @@
 char *cgi_log_name;
 int cgi_log_fd;
 
+FILE *fopen_gen_fd(char *spec, const char *mode)
+{
+	int fd;
+	if (!spec || *spec=='\0') return NULL;
+	fd = open_gen_fd(spec);
+	if (!fd) return NULL;
+	return fdopen(fd, mode);
+}
+
 /*
  * Name: open_logs
  *
@@ -41,10 +50,13 @@
 
 void open_logs(void)
 {
-	FILE *error_log;
+	int error_log;
 
 	if (access_log_name) {
-		if (!(access_log = fopen(access_log_name, "a"))) {
+		/* Used the "a" flag with fopen, but fopen_gen_fd builds that in
+		 * implicitly when used as a file, and "a" is incompatible with
+		 * pipes and network sockets. */
+		if (!(access_log = fopen_gen_fd(access_log_name, "w"))) {
 			int errno_save = errno;
 			fprintf(stderr, "Cannot open %s for logging: ", access_log_name);
 			errno = errno_save;
@@ -64,9 +76,7 @@
 		cgi_log_name = strdup("/dev/null");
 
 	{
-		cgi_log_fd = open(cgi_log_name,
-						  O_WRONLY | O_CREAT | O_APPEND,
-						  S_IRUSR | S_IWUSR | S_IROTH | S_IRGRP);
+		cgi_log_fd = open_gen_fd(cgi_log_name);
 		if (cgi_log_fd == -1) {
 			log_error_time();
 			perror("open cgi_log");
@@ -88,15 +98,15 @@
 	/* otherwise, leave stderr alone */
 	if (error_log_name) {
 	    /* open the log file */
-	    if (!(error_log = fopen(error_log_name, "a")))
+	    if (!(error_log = open_gen_fd(error_log_name)))
 		die(NO_OPEN_LOG);
 	    
 	    /* redirect stderr to error_log */
-	    if (dup2(fileno(error_log), STDERR_FILENO) == -1) {
+	    if (dup2(error_log, STDERR_FILENO) == -1) {
 		perror("unable to dup2 the error log");
 		die(NO_OPEN_LOG);
 	    }
-	    fclose(error_log);
+	    close(error_log);
 	}
 
 	/* set the close-on-exec to true */
diff -urN boa-0.93.16.2/src/mmap_cache.c boa-0.93.17.1/src/mmap_cache.c
--- boa-0.93.16.2/src/mmap_cache.c	Thu Dec 24 00:24:59 1998
+++ boa-0.93.17.1/src/mmap_cache.c	Sat Jul 10 23:05:09 1999
@@ -1,3 +1,22 @@
+/*
+ *  Boa, an http server
+ *  Copyright (C) 1999 Larry Doolittle <ldoolitt@boa.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 1, or (at your option)
+ *  any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
 #include "boa.h"
 
 int mmap_list_entries_used=0;
@@ -117,4 +136,4 @@
  fprintf(stderr, "total_requests=%d  ",mmap_list_total_requests);
  fprintf(stderr, "hash_bounces=%d\n",mmap_list_hash_bounces);
 
-*/
\ No newline at end of file
+*/
diff -urN boa-0.93.16.2/src/parse.h boa-0.93.17.1/src/parse.h
--- boa-0.93.16.2/src/parse.h	Wed Dec 31 16:00:00 1969
+++ boa-0.93.17.1/src/parse.h	Sat Jul 10 22:50:22 1999
@@ -0,0 +1,30 @@
+/*
+ *  Boa, an http server
+ *  Copyright (C) 1999 Larry Doolittle <ldoolitt@boa.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 1, or (at your option)
+ *  any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *  parse.h
+ *  minimum interaction point between Boa's parser (boa_lexer.l and
+ *  boa_grammar.y) and the rest of Boa.
+ */ 
+struct ccommand {
+        char *name;
+        int type;
+        void (*action) (char *,char *,void *);
+        void *object;
+};
+struct ccommand *lookup_keyword(char *c);
+void add_mime_type(char *extension, char *type);
diff -urN boa-0.93.16.2/src/sublog.c boa-0.93.17.1/src/sublog.c
--- boa-0.93.16.2/src/sublog.c	Wed Dec 31 16:00:00 1969
+++ boa-0.93.17.1/src/sublog.c	Sat Jul 10 23:02:57 1999
@@ -0,0 +1,119 @@
+/*
+ *  Boa, an http server
+ *  Copyright (C) 1999 Larry Doolittle <ldoolitt@boa.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 1, or (at your option)
+ *  any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+/* Like popen, but gives fd instead of FILE * */
+int open_pipe_fd(char *command)
+{
+	int pipe_fds[2];
+	int pid;
+	/* "man pipe" says "filedes[0] is for reading,
+	 * filedes[1] is for writing. */
+	if (pipe(pipe_fds) == -1) return -1;
+	pid = fork();
+	if (pid == 0) {   /* child */
+		close(pipe_fds[1]);
+		if (pipe_fds[0] != 0) {
+			dup2(pipe_fds[0],0);
+			close(pipe_fds[0]);
+		}
+		execl("/bin/sh", "sh", "-c", command, (char *)0);
+		exit(127);
+	}
+	close(pipe_fds[0]);
+	if (pid < 0) {close(pipe_fds[1]); return -1;}
+	return pipe_fds[1];
+}
+
+int open_net_fd(char *spec)
+{
+	char *p;
+	int fd, port;
+	struct sockaddr_in sa;
+	struct hostent *he;
+	p = strchr(spec,':');
+	if (!p) return -1;
+	*p++='\0';
+	port=strtol(p,NULL,10);
+	/* printf("Host %s, port %d\n",spec,port); */
+	sa.sin_family=AF_INET;
+	sa.sin_port=htons(port);
+	he=gethostbyname(spec);
+	if (!he) { herror("open_net_fd"); return -1;}
+	memcpy(&sa.sin_addr,he->h_addr,he->h_length);
+	/* printf("using ip %s\n",inet_ntoa(sa.sin_addr)); */
+	fd=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+	if (fd < 0) return fd;
+	if (connect(fd,(struct sockaddr *)&sa,sizeof(sa))<0) return -1;
+	return fd;
+}
+
+int open_gen_fd(char *spec)
+{
+	int fd;
+	if (*spec == '|') {
+		fd = open_pipe_fd(spec+1);
+	} else if (*spec == ':') {
+		fd = open_net_fd(spec+1);
+	} else {
+		fd = open(spec, 
+			O_WRONLY | O_CREAT | O_APPEND,
+			S_IRUSR | S_IWUSR | S_IROTH | S_IRGRP);
+	}
+	return fd;
+}
+
+#ifdef STANDALONE_TEST
+int main(int argc, char *argv[])
+{
+	char buff[1024];
+	int fd, nr, nw;
+	if (argc < 2) {
+		fprintf(stderr,
+			"usage: %s output-filename\n"
+			"       %s |output-command\n"
+			"       %s :host:port\n",
+			argv[0],argv[0],argv[0]);
+		return 1;
+	}
+	fd = open_gen_fd(argv[1]);
+	if (fd < 0) {perror("open_gen_fd"); exit(1);}
+	while((nr=read(0,buff,sizeof(buff)))!=0) {
+		if (nr<0) {
+			if (errno == EINTR) continue;
+			perror("read"); exit(1);
+		}
+		nw=write(fd,buff,nr);
+		if (nw<0) {perror("write"); exit(1);}
+	}
+	return 0;
+}
+#endif
