/* simple httpd to be started from tcpserver */
#define _FILE_OFFSET_BITS 64
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
//#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <grp.h>
#include <errno.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <dirent.h>
#include <sys/mman.h>
#include <limits.h>
#include "fmt.h"
#include "buffer.h"
#include "byte.h"
#include "scan.h"

/* uncomment the following line to enable support for CGI */
// #define CGI

#ifdef CGI
/* uncomment the following line to enable support for "index.cgi"
 * That is: if "index.html" is not present then look for "index.cgi" */
#define INDEX_CGI
#endif

/* the following switch will make fnord normalize the Host: HTTP header
 * from "foo" to "foo:80" */
#define NORMALIZE_HOST

/* uncomment the following line to enable support for autogenerated
 * directory-listings for directories without index */
/* #define DIR_LIST */

#ifdef DIR_LIST
/* uncomment the following line to enable support for system symlink
 * dereferencingr.
 * HOPE YOU KNOW WHAT YOU'RE LINKING !
 *
 * e.g.: if a file foo is a symlink to /etc/passwd and you don't have a
 * chroot enviroment then the system-wide /etc/passwd is provided !!!
 *
 * If the symlink is dangling OR this option is not active the symlink is
 * provided as a new http-uri.
 *
 * e.g.: is foo a symlink to /etc/passwd than the clinet gets a href to
 * http://<vhost>/etc/passwd */
/* #define SYSTEM_SYMLINK_DEREF */
#endif

/* uncomment the following line to get full-host redirection.
 * If a file is not found locally, and $REDIRECT_HOST is set, fnord will
 * issue a redirect to strcat($REDIRECT_HOST,uri).  Otherwise, if
 * $REDIRECT_URI is set, fnord will issue a redirect to $REDIRECT_URI.
 * Only if those fail will a 404 error be returned. */
#define OLD_STYLE_REDIRECT

/* uncomment the following line to get full-host redirection.
 * if the virtual host directory/symlink is a broken symlink, fnord will
 * issue a redirect.  If the contents of the symlink starts with an
 * equal sign ('='), fnord will throw away the URI part. */
#define REDIRECT

/* uncomment the following line to get apache-style common log file
 * format.  Please note that that format is not usable for ad-hoc
 * analysis using awk and friends, but you will be able to use Apache
 * log analysis tools. */
/* #define COLF */

/* uncomment the following line to make fnord tarpit queries from
 * EmailSiphon (an email harvester for spammers) */
#define TARPIT

/* uncomment the following line to make fnord chroot to the current
 * working directory and drop privileges */
#define CHROOT

/* uncomment the following line to make fnord support connection
 * keep-alive */
#define KEEPALIVE

/* the following is the time in seconds that fnord should wait for a
 * valid HTTP request */
#define READTIMEOUT 20

/* the following is the time in seconds that fnord should wait before
 * aborting a request when trying to write the answer */
#define WRITETIMEOUT 20

#define CGI_TIMEOUT	(5*60)	/* 5 minutes time-out for CGI to complete */

/* defining USE_SENDFILE enables zero-copy TCP on Linux for static
 * files.  I measured over 320 meg per second with apache bench over
 * localhost with sendfile and keep-alive.  However, sendfile does not
 * work with large files and may be considered cheating ;-)
 * Also, sendfile is a blocking operation.  Thus, no timeout handling. */
#define USE_SENDFILE

#ifndef __linux__
#undef USE_SENDFILE
#endif

#ifdef USE_SENDFILE
#include <sys/sendfile.h>
#endif

#ifndef O_NDELAY
#define O_NDELAY O_NONBLOCK
#endif

#define USE_MMAP
#ifndef _POSIX_MAPPED_FILES
#undef USE_MMAP
#endif

enum { UNKNOWN, GET, HEAD, POST } method;

#ifdef TCP_CORK
static int corked;
#endif
static long retcode=404;	/* used for logging code */
char *host="?";			/* Host: header */
char *port;			/* also Host: header, :80 part */
char *args;			/* URL behind ? (for CGIs) */
char *url;			/* string between GET and HTTP/1.0, demangled */
char *ua="?";			/* user-agent */
char *refer;			/* Referrer: header */
char *accept_enc;		/* Accept-Encoding */
int httpversion;		/* 0 == 1.0, 1 == 1.1 */
#ifdef KEEPALIVE
int keepalive=0;		/* should we keep the connection alive? */
int rootdir;	/* fd of root directory, so we can fchdir back for keep-alive */
#endif
#ifdef CGI
char *cookie;			/* Referrer: header */
char *uri;			/* copy of url before demangling */
char *content_type;
char *content_len;
char *auth_type;
char *post_miss;
unsigned long post_mlen;
unsigned long post_len=0;
#endif

#if _FILE_OFFSET_BITS == 64
static unsigned long long rangestart, rangeend;	/* for ranged queries */
#define scan_range scan_ulonglong
#define buffer_putrange buffer_putulonglong
#include "scan_ulonglong.c"
#include "fmt_ulonglong.c"
#include "buffer_putulonglong.c"
#else
static unsigned long rangestart, rangeend;	/* for ranged queries */
#define scan_range scan_ulong
#define buffer_putrange buffer_putulong
#endif

static const char days[] = "SunMonTueWedThuFriSat";
static const char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";

#define MAXHEADERLEN 8192

char* remote_ip;
#ifdef CGI
char* remote_port;
char* remote_ident;
#endif

static int buffer_put2digits(buffer* b,unsigned int i) {
  char x[2];
  x[0]=(i/10)+'0';
  x[1]=(i%10)+'0';
  return buffer_put(b,x,2);
}

static void buffer_putsescaped(buffer* b,const char* url,int aos) {
  while (url && *url) {
    char c=*url;
    if (c==' ' || c=='\n' || c=='\t' || c=='\r' && aos) return;
    if (c<=' ' || c>=127) c='_';
    buffer_put(b,&c,1);
    ++url;
  }
}

