version 1.7.8.4, 2010/10/15 23:25:45 |
version 1.7.8.4.2.1, 2014/07/09 16:09:39 |
|
|
/* $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 |
|
|
* 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 |
|
|
|
|
/* |
/* |
* 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 |
* |
* |
|
|
* |
* |
* - 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" |
|
|
#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 .. |
|
|
#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 |
|
|
#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: |
|
|
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; |
|
|
} |
} |
} |
} |
|
|
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, ×tamp) && |
|
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, "<", 4); |
|
j += 4; |
|
break; |
|
case '>': |
|
memcpy(tmp + j, ">", 4); |
|
j += 4; |
|
break; |
|
case '&': |
|
memcpy(tmp + j, "&", 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, ×tamp) && |
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, "<", 4); |
bozoprintf("Allow: %s\r\n", request->hr_allow); |
j += 4; |
bozoprintf("\r\n"); |
break; |
if (size) |
case '>': |
bozoprintf("%s", buf); |
memcpy(tmp + j, ">", 4); |
bozoflush(stdout); |
j += 4; |
|
break; |
|
case '&': |
|
memcpy(tmp + j, "&", 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; |
|
} |