[BACK]Return to bozohttpd.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / libexec / httpd

Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.

Diff for /src/libexec/httpd/bozohttpd.c between version 1.7.8.4 and 1.7.8.4.2.1

version 1.7.8.4, 2010/10/15 23:25:45 version 1.7.8.4.2.1, 2014/07/09 16:09:39
Line 1 
Line 1 
 /*      $NetBSD$        */  /*      $NetBSD$        */
   
 /*      $eterna: bozohttpd.c,v 1.142 2008/03/03 03:36:11 mrg Exp $      */  /*      $eterna: bozohttpd.c,v 1.178 2011/11/18 09:21:15 mrg Exp $      */
   
 /*  /*
  * Copyright (c) 1997-2008 Matthew R. Green   * Copyright (c) 1997-2014 Matthew R. Green
  * All rights reserved.   * All rights reserved.
  *   *
  * Redistribution and use in source and binary forms, with or without   * Redistribution and use in source and binary forms, with or without
Line 15 
Line 15 
  *    notice, this list of conditions and the following disclaimer and   *    notice, this list of conditions and the following disclaimer and
  *    dedication in the documentation and/or other materials provided   *    dedication in the documentation and/or other materials provided
  *    with the distribution.   *    with the distribution.
  * 3. The name of the author may not be used to endorse or promote products  
  *    derived from this software without specific prior written permission.  
  *   *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES   * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
Line 57 
Line 55 
   
 /*  /*
  * requirements for minimal http/1.1 (at least, as documented in   * requirements for minimal http/1.1 (at least, as documented in
  * <draft-ietf-http-v11-spec-rev-06> which expired may 18, 1999):   * RFC 2616 (HTTP/1.1):
  *   *
  *      - 14.15: content-encoding handling. [1]   *      - 14.11: content-encoding handling. [1]
  *   *
  *      - 14.16: content-length handling.  this is only a SHOULD header   *      - 14.13: content-length handling.  this is only a SHOULD header
  *        thus we could just not send it ever.  [1]   *        thus we could just not send it ever.  [1]
  *   *
  *      - 14.17: content-type handling. [1]   *      - 14.17: content-type handling. [1]
  *   *
  *      - 14.25/28: if-{,un}modified-since handling.  maybe do this, but   *      - 14.28: if-unmodified-since handling.  if-modified-since is
  *        i really don't want to have to parse 3 differnet date formats   *        done since, shouldn't be too hard for this one.
  *   *
  * [1] need to revisit to ensure proper behaviour   * [1] need to revisit to ensure proper behaviour
  *   *
Line 90 
Line 88 
  *   *
  *      - 10.3.3/10.3.4/10.3.8:  just use '302' codes always.   *      - 10.3.3/10.3.4/10.3.8:  just use '302' codes always.
  *   *
  *      - 14.1/14.2/14.3/14.27: we do not support Accept: headers..   *      - 14.1/14.2/14.3/14.27: we do not support Accept: headers.
  *        just ignore them and send the request anyway.  they are   *        just ignore them and send the request anyway.  they are
  *        only SHOULD.   *        only SHOULD.
  *   *
  *      - 14.5/14.16/14.35: we don't do ranges.  from section 14.35.2   *      - 14.5/14.16/14.35: only support simple ranges: %d- and %d-%d
  *        `A server MAY ignore the Range header'.  but it might be nice.   *        would be nice to support more.
  *        since 20080301 we support simple range headers.  
  *   *
  *      - 14.9: we aren't a cache.   *      - 14.9: we aren't a cache.
  *   *
  *      - 14.15: content-md5 would be nice...   *      - 14.15: content-md5 would be nice.
  *  
  *      - 14.24/14.26/14.27: be nice to support this...  
  *   *
  *      - 14.44: not sure about this Vary: header.  ignore it for now.   *      - 14.24/14.26/14.27: if-match, if-none-match, if-range.  be
    *        nice to support this.
    *
    *      - 14.44: Vary: seems unneeded.  ignore it for now.
  */   */
   
 #ifndef INDEX_HTML  #ifndef INDEX_HTML
 #define INDEX_HTML              "index.html"  #define INDEX_HTML              "index.html"
 #endif  #endif
 #ifndef SERVER_SOFTWARE  #ifndef SERVER_SOFTWARE
 #define SERVER_SOFTWARE         "bozohttpd/20080303-nb1"  #define SERVER_SOFTWARE         "bozohttpd/20140708"
 #endif  #endif
 #ifndef DIRECT_ACCESS_FILE  #ifndef DIRECT_ACCESS_FILE
 #define DIRECT_ACCESS_FILE      ".bzdirect"  #define DIRECT_ACCESS_FILE      ".bzdirect"
Line 122 
Line 120 
 #ifndef ABSREDIRECT_FILE  #ifndef ABSREDIRECT_FILE
 #define ABSREDIRECT_FILE        ".bzabsredirect"  #define ABSREDIRECT_FILE        ".bzabsredirect"
 #endif  #endif
   #ifndef PUBLIC_HTML
   #define PUBLIC_HTML             "public_html"
   #endif
   
   #ifndef USE_ARG
   #define USE_ARG(x)      /*LINTED*/(void)&(x)
   #endif
   
 /*  /*
  * And so it begins ..   * And so it begins ..
Line 149 
Line 154 
 #include <time.h>  #include <time.h>
 #include <unistd.h>  #include <unistd.h>
   
 #ifndef __attribute__  
 #define __attribute__(x)  
 #endif /* __attribute__ */  
   
 #include "bozohttpd.h"  #include "bozohttpd.h"
   
 #ifndef MAX_WAIT_TIME  #ifndef MAX_WAIT_TIME