static void dolog(off_t len) {	/* write a log line to stderr */
#ifdef COLF
  time_t t=time(0);
  struct tm* x=localtime(&t);
  int l=-(timezone/60);
  buffer_puts(buffer_2,remote_ip?remote_ip:"0.0.0.0");
  buffer_puts(buffer_2," - - [");

  buffer_put2digits(buffer_2,x->tm_mday);
  buffer_puts(buffer_2,"/");
  buffer_put(buffer_2,months+3*x->tm_mon,3);
  buffer_puts(buffer_2,"/");
  buffer_put2digits(buffer_2,(x->tm_year+1900)/100);
  buffer_put2digits(buffer_2,(x->tm_year+1900)%100);
  buffer_puts(buffer_2,":");
  buffer_put2digits(buffer_2,x->tm_hour);
  buffer_puts(buffer_2,":");
  buffer_put2digits(buffer_2,x->tm_min);
  buffer_puts(buffer_2,":");
  buffer_put2digits(buffer_2,x->tm_sec);
  buffer_puts(buffer_2,l>=0?" +":" -");
  if (l<0) l=-l;
  buffer_put2digits(buffer_2,l/60);
  buffer_put2digits(buffer_2,l%60);
  buffer_puts(buffer_2,"] \"");
  switch (method) {
  case GET: buffer_puts(buffer_2,"GET "); break;
  case POST: buffer_puts(buffer_2,"POST "); break;
  case HEAD: buffer_puts(buffer_2,"HEAD "); break;
  default: buffer_puts(buffer_2,"? "); break;
  }
  buffer_putsescaped(buffer_2,url,1);
  buffer_puts(buffer_2,httpversion?" HTTP/1.1\" ":" HTTP/1.0\" ");
  buffer_putulong(buffer_2,retcode);
  buffer_putspace(buffer_2);
  buffer_putrange(buffer_2,len);

#else
  buffer_puts(buffer_2,remote_ip?remote_ip:"0.0.0.0");
  buffer_putspace(buffer_2);
  buffer_putulong(buffer_2,retcode);
  buffer_putspace(buffer_2);
  buffer_putrange(buffer_2,len);
  buffer_putspace(buffer_2);
  buffer_putsescaped(buffer_2,host,0);
  buffer_putspace(buffer_2);
  buffer_putsescaped(buffer_2,ua,0);
  buffer_putspace(buffer_2);
  if (!refer) refer="none";
  buffer_putsescaped(buffer_2,refer,0);
  buffer_putspace(buffer_2);
  if (url)
    buffer_putsescaped(buffer_2,url,1);
  else
    buffer_puts(buffer_2,"(null)");
#endif
  buffer_puts(buffer_2,"\n");
  buffer_flush(buffer_2);
}

/* output an error message and exit */
static void badrequest(long code,const char *httpcomment,const char *message) {
  retcode=code;
  dolog(0);
  buffer_puts(buffer_1,"HTTP/1.0 ");
  buffer_putulong(buffer_1,code);
  buffer_putspace(buffer_1);
  buffer_puts(buffer_1,httpcomment);
  buffer_puts(buffer_1,"\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n");
  buffer_puts(buffer_1,message);
  buffer_flush(buffer_1);
  exit(0);
}

#ifdef CGI
#define CGIENVLEN 21

static const char *cgivars[CGIENVLEN] = {
  "GATEWAY_INTERFACE=",
  "SERVER_PROTOCOL=",
  "SERVER_SOFTWARE=",
  "SERVER_NAME=",
  "SERVER_PORT=",
  "REQUEST_METHOD=",
  "REQUEST_URI=",
  "SCRIPT_NAME=",
  "REMOTE_ADDR=",
  "REMOTE_PORT=",
  "REMOTE_IDENT=",
  "HTTP_USER_AGENT=",
  "HTTP_COOKIE=",
  "HTTP_REFERER=",
  "HTTP_ACCEPT_ENCODING=",
  "AUTH_TYPE=",
  "CONTENT_TYPE=",
  "CONTENT_LENGTH=",
  "QUERY_STRING=",
  "PATH_INFO=",
  "PATH_TRANSLATED="
};

static int iscgivar(const char *s) {
  register unsigned int i=0;
  for (;i<CGIENVLEN;i++)
    if (str_start(s, cgivars[i])) return 1;
  return 0;
}

static unsigned int elen(register const char *const *e) {
  register unsigned int i=0;
  while (e[i]) i++;
  return i;
}

static void do_cgi(const char* pathinfo,const char* const* envp) {
  const char *method_name[]={ "?", "GET", "HEAD", "POST"};
  char cgi_env_buf[MAXHEADERLEN*2+PATH_MAX+200];
  register unsigned int en=elen(envp);
  char *tmp=cgi_env_buf;
  char **cgi_arg;
  register int i;
  char **cgi_env=(char **)alloca((CGIENVLEN+en+1)*sizeof(char *)) ;

  cgi_env[0]="GATEWAY_INTERFACE=CGI/1.1";
  cgi_env[1]="SERVER_PROTOCOL=HTTP/1.0";
  cgi_env[2]="SERVER_SOFTWARE="FNORD;

  cgi_env[3]=tmp;
  tmp+=str_copy(tmp,"SERVER_NAME=");
  tmp+=str_copy(tmp,host);
  *tmp=0; ++tmp;

  cgi_env[4]=tmp;
  tmp+=str_copy(tmp,"SERVER_PORT=");
  tmp+=str_copy(tmp,port);
  *tmp=0; ++tmp;

  cgi_env[5]=tmp;
  tmp+=str_copy(tmp,"REQUEST_METHOD=");
  tmp+=str_copy(tmp,method_name[method]);
  *tmp=0; ++tmp;

  cgi_env[6]=tmp;
  tmp+=str_copy(tmp,"REQUEST_URI=");
  tmp+=str_copy(tmp,uri);
  *tmp=0; ++tmp;

  cgi_env[7]=tmp;
  tmp+=str_copy(tmp,"SCRIPT_NAME=");
  tmp+=str_copy(tmp,url);
  *tmp=0; ++tmp;

  i=7;
  if (remote_ip) {
    cgi_env[++i]=tmp;
    tmp+=str_copy(tmp,"REMOTE_ADDR=");
    tmp+=str_copy(tmp,remote_ip);
    *tmp=0; ++tmp;
  }

  if (remote_port) {
    cgi_env[++i]=tmp;
    tmp+=str_copy(tmp,"REMOTE_PORT=");
    tmp+=str_copy(tmp,remote_port);
    *tmp=0; ++tmp;
  }

  if (remote_ident) {
    cgi_env[++i]=tmp;
    tmp+=str_copy(tmp,"REMOTE_IDENT=");
    tmp+=str_copy(tmp,remote_ident);
    *tmp=0; ++tmp;
  }

  if (ua) {
    cgi_env[++i]=tmp;
    tmp+=str_copy(tmp,"HTTP_USER_AGENT=");
    tmp+=str_copy(tmp,ua);
    *tmp=0; ++tmp;
  }

  if (cookie) {
    cgi_env[++i]=tmp;
    tmp+=str_copy(tmp,"HTTP_COOKIE=");
    tmp+=str_copy(tmp,cookie);
    *tmp=0; ++tmp;
  }

  if (refer) {
    cgi_env[++i]=tmp;
    tmp+=str_copy(tmp,"HTTP_REFERER=");
    tmp+=str_copy(tmp,refer);
    *tmp=0; ++tmp;
  }

  if (accept_enc) {
    cgi_env[++i]=tmp;
    tmp+=str_copy(tmp,"HTTP_ACCEPT_ENCODING=");
    tmp+=str_copy(tmp,accept_enc);
    *tmp=0; ++tmp;
  }

  if (auth_type) {
    cgi_env[++i]=tmp;
    tmp+=str_copy(tmp,"AUTH_TYPE=");
    tmp+=str_copy(tmp,auth_type);
    *tmp=0; ++tmp;
  }

  if (content_type) {
    cgi_env[++i]=tmp;
    tmp+=str_copy(tmp,"CONTENT_TYPE=");
    tmp+=str_copy(tmp,content_type);
    *tmp=0; ++tmp;
    cgi_env[++i]=tmp;
    tmp+=str_copy(tmp,"CONTENT_LENGTH=");
    tmp+=str_copy(tmp,content_len);
    *tmp=0; ++tmp;
  }

  if (args) {
    cgi_env[++i]=tmp;
    tmp+=str_copy(tmp,"QUERY_STRING=");
    tmp+=str_copy(tmp,args);
    *tmp=0; ++tmp;
  }

  if (pathinfo) {
    cgi_env[++i]=tmp;
    tmp+=str_copy(tmp,"PATH_INFO=");
    tmp+=str_copy(tmp,pathinfo);
    *tmp=0; ++tmp;
    cgi_env[++i]=tmp;
    tmp+=str_copy(tmp,"PATH_TRANSLATED=");
    tmp+=realpath(pathinfo,tmp)?str_len(tmp):str_copy(tmp,pathinfo);
    ++tmp;
  }

  {
    unsigned int j=0;
    for (;j<en;j++)
      if (!iscgivar(envp[j])) cgi_env[++i]=(char*)envp[j];
  }
  cgi_env[++i]=0;

  /* argv */
  if (args && (args[str_chr(args,'=')]==0)) {
    int n=3;
    for (i=0;args[i];++i) if (args[i]=='+') ++n;
    cgi_arg=alloca(n*sizeof(char*));
    cgi_arg[n=1]=args;
    for (i=0;args[i];++i) {
      if (args[i]=='+') {
	args[i]=0;
	++i;
	cgi_arg[++n]=args+i;
      }
    }
    cgi_arg[++n]=0;
  } else {
    cgi_arg=alloca(2*sizeof(char*));
    cgi_arg[1]=0;
  }

  i=strrchr(url,'/')-url;
  strncpy(tmp,url+1,i);
  tmp[i]=0;
  chdir(tmp);

  /* program name */
  cgi_arg[0]=tmp;
  tmp[0]='.';
  tmp[str_copy(tmp+1,url+i)+1]=0;

  /* start cgi */
  execve(cgi_arg[0],cgi_arg,cgi_env);
  raise(SIGQUIT);	/* gateway unavailable. */
}

static void cgi_child(int sig) {
  int n,pid=waitpid(0,&n,WNOHANG);
  if (pid>0) {
    if (WIFSIGNALED(n)) {
      if (WTERMSIG(n)==SIGALRM)
	badrequest(504,"Gateway Time-out","Gateway has hit the Time-out.");
      else
	badrequest(502,"Bad Gateway","Gateway broken or unavailable.");
    }
  }
  signal(SIGCHLD,cgi_child);
}

static void cgi_send_correct_http(const char*s,unsigned int sl) {
  unsigned int i;
  char ch=0;
  for (i=0;i<sl;++i) {
    if ((s[i]=='\r')&&(s[i+1]=='\n')) {
      ++i; goto out_nl;
    }
    else {
      if (s[i]!='\n') buffer_put(buffer_1,s+i,1);
      else {
out_nl: buffer_put(buffer_1,"\r\n",2);
	if (ch=='\n') { ++i; break; }
      }
    }
    ch=s[i];
  }
  buffer_put(buffer_1,s+i,sl-i);
}

static void start_cgi(int nph,const char* pathinfo,const char *const *envp) {
  size_t size=0;
  int n;
  int pid;
  char ibuf[8192],obuf[8192];
  int fd[2],df[2];

  if (pipe(fd)||pipe(df)) {
    badrequest(500,"Internal Server Error","Server Resource problem.");
  }

  if ((pid=fork())) {
    if (pid>0) {
      struct pollfd pfd[2];
      int nr=1;
      int startup=1;

      signal(SIGCHLD,cgi_child);
      signal(SIGPIPE,SIG_IGN);		/* NO! no signal! */

      close(df[0]);
      close(fd[1]);

      pfd[0].fd=fd[0];
      pfd[0].events=POLLIN;
      pfd[0].revents=0;

      pfd[1].fd=df[1];
      pfd[1].events=POLLOUT;
      pfd[1].revents=0;

      if (post_len) ++nr;	/* have post data */
      else close(df[1]);	/* no post data */

      while(poll(pfd,nr,-1)!=-1) {
	/* read from cgi */
	if (pfd[0].revents&POLLIN) {
	  if (!(n=read(fd[0],ibuf,sizeof(ibuf)))) break;
	  if (n<0) goto cgi_500;
	  /* startup */
	  if (startup) {
	    startup=0;
	    if (nph) {	/* NPH-CGI */
	      buffer_put(buffer_1,ibuf,n);
	      scan_ulong(ibuf+9,&retcode); /* only get error code / str_len("HTTP/x.x ")==9 */
	    }
	    else {	/* CGI */
	      if (byte_diff(ibuf,10,"Location: ")==0) {
		retcode=302;
		buffer_puts(buffer_1,"HTTP/1.0 302 CGI-Redirect\r\nConnection: close\r\n");
		signal(SIGCHLD,SIG_IGN);
		cgi_send_correct_http(ibuf,n);
		buffer_flush(buffer_1);
		dolog(0);
		exit(0);
	      }
	      else {
		retcode=200;
		buffer_puts(buffer_1,"HTTP/1.0 200 OK\r\nServer: "FNORD"\r\nPragma: no-cache\r\nConnection: close\r\n");
		signal(SIGCHLD,SIG_IGN);
		cgi_send_correct_http(ibuf,n);
	      }
	    }
	  }
	  /* non startup */
	  else {
	    buffer_put(buffer_1,ibuf,n);
	  }
	  size+=n;
	  if (pfd[0].revents&POLLHUP) break;
	}
	/* write to cgi the post data */
	else if (nr>1 && pfd[1].revents&POLLOUT) {
	  if (post_miss) {
	    write(df[1],post_miss,post_mlen);
	    post_miss=0;
	  }
	  else if (post_mlen<post_len) {
	    n=read(0,obuf,sizeof(obuf));
	    if (n<1) goto cgi_500;
	    post_mlen+=n;
	    write(df[1],obuf,n);
	  }
	  else {
	    --nr;
	    close(df[1]);
	  }
	}
	else if (pfd[0].revents&POLLHUP) break;
	else {
cgi_500:  if (startup)
	    badrequest(500,"Internal Server Error","Looks like the CGI crashed.");
	  else {
	    buffer_puts(buffer_1,"\n\n");
	    buffer_puts(buffer_1,"Looks like the CGI crashed.");
	    buffer_puts(buffer_1,"\n\n");
	    break;
	  }
	}
      }

      buffer_flush(buffer_1);
      dolog(size);
#ifdef TCP_CORK
      {
	int zero=0;
	setsockopt(1,IPPROTO_TCP,TCP_CORK,&zero,sizeof(zero));
      }
#endif
    }
  }
  else {
    close(df[1]);
    close(fd[0]);

    dup2(df[0],0);
    dup2(fd[1],1);

    close(df[0]);
    close(fd[1]);

    alarm(CGI_TIMEOUT);
    do_cgi(pathinfo,envp);
  }
  exit(0);
}
#endif

static int fromhex(int c) {
  if (c>='0' && c<='9') return c-'0';
  else { c|=' ';
    if (c>='a' && c<='f') return c-'a'+10;
  }
  return -1;
}