Line 160 
Line 161 
 #endif  #endif
   
 /* variables and functions */  /* variables and functions */
   
         int     bflag;          /* background; drop into daemon mode */  
         int     fflag;          /* keep daemon mode in foreground */  
 static  int     eflag;          /* don't clean environ; -t/-U only */  
         const char *Iflag = "http";/* bind port; default "http" */  
         int     Iflag_set;  
         int     dflag = 0;      /* debugging level */  
         char    *myname;        /* my name */  
   
 #ifndef LOG_FTP  #ifndef LOG_FTP
 #define LOG_FTP LOG_DAEMON  #define LOG_FTP LOG_DAEMON
 #endif  #endif
   
 static  char    *tflag;         /* root directory */  
 static  char    *Uflag;         /* user name to switch to */  
 static  int     Vflag;          /* unknown vhosts go to normal slashdir */  
 static  int     nflag;          /* avoid gethostby*() */  
 static  int     rflag;          /* make sure referrer = me unless url = / */  
 static  int     sflag;          /* log to stderr even if it is not a TTY */  
 static  char    *vpath;         /* virtual directory base */  
   
         char    *slashdir;      /* www slash directory */  
   
         const char *server_software = SERVER_SOFTWARE;  
         const char *index_html = INDEX_HTML;  
         const char http_09[] = "HTTP/0.9";  
         const char http_10[] = "HTTP/1.0";  
         const char http_11[] = "HTTP/1.1";  
         const char text_plain[] = "text/plain";  
   
 static  void    usage(void);  
 static  void    alarmer(int);  
 volatile sig_atomic_t   alarmhit;  volatile sig_atomic_t   alarmhit;
   
 static  void    parse_request(char *, char **, char **, char **, char **);  /*
 static  http_req *read_request(void);   * check there's enough space in the prefs and names arrays.
 static  struct headers *addmerge_header(http_req *request, char *val,   */
                                         char *str, ssize_t len);  static int
 static  void    process_request(http_req *);  size_arrays(bozoprefs_t *bozoprefs, unsigned needed)
 static  int     check_direct_access(http_req *request);  {
 static  char    *transform_request(http_req *, int *);          char    **temp;
 static  void    handle_redirect(http_req *, const char *, int);  
   
 static  void    check_virtual(http_req *);  
 static  void    check_bzredirect(http_req *);  
 static  void    fix_url_percent(http_req *);  
 static  void    process_method(http_req *, const char *);  
 static  void    process_proto(http_req *, const char *);  
 static  void    escape_html(http_req *);  
   
 static  const char *http_errors_short(int);  
 static  const char *http_errors_long(int);  
   
   
 void    *bozomalloc(size_t);  
 void    *bozorealloc(void *, size_t);  
 char    *bozostrdup(const char *);  
   
 /* bozotic io */  
 int     (*bozoprintf)(const char *, ...) = printf;  
 ssize_t (*bozoread)(int, void *, size_t) = read;  
 ssize_t (*bozowrite)(int, const void *, size_t) = write;  
 int     (*bozoflush)(FILE *) = fflush;  
   
         char    *progname;  
   
         int     main(int, char **);          if (bozoprefs->size == 0) {
                   /* only get here first time around */
                   bozoprefs->size = needed;
                   if ((bozoprefs->name = calloc(sizeof(char *), needed)) == NULL) {
                           (void) fprintf(stderr, "size_arrays: bad alloc\n");
                           return 0;
                   }
                   if ((bozoprefs->value = calloc(sizeof(char *), needed)) == NULL) {
                           free(bozoprefs->name);
                           (void) fprintf(stderr, "size_arrays: bad alloc\n");
                           return 0;
                   }
           } else if (bozoprefs->c == bozoprefs->size) {
                   /* only uses 'needed' when filled array */
                   bozoprefs->size += needed;
                   temp = realloc(bozoprefs->name, sizeof(char *) * needed);
                   if (temp == NULL) {
                           (void) fprintf(stderr, "size_arrays: bad alloc\n");
                           return 0;
                   }
                   bozoprefs->name = temp;
                   temp = realloc(bozoprefs->value, sizeof(char *) * needed);
                   if (temp == NULL) {
                           (void) fprintf(stderr, "size_arrays: bad alloc\n");
                           return 0;
                   }
                   bozoprefs->value = temp;
           }
           return 1;
   }
   
 static void  static int
 usage(void)  findvar(bozoprefs_t *bozoprefs, const char *name)
 {  {
         warning("usage: %s [options] slashdir [myname]", progname);          unsigned        i;
         warning("options:");  
 #ifdef DEBUG          for (i = 0 ; i < bozoprefs->c && strcmp(bozoprefs->name[i], name) != 0; i++)
         warning("   -d\t\t\tenable debug support");                  ;
 #endif          return (i == bozoprefs->c) ? -1 : (int)i;
         warning("   -s\t\t\talways log to stderr");  
 #ifndef NO_USER_SUPPORT  
         warning("   -u\t\t\tenable ~user/public_html support");  
         warning("   -p dir\t\tchange `public_html' directory name]");  
 #endif  
 #ifndef NO_DYNAMIC_CONTENT  
         warning("   -M arg t c c11\tadd this mime extenstion");  
 #endif  
 #ifndef NO_CGIBIN_SUPPORT  
 #ifndef NO_DYNAMIC_CONTENT  
         warning("   -C arg prog\t\tadd this CGI handler");  
 #endif  
         warning("   -c cgibin\t\tenable cgi-bin support in this directory");  
 #endif  
 #ifndef NO_DAEMON_MODE  
         warning("   -b\t\t\tbackground and go into daemon mode");  
         warning("   -f\t\t\tkeep daemon mode in the foreground");  
         warning("   -i address\t\tbind on this address (daemon mode only)");  
         warning("   -I port\t\tbind on this port (daemon mode only)");  
 #endif  
         warning("   -S version\t\tset server version string");  
         warning("   -t dir\t\tchroot to `dir'");  
         warning("   -U username\t\tchange user to `user'");  
         warning("   -e\t\t\tdon't clean the environment (-t and -U only)");  
         warning("   -v virtualroot\tenable virtual host support in this directory");  
         warning("   -r\t\t\tmake sure sub-pages come from this host via referrer");  
 #ifndef NO_DIRINDEX_SUPPORT  
         warning("   -X\t\t\tenable automatic directory index support");  
         warning("   -H\t\t\thide files starting with a period (.) in index mode");  
 #endif  
         warning("   -x index\t\tchange default `index.html' file name");  
 #ifndef NO_SSL_SUPPORT  
         warning("   -Z cert privkey\tspecify path to server certificate and private key file\n"  
                 "\t\t\tin pem format and enable bozohttpd in SSL mode");  
 #endif /* NO_SSL_SUPPORT */  
         error(1, "%s failed to start", progname);  
 }  }
   
 int  int
 main(int argc, char **argv)  bozo_set_pref(bozoprefs_t *bozoprefs, const char *name, const char *value)
 {  {
         http_req *http_request;          int     i;
         extern  char **environ;  
         char    *cleanenv[1];  
         uid_t   uid;  
         int     c;  
   
         uid = 0;        /* XXX gcc */  
   
         if ((progname = strrchr(argv[0], '/')) != NULL)  
                 progname++;  
         else  
                 progname = argv[0];  
   
         openlog(progname, LOG_PID|LOG_NDELAY, LOG_FTP);  
   
         while ((c = getopt(argc, argv,  
                            "C:HI:M:S:U:VXZ:bc:defhi:np:rst:uv:x:z:")) != -1) {  
                 switch(c) {  
   
                 case 'M':  
 #ifndef NO_DYNAMIC_CONTENT  
                         /* make sure there's four arguments */  
                         if (argc - optind < 3)  
                                 usage();  
                         add_content_map_mime(optarg, argv[optind],  
                             argv[optind+1], argv[optind+2]);  
                         optind += 3;  
                         break;  
 #else  
                         error(1, "dynmic mime content support is not enabled");  
                         /* NOTREACHED */  
 #endif /* NO_DYNAMIC_CONTENT */  
   
                 case 'n':  
                         nflag = 1;  
                         break;  
   
                 case 'r':  
                         rflag = 1;  
                         break;  
   
                 case 's':  
                         sflag = 1;  
                         break;  
   
                 case 'S':  
                         server_software = optarg;  
                         break;  
                 case 'Z':  
 #ifndef NO_SSL_SUPPORT  
                         /* make sure there's two arguments */  
                         if (argc - optind < 1)  
                                 usage();  
                         ssl_set_opts(optarg, argv[optind++]);  
                         break;  
 #else  
                         error(1, "ssl support is not enabled");  
                         /* NOT REACHED */  
 #endif /* NO_SSL_SUPPORT */  
                 case 'U':  
                         Uflag = optarg;  
                         break;  
   
                 case 'V':  
                         Vflag = 1;  
                         break;  
   
                 case 'v':  
                         vpath = optarg;  
                         break;  
   
                 case 'x':  
                         index_html = optarg;  
                         break;  
   
 #ifndef NO_DAEMON_MODE  
                 case 'b':  
                         bflag = 1;  
                         break;  
   
                 case 'e':  
                         eflag = 1;  
                         break;  
   
                 case 'f':  
                         fflag = 1;  
                         break;  
   
                 case 'i':  
                         iflag = optarg;  
                         break;  
   
                 case 'I':  
                         Iflag_set = 1;  
                         Iflag = optarg;  
                         break;  
 #else /* NO_DAEMON_MODE */  
                 case 'b':  
                 case 'e':  
                 case 'f':  
                 case 'i':  
                 case 'I':  
                         error(1, "Daemon mode is not enabled");  
                         /* NOTREACHED */  
 #endif /* NO_DAEMON_MODE */  
   
 #ifndef NO_CGIBIN_SUPPORT  
                 case 'c':  
                         set_cgibin(optarg);  
                         break;  
   
                 case 'C':  
 #ifndef NO_DYNAMIC_CONTENT  
                         /* make sure there's two arguments */  
                         if (argc - optind < 1)  
                                 usage();  
                         add_content_map_cgi(optarg, argv[optind++]);  
                         break;  
 #else  
                         error(1, "dynmic CGI handler support is not enabled");  
                         /* NOTREACHED */  
 #endif /* NO_DYNAMIC_CONTENT */  
   
 #else  
                 case 'c':  
                 case 'C':  
                         error(1, "CGI is not enabled");  
                         /* NOTREACHED */  
 #endif /* NO_CGIBIN_SUPPORT */  
   
                 case 'd':  
                         dflag++;  
 #ifndef DEBUG  
                         if (dflag == 1)  
                                 warning("Debugging is not enabled");  
 #endif /* !DEBUG */  
                         break;  
   
 #ifndef NO_USER_SUPPORT  
                 case 'p':  
                         public_html = optarg;  
                         break;  
   
                 case 't':  
                         tflag = optarg;  
                         break;  
   
                 case 'u':  
                         uflag = 1;  
                         break;  
 #else  
                 case 'p':  
                 case 't':  
                 case 'u':  
                         error(1, "User support is not enabled");  
                         /* NOTREACHED */  
 #endif /* NO_USER_SUPPORT */  
   
 #ifndef NO_DIRINDEX_SUPPORT  
                 case 'H':  
                         Hflag = 1;  
                         break;  
   
                 case 'X':  
                         Xflag = 1;  
                         break;  
   
 #else  
                 case 'H':  
                 case 'X':  
                         error(1, "directory indexing is not enabled");  
                         /* NOTREACHED */  
 #endif /* NO_DIRINDEX_SUPPORT */  
   
                 default:          if ((i = findvar(bozoprefs, name)) < 0) {
                         usage();                  /* add the element to the array */
                         /* NOTREACHED */                  if (size_arrays(bozoprefs, bozoprefs->size + 15)) {
                           bozoprefs->name[i = bozoprefs->c++] = strdup(name);
                   }
           } else {
                   /* replace the element in the array */
                   if (bozoprefs->value[i]) {
                           free(bozoprefs->value[i]);
                           bozoprefs->value[i] = NULL;
                 }                  }
         }          }
         argc -= optind;          /* sanity checks for range of values go here */
         argv += optind;          bozoprefs->value[i] = strdup(value);
           return 1;
         if (argc == 1) {  }
                 myname = bozomalloc(MAXHOSTNAMELEN+1);  
                 /* XXX we do not check for FQDN here */  
                 if (gethostname(myname, MAXHOSTNAMELEN+1) < 0)  
                         error(1, "gethostname");  
                 myname[MAXHOSTNAMELEN] = '\0';  
         } else if (argc == 2)  
                 myname = argv[1];  
         else  
                 usage();  
   
         slashdir = argv[0];  
         debug((DEBUG_OBESE, "myname is %s, slashdir is %s", myname, slashdir));  
   
         /*  
          * initialise ssl and daemon mode if necessary.  
          */  
         ssl_init();  
         daemon_init();  
   
         /*  
          * prevent info leakage between different compartments.  
          * some PATH values in the environment would be invalided  
          * by chroot. cross-user settings might result in undesirable  
          * effects.  
          */  
         if ((tflag != NULL || Uflag != NULL) && !eflag) {  
                 cleanenv[0] = NULL;  
                 environ = cleanenv;  
         }  
   
         /*  
          * look up user/group information.  
          */  
         if (Uflag != NULL) {  
                 struct passwd *pw;  
   
                 if ((pw = getpwnam(Uflag)) == NULL)  
                         error(1, "getpwnam(%s): %s", Uflag, strerror(errno));  
                 if (initgroups(pw->pw_name, pw->pw_gid) == -1)  
                         error(1, "initgroups: %s", strerror(errno));  
                 if (setgid(pw->pw_gid) == -1)  
                         error(1, "setgid(%u): %s", pw->pw_gid, strerror(errno));  
                 uid = pw->pw_uid;  
         }  
   
         /*  
          * handle chroot.  
          */  
         if (tflag != NULL) {  
                 if (chdir(tflag) == -1)  
                         error(1, "chdir(%s): %s", tflag, strerror(errno));  
                 if (chroot(tflag) == -1)  
                         error(1, "chroot(%s): %s", tflag, strerror(errno));  
         }  
   
         if (Uflag != NULL)  
                 if (setuid(uid) == -1)  
                         error(1, "setuid(%d): %s", uid, strerror(errno));  
   
         /*  
          * be sane, don't start serving up files from a  
          * hierarchy we don't have permission to get to.  
          */  
         if (tflag != NULL)  
                 if (chdir("/") == -1)  
                         error(1, "chdir /: %s", strerror(errno));  
   
         /*  /*
          * read and process the HTTP request.   * get a variable's value, or NULL
          */   */
         do {  char *
                 http_request = read_request();  bozo_get_pref(bozoprefs_t *bozoprefs, const char *name)
                 if (http_request) {  {
                         process_request(http_request);          int     i;
                         return (0);  
                 }  
         } while (bflag);  
   
         return (0);          return ((i = findvar(bozoprefs, name)) < 0) ? NULL :
                           bozoprefs->value[i];
 }  }
   
 char *  char *
 http_date(void)  bozo_http_date(char *date, size_t datelen)
 {  {
         static  char date[40];  
         struct  tm *tm;          struct  tm *tm;
         time_t  now;          time_t  now;
   
         /* Sun, 06 Nov 1994 08:49:37 GMT */          /* Sun, 06 Nov 1994 08:49:37 GMT */
         now = time(NULL);          now = time(NULL);
         tm = gmtime(&now);      /* HTTP/1.1 spec rev 06 sez GMT only */          tm = gmtime(&now);      /* HTTP/1.1 spec rev 06 sez GMT only */
         strftime(date, sizeof date, "%a, %d %b %Y %H:%M:%S GMT", tm);          strftime(date, datelen, "%a, %d %b %Y %H:%M:%S GMT", tm);
         return date;          return date;
 }  }
   
 /*  /*
  * convert "in" into the three parts of a request (first line)   * convert "in" into the three parts of a request (first line).
    * we allocate into file and query, but return pointers into
    * "in" for proto and method.
  */   */
 static void  static void
 parse_request(char *in, char **method, char **file, char **query, char **proto)  parse_request(bozohttpd_t *httpd, char *in, char **method, char **file,
                   char **query, char **proto)
 {  {
         ssize_t len;          ssize_t len;
         char    *val;          char    *val;
   
         *method = *file = *query = *proto = NULL;               /* set them up */          USE_ARG(httpd);
           debug((httpd, DEBUG_EXPLODING, "parse in: %s", in));
           *method = *file = *query = *proto = NULL;
   
         len = (ssize_t)strlen(in);          len = (ssize_t)strlen(in);
         val = bozostrnsep(&in, " \t\n\r", &len);          val = bozostrnsep(&in, " \t\n\r", &len);
         if (len < 1 || val == NULL)          if (len < 1 || val == NULL)
                 return;                  return;
         *method = val;          *method = val;
   
         while (*in == ' ' || *in == '\t')          while (*in == ' ' || *in == '\t')
                 in++;                  in++;
         val = bozostrnsep(&in, " \t\n\r", &len);          val = bozostrnsep(&in, " \t\n\r", &len);
Line 579  parse_request(char *in, char **method, c
Line 293  parse_request(char *in, char **method, c
                         *file = val;                          *file = val;
                 else                  else
                         *file = in;                          *file = in;
                 return;          } else {
         }                  *file = val;
   
         *file = val;                  *query = strchr(*file, '?');
         *query = strchr(*file, '?');                  if (*query)
         if (*query)  {                          *(*query)++ = '\0';
           *query = *query + 1;  
           *(*query - 1) = '\0';                  if (in) {
                           while (*in && (*in == ' ' || *in == '\t'))
                                   in++;
                           if (*in)
                                   *proto = in;
                   }
         }          }
   
         if (in) {          /* allocate private copies */
                 while (*in && (*in == ' ' || *in == '\t'))          *file = bozostrdup(httpd, *file);
                         in++;          if (*query)
                 if (*in)                  *query = bozostrdup(httpd, *query);
                         *proto = in;  
         }          debug((httpd, DEBUG_FAT,
         debug((DEBUG_FAT, "URL INFO: |m: %s |f: %s |q: %s |p: %s |",                  "url: method: \"%s\" file: \"%s\" query: \"%s\" proto: \"%s\"",
                *method, *file, *query, *proto));                  *method, *file, *query, *proto));
   }
   
   /*
    * cleanup a bozo_httpreq_t after use
    */
   void
   bozo_clean_request(bozo_httpreq_t *request)
   {
           struct bozoheaders *hdr, *ohdr = NULL;
   
           if (request == NULL)
                   return;
   
           /* If SSL enabled cleanup SSL structure. */
           bozo_ssl_destroy(request->hr_httpd);
   
           /* clean up request */
           free(request->hr_remotehost);
           free(request->hr_remoteaddr);
           free(request->hr_serverport);
           free(request->hr_virthostname);
           free(request->hr_file);
           free(request->hr_oldfile);
           free(request->hr_query);
           free(request->hr_host);
           bozo_auth_cleanup(request);
           for (hdr = SIMPLEQ_FIRST(&request->hr_headers); hdr;
               hdr = SIMPLEQ_NEXT(hdr, h_next)) {
                   free(hdr->h_value);
                   free(hdr->h_header);
                   free(ohdr);
                   ohdr = hdr;
           }
           free(ohdr);
   
           free(request);
 }  }
   
 /*  /*
Line 611  alarmer(int sig)
Line 365  alarmer(int sig)
 }  }
   
 /*  /*
  * This function reads a http request from stdin, returning a pointer to a   * add or merge this header (val: str) into the requests list
  * http_req structure, describing the request.  
  */   */
 static http_req *  static bozoheaders_t *
 read_request(void)  addmerge_header(bozo_httpreq_t *request, char *val,
                   char *str, ssize_t len)
 {  {
         struct  sigaction       sa;          struct  bozoheaders *hdr;
         char    *str, *val, *method, *file, *proto, *query;  
         char    *host, *addr, *port;  
         char    bufport[10];  
         char    hbuf[NI_MAXHOST], abuf[NI_MAXHOST];  
         struct  sockaddr_storage ss;  
         ssize_t len;  
         int     line = 0;  
         socklen_t slen;  
         http_req *request;  
   
         /*  
          * if we're in daemon mode, daemon_fork() will return here once  
          * for each child, then we can setup SSL.  
          */  
         daemon_fork();  
         ssl_accept();  
   
         request = bozomalloc(sizeof *request);          USE_ARG(len);
         memset(request, 0, sizeof *request);          /* do we exist already? */
         request->hr_allow = request->hr_host = NULL;          SIMPLEQ_FOREACH(hdr, &request->hr_headers, h_next) {
         request->hr_content_type = request->hr_content_length = NULL;                  if (strcasecmp(val, hdr->h_header) == 0)
         request->hr_range = NULL;                          break;
         request->hr_if_modified_since = NULL;  
         request->hr_last_byte_pos = -1;  
   
         slen = sizeof(ss);  
         if (getpeername(0, (struct sockaddr *)&ss, &slen) < 0)  
                 host = addr = NULL;  
         else {  
                 if (getnameinfo((struct sockaddr *)&ss, slen,  
                     abuf, sizeof abuf, NULL, 0, NI_NUMERICHOST) == 0)  
                         addr = abuf;  
                 else  
                         addr = NULL;  
                 if (nflag == 0 && getnameinfo((struct sockaddr *)&ss, slen,  
                     hbuf, sizeof hbuf, NULL, 0, 0) == 0)  
                         host = hbuf;  
                 else  
                         host = NULL;  
         }  
         if (host != NULL)  
                 request->hr_remotehost = bozostrdup(host);  
         if (addr != NULL)  
                 request->hr_remoteaddr = bozostrdup(addr);  
         slen = sizeof(ss);  
         if (getsockname(0, (struct sockaddr *)&ss, &slen) < 0)  
                 port = NULL;  
         else {  
                 if (getnameinfo((struct sockaddr *)&ss, slen, NULL, 0,  
                     bufport, sizeof bufport, NI_NUMERICSERV) == 0)  
                         port = bufport;  
                 else  
                         port = NULL;  
         }          }
         if (port != NULL)  
                 request->hr_serverport = bozostrdup(port);  
   
         /*          if (hdr) {
          * setup a timer to make sure the request is not hung                  /* yup, merge it in */
          */                  char *nval;
         sa.sa_handler = alarmer;  
         sigemptyset(&sa.sa_mask);  
         sigaddset(&sa.sa_mask, SIGALRM);  
         sa.sa_flags = 0;  
         sigaction(SIGALRM, &sa, NULL);  /* XXX */  
   
         alarm(MAX_WAIT_TIME);                  if (asprintf(&nval, "%s, %s", hdr->h_value, str) == -1) {
         while ((str = bozodgetln(STDIN_FILENO, &len, bozoread)) != NULL) {                          (void)bozo_http_error(request->hr_httpd, 500, NULL,
                 alarm(0);                               "memory allocation failure");
                 if (alarmhit)                          return NULL;
                         http_error(408, NULL, "request timed out");                  }
                 line++;                  free(hdr->h_value);
                   hdr->h_value = nval;
           } else {
                   /* nope, create a new one */
   
                 if (line == 1) {                  hdr = bozomalloc(request->hr_httpd, sizeof *hdr);
                         str = bozostrdup(str);  /* we use this copy */                  hdr->h_header = bozostrdup(request->hr_httpd, val);
                   if (str && *str)
                           hdr->h_value = bozostrdup(request->hr_httpd, str);
                   else
                           hdr->h_value = bozostrdup(request->hr_httpd, " ");
   
                         if (len < 1)                  SIMPLEQ_INSERT_TAIL(&request->hr_headers, hdr, h_next);
                                 http_error(404, NULL, "null method");                  request->hr_nheaders++;
                         warning("got request ``%s'' from host %s to port %s",          }
                             str,  
                             host ? host : addr ? addr : "<local>",  
                             port ? port : "<stdin>");  
                         debug((DEBUG_FAT, "read_req, getting request: ``%s''",  
                             str));  
   
                         parse_request(str, &method, &file, &query, &proto);  
   
                         if (method == NULL)  
                                 http_error(404, NULL, "null method");  
                         if (file == NULL)  
                                 http_error(404, NULL, "null file");  
   
                         /*          return hdr;
                          * note that we parse the proto first, so that we  }
                          * can more properly parse the method and the url.  
                          */  
                         request->hr_file = file;  
                         request->hr_query = query;  
   
                         process_proto(request, proto);  /*
                         process_method(request, method);   * as the prototype string is not constant (eg, "HTTP/1.1" is equivalent
    * to "HTTP/001.01"), we MUST parse this.
    */
   static int
   process_proto(bozo_httpreq_t *request, const char *proto)
   {
           char    majorstr[16], *minorstr;
           int     majorint, minorint;
   
                         /* http/0.9 has no header processing */          if (proto == NULL) {
                         if (request->hr_proto == http_09)  got_proto_09:
                                 break;                  request->hr_proto = request->hr_httpd->consts.http_09;
                 } else {                /* incoming headers */                  debug((request->hr_httpd, DEBUG_FAT, "request %s is http/0.9",
                         struct  headers *hdr;                          request->hr_file));
                   return 0;
           }
   
                         if (*str == '\0')          if (strncasecmp(proto, "HTTP/", 5) != 0)
                                 break;                  goto bad;
           strncpy(majorstr, proto + 5, sizeof majorstr);
           majorstr[sizeof(majorstr)-1] = 0;
           minorstr = strchr(majorstr, '.');
           if (minorstr == NULL)
                   goto bad;
           *minorstr++ = 0;
   
                         val = bozostrnsep(&str, ":", &len);          majorint = atoi(majorstr);
                         debug((DEBUG_EXPLODING,          minorint = atoi(minorstr);
                             "read_req2: after bozostrnsep: str ``%s'' val ``%s''",  
                             str, val));          switch (majorint) {
                         if (val == NULL || len == -1)          case 0:
                                 http_error(404, request, "no header");                  if (minorint != 9)
                         while (*str == ' ' || *str == '\t')                          break;
                   goto got_proto_09;
           case 1:
                   if (minorint == 0)
                           request->hr_proto = request->hr_httpd->consts.http_10;
                   else if (minorint == 1)
                           request->hr_proto = request->hr_httpd->consts.http_11;
                   else
                           break;
   
                   debug((request->hr_httpd, DEBUG_FAT, "request %s is %s",
                       request->hr_file, request->hr_proto));
                   SIMPLEQ_INIT(&request->hr_headers);
                   request->hr_nheaders = 0;
                   return 0;
           }
   bad:
           return bozo_http_error(request->hr_httpd, 404, NULL, "unknown prototype");
   }
   
   /*
    * process each type of HTTP method, setting this HTTP requests
    # method type.
    */
   static struct method_map {
           const char *name;
           int     type;
   } method_map[] = {
           { "GET",        HTTP_GET, },
           { "POST",       HTTP_POST, },
           { "HEAD",       HTTP_HEAD, },
   #if 0   /* other non-required http/1.1 methods */
           { "OPTIONS",    HTTP_OPTIONS, },
           { "PUT",        HTTP_PUT, },
           { "DELETE",     HTTP_DELETE, },
           { "TRACE",      HTTP_TRACE, },
           { "CONNECT",    HTTP_CONNECT, },
   #endif
           { NULL,         0, },
   };
   
   static int
   process_method(bozo_httpreq_t *request, const char *method)
   {
           struct  method_map *mmp;
   
           if (request->hr_proto == request->hr_httpd->consts.http_11)
                   request->hr_allow = "GET, HEAD, POST";
   
           for (mmp = method_map; mmp->name; mmp++)
                   if (strcasecmp(method, mmp->name) == 0) {
                           request->hr_method = mmp->type;
                           request->hr_methodstr = mmp->name;
                           return 0;
                   }
   
           return bozo_http_error(request->hr_httpd, 404, request, "unknown method");
   }
   
   /*
    * This function reads a http request from stdin, returning a pointer to a
    * bozo_httpreq_t structure, describing the request.
    */
   bozo_httpreq_t *
   bozo_read_request(bozohttpd_t *httpd)
   {
           struct  sigaction       sa;
           char    *str, *val, *method, *file, *proto, *query;
           char    *host, *addr, *port;
           char    bufport[10];
           char    hbuf[NI_MAXHOST], abuf[NI_MAXHOST];
           struct  sockaddr_storage ss;
           ssize_t len;
           int     line = 0;
           socklen_t slen;
           bozo_httpreq_t *request;
   
           /*
            * if we're in daemon mode, bozo_daemon_fork() will return here twice
            * for each call.  once in the child, returning 0, and once in the
            * parent, returning 1.  for each child, then we can setup SSL, and
            * the parent can signal the caller there was no request to process
            * and it will wait for another.
            */
           if (bozo_daemon_fork(httpd))
                   return NULL;
           bozo_ssl_accept(httpd);
   
           request = bozomalloc(httpd, sizeof(*request));
           memset(request, 0, sizeof(*request));
           request->hr_httpd = httpd;
           request->hr_allow = request->hr_host = NULL;
           request->hr_content_type = request->hr_content_length = NULL;
           request->hr_range = NULL;
           request->hr_last_byte_pos = -1;
           request->hr_if_modified_since = NULL;
           request->hr_virthostname = NULL;
           request->hr_file = NULL;
           request->hr_oldfile = NULL;
   
           slen = sizeof(ss);
           if (getpeername(0, (struct sockaddr *)(void *)&ss, &slen) < 0)
                   host = addr = NULL;
           else {
                   if (getnameinfo((struct sockaddr *)(void *)&ss, slen,
                       abuf, sizeof abuf, NULL, 0, NI_NUMERICHOST) == 0)
                           addr = abuf;
                   else
                           addr = NULL;
                   if (httpd->numeric == 0 &&
                       getnameinfo((struct sockaddr *)(void *)&ss, slen,
                                   hbuf, sizeof hbuf, NULL, 0, 0) == 0)
                           host = hbuf;
                   else
                           host = NULL;
           }
           if (host != NULL)
                   request->hr_remotehost = bozostrdup(request->hr_httpd, host);
           if (addr != NULL)
                   request->hr_remoteaddr = bozostrdup(request->hr_httpd, addr);
           slen = sizeof(ss);
   
           /*
            * Override the bound port from the request value, so it works even
            * if passed through a proxy that doesn't rewrite the port.
            */
           if (httpd->bindport) {
                   if (strcmp(httpd->bindport, "80") != 0)
                           port = httpd->bindport;
                   else
                           port = NULL;
           } else {
                   if (getsockname(0, (struct sockaddr *)(void *)&ss, &slen) < 0)
                           port = NULL;
                   else {
                           if (getnameinfo((struct sockaddr *)(void *)&ss, slen, NULL, 0,
                                           bufport, sizeof bufport, NI_NUMERICSERV) == 0)
                                   port = bufport;
                           else
                                   port = NULL;
                   }
           }
           if (port != NULL)
                   request->hr_serverport = bozostrdup(request->hr_httpd, port);
   
           /*
            * setup a timer to make sure the request is not hung
            */
           sa.sa_handler = alarmer;
           sigemptyset(&sa.sa_mask);
           sigaddset(&sa.sa_mask, SIGALRM);
           sa.sa_flags = 0;
           sigaction(SIGALRM, &sa, NULL);  /* XXX */
   
           alarm(MAX_WAIT_TIME);
           while ((str = bozodgetln(httpd, STDIN_FILENO, &len, bozo_read)) != NULL) {
                   alarm(0);
                   if (alarmhit) {
                           (void)bozo_http_error(httpd, 408, NULL,
                                           "request timed out");
                           goto cleanup;
                   }
                   line++;
   
                   if (line == 1) {
   
                           if (len < 1) {
                                   (void)bozo_http_error(httpd, 404, NULL,
                                                   "null method");
                                   goto cleanup;
                           }
   
                           bozo_warn(httpd, "got request ``%s'' from host %s to port %s",
                                   str,
                                   host ? host : addr ? addr : "<local>",
                                   port ? port : "<stdin>");
   
                           /* we allocate return space in file and query only */
                           parse_request(httpd, str, &method, &file, &query, &proto);
                           request->hr_file = file;
                           request->hr_query = query;
                           if (method == NULL) {
                                   (void)bozo_http_error(httpd, 404, NULL,
                                                   "null method");
                                   goto cleanup;
                           }
                           if (file == NULL) {
                                   (void)bozo_http_error(httpd, 404, NULL,
                                                   "null file");
                                   goto cleanup;
                           }
   
                           /*
                            * note that we parse the proto first, so that we
                            * can more properly parse the method and the url.
                            */
   
                           if (process_proto(request, proto) ||
                               process_method(request, method)) {
                                   goto cleanup;
                           }
   
                           debug((httpd, DEBUG_FAT, "got file \"%s\" query \"%s\"",
                               request->hr_file,
                               request->hr_query ? request->hr_query : "<none>"));
   
                           /* http/0.9 has no header processing */
                           if (request->hr_proto == httpd->consts.http_09)
                                   break;
                   } else {                /* incoming headers */
                           bozoheaders_t *hdr;
   
                           if (*str == '\0')
                                   break;
   
                           val = bozostrnsep(&str, ":", &len);
                           debug((httpd, DEBUG_EXPLODING,
                               "read_req2: after bozostrnsep: str ``%s'' val ``%s''",
                               str, val));
                           if (val == NULL || len == -1) {
                                   (void)bozo_http_error(httpd, 404, request,
                                                   "no header");
                                   goto cleanup;
                           }
                           while (*str == ' ' || *str == '\t')
                                 len--, str++;                                  len--, str++;
                         while (*val == ' ' || *val == '\t')                          while (*val == ' ' || *val == '\t')
                                 val++;                                  val++;
   
                         if (auth_check_headers(request, val, str, len))                          if (bozo_auth_check_headers(request, val, str, len))
                                 goto next_header;                                  goto next_header;
   
                         hdr = addmerge_header(request, val, str, len);                          hdr = addmerge_header(request, val, str, len);
Line 750  read_request(void)
Line 680  read_request(void)
                         else if (strcasecmp(hdr->h_header, "content-length") == 0)                          else if (strcasecmp(hdr->h_header, "content-length") == 0)
                                 request->hr_content_length = hdr->h_value;                                  request->hr_content_length = hdr->h_value;
                         else if (strcasecmp(hdr->h_header, "host") == 0)                          else if (strcasecmp(hdr->h_header, "host") == 0)
                                 request->hr_host = hdr->h_value;                                  request->hr_host = bozostrdup(httpd, hdr->h_value);
                         /* HTTP/1.1 rev06 draft spec: 14.20 */                          /* RFC 2616 (HTTP/1.1): 14.20 */
                         else if (strcasecmp(hdr->h_header, "expect") == 0)                          else if (strcasecmp(hdr->h_header, "expect") == 0) {
                                 http_error(417, request, "we don't support Expect:");                                  (void)bozo_http_error(httpd, 417, request,
                                                   "we don't support Expect:");
                                   goto cleanup;
                           }
                         else if (strcasecmp(hdr->h_header, "referrer") == 0 ||                          else if (strcasecmp(hdr->h_header, "referrer") == 0 ||
                                  strcasecmp(hdr->h_header, "referer") == 0)                                   strcasecmp(hdr->h_header, "referer") == 0)
                                 request->hr_referrer = hdr->h_value;                                  request->hr_referrer = hdr->h_value;
                         else if (strcasecmp(hdr->h_header, "range") == 0)                          else if (strcasecmp(hdr->h_header, "range") == 0)
                                 request->hr_range = hdr->h_value;                                  request->hr_range = hdr->h_value;
                         else if (strcasecmp(hdr->h_header, "if-modified-since") == 0)                          else if (strcasecmp(hdr->h_header,
                                           "if-modified-since") == 0)
                                 request->hr_if_modified_since = hdr->h_value;                                  request->hr_if_modified_since = hdr->h_value;
                           else if (strcasecmp(hdr->h_header,
                                           "accept-encoding") == 0)
                                   request->hr_accept_encoding = hdr->h_value;
   
                         debug((DEBUG_FAT, "adding header %s: %s",                          debug((httpd, DEBUG_FAT, "adding header %s: %s",
                             hdr->h_header, hdr->h_value));                              hdr->h_header, hdr->h_value));
                 }                  }
 next_header:  next_header:
Line 774  next_header:
Line 711  next_header:
         signal(SIGALRM, SIG_DFL);          signal(SIGALRM, SIG_DFL);
   
         /* RFC1945, 8.3 */          /* RFC1945, 8.3 */
         if (request->hr_method == HTTP_POST && request->hr_content_length == NULL)          if (request->hr_method == HTTP_POST &&
                 http_error(400, request, "missing content length");              request->hr_content_length == NULL) {
                   (void)bozo_http_error(httpd, 400, request,
                                   "missing content length");
                   goto cleanup;
           }
   
         /* HTTP/1.1 draft rev-06, 14.23 & 19.6.1.1 */          /* RFC 2616 (HTTP/1.1), 14.23 & 19.6.1.1 */
         if (request->hr_proto == http_11 && request->hr_host == NULL)          if (request->hr_proto == httpd->consts.http_11 &&
                 http_error(400, request, "missing Host header");              /*(strncasecmp(request->hr_file, "http://", 7) != 0) &&*/
               request->hr_host == NULL) {
                   (void)bozo_http_error(httpd, 400, request,
                                   "missing Host header");
                   goto cleanup;
           }
   
         if (request->hr_range != NULL) {          if (request->hr_range != NULL) {
                 debug((DEBUG_FAT, "hr_range: %s", request->hr_range));                  debug((httpd, DEBUG_FAT, "hr_range: %s", request->hr_range));
                 /* support only simple ranges %d- and %d-%d */                  /* support only simple ranges %d- and %d-%d */
                 if (strchr(request->hr_range, ',') == NULL) {                  if (strchr(request->hr_range, ',') == NULL) {
                         const char *rstart, *dash;                          const char *rstart, *dash;
Line 809  next_header:
Line 755  next_header:
                 }                  }
         }          }
   
         debug((DEBUG_FAT, "read_request returns url %s in request",          debug((httpd, DEBUG_FAT, "bozo_read_request returns url %s in request",
                request->hr_file));                 request->hr_file));
         return (request);          return request;
   
   cleanup:
           bozo_clean_request(request);
   
           return NULL;
 }  }
   
 /*  static int
  * add or merge this header (val: str) into the requests list  mmap_and_write_part(bozohttpd_t *httpd, int fd, off_t first_byte_pos, size_t sz)
  */  
 static struct headers *  
 addmerge_header(http_req *request, char *val, char *str, ssize_t len)  
 {  {
         struct  headers *hdr;          size_t mappedsz, wroffset;
         static char space[2] = { ' ', 0 };          off_t mappedoffset;
           char *addr;
           void *mappedaddr;
   
         /* do we exist already? */          /*
         SIMPLEQ_FOREACH(hdr, &request->hr_headers, h_next) {           * we need to ensure that both the size *and* offset arguments to
                 if (strcasecmp(val, hdr->h_header) == 0)           * mmap() are page-aligned.  our formala for this is:
                         break;           *
            *    input offset: first_byte_pos
            *    input size: sz
            *
            *    mapped offset = page align truncate (input offset)
            *    mapped size   =
            *        page align extend (input offset - mapped offset + input size)
            *    write offset  = input offset - mapped offset
            *
            * we use the write offset in all writes
            */
           mappedoffset = first_byte_pos & ~(httpd->page_size - 1);
           mappedsz = (size_t)
                   (first_byte_pos - mappedoffset + sz + httpd->page_size - 1) &
                   ~(httpd->page_size - 1);
           wroffset = (size_t)(first_byte_pos - mappedoffset);
   
           addr = mmap(0, mappedsz, PROT_READ, MAP_SHARED, fd, mappedoffset);
           if (addr == (char *)-1) {
                   bozo_warn(httpd, "mmap failed: %s", strerror(errno));
                   return -1;
         }          }
           mappedaddr = addr;
   
         if (hdr) {  #ifdef MADV_SEQUENTIAL
                 /* yup, merge it in */          (void)madvise(addr, sz, MADV_SEQUENTIAL);
                 if (hdr->h_value == space)  #endif
                         hdr->h_value = bozostrdup(str);          while (sz > BOZO_WRSZ) {
                 else  {                  if (bozo_write(httpd, STDOUT_FILENO, addr + wroffset,
                         char *nval;                                  BOZO_WRSZ) != BOZO_WRSZ) {
                           bozo_warn(httpd, "write failed: %s", strerror(errno));
                         if (asprintf(&nval, "%s, %s", hdr->h_value, str) == -1)                          goto out;
                                 http_error(500, NULL,  
                                      "memory allocation failure");  
                         free(hdr->h_value);  
                         hdr->h_value = nval;  
                 }                  }
         } else {                  debug((httpd, DEBUG_OBESE, "wrote %d bytes", BOZO_WRSZ));
                 /* nope, create a new one */                  sz -= BOZO_WRSZ;
                   addr += BOZO_WRSZ;
                 hdr = bozomalloc(sizeof *hdr);          }
                 hdr->h_header = bozostrdup(val);          if (sz && (size_t)bozo_write(httpd, STDOUT_FILENO, addr + wroffset,
                 if (str && *str)                                  sz) != sz) {
                         hdr->h_value = bozostrdup(str);                  bozo_warn(httpd, "final write failed: %s", strerror(errno));
                 else                  goto out;
                         hdr->h_value = space;          }
           debug((httpd, DEBUG_OBESE, "wrote %d bytes", (int)sz));
                 SIMPLEQ_INSERT_TAIL(&request->hr_headers, hdr, h_next);   out:
                 request->hr_nheaders++;          if (munmap(mappedaddr, mappedsz) < 0) {
                   bozo_warn(httpd, "munmap failed");
                   return -1;
         }          }
   
         return hdr;          return 0;
 }  }
   
 static int  static int
Line 878  parse_http_date(const char *val, time_t 
Line 847  parse_http_date(const char *val, time_t 
 }  }
   
 /*  /*
  * process_request does the following:   * given an url, encode it ala rfc 3986.  ie, escape ? and friends.
  *      - check the request is valid   * note that this function returns a static buffer, and thus needs
  *      - process cgi-bin if necessary   * to be updated for any sort of parallel processing.
  *      - transform a filename if necesarry  
  *      - return the HTTP request  
  */   */
 static void  char *
 process_request(http_req *request)  bozo_escape_rfc3986(bozohttpd_t *httpd, const char *url)
 {  {
         struct  stat sb;          static char *buf;
         time_t timestamp;          static size_t buflen = 0;
         char    *file;          size_t len;
         const char *type, *encoding;          const char *s;
         int     fd, isindex;          char *d;
   
         /*  
          * note that transform_request chdir()'s if required.  also note  
          * that cgi is handed here, and a cgi request will never return  
          * back here.  
          */  
         file = transform_request(request, &isindex);  
         if (file == NULL)  
                 http_error(404, request, "empty file after transform");  
   
         fd = open(file, O_RDONLY);          len = strlen(url);
         if (fd < 0) {          if (buflen < len * 3 + 1) {
                 debug((DEBUG_FAT, "open failed: %s", strerror(errno)));                  buflen = len * 3 + 1;
                 if (errno == EPERM)                  buf = bozorealloc(httpd, buf, buflen);
                         http_error(403, request, "no permission to open file");  
                 else if (errno == ENOENT) {  
                         if (directory_index(request, file, isindex))  
                                 return;  
                         http_error(404, request, "no file");  
                 } else  
                         http_error(500, request, "open file");  
         }          }
         if (fstat(fd, &sb) < 0)  
                 http_error(500, request, "can't fstat");          if (url == NULL) {
         if (S_ISDIR(sb.st_mode))                  buf[0] = 0;
                 handle_redirect(request, NULL, 0);                  return buf;
                 /* NOTREACHED */  
         if (request->hr_if_modified_since &&  
             parse_http_date(request->hr_if_modified_since, &timestamp) &&  
             timestamp >= sb.st_mtime) {  
                 /* XXX ignore subsecond of timestamp */  
                 bozoprintf("%s 304 Not Modified\r\n", request->hr_proto);  
                 bozoprintf("\r\n");  
                 bozoflush(stdout);  
                 exit(0);  
         }          }
   
         /* validate requested range */          for (len = 0, s = url, d = buf; *s;) {
         if (request->hr_last_byte_pos == -1 ||                  if (*s & 0x80)
             request->hr_last_byte_pos >= sb.st_size)                          goto encode_it;
                 request->hr_last_byte_pos = sb.st_size - 1;                  switch (*s) {
         if (request->hr_have_range &&                  case ':':
             request->hr_first_byte_pos > request->hr_last_byte_pos) {                  case '/':
                 request->hr_have_range = 0;     /* punt */                  case '?':
                 request->hr_first_byte_pos = 0;                  case '#':
                 request->hr_last_byte_pos = sb.st_size - 1;                  case '[':
                   case ']':
                   case '@':
                   case '!':
                   case '$':
                   case '&':
                   case '\'':
                   case '(':
                   case ')':
                   case '*':
                   case '+':
                   case ',':
                   case ';':
                   case '=':
                   case '%':
                   encode_it:
                           snprintf(d, 4, "%%%2X", *s++);
                           d += 3;
                           len += 3;
                           break;
                   default:
                           *d++ = *s++;
                           len++;
                           break;
                   }
         }          }
         debug((DEBUG_FAT, "have_range %d first_pos %qd last_pos %qd",          buf[len] = 0;
             request->hr_have_range,  
             request->hr_first_byte_pos, request->hr_last_byte_pos));  
         if (request->hr_have_range)  
                 bozoprintf("%s 206 Partial Content\r\n", request->hr_proto);  
         else  
                 bozoprintf("%s 200 OK\r\n", request->hr_proto);  
   
         if (request->hr_proto != http_09) {          return buf;
                 type = content_type(request, file);  }
                 encoding = content_encoding(request, file);  
   /*
    * checks to see if this request has a valid .bzdirect file.  returns
    * 0 on failure and 1 on success.
    */
   static int
   check_direct_access(bozo_httpreq_t *request)
   {
           FILE *fp;
           struct stat sb;
           char dir[MAXPATHLEN], dirfile[MAXPATHLEN], *basename;
   
           snprintf(dir, sizeof(dir), "%s", request->hr_file + 1);
           debug((request->hr_httpd, DEBUG_FAT, "check_direct_access: dir %s", dir));
           basename = strrchr(dir, '/');
   
                 print_header(request, &sb, type, encoding);          if ((!basename || basename[1] != '\0') &&
                 bozoprintf("\r\n");              lstat(dir, &sb) == 0 && S_ISDIR(sb.st_mode))
                   /* nothing */;
           else if (basename == NULL)
                   strcpy(dir, ".");
           else {
                   *basename++ = '\0';
                   bozo_check_special_files(request, basename);
         }          }
         bozoflush(stdout);  
   
         if (request->hr_method != HTTP_HEAD) {          if ((size_t)snprintf(dirfile, sizeof(dirfile), "%s/%s", dir,
                 char *addr;            DIRECT_ACCESS_FILE) >= sizeof(dirfile)) {
                 void *oaddr;                  bozo_http_error(request->hr_httpd, 404, request,
                 size_t mappedsz;                    "directfile path too long");
                 size_t sz;                  return 0;
           }
                 sz = mappedsz = request->hr_last_byte_pos - request->hr_first_byte_pos + 1;          if (stat(dirfile, &sb) < 0 ||
                 oaddr = addr = mmap(0, mappedsz, PROT_READ,              (fp = fopen(dirfile, "r")) == NULL)
                     MAP_SHARED, fd, request->hr_first_byte_pos);                  return 0;
                 if (addr == (char *)-1)          fclose(fp);
                         error(1, "mmap failed: %s", strerror(errno));          return 1;
   }
   
 #ifdef MADV_SEQUENTIAL  /*
                 madvise(addr, sz, MADV_SEQUENTIAL);   * do automatic redirection -- if there are query parameters for the URL
 #endif   * we will tack these on to the new (redirected) URL.
                 while (sz > WRSZ) {   */
                         if (bozowrite(STDOUT_FILENO, addr, WRSZ) != WRSZ)  static void
                                 error(1, "write failed: %s", strerror(errno));  handle_redirect(bozo_httpreq_t *request,
                         debug((DEBUG_OBESE, "wrote %d bytes", WRSZ));                  const char *url, int absolute)
                         sz -= WRSZ;  {
                         addr += WRSZ;          bozohttpd_t *httpd = request->hr_httpd;
                 }          char *urlbuf;
                 if (sz && bozowrite(STDOUT_FILENO, addr, sz) != sz)          char portbuf[20];
                         error(1, "final write failed: %s", strerror(errno));          const char *hostname = BOZOHOST(httpd, request);
                 debug((DEBUG_OBESE, "wrote %d bytes", (int)sz));          int query = 0;
                 if (munmap(oaddr, mappedsz) < 0)  
                         warning("munmap failed");          if (url == NULL) {
                   if (asprintf(&urlbuf, "/%s/", request->hr_file) < 0)
                           bozo_err(httpd, 1, "asprintf");
                   url = urlbuf;
           } else
                   urlbuf = NULL;
           url = bozo_escape_rfc3986(request->hr_httpd, url);
   
           if (request->hr_query && strlen(request->hr_query))
                   query = 1;
   
           if (request->hr_serverport && strcmp(request->hr_serverport, "80") != 0)
                   snprintf(portbuf, sizeof(portbuf), ":%s",
                       request->hr_serverport);
           else
                   portbuf[0] = '\0';
           if (absolute)
                   bozo_warn(httpd, "redirecting %s", url);
           else
                   bozo_warn(httpd, "redirecting %s%s%s", hostname, portbuf, url);
           debug((httpd, DEBUG_FAT, "redirecting %s", url));
           bozo_printf(httpd, "%s 301 Document Moved\r\n", request->hr_proto);
           if (request->hr_proto != httpd->consts.http_09)
                   bozo_print_header(request, NULL, "text/html", NULL);
           if (request->hr_proto != httpd->consts.http_09) {
                   bozo_printf(httpd, "Location: http://");
                   if (absolute == 0)
                           bozo_printf(httpd, "%s%s", hostname, portbuf);
                   if (query) {
                           bozo_printf(httpd, "%s?%s\r\n", url, request->hr_query);
                   } else {
                           bozo_printf(httpd, "%s\r\n", url);
                   }
         }          }
         /* If SSL enabled cleanup SSL structure. */          bozo_printf(httpd, "\r\n");
         ssl_destroy();          if (request->hr_method == HTTP_HEAD)
         close(fd);                  goto head;
         free(file);          bozo_printf(httpd, "<html><head><title>Document Moved</title></head>\n");
           bozo_printf(httpd, "<body><h1>Document Moved</h1>\n");
           bozo_printf(httpd, "This document had moved <a href=\"http://");
           if (query) {
                   if (absolute)
                           bozo_printf(httpd, "%s?%s", url, request->hr_query);
                   else
                           bozo_printf(httpd, "%s%s%s?%s", hostname,
                                       portbuf, url, request->hr_query);
           } else {
                   if (absolute)
                           bozo_printf(httpd, "%s", url);
                   else
                           bozo_printf(httpd, "%s%s%s", hostname,
                                       portbuf, url);
           }
           bozo_printf(httpd, "\">here</a>\n");
           bozo_printf(httpd, "</body></html>\n");
   head:
           bozo_flush(httpd, stdout);
           free(urlbuf);
 }  }
   
 /*  /*
  * deal with virtual host names; we do this:   * deal with virtual host names; we do this:
  *      if we have a virtual path root (vpath), and we are given a   *      if we have a virtual path root (httpd->virtbase), and we are given a
  *      virtual host spec (Host: ho.st or http://ho.st/), see if this   *      virtual host spec (Host: ho.st or http://ho.st/), see if this
  *      directory exists under vpath.  if it does, use this as the   *      directory exists under httpd->virtbase.  if it does, use this as the
  #      new slashdir.   #      new slashdir.
  */   */
 static void  static int
 check_virtual(http_req *request)  check_virtual(bozo_httpreq_t *request)
 {  {
           bozohttpd_t *httpd = request->hr_httpd;
         char *file = request->hr_file, *s;          char *file = request->hr_file, *s;
         struct dirent **list;  
         size_t len;          size_t len;
         int i;  
   
         if (!vpath)          if (!httpd->virtbase)
                 goto use_slashdir;                  goto use_slashdir;
   
         /*          /*
          * convert http://virtual.host/ to request->hr_host           * convert http://virtual.host/ to request->hr_host
          */           */
         debug((DEBUG_OBESE, "checking for http:// virtual host in ``%s''", file));          debug((httpd, DEBUG_OBESE, "checking for http:// virtual host in ``%s''",
                           file));
         if (strncasecmp(file, "http://", 7) == 0) {          if (strncasecmp(file, "http://", 7) == 0) {
                 /* we would do virtual hosting here? */                  /* we would do virtual hosting here? */
                 file += 7;                  file += 7;
                   /* RFC 2616 (HTTP/1.1), 5.2: URI takes precedence over Host: */
                   free(request->hr_host);
                   request->hr_host = bozostrdup(request->hr_httpd, file);
                   if ((s = strchr(request->hr_host, '/')) != NULL)
                           *s = '\0';
                 s = strchr(file, '/');                  s = strchr(file, '/');
                 /* HTTP/1.1 draft rev-06, 5.2: URI takes precedence over Host: */                  free(request->hr_file);
                 request->hr_host = file;                  request->hr_file = bozostrdup(request->hr_httpd, s ? s : "/");
                 request->hr_file = bozostrdup(s ? s : "/");                  debug((httpd, DEBUG_OBESE, "got host ``%s'' file is now ``%s''",
                 debug((DEBUG_OBESE, "got host ``%s'' file is now ``%s''",  
                     request->hr_host, request->hr_file));                      request->hr_host, request->hr_file));
         } else if (!request->hr_host)          } else if (!request->hr_host)
                 goto use_slashdir;                  goto use_slashdir;
   
   
         /*          /*
          * ok, we have a virtual host, use scandir(3) to find a case           * canonicalise hr_host - that is, remove any :80.
            */
           len = strlen(request->hr_host);
           if (len > 3 && strcmp(request->hr_host + len - 3, ":80") == 0) {
                   request->hr_host[len - 3] = '\0';
                   len = strlen(request->hr_host);
           }
   
           /*
            * ok, we have a virtual host, use opendir(3) to find a case
          * insensitive match for the virtual host we are asked for.           * insensitive match for the virtual host we are asked for.
          * note that if the virtual host is the same as the master,           * note that if the virtual host is the same as the master,
          * we don't need to do anything special.           * we don't need to do anything special.
          */           */
         len = strlen(request->hr_host);          debug((httpd, DEBUG_OBESE,
         debug((DEBUG_OBESE,              "check_virtual: checking host `%s' under httpd->virtbase `%s' "
             "check_virtual: checking host `%s' under vpath `%s' for file `%s'",              "for file `%s'",
             request->hr_host, vpath, request->hr_file));              request->hr_host, httpd->virtbase, request->hr_file));
         if (strncasecmp(myname, request->hr_host, len) != 0) {          if (strncasecmp(httpd->virthostname, request->hr_host, len) != 0) {
                 s = 0;                  s = 0;
                 for (i = scandir(vpath, &list, 0, 0); i--; list++) {                  DIR *dirp;
                         if (strcmp((*list)->d_name, ".") == 0 ||                  struct dirent *d;
                             strcmp((*list)->d_name, "..") == 0)  
                                 continue;                  if ((dirp = opendir(httpd->virtbase)) != NULL) {
                         debug((DEBUG_OBESE, "looking at dir``%s''",                          while ((d = readdir(dirp)) != NULL) {
                             (*list)->d_name));                                  if (strcmp(d->d_name, ".") == 0 ||
                         if (strncasecmp((*list)->d_name, request->hr_host,                                      strcmp(d->d_name, "..") == 0) {
                             len) == 0) {                                          continue;
                                 /* found it, punch it */                                  }
                                 myname = (*list)->d_name;                                  debug((httpd, DEBUG_OBESE, "looking at dir``%s''",
                                 if (asprintf(&s, "%s/%s", vpath, myname) < 0)                                     d->d_name));
                                         error(1, "asprintf");                                  if (strncasecmp(d->d_name, request->hr_host,
                                 break;                                      len) == 0) {
                                           /* found it, punch it */
                                           debug((httpd, DEBUG_OBESE, "found it punch it"));
                                           request->hr_virthostname =
                                               bozostrdup(httpd, d->d_name);
                                           if (asprintf(&s, "%s/%s", httpd->virtbase,
                                               request->hr_virthostname) < 0)
                                                   bozo_err(httpd, 1, "asprintf");
                                           break;
                                   }
                         }                          }
                           closedir(dirp);
                   }
                   else {
                           debug((httpd, DEBUG_FAT, "opendir %s failed: %s",
                               httpd->virtbase, strerror(errno)));
                 }                  }
                 if (s == 0) {                  if (s == 0) {
                         if (Vflag)                          if (httpd->unknown_slash)
                                 goto use_slashdir;                                  goto use_slashdir;
                         http_error(404, request, "unknown URL");                          return bozo_http_error(httpd, 404, request,
                                                   "unknown URL");
                 }                  }
         } else          } else
 use_slashdir:  use_slashdir:
                 s = slashdir;                  s = httpd->slashdir;
   
         /*          /*
          * ok, nailed the correct slashdir, chdir to it           * ok, nailed the correct slashdir, chdir to it
          */           */
         if (chdir(s) < 0)          if (chdir(s) < 0)
                 error(1, "can't chdir %s: %s", s, strerror(errno));                  return bozo_http_error(httpd, 404, request,
 }                                          "can't chdir to slashdir");
           return 0;
 /* make sure we're not trying to access special files */  
 void  
 check_special_files(http_req *request, const char *name)  
 {  
         /* ensure basename(name) != special files */  
         if (strcmp(name, DIRECT_ACCESS_FILE) == 0)  
                 http_error(403, request,  
                     "no permission to open direct access file");  
         if (strcmp(name, REDIRECT_FILE) == 0)  
                 http_error(403, request,  
                     "no permission to open redirect file");  
         if (strcmp(name, ABSREDIRECT_FILE) == 0)  
                 http_error(403, request,  
                     "no permission to open redirect file");  
         auth_check_special_files(request, name);  
 }  }
   
 /*  /*
  * checks to see if this request has a valid .bzredirect file.  returns   * checks to see if this request has a valid .bzredirect file.  returns
  * 0 on failure and 1 on success.   * 0 when no redirection happend, or 1 when handle_redirect() has been
    * called, -1 on error.
  */   */
 static void  static int
 check_bzredirect(http_req *request)  check_bzredirect(bozo_httpreq_t *request)
 {  {
         struct stat sb;          struct stat sb;
         char dir[MAXPATHLEN], redir[MAXPATHLEN], redirpath[MAXPATHLEN + 1];          char dir[MAXPATHLEN], redir[MAXPATHLEN], redirpath[MAXPATHLEN + 1],
               path[MAXPATHLEN];
         char *basename, *finalredir;          char *basename, *finalredir;
         int rv, absolute;          int rv, absolute;
   
Line 1101  check_bzredirect(http_req *request)
Line 1147  check_bzredirect(http_req *request)
          * if this pathname is really a directory, but doesn't end in /,           * if this pathname is really a directory, but doesn't end in /,
          * use it as the directory to look for the redir file.           * use it as the directory to look for the redir file.
          */           */
         snprintf(dir, sizeof(dir), "%s", request->hr_file + 1);          if((size_t)snprintf(dir, sizeof(dir), "%s", request->hr_file + 1) >=
         debug((DEBUG_FAT, "check_bzredirect: dir %s", dir));            sizeof(dir)) {
                   bozo_http_error(request->hr_httpd, 404, request,
                     "file path too long");
                   return -1;
           }
           debug((request->hr_httpd, DEBUG_FAT, "check_bzredirect: dir %s", dir));
         basename = strrchr(dir, '/');          basename = strrchr(dir, '/');
   
         if ((!basename || basename[1] != '\0') &&          if ((!basename || basename[1] != '\0') &&
Line 1112  check_bzredirect(http_req *request)
Line 1163  check_bzredirect(http_req *request)
                 strcpy(dir, ".");                  strcpy(dir, ".");
         else {          else {
                 *basename++ = '\0';                  *basename++ = '\0';
                 check_special_files(request, basename);                  bozo_check_special_files(request, basename);
         }          }
   
         snprintf(redir, sizeof(redir), "%s/%s", dir, REDIRECT_FILE);          if ((size_t)snprintf(redir, sizeof(redir), "%s/%s", dir,
             REDIRECT_FILE) >= sizeof(redir)) {
                   bozo_http_error(request->hr_httpd, 404, request,
                     "redirectfile path too long");
                   return -1;
           }
         if (lstat(redir, &sb) == 0) {          if (lstat(redir, &sb) == 0) {
                 if (S_ISLNK(sb.st_mode) == 0)                  if (!S_ISLNK(sb.st_mode))
                         return;                          return 0;
                 absolute = 0;                  absolute = 0;
         } else {          } else {
                 snprintf(redir, sizeof(redir), "%s/%s", dir, ABSREDIRECT_FILE);                  if((size_t)snprintf(redir, sizeof(redir), "%s/%s", dir,
                 if (lstat(redir, &sb) < 0 || S_ISLNK(sb.st_mode) == 0)                    ABSREDIRECT_FILE) >= sizeof(redir)) {
                         return;                          bozo_http_error(request->hr_httpd, 404, request,
                             "redirectfile path too long");
                           return -1;
                   }
                   if (lstat(redir, &sb) < 0 || !S_ISLNK(sb.st_mode))
                           return 0;
                 absolute = 1;                  absolute = 1;
         }          }
         debug((DEBUG_FAT, "check_bzredirect: calling readlink"));          debug((request->hr_httpd, DEBUG_FAT,
                  "check_bzredirect: calling readlink"));
         rv = readlink(redir, redirpath, sizeof redirpath - 1);          rv = readlink(redir, redirpath, sizeof redirpath - 1);
         if (rv == -1 || rv == 0) {          if (rv == -1 || rv == 0) {
                 debug((DEBUG_FAT, "readlink failed"));                  debug((request->hr_httpd, DEBUG_FAT, "readlink failed"));
                 return;                  return 0;
         }          }
         redirpath[rv] = '\0';          redirpath[rv] = '\0';
         debug((DEBUG_FAT, "readlink returned \"%s\"", redirpath));          debug((request->hr_httpd, DEBUG_FAT,
                  "readlink returned \"%s\"", redirpath));
   
           /* check if we need authentication */
           snprintf(path, sizeof(path), "%s/", dir);
           if (bozo_auth_check(request, path))
                   return 1;
   
         /* now we have the link pointer, redirect to the real place */          /* now we have the link pointer, redirect to the real place */
         if (absolute)          if (absolute)
                 finalredir = redirpath;                  finalredir = redirpath;
         else          else {
                 snprintf(finalredir = redir, sizeof(redir), "/%s/%s", dir,                  if ((size_t)snprintf(finalredir = redir, sizeof(redir), "/%s/%s",
                          redirpath);                    dir, redirpath) >= sizeof(redir)) {
                           bozo_http_error(request->hr_httpd, 404, request,
                             "redirect path too long");
                           return -1;
                   }
           }
   
         debug((DEBUG_FAT, "check_bzredirect: new redir %s", finalredir));          debug((request->hr_httpd, DEBUG_FAT,
                  "check_bzredirect: new redir %s", finalredir));
         handle_redirect(request, finalredir, absolute);          handle_redirect(request, finalredir, absolute);
 }          return 1;
   }
 /*  
  * checks to see if this request has a valid .bzdirect file.  returns  /* this fixes the %HH hack that RFC2396 requires.  */
  * 0 on failure and 1 on success.  
  */  
 static int  static int
 check_direct_access(http_req *request)  fix_url_percent(bozo_httpreq_t *request)
 {  {
         FILE *fp;          bozohttpd_t *httpd = request->hr_httpd;
         struct stat sb;          char    *s, *t, buf[3], *url;
         char dir[MAXPATHLEN], dirfile[MAXPATHLEN], *basename;          char    *end;   /* if end is not-zero, we don't translate beyond that */
   
         snprintf(dir, sizeof(dir), "%s", request->hr_file + 1);          url = request->hr_file;
         debug((DEBUG_FAT, "check_bzredirect: dir %s", dir));  
         basename = strrchr(dir, '/');  
   
         if ((!basename || basename[1] != '\0') &&          end = url + strlen(url);
             lstat(dir, &sb) == 0 && S_ISDIR(sb.st_mode))  
                 /* nothing */;  
         else if (basename == NULL)  
                 strcpy(dir, ".");  
         else {  
                 *basename++ = '\0';  
                 check_special_files(request, basename);  
         }  
   
         snprintf(dirfile, sizeof(dirfile), "%s/%s", dir, DIRECT_ACCESS_FILE);          /* fast forward to the first % */
         if (stat(dirfile, &sb) < 0 ||          if ((s = strchr(url, '%')) == NULL)
             (fp = fopen(dirfile, "r")) == NULL)  
                 return 0;                  return 0;
         fclose(fp);  
         return 1;          t = s;
           do {
                   if (end && s >= end) {
                           debug((httpd, DEBUG_EXPLODING,
                                   "fu_%%: past end, filling out.."));
                           while (*s)
                                   *t++ = *s++;
                           break;
                   }
                   debug((httpd, DEBUG_EXPLODING,
                           "fu_%%: got s == %%, s[1]s[2] == %c%c",
                           s[1], s[2]));
                   if (s[1] == '\0' || s[2] == '\0') {
                           (void)bozo_http_error(httpd, 400, request,
                               "percent hack missing two chars afterwards");
                           return 1;
                   }
                   if (s[1] == '0' && s[2] == '0') {
                           (void)bozo_http_error(httpd, 404, request,
                                           "percent hack was %00");
                           return 1;
                   }
                   if (s[1] == '2' && s[2] == 'f') {
                           (void)bozo_http_error(httpd, 404, request,
                                           "percent hack was %2f (/)");
                           return 1;
                   }
   
                   buf[0] = *++s;
                   buf[1] = *++s;
                   buf[2] = '\0';
                   s++;
                   *t = (char)strtol(buf, NULL, 16);
                   debug((httpd, DEBUG_EXPLODING,
                                   "fu_%%: strtol put '%02x' into *t", *t));
                   if (*t++ == '\0') {
                           (void)bozo_http_error(httpd, 400, request,
                                           "percent hack got a 0 back");
                           return 1;
                   }
   
                   while (*s && *s != '%') {
                           if (end && s >= end)
                                   break;
                           *t++ = *s++;
                   }
           } while (*s);
           *t = '\0';
   
           debug((httpd, DEBUG_FAT, "fix_url_percent returns %s in url",
                           request->hr_file));
   
           return 0;
 }  }
   
 /*  /*
  * transform_request does this:   * transform_request does this:
  *      - ``expand'' %20 crapola   *      - ``expand'' %20 crapola
  *      - punt if it doesn't start with /   *      - punt if it doesn't start with /
  *      - check rflag / referrer   *      - check httpd->untrustedref / referrer
  *      - look for "http://myname/" and deal with it.   *      - look for "http://myname/" and deal with it.
  *      - maybe call process_cgi()   *      - maybe call bozo_process_cgi()
  *      - check for ~user and call user_transform() if so   *      - check for ~user and call bozo_user_transform() if so
  *      - if the length > 1, check for trailing slash.  if so,   *      - if the length > 1, check for trailing slash.  if so,
  *        add the index.html file   *        add the index.html file
  *      - if the length is 1, return the index.html file   *      - if the length is 1, return the index.html file
  *      - disallow anything ending up with a file starting   *      - disallow anything ending up with a file starting
  *        at "/" or having ".." in it.   *        at "/" or having ".." in it.
  *      - anything else is a really weird internal error   *      - anything else is a really weird internal error
    *      - returns malloced file to serve, if unhandled
  */   */
 static char *  static int
 transform_request(http_req *request, int *isindex)  transform_request(bozo_httpreq_t *request, int *isindex)
 {  {
         char    *new_file;  // the new file name we're going to fetch          bozohttpd_t *httpd = request->hr_httpd;
         char    *req_file;  // the original file in the request          char    *file, *newfile = NULL;
         size_t  len;          size_t  len;
           const char *hostname = BOZOHOST(httpd, request);
   
         new_file = NULL;          file = NULL;
         *isindex = 0;          *isindex = 0;
         debug((DEBUG_FAT, "tf_req: url %s", request->hr_file));          debug((httpd, DEBUG_FAT, "tf_req: file %s", request->hr_file));
         fix_url_percent(request);          if (fix_url_percent(request)) {
         check_virtual(request);                  goto bad_done;
         req_file = request->hr_file;          }
           if (check_virtual(request)) {
                   goto bad_done;
           }
           file = request->hr_file;
   
         if (req_file[0] != '/')          if (file[0] != '/') {
                 http_error(404, request, "unknown URL");                  (void)bozo_http_error(httpd, 404, request, "unknown URL");
                   goto bad_done;
           }
   
         check_bzredirect(request);          switch(check_bzredirect(request)) {
           case -1:
                   goto bad_done;
           case 1:
                   return 0;
           }
   
         if (rflag) {          if (httpd->untrustedref) {
                 int to_indexhtml = 0;                  int to_indexhtml = 0;
   
 #define TOP_PAGE(x)     (strcmp((x), "/") == 0 || \  #define TOP_PAGE(x)     (strcmp((x), "/") == 0 || \
                          strcmp((x) + 1, index_html) == 0 || \                           strcmp((x) + 1, httpd->index_html) == 0 || \
                          strcmp((x) + 1, "favicon.ico") == 0)                           strcmp((x) + 1, "favicon.ico") == 0)
   
                 debug((DEBUG_EXPLODING, "checking rflag"));                  debug((httpd, DEBUG_EXPLODING, "checking httpd->untrustedref"));
                 /*                  /*
                  * first check that this path isn't allowed via .bzdirect file,                   * first check that this path isn't allowed via .bzdirect file,
                  * and then check referrer; make sure that people come via the                   * and then check referrer; make sure that people come via the
Line 1232  transform_request(http_req *request, int
Line 1358  transform_request(http_req *request, int
                 else if (request->hr_referrer) {                  else if (request->hr_referrer) {
                         const char *r = request->hr_referrer;                          const char *r = request->hr_referrer;
   
                         debug((DEBUG_FAT,                          debug((httpd, DEBUG_FAT,
                            "checking referrer \"%s\" vs myname %s", r, myname));                                  "checking referrer \"%s\" vs virthostname %s",
                                   r, hostname));
                         if (strncmp(r, "http://", 7) != 0 ||                          if (strncmp(r, "http://", 7) != 0 ||
                             (strncasecmp(r + 7, myname, strlen(myname)) != 0 &&                              (strncasecmp(r + 7, hostname,
                              !TOP_PAGE(req_file)))                                           strlen(hostname)) != 0 &&
                                !TOP_PAGE(file)))
                                 to_indexhtml = 1;                                  to_indexhtml = 1;
                 } else {                  } else {
                         const char *h = request->hr_host;                          const char *h = request->hr_host;
   
                         debug((DEBUG_FAT, "url has no referrer at all"));                          debug((httpd, DEBUG_FAT, "url has no referrer at all"));
                         /* if there's no referrer, let / or /index.html past */                          /* if there's no referrer, let / or /index.html past */
                         if (!TOP_PAGE(req_file) ||                          if (!TOP_PAGE(file) ||
                             (h && strncasecmp(h, myname, strlen(myname)) != 0))                              (h && strncasecmp(h, hostname,
                                           strlen(hostname)) != 0))
                                 to_indexhtml = 1;                                  to_indexhtml = 1;
                 }                  }
   
                 if (to_indexhtml) {                  if (to_indexhtml) {
                         char *slashindexhtml;                          char *slashindexhtml;
   
                         if (asprintf(&slashindexhtml, "/%s", index_html) < 0)                          if (asprintf(&slashindexhtml, "/%s",
                                 error(1, "asprintf");                                          httpd->index_html) < 0)
                         debug((DEBUG_FAT, "rflag: redirecting %s to %s", req_file, slashindexhtml));                                  bozo_err(httpd, 1, "asprintf");
                           debug((httpd, DEBUG_FAT,
                                   "httpd->untrustedref: redirecting %s to %s",
                                   file, slashindexhtml));
                         handle_redirect(request, slashindexhtml, 0);                          handle_redirect(request, slashindexhtml, 0);
                         /* NOTREACHED */                          free(slashindexhtml);
                           return 0;
                 }                  }
         }          }
   
         len = strlen(req_file);          len = strlen(file);
         if (0) {          if (/*CONSTCOND*/0) {
 #ifndef NO_USER_SUPPORT  #ifndef NO_USER_SUPPORT
         } else if (len > 1 && uflag && req_file[1] == '~') {          } else if (len > 1 && httpd->enable_users && file[1] == '~') {
                 if (req_file[2] == '\0')                  if (file[2] == '\0') {
                         http_error(404, request, "missing username");                          (void)bozo_http_error(httpd, 404, request,
                 if (strchr(req_file + 2, '/') == NULL)                                                  "missing username");
                           goto bad_done;
                   }
                   if (strchr(file + 2, '/') == NULL) {
                         handle_redirect(request, NULL, 0);                          handle_redirect(request, NULL, 0);
                         /* NOTREACHED */                          return 0;
                 debug((DEBUG_FAT, "calling user_transform"));                  }
                 return (user_transform(request, isindex));                  debug((httpd, DEBUG_FAT, "calling bozo_user_transform"));
   
                   return bozo_user_transform(request, isindex);
 #endif /* NO_USER_SUPPORT */  #endif /* NO_USER_SUPPORT */
         } else if (len > 1) {          } else if (len > 1) {
                 debug((DEBUG_FAT, "url[len-1] == %c", req_file[len-1]));                  debug((httpd, DEBUG_FAT, "file[len-1] == %c", file[len-1]));
                 if (req_file[len-1] == '/') {   /* append index.html */                  if (file[len-1] == '/') {       /* append index.html */
                         *isindex = 1;                          *isindex = 1;
                         debug((DEBUG_FAT, "appending index.html"));                          debug((httpd, DEBUG_FAT, "appending index.html"));
                         new_file = bozomalloc(len + strlen(index_html) + 1);                          newfile = bozomalloc(httpd,
                         strcpy(new_file, req_file + 1);                                          len + strlen(httpd->index_html) + 1);
                         strcat(new_file, index_html);                          strcpy(newfile, file + 1);
                           strcat(newfile, httpd->index_html);
                 } else                  } else
                         new_file = bozostrdup(req_file + 1);                          newfile = bozostrdup(request->hr_httpd, file + 1);
         } else if (len == 1) {          } else if (len == 1) {
                 debug((DEBUG_EXPLODING, "tf_req: len == 1"));                  debug((httpd, DEBUG_EXPLODING, "tf_req: len == 1"));
                 new_file = bozostrdup(index_html);                  newfile = bozostrdup(request->hr_httpd, httpd->index_html);
                 *isindex = 1;                  *isindex = 1;
         } else          /* len == 0 ? */          } else {        /* len == 0 ? */
                 http_error(500, request, "request->hr_file is nul?");                  (void)bozo_http_error(httpd, 500, request,
                                           "request->hr_file is nul?");
                   goto bad_done;
           }
   
         if (new_file == NULL)          if (newfile == NULL) {
                 http_error(500, request, "internal failure");                  (void)bozo_http_error(httpd, 500, request, "internal failure");
                   goto bad_done;
           }
   
         /*          /*
          * look for "http://myname/" and deal with it as necessary.           * look for "http://myname/" and deal with it as necessary.
          */           */
   
         /*          /*
          * stop traversing outside our domain           * stop traversing outside our domain
          *           *
          * XXX true security only comes from our parent using chroot(2)           * XXX true security only comes from our parent using chroot(2)
          * before execve(2)'ing us.  or our own built in chroot(2) support.           * before execve(2)'ing us.  or our own built in chroot(2) support.
          */           */
         if (*new_file == '/' || strcmp(new_file, "..") == 0 ||          if (*newfile == '/' || strcmp(newfile, "..") == 0 ||
             strstr(new_file, "/..") || strstr(new_file, "../"))              strstr(newfile, "/..") || strstr(newfile, "../")) {
                 http_error(403, request, "illegal request");                  (void)bozo_http_error(httpd, 403, request, "illegal request");
                   goto bad_done;
           }
   
         auth_check(request, new_file);          if (bozo_auth_check(request, newfile))
                   goto bad_done;
   
         if (new_file && strlen(new_file)) {          if (strlen(newfile)) {
           free(request->hr_file);                  request->hr_oldfile = request->hr_file;
           request->hr_file = new_file;                  request->hr_file = newfile;
         }          }
   
         process_cgi(request);          if (bozo_process_cgi(request))
                   return 0;
         debug((DEBUG_FAT, "transform_request returned: %s", new_file));  
         return (new_file);          if (bozo_process_lua(request))
                   return 0;
   
           debug((httpd, DEBUG_FAT, "transform_request set: %s", newfile));
           return 1;
   bad_done:
           debug((httpd, DEBUG_FAT, "transform_request returning: 0"));
           free(newfile);
           return 0;
 }  }
   
 /*  /*
  * do automatic redirection -- if there are query parameters for the URL   * can_gzip checks if the request supports and prefers gzip encoding.
  * we will tack these on to the new (redirected) URL.   *
    * XXX: we do not consider the associated q with gzip in making our
    *      decision which is broken.
  */   */
 static void  
 handle_redirect(http_req *request, const char *url, int absolute)  
 {  
         char *urlbuf;  
         char portbuf[20];  
         int query = 0;  
   
         if (url == NULL) {  static int
                 if (asprintf(&urlbuf, "/%s/", request->hr_file) < 0)  can_gzip(bozo_httpreq_t *request)
                         error(1, "asprintf");  {
                 url = urlbuf;          const char      *pos;
         }          const char      *tmp;
           size_t           len;
         if (request->hr_query && strlen(request->hr_query)) {  
           query = 1;  
         }  
   
         if (request->hr_serverport && strcmp(request->hr_serverport, "80") != 0)          /* First we decide if the request can be gzipped at all. */
                 snprintf(portbuf, sizeof(portbuf), ":%s",  
                     request->hr_serverport);  
         else  
                 portbuf[0] = '\0';  
         warning("redirecting %s%s%s", myname, portbuf, url);  
         debug((DEBUG_FAT, "redirecting %s", url));  
         bozoprintf("%s 301 Document Moved\r\n", request->hr_proto);  
         if (request->hr_proto != http_09)  
                 print_header(request, NULL, "text/html", NULL);  
         if (request->hr_proto != http_09) {  
                 bozoprintf("Location: http://");  
                 if (absolute == 0)  
                         bozoprintf("%s%s", myname, portbuf);  
                 if (query) {  
                   bozoprintf("%s?%s\r\n", url, request->hr_query);  
                 } else {  
                   bozoprintf("%s\r\n", url);  
                 }  
         }  
         bozoprintf("\r\n");  
         if (request->hr_method == HTTP_HEAD)  
                 goto head;  
         bozoprintf("<html><head><title>Document Moved</title></head>\n");  
         bozoprintf("<body><h1>Document Moved</h1>\n");  
         bozoprintf("This document had moved <a href=\"http://");  
         if (query) {  
           if (absolute)  
             bozoprintf("%s?%s", url, request->hr_query);  
           else  
             bozoprintf("%s%s%s?%s", myname, portbuf, url, request->hr_query);  
         } else {  
           if (absolute)  
             bozoprintf("%s", url);  
           else  
             bozoprintf("%s%s%s", myname, portbuf, url);  
         }  
         bozoprintf("\">here</a>\n");  
         bozoprintf("</body></html>\n");  
 head:  
         bozoflush(stdout);  
         exit(0);  
 }  
   
 /* generic header printing routine */          /* not if we already are encoded... */
 void          tmp = bozo_content_encoding(request, request->hr_file);
 print_header(http_req *request, struct stat *sbp, const char *type,          if (tmp && *tmp)
              const char *encoding)                  return 0;
 {  
         off_t len;  
   
         bozoprintf("Date: %s\r\n", http_date());          /* not if we are not asking for the whole file... */
         bozoprintf("Server: %s\r\n", server_software);          if (request->hr_last_byte_pos != -1 || request->hr_have_range)
         bozoprintf("Accept-Ranges: bytes\r\n");                  return 0;
         if (sbp) {  
                 char filedate[40];  
                 struct  tm *tm;  
   
                 tm = gmtime(&sbp->st_mtime);          /* Then we determine if gzip is on the cards. */
                 strftime(filedate, sizeof filedate,  
                     "%a, %d %b %Y %H:%M:%S GMT", tm);  
                 bozoprintf("Last-Modified: %s\r\n", filedate);  
         }  
         if (type && *type)  
                 bozoprintf("Content-Type: %s\r\n", type);  
         if (encoding && *encoding)  
                 bozoprintf("Content-Encoding: %s\r\n", encoding);  
         if (sbp) {  
                 if (request->hr_have_range) {  
                         len = request->hr_last_byte_pos - request->hr_first_byte_pos +1;  
                         bozoprintf("Content-Range: bytes %qd-%qd/%qd\r\n",  
                             (long long) request->hr_first_byte_pos,  
                             (long long) request->hr_last_byte_pos,  
                             (long long) sbp->st_size);  
                 }  
                 else  
                         len = sbp->st_size;  
                 bozoprintf("Content-Length: %qd\r\n", (long long)len);  
         }  
         if (request && request->hr_proto == http_11)  
                 bozoprintf("Connection: close\r\n");  
         bozoflush(stdout);  
 }  
   
 /* this escape HTML tags */          for (pos = request->hr_accept_encoding; pos && *pos; pos += len) {
 static void                  while (*pos == ' ')
 escape_html(http_req *request)                          pos++;
 {  
         int     i, j;  
         char    *url = request->hr_file, *tmp;  
   
         for (i = 0, j = 0; url[i]; i++) {                  len = strcspn(pos, ";,");
                 switch (url[i]) {  
                 case '<':  
                 case '>':  
                         j += 4;  
                         break;  
                 case '&':  
                         j += 5;  
                         break;  
                 }  
         }  
   
         if (j == 0)                  if ((len == 4 && strncasecmp("gzip", pos, 4) == 0) ||
                 return;                      (len == 6 && strncasecmp("x-gzip", pos, 6) == 0))
                           return 1;
   
         if ((tmp = (char *) malloc(strlen(url) + j)) == 0)                  if (pos[len] == ';')
                 /*                          len += strcspn(&pos[len], ",");
                  * ouch, but we are only called from an error context, and  
                  * most paths here come from malloc(3) failures anyway...  
                  * we could completely punt and just exit, but isn't returning  
                  * an not-quite-correct error better than nothing at all?  
                  */  
                 return;  
   
         for (i = 0, j = 0; url[i]; i++) {                  if (pos[len])
                 switch (url[i]) {                          len++;
                 case '<':  
                         memcpy(tmp + j, "&lt;", 4);  
                         j += 4;  
                         break;  
                 case '>':  
                         memcpy(tmp + j, "&gt;", 4);  
                         j += 4;  
                         break;  
                 case '&':  
                         memcpy(tmp + j, "&amp;", 5);  
                         j += 5;  
                         break;  
                 default:  
                         tmp[j++] = url[i];  
                 }  
         }          }
         tmp[j] = 0;  
   
         /*          return 0;
          * original "url" is a substring of an allocation, so we  
          * can't touch it.  so, ignore it and replace the request.  
          */  
         request->hr_file = tmp;  
 }  }
   
 /* this fixes the %HH hack that RFC2396 requires.  */  /*
 static void   * bozo_process_request does the following:
 fix_url_percent(http_req *request)   *      - check the request is valid
    *      - process cgi-bin if necessary
    *      - transform a filename if necesarry
    *      - return the HTTP request
    */
   void
   bozo_process_request(bozo_httpreq_t *request)
 {  {
         char    *s, *t, buf[3], *url;          bozohttpd_t *httpd = request->hr_httpd;
         char    *end;   /* if end is not-zero, we don't translate beyond that */          struct  stat sb;
           time_t timestamp;
           char    *file;
           const char *type, *encoding;
           int     fd, isindex;
   
         url = request->hr_file;          /*
            * note that transform_request chdir()'s if required.  also note
            * that cgi is handed here.  if transform_request() returns 0
            * then the request has been handled already.
            */
           if (transform_request(request, &isindex) == 0)
                   return;
   
         end = url + strlen(url);          fd = -1;
           encoding = NULL;
           if (can_gzip(request)) {
                   asprintf(&file, "%s.gz", request->hr_file);
                   fd = open(file, O_RDONLY);
                   if (fd >= 0)
                           encoding = "gzip";
                   free(file);
           }
   
         /* fast forward to the first % */          file = request->hr_file;
         if ((s = strchr(url, '%')) == NULL)  
                 return;  
   
         t = s;          if (fd < 0)
         do {                  fd = open(file, O_RDONLY);
                 if (end && s >= end) {  
                         debug((DEBUG_EXPLODING, "fu_%%: past end, filling out.."));          if (fd < 0) {
                         while (*s)                  debug((httpd, DEBUG_FAT, "open failed: %s", strerror(errno)));
                                 *t++ = *s++;                  switch(errno) {
                   case EPERM:
                           (void)bozo_http_error(httpd, 403, request,
                                                   "no permission to open file");
                           break;
                   case ENAMETOOLONG:
                           /*FALLTHROUGH*/
                   case ENOENT:
                           if (!bozo_dir_index(request, file, isindex))
                                   (void)bozo_http_error(httpd, 404, request,
                                                           "no file");
                         break;                          break;
                   default:
                           (void)bozo_http_error(httpd, 500, request, "open file");
                 }                  }
                 debug((DEBUG_EXPLODING, "fu_%%: got s == %%, s[1]s[2] == %c%c",                  goto cleanup_nofd;
                     s[1], s[2]));          }
                 if (s[1] == '\0' || s[2] == '\0')          if (fstat(fd, &sb) < 0) {
                         http_error(400, request,                  (void)bozo_http_error(httpd, 500, request, "can't fstat");
                             "percent hack missing two chars afterwards");                  goto cleanup;
                 if (s[1] == '0' && s[2] == '0')          }
                         http_error(404, request, "percent hack was %00");          if (S_ISDIR(sb.st_mode)) {
                 if (s[1] == '2' && s[2] == 'f')                  handle_redirect(request, NULL, 0);
                         http_error(404, request, "percent hack was %2f (/)");                  goto cleanup;
           }
                 buf[0] = *++s;  
                 buf[1] = *++s;  
                 buf[2] = '\0';  
                 s++;  
                 *t = (char)strtol(buf, NULL, 16);  
                 debug((DEBUG_EXPLODING, "fu_%%: strtol put '%c' into *t", *t));  
                 if (*t++ == '\0')  
                         http_error(400, request, "percent hack got a 0 back");  
   
                 while (*s && *s != '%') {          if (request->hr_if_modified_since &&
                         if (end && s >= end)              parse_http_date(request->hr_if_modified_since, &timestamp) &&
                                 break;              timestamp >= sb.st_mtime) {
                         *t++ = *s++;                  /* XXX ignore subsecond of timestamp */
                 }                  bozo_printf(httpd, "%s 304 Not Modified\r\n",
         } while (*s);                                  request->hr_proto);
         *t = '\0';                  bozo_printf(httpd, "\r\n");
         debug((DEBUG_FAT, "fix_url_percent returns %s in url", request->hr_file));                  bozo_flush(httpd, stdout);
 }                  goto cleanup;
           }
   
 /*          /* validate requested range */
  * process each type of HTTP method, setting this HTTP requests          if (request->hr_last_byte_pos == -1 ||
  # method type.              request->hr_last_byte_pos >= sb.st_size)
  */                  request->hr_last_byte_pos = sb.st_size - 1;
 static struct method_map {          if (request->hr_have_range &&
         const char *name;              request->hr_first_byte_pos > request->hr_last_byte_pos) {
         int     type;                  request->hr_have_range = 0;     /* punt */
 } method_map[] = {                  request->hr_first_byte_pos = 0;
         { "GET",        HTTP_GET, },                  request->hr_last_byte_pos = sb.st_size - 1;
         { "POST",       HTTP_POST, },          }
         { "HEAD",       HTTP_HEAD, },          debug((httpd, DEBUG_FAT, "have_range %d first_pos %lld last_pos %lld",
 #if 0   /* other non-required http/1.1 methods */              request->hr_have_range,
         { "OPTIONS",    HTTP_OPTIONS, },              (long long)request->hr_first_byte_pos,
         { "PUT",        HTTP_PUT, },              (long long)request->hr_last_byte_pos));
         { "DELETE",     HTTP_DELETE, },          if (request->hr_have_range)
         { "TRACE",      HTTP_TRACE, },                  bozo_printf(httpd, "%s 206 Partial Content\r\n",
         { "CONNECT",    HTTP_CONNECT, },                                  request->hr_proto);
 #endif          else
         { NULL,         0, },                  bozo_printf(httpd, "%s 200 OK\r\n", request->hr_proto);
 };  
   
 static void          if (request->hr_proto != httpd->consts.http_09) {
 process_method(http_req *request, const char *method)                  type = bozo_content_type(request, file);
 {                  if (!encoding)
         struct  method_map *mmp;                          encoding = bozo_content_encoding(request, file);
   
         for (mmp = method_map; mmp->name; mmp++)                  bozo_print_header(request, &sb, type, encoding);
                 if (strcasecmp(method, mmp->name) == 0) {                  bozo_printf(httpd, "\r\n");
                         request->hr_method = mmp->type;          }
                         request->hr_methodstr = mmp->name;          bozo_flush(httpd, stdout);
                         return;  
                 }  
   
         if (request->hr_proto == http_11)          if (request->hr_method != HTTP_HEAD) {
                 request->hr_allow = "GET, HEAD, POST";                  off_t szleft, cur_byte_pos;
         http_error(404, request, "unknown method");  
                   szleft =
                        request->hr_last_byte_pos - request->hr_first_byte_pos + 1;
                   cur_byte_pos = request->hr_first_byte_pos;
   
    retry:
                   while (szleft) {
                           size_t sz;
   
                           /* This should take care of the first unaligned chunk */
                           if ((cur_byte_pos & (httpd->page_size - 1)) != 0)
                                   sz = (size_t)(cur_byte_pos & ~httpd->page_size);
                           if ((off_t)httpd->mmapsz < szleft)
                                   sz = httpd->mmapsz;
                           else
                                   sz = (size_t)szleft;
                           if (mmap_and_write_part(httpd, fd, cur_byte_pos, sz)) {
                                   if (errno == ENOMEM) {
                                           httpd->mmapsz /= 2;
                                           if (httpd->mmapsz >= httpd->page_size)
                                                   goto retry;
                                   }
                                   goto cleanup;
                           }
                           cur_byte_pos += sz;
                           szleft -= sz;
                   }
           }
    cleanup:
           close(fd);
    cleanup_nofd:
           close(STDIN_FILENO);
           close(STDOUT_FILENO);
           /*close(STDERR_FILENO);*/
 }  }
   
 /*  /* make sure we're not trying to access special files */
  * as the prototype string is not constant (eg, "HTTP/1.1" is equivalent  int
  * to "HTTP/001.01"), we MUST parse this.  bozo_check_special_files(bozo_httpreq_t *request, const char *name)
  */  
 static void  
 process_proto(http_req *request, const char *proto)  
 {  {
         char    majorstr[16], *minorstr;          bozohttpd_t *httpd = request->hr_httpd;
         int     majorint, minorint;  
   
         if (proto == NULL) {  
 got_proto_09:  
                 request->hr_proto = http_09;  
                 debug((DEBUG_FAT, "request %s is http/0.9", request->hr_file));  
                 return;  
         }  
   
         if (strncasecmp(proto, "HTTP/", 5) != 0)          /* ensure basename(name) != special files */
                 goto bad;          if (strcmp(name, DIRECT_ACCESS_FILE) == 0)
         strncpy(majorstr, proto + 5, sizeof majorstr);                  return bozo_http_error(httpd, 403, request,
         majorstr[sizeof(majorstr)-1] = 0;                      "no permission to open direct access file");
         minorstr = strchr(majorstr, '.');          if (strcmp(name, REDIRECT_FILE) == 0)
         if (minorstr == NULL)                  return bozo_http_error(httpd, 403, request,
                 goto bad;                      "no permission to open redirect file");
         *minorstr++ = 0;          if (strcmp(name, ABSREDIRECT_FILE) == 0)
                   return bozo_http_error(httpd, 403, request,
                       "no permission to open redirect file");
           return bozo_auth_check_special_files(request, name);
   }
   
         majorint = atoi(majorstr);  /* generic header printing routine */
         minorint = atoi(minorstr);  void
   bozo_print_header(bozo_httpreq_t *request,
                   struct stat *sbp, const char *type, const char *encoding)
   {
           bozohttpd_t *httpd = request->hr_httpd;
           off_t len;
           char    date[40];
   
         switch (majorint) {          bozo_printf(httpd, "Date: %s\r\n", bozo_http_date(date, sizeof(date)));
         case 0:          bozo_printf(httpd, "Server: %s\r\n", httpd->server_software);
                 if (minorint != 9)          bozo_printf(httpd, "Accept-Ranges: bytes\r\n");
                         break;          if (sbp) {
                 goto got_proto_09;                  char filedate[40];
         case 1:                  struct  tm *tm;
                 if (minorint == 0)  
                         request->hr_proto = http_10;  
                 else if (minorint == 1)  
                         request->hr_proto = http_11;  
                 else  
                         break;  
   
                 debug((DEBUG_FAT, "request %s is %s", request->hr_file,                  tm = gmtime(&sbp->st_mtime);
                     request->hr_proto));                  strftime(filedate, sizeof filedate,
                 SIMPLEQ_INIT(&request->hr_headers);                      "%a, %d %b %Y %H:%M:%S GMT", tm);
                 request->hr_nheaders = 0;                  bozo_printf(httpd, "Last-Modified: %s\r\n", filedate);
                 return;  
         }          }
 bad:          if (type && *type)
         http_error(404, NULL, "unknown prototype");                  bozo_printf(httpd, "Content-Type: %s\r\n", type);
           if (encoding && *encoding)
                   bozo_printf(httpd, "Content-Encoding: %s\r\n", encoding);
           if (sbp) {
                   if (request->hr_have_range) {
                           len = request->hr_last_byte_pos -
                                           request->hr_first_byte_pos +1;
                           bozo_printf(httpd,
                                   "Content-Range: bytes %qd-%qd/%qd\r\n",
                                   (long long) request->hr_first_byte_pos,
                                   (long long) request->hr_last_byte_pos,
                                   (long long) sbp->st_size);
                   } else
                           len = sbp->st_size;
                   bozo_printf(httpd, "Content-Length: %qd\r\n", (long long)len);
           }
           if (request && request->hr_proto == httpd->consts.http_11)
                   bozo_printf(httpd, "Connection: close\r\n");
           bozo_flush(httpd, stdout);
 }  }
   
 #ifdef DEBUG  #ifndef NO_DEBUG
 void  void
 debug__(int level, const char *fmt, ...)  debug__(bozohttpd_t *httpd, int level, const char *fmt, ...)
 {  {
         va_list ap;          va_list ap;
         int savederrno;          int savederrno;
   
         /* only log if the level is low enough */          /* only log if the level is low enough */
         if (dflag < level)          if (httpd->debug < level)
                 return;                  return;
   
         savederrno = errno;          savederrno = errno;
         va_start(ap, fmt);          va_start(ap, fmt);
         if (sflag) {          if (httpd->logstderr) {
                 vfprintf(stderr, fmt, ap);                  vfprintf(stderr, fmt, ap);
                 fputs("\n", stderr);                  fputs("\n", stderr);
         } else          } else
Line 1644  debug__(int level, const char *fmt, ...)
Line 1750  debug__(int level, const char *fmt, ...)
         va_end(ap);          va_end(ap);
         errno = savederrno;          errno = savederrno;
 }  }
 #endif /* DEBUG */  #endif /* NO_DEBUG */
   
 /* these are like warn() and err(), except for syslog not stderr */  /* these are like warn() and err(), except for syslog not stderr */
 void  void
 warning(const char *fmt, ...)  bozo_warn(bozohttpd_t *httpd, const char *fmt, ...)
 {  {
         va_list ap;          va_list ap;
   
         va_start(ap, fmt);          va_start(ap, fmt);
         if (sflag || isatty(STDERR_FILENO)) {          if (httpd->logstderr || isatty(STDERR_FILENO)) {
                   //fputs("warning: ", stderr);
                 vfprintf(stderr, fmt, ap);                  vfprintf(stderr, fmt, ap);
                 fputs("\n", stderr);                  fputs("\n", stderr);
         } else          } else
Line 1662  warning(const char *fmt, ...)
Line 1769  warning(const char *fmt, ...)
 }  }
   
 void  void
 error(int code, const char *fmt, ...)  bozo_err(bozohttpd_t *httpd, int code, const char *fmt, ...)
 {  {
         va_list ap;          va_list ap;
   
         va_start(ap, fmt);          va_start(ap, fmt);
         if (sflag || isatty(STDERR_FILENO)) {          if (httpd->logstderr || isatty(STDERR_FILENO)) {
                   //fputs("error: ", stderr);
                 vfprintf(stderr, fmt, ap);                  vfprintf(stderr, fmt, ap);
                 fputs("\n", stderr);                  fputs("\n", stderr);
         } else          } else
Line 1676  error(int code, const char *fmt, ...)
Line 1784  error(int code, const char *fmt, ...)
         exit(code);          exit(code);
 }  }
   
 /* the follow functions and variables are used in handling HTTP errors */  /*
 /* ARGSUSED */   * this escapes HTML tags.  returns allocated escaped
 void   * string if needed, or NULL on allocation failure or
 http_error(int code, http_req *request, const char *msg)   * lack of escape need.
    * call with NULL httpd in error paths, to avoid recursive
    * malloc failure.  call with valid httpd in normal paths
    * to get automatic allocation failure handling.
    */
   char *
   bozo_escape_html(bozohttpd_t *httpd, const char *url)
 {  {
         static  char buf[BUFSIZ];          int     i, j;
         char portbuf[20];          char    *tmp;
         const char *header = http_errors_short(code);          size_t  len;
         const char *reason = http_errors_long(code);  
         const char *proto = (request && request->hr_proto) ? request->hr_proto : http_11;  
         int     size;  
   
         debug((DEBUG_FAT, "http_error %d: %s", code, msg));  
         if (header == NULL || reason == NULL)  
                 error(1, "http_error() failed (short = %p, long = %p)",  
                     header, reason);  
   
         if (request && request->hr_serverport &&          for (i = 0, j = 0; url[i]; i++) {
             strcmp(request->hr_serverport, "80") != 0)                  switch (url[i]) {
                 snprintf(portbuf, sizeof(portbuf), ":%s", request->hr_serverport);                  case '<':
         else                  case '>':
                 portbuf[0] = '\0';                          j += 4;
                           break;
                   case '&':
                           j += 5;
                           break;
                   }
           }
   
         if (request && request->hr_file) {          if (j == 0)
                 escape_html(request);                  return NULL;
                 size = snprintf(buf, sizeof buf,  
                     "<html><head><title>%s</title></head>\n"  
                     "<body><h1>%s</h1>\n"  
                     "%s: <pre>%s</pre>\n"  
                     "<hr><address><a href=\"http://%s%s/\">%s%s</a></address>\n"  
                     "</body></html>\n",  
                     header, header, request->hr_file, reason,  
                     myname, portbuf, myname, portbuf);  
                 if (size >= sizeof buf)  
                         warning("http_error buffer too small, truncated");  
         } else  
                 size = 0;  
   
         bozoprintf("%s %s\r\n", proto, header);          /*
         auth_check_401(request, code);           * we need to handle being called from different
            * pathnames.
            */
           len = strlen(url) + j;
           if (httpd)
                   tmp = bozomalloc(httpd, len);
           else if ((tmp = malloc(len)) == 0)
                           return NULL;
   
         bozoprintf("Content-Type: text/html\r\n");          for (i = 0, j = 0; url[i]; i++) {
         bozoprintf("Content-Length: %d\r\n", size);                  switch (url[i]) {
         bozoprintf("Server: %s\r\n", server_software);                  case '<':
         if (request && request->hr_allow)                          memcpy(tmp + j, "&lt;", 4);
                 bozoprintf("Allow: %s\r\n", request->hr_allow);                          j += 4;
         bozoprintf("\r\n");                          break;
         if (size)                  case '>':
                 bozoprintf("%s", buf);                          memcpy(tmp + j, "&gt;", 4);
         bozoflush(stdout);                          j += 4;
                           break;
                   case '&':
                           memcpy(tmp + j, "&amp;", 5);
                           j += 5;
                           break;
                   default:
                           tmp[j++] = url[i];
                   }
           }
           tmp[j] = 0;
   
         exit(1);          return tmp;
 }  }
   
 /* short map between error code, and short/long messages */  /* short map between error code, and short/long messages */
Line 1771  http_errors_long(int code)
Line 1888  http_errors_long(int code)
         return (help);          return (help);
 }  }
   
   /* the follow functions and variables are used in handling HTTP errors */
   /* ARGSUSED */
   int
   bozo_http_error(bozohttpd_t *httpd, int code, bozo_httpreq_t *request,
                   const char *msg)
   {
           char portbuf[20];
           const char *header = http_errors_short(code);
           const char *reason = http_errors_long(code);
           const char *proto = (request && request->hr_proto) ?
                                   request->hr_proto : httpd->consts.http_11;
           int     size;
   
           debug((httpd, DEBUG_FAT, "bozo_http_error %d: %s", code, msg));
           if (header == NULL || reason == NULL) {
                   bozo_err(httpd, 1,
                           "bozo_http_error() failed (short = %p, long = %p)",
                           header, reason);
                   return code;
           }
   
           if (request && request->hr_serverport &&
               strcmp(request->hr_serverport, "80") != 0)
                   snprintf(portbuf, sizeof(portbuf), ":%s",
                                   request->hr_serverport);
           else
                   portbuf[0] = '\0';
   
           if (request && request->hr_file) {
                   char *file = NULL;
                   const char *hostname = BOZOHOST(httpd, request);
   
                   /* bozo_escape_html() failure here is just too bad. */
                   file = bozo_escape_html(NULL, request->hr_file);
                   if (file == NULL)
                           file = request->hr_file;
                   size = snprintf(httpd->errorbuf, BUFSIZ,
                       "<html><head><title>%s</title></head>\n"
                       "<body><h1>%s</h1>\n"
                       "%s: <pre>%s</pre>\n"
                       "<hr><address><a href=\"http://%s%s/\">%s%s</a></address>\n"
                       "</body></html>\n",
                       header, header, file, reason,
                       hostname, portbuf, hostname, portbuf);
                   if (size >= (int)BUFSIZ) {
                           bozo_warn(httpd,
                                   "bozo_http_error buffer too small, truncated");
                           size = (int)BUFSIZ;
                   }
           } else
                   size = 0;
   
           bozo_printf(httpd, "%s %s\r\n", proto, header);
           if (request)
                   bozo_auth_check_401(request, code);
   
           bozo_printf(httpd, "Content-Type: text/html\r\n");
           bozo_printf(httpd, "Content-Length: %d\r\n", size);
           bozo_printf(httpd, "Server: %s\r\n", httpd->server_software);
           if (request && request->hr_allow)
                   bozo_printf(httpd, "Allow: %s\r\n", request->hr_allow);
           bozo_printf(httpd, "\r\n");
           /* According to the RFC 2616 sec. 9.4 HEAD method MUST NOT return a
            * message-body in the response */
           if (size && request && request->hr_method != HTTP_HEAD)
                   bozo_printf(httpd, "%s", httpd->errorbuf);
           bozo_flush(httpd, stdout);
   
           return code;
   }
   
 /* Below are various modified libc functions */  /* Below are various modified libc functions */
   
 /*  /*
Line 1813  bozostrnsep(char **strp, const char *del
Line 2001  bozostrnsep(char **strp, const char *del
  * terminate the string.   * terminate the string.
  */   */
 char *  char *
 bozodgetln(int fd, ssize_t *lenp, ssize_t (*readfn)(int, void *, size_t))  bozodgetln(bozohttpd_t *httpd, int fd, ssize_t *lenp,
           ssize_t (*readfn)(bozohttpd_t *, int, void *, size_t))
 {  {
         static  char *buffer;  
         static  ssize_t buflen = 0;  
         ssize_t len;          ssize_t len;
         int     got_cr = 0;          int     got_cr = 0;
         char    c, *nbuffer;          char    c, *nbuffer;
   
         /* initialise */          /* initialise */
         if (buflen == 0) {          if (httpd->getln_buflen == 0) {
                 buflen = 128;   /* should be plenty for most requests */                  /* should be plenty for most requests */
                 buffer = malloc(buflen);                  httpd->getln_buflen = 128;
                 if (buffer == NULL) {                  httpd->getln_buffer = malloc((size_t)httpd->getln_buflen);
                         buflen = 0;                  if (httpd->getln_buffer == NULL) {
                           httpd->getln_buflen = 0;
                         return NULL;                          return NULL;
                 }                  }
         }          }
Line 1837  bozodgetln(int fd, ssize_t *lenp, ssize_
Line 2025  bozodgetln(int fd, ssize_t *lenp, ssize_
          * programs (for we pass stdin off to them).  could fix this           * programs (for we pass stdin off to them).  could fix this
          * by becoming a fd-passing program instead of just exec'ing           * by becoming a fd-passing program instead of just exec'ing
          * the program           * the program
            *
            * the above is no longer true, we are the fd-passing
            * program already.
          */           */
         for (; readfn(fd, &c, 1) == 1; ) {          for (; readfn(httpd, fd, &c, 1) == 1; ) {
                 debug((DEBUG_EXPLODING, "bozodgetln read %c", c));                  debug((httpd, DEBUG_EXPLODING, "bozodgetln read %c", c));
   
                 if (len >= buflen - 1) {                  if (len >= httpd->getln_buflen - 1) {
                         buflen *= 2;                          httpd->getln_buflen *= 2;
                         debug((DEBUG_EXPLODING, "bozodgetln: "                          debug((httpd, DEBUG_EXPLODING, "bozodgetln: "
                             "reallocating buffer to buflen %zu", buflen));                                  "reallocating buffer to buflen %zu",
                         nbuffer = realloc(buffer, buflen);                                  httpd->getln_buflen));
                         if (nbuffer == NULL) {                          nbuffer = bozorealloc(httpd, httpd->getln_buffer,
                                 free(buffer);                                  (size_t)httpd->getln_buflen);
                                 buflen = 0;                          httpd->getln_buffer = nbuffer;
                                 buffer = NULL;  
                                 return NULL;  
                         }  
                         buffer = nbuffer;  
                 }                  }
   
                 buffer[len++] = c;                  httpd->getln_buffer[len++] = c;
                 if (c == '\r') {                  if (c == '\r') {
                         got_cr = 1;                          got_cr = 1;
                         continue;                          continue;
Line 1875  bozodgetln(int fd, ssize_t *lenp, ssize_
Line 2062  bozodgetln(int fd, ssize_t *lenp, ssize_
                 }                  }
   
         }          }
         buffer[len] = '\0';          httpd->getln_buffer[len] = '\0';
         debug((DEBUG_OBESE, "bozodgetln returns: ``%s'' with len %d",          debug((httpd, DEBUG_OBESE, "bozodgetln returns: ``%s'' with len %zd",
                buffer, len));                 httpd->getln_buffer, len));
         *lenp = len;          *lenp = len;
         return (buffer);          return httpd->getln_buffer;
 }  }
   
 void *  void *
 bozorealloc(void *ptr, size_t size)  bozorealloc(bozohttpd_t *httpd, void *ptr, size_t size)
 {  {
         void    *p;          void    *p;
   
         p = realloc(ptr, size);          p = realloc(ptr, size);
         if (p == NULL)          if (p == NULL) {
                 http_error(500, NULL, "memory allocation failure");                  (void)bozo_http_error(httpd, 500, NULL,
                                   "memory allocation failure");
                   exit(1);
           }
         return (p);          return (p);
 }  }
   
 void *  void *
 bozomalloc(size_t size)  bozomalloc(bozohttpd_t *httpd, size_t size)
 {  {
         void    *p;          void    *p;
   
         p = malloc(size);          p = malloc(size);
         if (p == NULL)          if (p == NULL) {
                 http_error(500, NULL, "memory allocation failure");                  (void)bozo_http_error(httpd, 500, NULL,
                                   "memory allocation failure");
                   exit(1);
           }
         return (p);          return (p);
 }  }
   
 char *  char *
 bozostrdup(const char *str)  bozostrdup(bozohttpd_t *httpd, const char *str)
 {  {
         char    *p;          char    *p;
   
         p = strdup(str);          p = strdup(str);
         if (p == NULL)          if (p == NULL) {
                 http_error(500, NULL, "memory allocation failure");                  (void)bozo_http_error(httpd, 500, NULL,
                                           "memory allocation failure");
                   exit(1);
           }
         return (p);          return (p);
 }  }
   
   /* set default values in bozohttpd_t struct */
   int
   bozo_init_httpd(bozohttpd_t *httpd)
   {
           /* make sure everything is clean */
           (void) memset(httpd, 0x0, sizeof(*httpd));
   
           /* constants */
           httpd->consts.http_09 = "HTTP/0.9";
           httpd->consts.http_10 = "HTTP/1.0";
           httpd->consts.http_11 = "HTTP/1.1";
           httpd->consts.text_plain = "text/plain";
   
           /* mmap region size */
           httpd->mmapsz = BOZO_MMAPSZ;
   
           /* error buffer for bozo_http_error() */
           if ((httpd->errorbuf = malloc(BUFSIZ)) == NULL) {
                   (void) fprintf(stderr,
                           "bozohttpd: memory_allocation failure\n");
                   return 0;
           }
   #ifndef NO_LUA_SUPPORT
           SIMPLEQ_INIT(&httpd->lua_states);
   #endif
           return 1;
   }
   
   /* set default values in bozoprefs_t struct */
   int
   bozo_init_prefs(bozoprefs_t *prefs)
   {
           /* make sure everything is clean */
           (void) memset(prefs, 0x0, sizeof(*prefs));
   
           /* set up default values */
           bozo_set_pref(prefs, "server software", SERVER_SOFTWARE);
           bozo_set_pref(prefs, "index.html", INDEX_HTML);
           bozo_set_pref(prefs, "public_html", PUBLIC_HTML);
   
           return 1;
   }
   
   /* set default values */
   int
   bozo_set_defaults(bozohttpd_t *httpd, bozoprefs_t *prefs)
   {
           return bozo_init_httpd(httpd) && bozo_init_prefs(prefs);
   }
   
   /* set the virtual host name, port and root */
   int
   bozo_setup(bozohttpd_t *httpd, bozoprefs_t *prefs, const char *vhost,
                   const char *root)
   {
           struct passwd    *pw;
           extern char     **environ;
           static char      *cleanenv[1] = { NULL };
           uid_t             uid;
           char             *chrootdir;
           char             *username;
           char             *portnum;
           char             *cp;
           int               dirtyenv;
   
           dirtyenv = 0;
   
           if (vhost == NULL) {
                   httpd->virthostname = bozomalloc(httpd, MAXHOSTNAMELEN+1);
                   /* XXX we do not check for FQDN here */
                   if (gethostname(httpd->virthostname, MAXHOSTNAMELEN+1) < 0)
                           bozo_err(httpd, 1, "gethostname");
                   httpd->virthostname[MAXHOSTNAMELEN] = '\0';
           } else {
                   httpd->virthostname = strdup(vhost);
           }
           httpd->slashdir = strdup(root);
           if ((portnum = bozo_get_pref(prefs, "port number")) != NULL) {
                   httpd->bindport = strdup(portnum);
           }
   
           /* go over preferences now */
           if ((cp = bozo_get_pref(prefs, "numeric")) != NULL &&
               strcmp(cp, "true") == 0) {
                   httpd->numeric = 1;
           }
           if ((cp = bozo_get_pref(prefs, "trusted referal")) != NULL &&
               strcmp(cp, "true") == 0) {
                   httpd->untrustedref = 1;
           }
           if ((cp = bozo_get_pref(prefs, "log to stderr")) != NULL &&
               strcmp(cp, "true") == 0) {
                   httpd->logstderr = 1;
           }
           if ((cp = bozo_get_pref(prefs, "bind address")) != NULL) {
                   httpd->bindaddress = strdup(cp);
           }
           if ((cp = bozo_get_pref(prefs, "background")) != NULL) {
                   httpd->background = atoi(cp);
           }
           if ((cp = bozo_get_pref(prefs, "foreground")) != NULL &&
               strcmp(cp, "true") == 0) {
                   httpd->foreground = 1;
           }
           if ((cp = bozo_get_pref(prefs, "pid file")) != NULL) {
                   httpd->pidfile = strdup(cp);
           }
           if ((cp = bozo_get_pref(prefs, "unknown slash")) != NULL &&
               strcmp(cp, "true") == 0) {
                   httpd->unknown_slash = 1;
           }
           if ((cp = bozo_get_pref(prefs, "virtual base")) != NULL) {
                   httpd->virtbase = strdup(cp);
           }
           if ((cp = bozo_get_pref(prefs, "enable users")) != NULL &&
               strcmp(cp, "true") == 0) {
                   httpd->enable_users = 1;
           }
           if ((cp = bozo_get_pref(prefs, "dirty environment")) != NULL &&
               strcmp(cp, "true") == 0) {
                   dirtyenv = 1;
           }
           if ((cp = bozo_get_pref(prefs, "hide dots")) != NULL &&
               strcmp(cp, "true") == 0) {
                   httpd->hide_dots = 1;
           }
           if ((cp = bozo_get_pref(prefs, "directory indexing")) != NULL &&
               strcmp(cp, "true") == 0) {
                   httpd->dir_indexing = 1;
           }
           if ((cp = bozo_get_pref(prefs, "public_html")) != NULL) {
                   httpd->public_html = strdup(cp);
           }
           httpd->server_software =
                           strdup(bozo_get_pref(prefs, "server software"));
           httpd->index_html = strdup(bozo_get_pref(prefs, "index.html"));
   
           /*
            * initialise ssl and daemon mode if necessary.
            */
           bozo_ssl_init(httpd);
           bozo_daemon_init(httpd);
   
           if ((username = bozo_get_pref(prefs, "username")) == NULL) {
                   if ((pw = getpwuid(uid = 0)) == NULL)
                           bozo_err(httpd, 1, "getpwuid(0): %s", strerror(errno));
                   httpd->username = strdup(pw->pw_name);
           } else {
                   httpd->username = strdup(username);
                   if ((pw = getpwnam(httpd->username)) == NULL)
                           bozo_err(httpd, 1, "getpwnam(%s): %s", httpd->username,
                                           strerror(errno));
                   if (initgroups(pw->pw_name, pw->pw_gid) == -1)
                           bozo_err(httpd, 1, "initgroups: %s", strerror(errno));
                   if (setgid(pw->pw_gid) == -1)
                           bozo_err(httpd, 1, "setgid(%u): %s", pw->pw_gid,
                                           strerror(errno));
                   uid = pw->pw_uid;
           }
           /*
            * handle chroot.
            */
           if ((chrootdir = bozo_get_pref(prefs, "chroot dir")) != NULL) {
                   httpd->rootdir = strdup(chrootdir);
                   if (chdir(httpd->rootdir) == -1)
                           bozo_err(httpd, 1, "chdir(%s): %s", httpd->rootdir,
                                   strerror(errno));
                   if (chroot(httpd->rootdir) == -1)
                           bozo_err(httpd, 1, "chroot(%s): %s", httpd->rootdir,
                                   strerror(errno));
           }
   
           if (username != NULL)
                   if (setuid(uid) == -1)
                           bozo_err(httpd, 1, "setuid(%d): %s", uid,
                                           strerror(errno));
   
           /*
            * prevent info leakage between different compartments.
            * some PATH values in the environment would be invalided
            * by chroot. cross-user settings might result in undesirable
            * effects.
            */
           if ((chrootdir != NULL || username != NULL) && !dirtyenv)
                   environ = cleanenv;
   
   #ifdef _SC_PAGESIZE
           httpd->page_size = (long)sysconf(_SC_PAGESIZE);
   #else
           httpd->page_size = 4096;
   #endif
           debug((httpd, DEBUG_OBESE, "myname is %s, slashdir is %s",
                           httpd->virthostname, httpd->slashdir));
   
           return 1;
   }

Legend:
Removed from v.1.7.8.4  
changed lines
  Added in v.1.7.8.4.2.1

CVSweb <webmaster@jp.NetBSD.org>