/* header(buf,buflen,"User-Agent")="Mozilla" */
static char* header(char* buf,int buflen,const char* hname) {
  int slen=str_len(hname);
  int i;
  char* c;
//  printf("buflen %d, slen %d\n",buflen,slen);
  for (i=0; i<buflen-slen-2; ++i) {
//    printf("%.5s %s\n",buf+i,hname);
    if (!strncasecmp(buf+i,hname,slen)) {
//      printf("a %.20s\n",buf+i);
      if (i && buf[i-1]!='\n') continue;
//      printf("b %.20s\n",buf+i);
      if (buf[i+slen]!=':' || buf[i+slen+1]!=' ') continue;
//      printf("c %.20s\n",buf+i);
      c=buf+i+slen+2;
      i+=slen+2;
      for (; i<buflen; ++i)
//	printf("%c\n",buf[i]);
	if (buf[i]==0 || buf[i]=='\n' || buf[i]=='\r') {
	  buf[i]=0;
	  break;
	}
      while (*c==' ' || *c=='\t') ++c;
      return c;
    }
  }
  return 0;
}

static char* encoding=0;
static char* mimetype="text/plain";

static struct mimeentry { const char* name, *type; } mimetab[] = {
  { "html",	"text/html" },
  { "htm",	"text/html" },
  { "css",	"text/css" },
  { "dvi",	"application/x-dvi" },
  { "ps",	"application/postscript" },
  { "pdf",	"application/pdf" },
  { "gif",	"image/gif" },
  { "png",	"image/png" },
  { "jpeg",	"image/jpeg" },
  { "bild",	"image/jpeg" },
  { "jpg",	"image/jpeg" },
  { "mpeg",	"video/mpeg" },
  { "mpg",	"video/mpeg" },
  { "avi",	"video/x-msvideo" },
  { "mov",	"video/quicktime" },
  { "qt",	"video/quicktime" },
  { "mp3",	"audio/mpeg" },
  { "ogg",	"audio/x-oggvorbis" },
  { "wav",	"audio/x-wav" },
  { "pac",	"application/x-ns-proxy-autoconfig" },
  { "sig",	"application/pgp-signature" },
  { "torrent",	"application/x-bittorrent" },
  { "class",	"application/octet-stream" },
  { "js",	"application/x-javascript" },
  { "tar",	"application/x-tar" },
  { "zip",	"application/zip" },
  { "dtd",	"text/xml" },
  { "xml",	"text/xml" },
  { "xbm",	"image/x-xbitmap" },
  { "xpm",	"image/x-xpixmap" },
  { "xwd",	"image/x-xwindowdump" },
  { "ico",	"image/x-icon" },
  { 0 } };

/* try to find out MIME type and content encoding.
 * This is called twice, once for the actual URL and once for URL.gz.
 * If the actual URL already ende with .gz, return
 * application/octet-stream to make sure the client can download the
 * file even if he does not support gzip encoding */
static void getmimetype(char* url,int explicit) {
  char save;
  int ext;
  ext=str_len(url);
  while (ext>0 && url[ext]!='.' && url[ext]!='/') --ext;
  if (url[ext]=='.') {
    ++ext;
    if (str_equal(url+ext,"bz2")) goto octetstream;
    if (str_equal(url+ext,"gz")) {
      if (!encoding) {
	if (explicit) goto octetstream;
	encoding="gzip";
	save=url[ext-1];
	url[ext-1]=0;
	getmimetype(url,explicit);
	url[ext-1]=save;
      } else
octetstream:
	mimetype="application/octet-stream";
    } else {
      int i;
      for (i=0; mimetab[i].name; ++i)
	if (str_equal(mimetab[i].name,url+ext)) {
	  mimetype=(char*)mimetab[i].type;
	  break;
	}
    }
  }
}

static int matchcommalist(const char* needle,const char* haystack) {
  /* needle: "text/html",
   * haystack: the accept header, "text/html, text/plain\r\n" */
  /* return nonzero if match was found */
  int len=str_len(needle);
  if (!byte_equal(needle,len,haystack)) return 0;
  switch (haystack[len]) {
  case ';': case ',': case '\r': case '\n': case 0: return 1;
  }
  return 0;
}

static int findincommalist(const char* needle,const char* haystack) {
  const char* accept;
  for (accept=haystack; accept;) {
    /* format: foo/bar, */
    const char *tmp=accept;
    int final;
    while (*tmp) {
      if (*tmp==';') break; else
      if (*tmp==',') break;
      ++tmp;
    }
    final=(*tmp==0 || *tmp==';');
    if (matchcommalist("*/*",accept)) break;
    if (matchcommalist(haystack,accept)) break;
    accept=tmp+1;
    if (final) return 0;
  }
  return 1;
}

static int parsetime(const char*c,struct tm* x) {
  unsigned long tmp;
  c+=scan_ulong(c,&tmp); x->tm_hour=tmp;
  if (*c!=':') return -1; ++c;
  c+=scan_ulong(c,&tmp); x->tm_min=tmp;
  if (*c!=':') return -1; ++c;
  c+=scan_ulong(c,&tmp); x->tm_sec=tmp;
  if (*c!=' ') return -1;
  return 0;
}

static time_t parsedate(const char*c) {
  struct tm x;
  int i;
  unsigned long tmp;
  if (!c) return (time_t)-1;
  /* "Sun, 06 Nov 1994 08:49:37 GMT",
   * "Sunday, 06-Nov-94 08:49:37 GMT" and
   * "Sun Nov  6 08:49:37 1994" */
  if (c[3]==',') c+=5; else
  if (c[6]==',') c+=8; else {
    c+=4;
    for (i=0; i<12; ++i) {
//      fprintf(stderr,"comparing %s to %.3s\n",c,months+i*3);
      if (!strncasecmp(c,months+i*3,3)) {
	x.tm_mon=i; break;
      }
    }
    c+=4; if (*c==' ') ++c;
    c+=scan_ulong(c,&tmp); x.tm_mday=tmp;
    ++c;
    if (parsetime(c,&x)) return (time_t)-1;
    c+=9;
    c+=scan_ulong(c,&tmp); x.tm_year=tmp-1900;
    goto done;
  }
  c+=scan_ulong(c,&tmp); x.tm_mday=tmp;
  ++c;
  for (i=0; i<12; ++i)
    if (!strncasecmp(c,months+i*3,3)) {
      x.tm_mon=i; break;
    }
  c+=4;
  c+=scan_ulong(c,&tmp);
  if (tmp>1000) x.tm_year=tmp-1900; else
    if (tmp<70) x.tm_year=tmp+100; else
                x.tm_year=tmp;
  ++c;
  if (parsetime(c,&x)) return (time_t)-1;
done:
  x.tm_wday=x.tm_yday=x.tm_isdst=0;
  return mktime(&x);
}

static struct stat st;

/* try to return a file */
static int doit(char* buf,int buflen,char* url,int explicit) {
  int fd=-1;
  char* accept;
  time_t ims;
  while (url[0]=='/') ++url;
  getmimetype(url,explicit);
  {
    char *b=buf;
    int l=buflen;
    for (;;) {
      char *h=header(b,l,"Accept");
      if (!h) goto ok;
      if (findincommalist(mimetype,h)) goto ok;
      l-=(h-b)+1;
      b=h+1;
    }
    retcode=406; goto bad;
  }
ok:
  if (encoding) {	/* see if client accepts the encoding */
    char *tmp=header(buf,buflen,"Accept-Encoding");
    if (!tmp || !strstr(tmp,"gzip"))
      { retcode=406; goto bad; }
  }
  if ((fd=open(url,O_RDONLY))>=0) {
    if (fstat(fd,&st)) goto bad;
    /* no directories */
    if (S_ISDIR(st.st_mode)) goto bad;
    /* see if the peer accepts MIME type */
    /* see if the document has been changed */
    ims=parsedate(header(buf,buflen,"If-Modified-Since"));
    if (ims!=(time_t)-1 && st.st_mtime<=ims) { retcode=304; goto bad; }
    rangestart=0; rangeend=st.st_size;
    if ((accept=header(buf,buflen,"Range"))) {
      /* format: "bytes=17-23", "bytes=23-" */
      if (!strncmp(accept,"bytes=",6)) {
	int i;
	accept+=6;
	i=scan_range(accept,&rangestart);
	if (i) {
	  accept+=i;
	  if (*accept=='-') {
	    ++accept;
	    if (*accept) {
	      i=scan_range(accept,&rangeend);
	      if (!i) rangeend=st.st_size; else ++rangeend;
	    }
	  }
	}
      }
      if (rangestart>rangeend || rangeend>st.st_size) { retcode=416; goto bad; }
    }
    return fd;
bad:
    if (fd>=0) close(fd);
  }
  return -1;
}

static void redirectboilerplate() {
  buffer_puts(buffer_1,"HTTP/1.0 301 Go Away\r\nConnection: close\r\nContent-Length: 0\r\nLocation: ");
}

static void handleredirect(const char *url,const char* origurl) {
  char symlink[1024];
  int len;
#ifdef OLD_STYLE_REDIRECT
  char* env;
#endif
  while (*url=='/') ++url;
  if ((len=readlink(url,symlink,1023))>0) {
    /* el-cheapo redirection */
    redirectboilerplate();
    buffer_put(buffer_1,symlink,len);
#ifdef OLD_STYLE_REDIRECT
fini:
#endif
    retcode=301;
    buffer_puts(buffer_1,"\r\n\r\n");
    dolog(0);
    buffer_flush(buffer_1);
    exit(0);
  }
#ifdef OLD_STYLE_REDIRECT
  if ((env=getenv("REDIRECT_HOST"))) {
    redirectboilerplate();
    buffer_puts(buffer_1,env);
    while (*origurl=='/') ++origurl;
    buffer_puts(buffer_1,origurl);
    goto fini;
  } else if ((env=getenv("REDIRECT_URI"))) {
    redirectboilerplate();
    buffer_puts(buffer_1,env);
    goto fini;
  }
#endif
}

#ifdef DIR_LIST
static void hdl_encode_html(const char*s,unsigned int sl) {
  int i;
  for (i=0;i<sl;++i) {
    unsigned char ch=s[i];
    if (ch>159) {
encode_dec:
      buffer_puts(buffer_1,"&#");
      buffer_putulong(buffer_1,ch);
      buffer_puts(buffer_1,";");
    }
    else if ((ch>128)||(ch<32)) {
      buffer_put(buffer_1," ",1);
    }
    else if (ch=='"') buffer_puts(buffer_1,"&quot;");
    else if (ch=='&') buffer_puts(buffer_1,"&amp;");
    else if (ch=='<') buffer_puts(buffer_1,"&lt;");
    else if (ch=='>') buffer_puts(buffer_1,"&gt;");
    else buffer_put(buffer_1,&ch,1);
  }
}
static int buffer_puthex(unsigned int i) {
  unsigned int t;
  char x[4];
  t='0'|(i>>4)&0xf;
  if (t>'9') t+=39;
  i='0'|(i&0xf);
  if (i>'9') i+=39;
  x[0]='%';
  x[1]=t;
  x[2]=i;
  return buffer_put(buffer_1,x,3);
}
static void hdl_encode_uri(const char*s,unsigned int sl) {
  int i;
  for (i=0;i<sl;++i) {
    unsigned char ch=s[i];
    if ((ch!='%')&&(ch>32)&&(ch<127))
      buffer_put(buffer_1,&ch,1);
    else
      buffer_puthex(ch);
  }
}
static void handledirlist(const char*origurl) {
  DIR*dir;
  unsigned int nl=str_len(origurl);
  const char*nurl=origurl;
  url=(char*)origurl;
  while (nurl[0]=='/') ++nurl;
  if (nurl<=origurl) return;
  nl=str_len(nurl);
  if (nurl[nl-1]!='/') return;
  if (!stat(nl?nurl:".",&st) && (S_ISDIR(st.st_mode)) && ((st.st_mode&S_IRWXO)==5)) {
    if (nl) chdir(nurl);
    if (dir=opendir(".")) {
      struct dirent*de;
      unsigned int i,size=32+nl;
      buffer_puts(buffer_1,"HTTP/1.0 200 OK\r\nServer: "FNORD"\r\nConnection: close\r\n");
      buffer_puts(buffer_1,"Content-Type: text/html\r\n");
      buffer_puts(buffer_1,"\r\n<h3>Directory Listing: /");
      hdl_encode_html(nurl,nl);
      buffer_puts(buffer_1,"</h3>\n<pre>\n");
      if (nl!=0) {
	for (i=nl-2;i>0;--i) if (nurl[i]=='/') break;
	buffer_puts(buffer_1,"<a href=\"");
	buffer_puts(buffer_1,"/");
	hdl_encode_uri(nurl,i);
	if (i>0) buffer_puts(buffer_1,"/");
	buffer_puts(buffer_1,"\">Parent directory");
	buffer_puts(buffer_1,"</a>\n");
	size+=40+i;
      }
      while(de=readdir(dir)) {
	char symlink[1024];
	char*p=de->d_name;
	unsigned int pl,dl=str_len(de->d_name);
	pl=dl;
	if (de->d_name[0]=='.') continue;	/* hidden files -> skip */
	if (lstat(de->d_name,&st)) continue;	/* can't stat -> skip */
	if (S_ISDIR(st.st_mode)) buffer_puts(buffer_1,"[DIR] ");
	else if (S_ISLNK(st.st_mode)) {
#ifdef SYSTEM_SYMLINK_DEREF
	  if (stat(de->d_name,&st))			/* dangling symlink */
#endif
	  {
	    if ((pl=readlink(de->d_name,symlink,1023))<1) continue;
	    p=symlink;
	  }
	  buffer_puts(buffer_1,"[LNK] ");	/* a symlink to something ... */
	}
	else if (S_ISREG(st.st_mode)) buffer_puts(buffer_1,"[TXT] ");
	else continue;				/* not a file we can provide -> skip */
	/* write a href */
	buffer_puts(buffer_1,"<a href=\"");
	hdl_encode_uri(p,pl);
	if (S_ISDIR(st.st_mode)) buffer_puts(buffer_1,"/"),++size;
	buffer_puts(buffer_1,"\">");
	if (de->d_name[0]==':') de->d_name[0]='.';	/* fnord special ... */
	hdl_encode_html(de->d_name,dl);
	buffer_puts(buffer_1,"</a>\n");
	size+=22+(dl<<1);
      }
      closedir(dir);
      buffer_puts(buffer_1,"</pre>\n");
      buffer_flush(buffer_1);
      retcode=200;
      dolog(size);
      exit(0);
    }
  }
}
#endif

#ifdef INDEX_CGI
static int handleindexcgi(const char *testurl,const char* origurl,char* space) {
  unsigned int ul,ol=str_len(origurl);
  char*test;
  while (testurl[0]=='/') ++testurl,--ol;
  ul=str_len(testurl);
  if (str_diff(testurl+ol,"index.html")) return 0; /* no request for index.html */
  test=space;
  ++test;
  ul-=4;
  byte_copy(test,ul,testurl);
  test[ul]='c';
  test[++ul]='g';
  test[++ul]='i';
  test[++ul]=0;
  if (stat(test,&st)) return 0; /* no index.cgi present */
  ul=1;
  if (st.st_gid==getegid()) ul=010;
  if (st.st_uid==geteuid()) ul=0100;
  if (!(st.st_mode&ul)) return 0; /* should be executable */
  *(--test)='/';
  url=test;
  return 1; /* Wow... now start "index.cgi" */
}
#endif

static void get_ucspi_env(void) {
  char* ucspi=getenv("PROTO");
  if (ucspi) {
    char* buf=alloca(str_len(ucspi)+20);
    unsigned int tmp=str_copy(buf,ucspi);
    buf[tmp+str_copy(buf+tmp,"REMOTEIP")]=0;
    remote_ip=getenv(buf);
#ifdef CGI
    buf[tmp+str_copy(buf+tmp,"REMOTEPORT")]=0;
    remote_port=getenv(buf);
    buf[tmp+str_copy(buf+tmp,"REMOTEINFO")]=0;
    remote_ident=getenv(buf);
#endif
  }
}

#ifdef CGI
static int findcgi(const char* c) {
  return (c[0]=='.' && c[1]=='c' &&
	  c[2]=='g' && c[3]=='i' &&
	  (c[4]=='/' || c[4]==0));
}
#endif

static int serve_read_write(int fd) {
  char tmp[4096];
  struct pollfd duh;
  time_t now,fini;
  char* tmp2;
  int len;
  off_t todo=rangeend-rangestart;
  duh.fd=1;
  duh.events=POLLOUT;
  if (rangestart) lseek(fd,rangestart,SEEK_SET);
  while (todo>0) {
    int olen;
    fini=time(&now)+WRITETIMEOUT;
    len=read(fd,tmp,todo>4096?4096:todo);
    olen=len;
    tmp2=tmp;
    while (len>0) {
      int written;
      switch (poll(&duh,1,(fini-now)*1000)) {
      case 0: if (now<fini) continue;	/* fall through */
      case -1: return 1;	/* timeout or error */
      }
      if ((written=write(1,tmp2,len))<0) return -1;
      len-=written;
      tmp2+=written;
      time(&now);
    }
    todo-=olen;
  }
}

static int serve_mmap(int fd) {
  off_t mapstart,maplen;
  unsigned long mapofs;
  char* map, *tmp2;
  struct pollfd duh;
  time_t now,fini;
  mapstart=rangestart&(~(off_t)0xfff); /* round down to 4k page */
  maplen=rangeend-mapstart;
  mapofs=rangestart-mapstart;
  if (maplen>64*1024*1024) maplen=64*1024*1024;
  map=mmap(0,maplen,PROT_READ,MAP_PRIVATE,fd,mapstart);
  if (map==MAP_FAILED) {
    if (errno==EINVAL && mapstart) {
      /* try rounded to 64k pages */
      mapstart=rangestart&0xffff;
      maplen=rangeend-mapstart;
      mapofs=rangestart-mapstart;
      map=mmap(0,maplen,PROT_READ,MAP_PRIVATE,fd,mapstart);
      if (map==MAP_FAILED)
	/* didn't work, use read/write instead. */
	return serve_read_write(fd);
    } else return serve_read_write(fd);
  }
  duh.fd=1;
  duh.events=POLLOUT;
  while (rangestart<rangeend) {
    int len;
    fini=time(&now)+WRITETIMEOUT;
    len=maplen-mapofs;
    tmp2=map+mapofs;
    while (len>0) {
      int written;
      switch (poll(&duh,1,(fini-now)*1000)) {
      case 0: if (now<fini) continue;	/* fall through */
      case -1: return 1;	/* timeout or error */
      }
      if ((written=write(1,tmp2,len))<0) return -1;
      len-=written;
      tmp2+=written;
      time(&now);
    }
    rangestart+=maplen-mapofs;
    mapstart+=maplen;
    munmap(map,maplen); mapofs=0;
    maplen=rangeend-mapstart;
    if (maplen) {
      if (maplen>64*1024*1024) maplen=64*1024*1024;
      map=mmap(0,maplen,PROT_READ,MAP_SHARED,fd,mapstart);
      if (map==MAP_FAILED)
	/* can't happen, really */
	return serve_read_write(fd);
    }
  }
  return 0;
}

/* write from offset "rangestart" to offset "rangeend" to fd #1 */
static int serve_static_data(int fd) {
  off_t len=rangeend-rangestart;
#ifdef TCP_CORK
  corked=0;
#endif
  if (len<4096) {	/* for small files, sendfile is actually slower */
    char tmp[4096];
    if (rangestart) lseek(fd,rangestart,SEEK_SET);
    read(fd,tmp,len);	/* if read fails, we can't back down now.
			      We already committed on the content-length */
    buffer_put(buffer_1,tmp,len);
    buffer_flush(buffer_1);
    return 0;
  }
#ifdef USE_SENDFILE
  {
    off_t offset=rangestart;
#ifdef TCP_CORK
    {
      int one=1;
      setsockopt(1,IPPROTO_TCP,TCP_CORK,&one,sizeof(one));
      corked=1;
    }
#endif
    buffer_flush(buffer_1);
    {
      off_t l=rangeend-rangestart;
      do {
	off_t c;
	c=(l>(1ul<<31))?1ul<<31:l;
	if ((c=sendfile(1,fd,&offset,c))==-1)
#ifdef USE_MMAP
	  return serve_mmap(fd);
#else
	  return serve_read_write(fd);
#endif
	l-=c;
      } while (l);
    }
    return 0;
  }
#else
  buffer_flush(buffer_1);
#ifdef TCP_CORK
  {
    int one=1;
    setsockopt(1,IPPROTO_TCP,TCP_CORK,&one,sizeof(one));
    corked=1;
  }
#endif
#ifdef USE_MMAP
  return serve_mmap(fd);
#else
  return serve_read_write(fd);
#endif
#endif
}

int main(int argc,char *argv[],const char *const *envp) {
  char buf[MAXHEADERLEN];
#if 0
  char buf2[MAXHEADERLEN];
#endif
  char *nurl,*origurl;
  int len;
  int in;

  if (argc>1) chdir(argv[1]);

#ifdef CHROOT
  if (chroot(".")) {
    if (errno!=EPERM)
      goto error500;
    /* else fnord was called with uid!=0, i.e. it already is chroot */
  } else {
    char *tmp;
    if (chdir("/")) goto error500;
    if ((tmp=getenv("GID"))) {
      long gid;
      if (tmp[scan_ulong(tmp,&gid)]==0) {
	gid_t gi=gid;
	if (setgroups(1,&gi)) goto error500;
      } else goto error500;
    }
    if ((tmp=getenv("UID"))) {
      long uid;
      if (tmp[scan_ulong(tmp,&uid)]==0) {
	if (setuid(uid)) goto error500;
      } else goto error500;
    }
  }
#endif
  signal(SIGPIPE,SIG_IGN);
  get_ucspi_env();

#ifdef KEEPALIVE
handlenext:
  encoding=0;
#endif
//  alarm(20);

  {
    int found=0;
    time_t fini,now;
    struct pollfd duh;

    fini=time(&now)+READTIMEOUT;
    duh.fd=0;
    duh.events=POLLIN;
    for (in=len=0;found<2;) {
      int tmp;
      switch (poll(&duh,1,READTIMEOUT*1000)) {
      case 0: if (time(&now)<fini) continue;	/* fall through */
      case -1:	/* timeout or error */
//	badrequest(408,"Request Time-out","No request appeared within a reasonable time period.");
	return 1;
      }
      tmp=read(0,buf+len,MAXHEADERLEN-len-5);
      if (tmp<0) return 1;
      if (tmp==0) return 1;
      in+=tmp;
      now=time(0);
      for (;(found<2)&&(len<in);++len) {
	if (buf[len]=='\r') continue;
	if (buf[len]=='\n') ++found; else found=0;
	if (found>1) break;
      }
    }
  }
  if (len<10) badrequest(400,"Bad Request","<title>Bad Request</title>That does not look like HTTP to me...");
  buf[len]=0;

  if (!strncasecmp(buf,"GET /",5)) {
    method=GET;
    url=buf+4;
  } else if (!strncasecmp(buf,"POST /",6)) {
    method=POST;
    url=buf+5;
  } else if (!strncasecmp(buf,"HEAD /",6)) {
    method=HEAD;
    url=buf+5;
  } else
    badrequest(400,"Bad Request","<title>Bad Request</title>Unsupported HTTP method.");

  origurl=url;

  {
    int nl=str_chr(buf,'\r');
    int space=str_chr(url,' ');
    if (space>=nl)
      badrequest(400,"Bad Request","<title>Bad Request</title>HTTP/0.9 not supported");
    if (str_diffn(url+space+1,"HTTP/1.",7))
      badrequest(400,"Bad Request","<title>Bad Request</title>Only HTTP 1.x supported");
    url[space]=0;
    httpversion=url[space+8]-'0';
#ifdef KEEPALIVE
    keepalive=0;
#endif

    /* demangle path in-place */
    {
      register char *tmp,*d;
      for (tmp=d=url; *tmp; ++tmp) {
	if (*tmp=='?') { args=tmp+1; break; }
	if (*tmp==' ') break;
	if (*tmp=='%') {
	  int a,b;
	  a=fromhex(tmp[1]);
	  b=fromhex(tmp[2]);
	  if (a>=0 && b>=0) {
	    *d=(a<<4)+b;
	    tmp+=2;
	  } else
	    *d=*tmp;
	} else
	  *d=*tmp;
	if (d>url+1 && *d=='/' && d[-1]==':' && d[-2]=='/') d-=2;
	if (d>url && *d=='/' && d[-1]=='/') --d;
	if (d>url && *d=='.' && d[-1]=='/') *d=':';
	++d;
      }
      *d=0;
      /* not good enough, we need a second pass */
    }

#ifdef CGI
    uri=alloca(space+1);
    byte_copy(uri,space+1,url);
#endif
  }

  {
    char *tmp;
    ua=header(buf,len,"User-Agent");
    refer=header(buf,len,"Referer");
    accept_enc=header(buf,len,"Accept-Encoding");
#ifdef KEEPALIVE
    if ((tmp=header(buf,len,"Connection"))) {	/* see if it's "keep-alive" or "close" */
      if (!strcasecmp(tmp,"keep-alive"))
	keepalive=1;
      else if (!strcasecmp(tmp,"close"))
	keepalive=-1;
    }
#endif
#ifdef CGI
    cookie=header(buf,len,"Cookie");
    auth_type=header(buf,len,"Authorization");
    if (method==POST) {
      content_type=header(buf,len,"Content-Type");
      content_len=header(buf,len,"Content-Length");
      if (content_len) {
	scan_ulong(content_len,&post_len);
	post_miss=buf+len+1;
	post_mlen=in-len-1;
	if (post_len<=post_mlen) post_mlen=post_len;
      }
    }
#endif
  }

#ifdef TARPIT
  if (ua && str_equal(ua,"EmailSiphon")) { sleep(120); exit(0); }
#endif

  port=getenv("TCPLOCALPORT");
  if (!port) port="80";
  {
    char *Buf;
    int i;
    host=header(buf,len,"Host");
    if (!host) i=100; else i=str_len(host)+7;
    Buf=alloca(i);
    if (!host) {
      char *ip=getenv("TCPLOCALIP");
      if (!ip) ip="127.0.0.1";
      if (str_len(ip)+str_len(port)>90) exit(101);
      host=Buf;
      i=str_copy(Buf,ip);
      i+=str_copy(Buf+i,":");
      i+=str_copy(Buf+i,port);
#ifdef NORMALIZE_HOST
    } else {
      int colon=str_chr(host,':');
      if (host[colon]==0) {
	i=str_copy(Buf,host);
	i+=str_copy(Buf+i,":");
	i+=str_copy(Buf+i,port);
	host=Buf;
      }
#endif
    }
    for (i=str_len(host); i >= 0; --i)
      if ((host[i]=tolower(host[i]))=='/')
hostb0rken:
	badrequest(400,"Bad Request","<title>Bad Request</title>Bullshit Host header");
    if (host[0]=='.') goto hostb0rken;
//    fprintf(stderr,"host %s\n",host);
#ifdef KEEPALIVE
    if (keepalive>0) {
      if ((rootdir=open(".",O_RDONLY))<0)
	keepalive=-1;
    }
#endif
    if (chdir(host)) {
#ifdef REDIRECT
      char symlink[1024];
      int linklen;
      if ((linklen=readlink(host,symlink,sizeof symlink))>0) {
	/* it is a broken symlink.  Do a redirection */
	redirectboilerplate();
	if (symlink[0]=='=') {
	  buffer_put(buffer_1,symlink+1,linklen-1);
	} else {
	  buffer_put(buffer_1,symlink,linklen);
	  while (url[0]=='/') ++url;
	  buffer_puts(buffer_1,url);
	}
	retcode=301;
	buffer_puts(buffer_1,"\r\n\r\n");
	dolog(0);
	buffer_flush(buffer_1);
	exit(0);
      }
#endif
      if (chdir("default") && argc<2) {
	badrequest(404,"Not Found","<title>Not Found</title>This host is not served here.");
      }
    }
  }
#ifdef AUTH
  {
    char *auth_script = ".http-auth";
    struct stat st;

    if(!stat(auth_script, &st)) {
      pid_t child;
      const char *authorization;

      authorization = header(buf, len, "Authorization");
      child = fork();
      if(child < 0) {
	badrequest(500, "Internal Server Error", "Server Resource problem.");
      } else if(child == 0) {
	const char *argv[5] = { auth_script, host, url, authorization, NULL };

	dup2(2, 1);
	execve(auth_script, argv, envp);
	_exit(1);
      } else {
	int status;
	pid_t childr;

	while((childr = waitpid(child, &status, 0)) < 0 && errno == EINTR);
	if(childr != child)
	  badrequest(500, "Internal Server Error", "Server system problem.");
	if(!WIFEXITED(status) || WEXITSTATUS(status)) {
	  retcode = 401;
	  dolog(0);
	  buffer_puts(buffer_1,"HTTP/1.0 401 Authorization Required\r\n"
	    "WWW-Authenticate: Basic realm=\"");
	  buffer_puts(buffer_1, host);
	  buffer_puts(buffer_1,"\"\r\nConnection: close\r\n\r\n"
	    "Access to this site is restricted.\r\n"
	    "Please provide credentials.\r\n");
	  buffer_flush(buffer_1);
	  exit(0);
	}
      }
    }
  }
#endif /* AUTH */
  nurl=url+str_len(url);
  if (nurl>url) --nurl;
  if (*nurl=='/') {
    int i;
    nurl=alloca(str_len(url)+12);
    i=str_copy(nurl,url);
    i+=str_copy(nurl+i,"index.html");
    nurl[i]=0;
    url=nurl;
    nurl=url+i;
  }
#ifdef CGI
  nurl-=3;
  {
    char* tmp,* pathinfo;
    pathinfo=0;
    for (tmp=url; tmp<nurl; ++tmp)
      if (findcgi(tmp)) {
	nurl=tmp;
	if (tmp[4]=='/')
	  pathinfo=tmp+4;
	break;
      }
    if (pathinfo) {
      int len=str_len(pathinfo)+1;
      tmp=alloca(len);
      memcpy(tmp,pathinfo,len);
      *pathinfo=0;
      pathinfo=tmp;
    }
    if (findcgi(nurl)) {
      int i;
      if ((method==HEAD)) badrequest(400,"Bad Request","Illegal HTTP method for Gateway call.");
#ifdef TCP_CORK
      {
	int one=1;
	setsockopt(1,IPPROTO_TCP,TCP_CORK,&one,sizeof(one));
      }
#endif
      for(i=nurl-url;i>-1;--i) {
	if ((nurl[0]=='/')&&(nurl[1]=='n')&&(nurl[2]=='p')&&(nurl[3]=='h')&&(nurl[4]=='-'))
	  start_cgi(1,pathinfo,envp);	/* start a NPH-CGI */
	--nurl;
      }
#ifdef INDEX_CGI
  indexcgi:
#endif
      start_cgi(0,pathinfo,envp);	/* start a CGI */
    }
  }
#endif

  {
    int fd;
    if ((fd=doit(buf,len,url,1))>=0) {		/* file was there */
      /* look if file.gz is also there and acceptable */
      char *fnord=alloca(str_len(url)+4);
      int i,fd2,trypng=0;
      char *oldencoding=encoding;
      char *oldmimetype=mimetype;
      i=str_copy(fnord,url);
      if (i>4 && str_equal(fnord+i-4,".gif")) {
	trypng=1;
	str_copy(fnord+i-3,"png");
      } else
	str_copy(fnord+i,".gz");
      fd2=doit(buf,len,fnord,0);
      if (fd2>=0) {	/* yeah! */
	url=fnord;
	close(fd);
	fd=fd2;
      } else {
	encoding=oldencoding;
	if (trypng) mimetype=oldmimetype;
      }
      retcode=200;
      dolog(st.st_size);
      if (rangestart || rangeend!=st.st_size)
	buffer_puts(buffer_1,"HTTP/1.0 206 Partial Content\r\nServer: "FNORD"\r\nContent-Type: ");
      else
	buffer_puts(buffer_1,"HTTP/1.0 200 OK\r\nServer: "FNORD"\r\nContent-Type: ");
      buffer_puts(buffer_1,mimetype);
      buffer_puts(buffer_1,"\r\n");
#ifdef KEEPALIVE
      switch (keepalive) {
      case -1: buffer_puts(buffer_1,"Connection: close\r\n"); break;
      case 1: buffer_puts(buffer_1,"Connection: Keep-Alive\r\n"); break;
      }
#endif
      if (encoding) {
	buffer_puts(buffer_1,"Content-Encoding: ");
	buffer_puts(buffer_1,encoding);
	buffer_puts(buffer_1,"\r\n");
      }
      buffer_puts(buffer_1,"Content-Length: ");
      buffer_putrange(buffer_1,rangeend-rangestart);
      buffer_puts(buffer_1,"\r\nLast-Modified: ");
      {
	struct tm* x=gmtime(&st.st_mtime);
	/* "Sun, 06 Nov 1994 08:49:37 GMT" */
	buffer_put(buffer_1,days+3*x->tm_wday,3);
	buffer_puts(buffer_1,", ");
	buffer_put2digits(buffer_1,x->tm_mday);
	buffer_puts(buffer_1," ");
	buffer_put(buffer_1,months+3*x->tm_mon,3);
	buffer_puts(buffer_1," ");
	buffer_put2digits(buffer_1,(x->tm_year+1900)/100);
	buffer_put2digits(buffer_1,(x->tm_year+1900)%100);
	buffer_puts(buffer_1," ");
	buffer_put2digits(buffer_1,x->tm_hour);
	buffer_puts(buffer_1,":");
	buffer_put2digits(buffer_1,x->tm_min);
	buffer_puts(buffer_1,":");
	buffer_put2digits(buffer_1,x->tm_sec);
	buffer_puts(buffer_1," GMT\r\n");
      }
      if (rangestart || rangeend!=st.st_size) {
	buffer_puts(buffer_1,"Accept-Ranges: bytes\r\nContent-Range: bytes ");
	buffer_putrange(buffer_1,rangestart);
	buffer_puts(buffer_1,"-");
	buffer_putrange(buffer_1,rangeend-1);
	buffer_puts(buffer_1,"/");
	buffer_putrange(buffer_1,st.st_size);
	buffer_puts(buffer_1,"\r\n");
      }
      buffer_puts(buffer_1,"\r\n");
      if (method==GET || method==POST) {
	switch (serve_static_data(fd)) {
	case 0: break;
	case -1: goto error500;
	case 1: return 1;
	}
#ifdef KEEPALIVE
#ifdef TCP_CORK
	if (corked) {
	  int zero=0;
	  setsockopt(1,IPPROTO_TCP,TCP_CORK,&zero,sizeof(zero));
	}
#endif
	if (keepalive>0) {
	  close(fd);
	  fchdir(rootdir); close(rootdir);
	  goto handlenext;
	}
#endif
	exit(0);
error500:
	retcode=500;
      } else
	buffer_flush(buffer_1);
    }
  }
#ifdef CHROOT
tuttikaputti:
#endif
  switch (retcode) {
  case 404:
    {
      char* space=alloca(strlen(url)+2);
#ifdef INDEX_CGI
      if (handleindexcgi(url,origurl,space)) goto indexcgi;
#endif
      handleredirect(url,origurl);
#ifdef DIR_LIST
      handledirlist(origurl);
#endif
      badrequest(404,"Not Found","<title>Not Found</title>No such file or directory.");
    }
  case 406: badrequest(406,"Not Acceptable","<title>Not Acceptable</title>Nothing acceptable found.");
  case 416: badrequest(416,"Requested Range Not Satisfiable","");
  case 304: badrequest(304,"Not Changed","");
  case 500: badrequest(500,"Internal Server Error","");
  }
  return 1;
}
