/* * Copyright 1996, Michiel Boland. * All rights reserved. * * Redistribution and use in source and binary forms, with or * without modification, are permitted provided that the following * conditions are met: * * 1. Redistributions of source code must retain the above * copyright notice, this list of conditions and the following * disclaimer. * * 2. Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided 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 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ /* Mysterons */ static const char rcsid[] = "$Id: request.c,v 1.294.2.7 2007/07/21 10:48:00 boland Exp $"; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mathopd.h" static const char m_get[] = "GET"; static const char m_head[] = "HEAD"; static const char m_post[] = "POST"; static time_t timerfc(const char *s) { static const int daytab[2][12] = { { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }, { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 } }; unsigned sec, min, hour, day, mon, year; char month[3]; int c; unsigned n; char flag; char state; char isctime; enum { D_START, D_END, D_MON, D_DAY, D_YEAR, D_HOUR, D_MIN, D_SEC }; sec = 60; min = 60; hour = 24; day = 32; year = 1969; isctime = 0; month[0] = 0; state = D_START; n = 0; flag = 1; do { c = *s++; switch (state) { case D_START: if (c == ' ') { state = D_MON; isctime = 1; } else if (c == ',') state = D_DAY; break; case D_MON: if (isalpha(c)) { if (n < 3) month[n++] = c; } else { if (n < 3) return -1; n = 0; state = isctime ? D_DAY : D_YEAR; } break; case D_DAY: if (c == ' ' && flag) ; else if (isdigit(c)) { flag = 0; n = 10 * n + (c - '0'); } else { day = n; n = 0; state = isctime ? D_HOUR : D_MON; } break; case D_YEAR: if (isdigit(c)) n = 10 * n + (c - '0'); else { year = n; n = 0; state = isctime ? D_END : D_HOUR; } break; case D_HOUR: if (isdigit(c)) n = 10 * n + (c - '0'); else { hour = n; n = 0; state = D_MIN; } break; case D_MIN: if (isdigit(c)) n = 10 * n + (c - '0'); else { min = n; n = 0; state = D_SEC; } break; case D_SEC: if (isdigit(c)) n = 10 * n + (c - '0'); else { sec = n; n = 0; state = isctime ? D_YEAR : D_END; } break; } } while (state != D_END && c); switch (month[0]) { case 'A': mon = (month[1] == 'p') ? 4 : 8; break; case 'D': mon = 12; break; case 'F': mon = 2; break; case 'J': mon = (month[1] == 'a') ? 1 : ((month[2] == 'l') ? 7 : 6); break; case 'M': mon = (month[2] == 'r') ? 3 : 5; break; case 'N': mon = 11; break; case 'O': mon = 10; break; case 'S': mon = 9; break; default: return -1; } if (year <= 100) year += (year < 70) ? 2000 : 1900; --mon; --day; if (sec >= 60 || min >= 60 || hour >= 60 || day >= 31) return -1; if (year < 1970) return 0; return sec + 60L * (min + 60L * (hour + 24L * ( day + daytab[year % 4 == 0 && (year % 100 || year % 400 == 0)][mon] + 365L * (year - 1970L) + ((year - 1969L) >> 2)))); } char *rfctime(time_t t, char *buf) { struct tm *tp; tp = gmtime(&t); if (tp == 0) { log_d("gmtime failed!?!?!?"); strcpy(buf, "?"); return buf; } strftime(buf, 31, "%a, %d %b %Y %H:%M:%S GMT", tp); return buf; } static char *getline(struct pool *p, int fold) { char *s, *olds, *sp, *end; int f; end = p->middle; s = p->start; if (s >= end) return 0; olds = s; sp = s; f = 0; while (s < end) { switch (*s++) { case '\n': if (s == end || fold == 0 || (*s != ' ' && *s != '\t')) { if (f) *sp = 0; else s[-1] = 0; p->start = s; return olds; } case '\r': case '\t': s[-1] = ' '; case ' ': if (f == 0) { f = 1; sp = s - 1; } break; default: f = 0; break; } } log_d("getline: fallen off the end"); return 0; } static char *dirmatch(char *s, char *t) { size_t n; n = strlen(t); if (n == 0) return s; return !strncmp(s, t, n) && (s[n] == '/' || s[n] == 0 || s[n - 1] == '~') ? s + n : 0; } static char *exactmatch(char *s, char *t) { size_t n; n = strlen(t); return !strncmp(s, t, n) && s[n] == '/' && s[n + 1] == 0 ? s + n : 0; } static int evaluate_access(unsigned long ip, struct access *a) { while (a && ((ip & a->mask) != a->addr)) a = a->next; return a ? a->type : ALLOW; } static int get_mime(struct request *r, const char *s) { struct mime *m; char *saved_type; int saved_class; int l, le, lm; saved_type = 0; saved_class = 0; lm = 0; l = strlen(s); m = r->c->mimes; while (m) { if (m->ext) { le = strlen(m->ext); if (le > lm && le <= l && !strcasecmp(s + l - le, m->ext)) { lm = le; saved_type = m->name; saved_class = m->class; } } else if (saved_type == 0) { saved_type = m->name; saved_class = m->class; } m = m->next; } if (saved_type) { if (debug) log_d("get_mime: type=%s, class=%d", saved_type, saved_class); r->content_type = saved_type; r->class = saved_class; r->num_content = lm; return 0; } return -1; } static void close_rfd(struct request *r) { if (r->cn->rfd == -1) return; if (debug) log_d("close_rfd: %d", r->cn->rfd); close(r->cn->rfd); r->cn->rfd = -1; } static int assign_rfd(struct request *r, int fd) { if (r->cn->rfd != -1) log_d("assign_rfd: rfd already assigned!?!?"); fcntl(fd, F_SETFD, FD_CLOEXEC); if (fstat(fd, &r->finfo) == -1) { lerror("fstat"); return -1; } close_rfd(r); r->cn->rfd = fd; return 0; } static int get_path_info(struct request *r) { char *p, *pa, *end, *cp, *start, *cds; int fd, first; size_t m; m = r->location_length; if (m == 0) return -1; p = r->path_translated; start = p + m; end = p + strlen(p); pa = r->path_args; *pa = 0; cp = end; first = 0; while (cp >= start && cp[-1] == '/') --cp; while (cp >= start) { if (cp != end) *cp = 0; fd = open(p, O_RDONLY | O_NONBLOCK); if (debug) log_d("get_path_info: open(\"%s\") = %d", p, fd); if (fd == -1) switch (errno) { case ENOENT: case ENOTDIR: break; default: log_d("cannot open %s", p); lerror("open"); return -1; } else { if (assign_rfd(r, fd) == -1) { close(fd); return -1; } if (r->curdir[0] == 0) { first = 1; strcpy(r->curdir, p); } } if (cp != end) *cp = '/'; if (fd != -1) { strcpy(pa, cp); if (S_ISDIR(r->finfo.st_mode)) *cp++ = '/'; else if (first) { cds = strrchr(r->curdir, '/'); if (cds) *cds = 0; } *cp = 0; if (debug) log_d("get_path_info: curdir = \"%s\"", r->curdir); return 0; } else if (r->c->path_info_ok == 0) return -1; while (--cp >= start && *cp != '/') ; } return -1; } static void sanitize_path(struct request *r) { char *p, *q, c; enum { sp_normal, sp_slash, sp_slashdot, sp_slashdotdot } s; if (debug) log_d("sanitize_path: old path: %s", r->path); p = q = r->path; s = sp_normal; do { c = *p++; switch (s) { case sp_normal: if (c == '/') s = sp_slash; break; case sp_slash: switch (c) { case '/': /* * replace '//' with '/' */ --q; break; case '.': s = sp_slashdot; break; default: s = sp_normal; break; } break; case sp_slashdot: switch (c) { case '/': /* * replace '/./' with '/' */ q -= 2; s = sp_slash; break; case '.': s = sp_slashdotdot; break; default: s = sp_normal; break; } break; case sp_slashdotdot: switch (c) { case '/': /* * replace '/foo/../' with '/' */ q -= 3; while (q > r->path && q[-1] != '/') --q; if (q > r->path) --q; s = sp_slash; break; default: s = sp_normal; break; } break; } *q++ = c; } while (c); if (debug) log_d("sanitize_path: new path: %s", r->path); } static int check_path(struct request *r) { char *p; char c; enum { s_normal, s_slash, s_slashdot, s_slashdotdot, s_forbidden } s; p = r->path; s = s_normal; do { c = *p++; switch (s) { case s_normal: if (c == '/') s = s_slash; break; case s_slash: if (c == '/') s = s_forbidden; else if (c == '.') s = r->c->allow_dotfiles ? s_slashdot : s_forbidden; else s = s_normal; break; case s_slashdot: if (c == 0 || c == '/') s = s_forbidden; else if (c == '.') s = s_slashdotdot; else s = s_normal; break; case s_slashdotdot: if (c == 0 || c == '/') s = s_forbidden; else s = s_normal; break; case s_forbidden: c = 0; break; } } while (c); return s == s_forbidden ? -1 : 0; } static int makedir(struct request *r) { int l; if (r->args) l = snprintf(r->newloc, PATHLEN, "%s/?%s", r->url, r->args); else l = snprintf(r->newloc, PATHLEN, "%s/", r->url); if (l >= PATHLEN) { log_d("makedir: url too large"); r->status = 500; return 0; } if (debug) log_d("makedir: redirecting to %s", r->newloc); r->location = r->newloc; r->status = 302; return 0; } static int append_indexes(struct request *r) { char *p; struct simple_list *i; int fd; size_t l, n; p = r->path_translated; l = strlen(p); r->isindex = 1; for (i = r->c->index_names; i; i = i->next) { n = strlen(i->name); if (l + n >= PATHLEN - 1) continue; memcpy(p + l, i->name, n); p[l + n] = 0; fd = open(p, O_RDONLY | O_NONBLOCK); if (debug) log_d("append_indexes: open(\"%s\") = %d", p, fd); if (fd == -1) { if (errno != ENOENT) { log_d("append_indexes: cannot open %s", p); lerror("open"); p[l] = 0; r->error_file = r->c->error_404_file; r->status = 404; return -1; } } else { if (assign_rfd(r, fd) == -1) { close(fd); return -1; } break; } } if (i == 0) { p[l] = 0; return -1; } return 0; } static int process_external(struct request *r) { r->num_content = -1; return process_cgi(r); } static int process_special(struct request *r) { const char *ct; ct = r->content_type; r->num_content = -1; if (!strcasecmp(ct, CGI_MAGIC_TYPE)) return process_cgi(r); if (r->status) return 0; if (r->method == M_POST) { if (debug) log_d("POST to specialty rejected"); r->status = 405; return 0; } if (!strcasecmp(ct, IMAP_MAGIC_TYPE)) return process_imap(r); if (!strcasecmp(ct, REDIRECT_MAGIC_TYPE)) return process_redirect(r); if (!strcasecmp(ct, DUMP_MAGIC_TYPE)) return process_dump(r); if (debug) log_d("unknown specialty"); r->error_file = r->c->error_404_file; r->status = 404; return 0; } static int satisfy_range(struct request *r) { r->range_total = r->content_length; switch(r->range) { case -1: if (r->range_suffix == 0) return -1; r->range_ceiling = r->range_total - 1; if (r->range_suffix < r->range_total) r->range_floor = r->range_total - r->range_suffix; else r->range_floor = 0; break; case 1: if (r->range_floor >= r->range_total) return -1; r->range_ceiling = r->range_total - 1; break; case 2: if (r->range_floor >= r->range_total) return -1; if (r->range_ceiling >= r->range_total) r->range_ceiling = r->range_total - 1; break; } if (r->range_floor == 0 && r->range_ceiling == r->range_total - 1) { r->range = 0; return 0; } if (r->if_range_s && r->last_modified > r->if_range) { r->range = 0; return 0; } return 0; } static int process_fd(struct request *r) { if (r->path_args[0] && r->c->path_args_ok == 0 && (r->path_args[1] || r->isindex == 0)) { close_rfd(r); if (debug) log_d("process_fd: path_args is not empty"); r->error_file = r->c->error_404_file; r->status = 404; return 1; } if (r->method == M_POST) { close_rfd(r); if (debug) log_d("POST to file rejected"); r->status = 405; return 0; } r->content_length = r->finfo.st_size; r->cn->file_offset = 0; if (r->status == 0) { r->last_modified = r->finfo.st_mtime; if (r->last_modified > current_time) { current_time = time(0); if (r->last_modified > current_time) { log_d("file %s has modification time in the future", r->path_translated); r->last_modified = current_time; } } if (r->last_modified <= r->ims) { close_rfd(r); r->num_content = -1; if (debug) log_d("file not modified (%ld <= %ld)", (long) r->last_modified, (long) r->ims); r->status = 304; return 0; } if (r->ius && r->last_modified > r->ius) { close_rfd(r); if (debug) log_d("file modified (%ld > %ld)", (long) r->last_modified, (long) r->ius); r->status = 412; return 0; } if (r->range == 0) r->status = 200; else { if (satisfy_range(r) == -1) { close_rfd(r); if (debug) log_d("satisfy_range failed"); r->status = 416; return 0; } if (r->range) { if (r->range_floor) r->cn->file_offset = r->range_floor; r->content_length = r->range_ceiling - r->range_floor + 1; if (debug) log_d("returning partial content"); r->status = 206; } else { if (debug) log_d("range covered entire file"); r->status = 200; } } } if (r->method != M_GET) close_rfd(r); return 0; } static int find_vs(struct request *r) { struct virtual *v, *d; d = 0; v = r->cn->s->children; if (r->host) while (v) { if (v->host) { if (strcmp(r->host, v->host) == 0) break; } else if (v->anyhost) d = v; v = v->next; } else while (v) { if (v->host == 0 && v->anyhost == 0) break; v = v->next; } if (v == 0) { if (d == 0) return -1; v = d; } r->vs = v; return 0; } static int check_realm(struct request *r) { char *a; if (r == 0 || r->c == 0 || r->c->realm == 0 || r->c->userfile == 0) return 0; a = r->authorization; if (a == 0) return -1; if (strncasecmp(a, "basic ", 6)) return -1; a += 6; while (*a == ' ') ++a; if (webuserok(a, r->c->userfile, r->user, sizeof r->user, r->c->do_crypt)) return 0; return -1; } static size_t expand_hostname(char *dest, const char *source, const char *host, int m) { int c, l, n; if (m <= 0) /* should never happen */ return 0; n = m; do { c = *source++; switch (c) { case '*': dest += l = sprintf(dest, "%.*s", n, host); n -= l; break; default: *dest++ = c; --n; break; case 0: *dest = 0; break; } } while (n && c); return m - n; } struct control *faketoreal(char *x, char *y, struct request *r, int update, int maxlen) { struct control *c; char *s, *t; struct passwd *p; int l; if (r->vs == 0) { log_d("virtualhost not initialized!"); return 0; } c = r->vs->vserver->controls; while (c) { if (c->locations && c->alias) { s = c->exact_match ? exactmatch(x, c->alias) : dirmatch(x, c->alias); if (s && (c->clients == 0 || evaluate_access(r->cn->peer.sin_addr.s_addr, c->clients) == APPLY)) { if (c->user_directory == 0) { if (r->host) l = expand_hostname(y, c->locations->name, r->host, maxlen - 1); else l = sprintf(y, "%.*s", maxlen - 1, c->locations->name); r->location_length = l; if (c->locations->name[0] == '/' || !c->path_args_ok) sprintf(y + l, "%.*s", maxlen - (l + 1), s); break; } else { if (*s == '/') ++s; t = strchr(s, '/'); if (t) *t = 0; p = getpwnam(s); if (t) *t = '/'; if (p && p->pw_dir) { l = strlen(p->pw_dir); if (l + 2 > maxlen) { log_d("overflow in faketoreal"); return 0; } l = sprintf(y, "%s/%.*s", p->pw_dir, maxlen - (l + 2), c->locations->name); r->location_length = l; maxlen -= l + 1; if (t && (c->locations->name[0] == '/' || !c->path_args_ok)) sprintf(y + l, "%.*s", maxlen, t); break; } } } } c = c->next; } if (c && update) c->locations = c->locations->next; return c; } static void process_path(struct request *r) { if (find_vs(r) == -1) { if (debug) log_d("find_vs failed (host=%s)", r->host ? r->host : "[not set]"); r->status = 400; return; } if (r->vs->vserver->controls && r->vs->vserver->controls->sanitize_path) sanitize_path(r); if ((r->c = faketoreal(r->path, r->path_translated, r, 1, sizeof r->path_translated)) == 0) { if (debug) log_d("faketoreal failed"); r->status = 500; return; } if (check_path(r) == -1) { if (debug) log_d("check_path failed for %s", r->path); r->error_file = r->c->error_404_file; r->status = 404; return; } if (r->c->accesses && evaluate_access(r->cn->peer.sin_addr.s_addr, r->c->accesses) == DENY) { if (debug) log_d("access denied"); r->error_file = r->c->error_403_file; r->status = 403; return; } if (r->c->realm && check_realm(r) == -1) { if (debug) log_d("login incorrect"); r->error_file = r->c->error_401_file; r->status = 401; return; } } static int process_path_translated(struct request *r) { if (r->path_translated[0] == 0) { if (debug) log_d("empty path_translated"); r->status = 500; return 0; } if (r->path_translated[0] != '/') { if (r->status) return 0; r->location = r->path_translated; if (debug) log_d("redirecting"); r->status = 302; return 0; } if (get_path_info(r) == -1) { if (debug) log_d("get_path_info failed for %s", r->path_translated); r->error_file = r->c->error_404_file; r->status = 404; return 1; } if (S_ISDIR(r->finfo.st_mode)) { close_rfd(r); if (r->status) return 0; if (r->path_args[0] != '/') return makedir(r); if (append_indexes(r) == -1) { if (r->status) return 1; if (r->path_args[1] == 0 && r->c->auto_index_command) { if (r->method == M_POST) { if (debug) log_d("POST to AutoIndexCommand rejected"); r->status = 405; return 0; } r->error_file = r->c->auto_index_command; return 1; } if (debug) log_d("file not found"); r->error_file = r->c->error_404_file; r->status = 404; return 1; } } if (r->path_args[0] && r->c->path_info_ok == 0 && r->isindex == 0) { close_rfd(r); if (debug) log_d("nonempty path_args while PathInfo is off"); r->error_file = r->c->error_404_file; r->status = 404; return 1; } if (!S_ISREG(r->finfo.st_mode)) { close_rfd(r); log_d("%s is not a regular file", r->path_translated); r->error_file = r->c->error_404_file; r->status = 404; return 1; } if (get_mime(r, r->path_translated) == -1) { close_rfd(r); log_d("get_mime failed for %s", r->path_translated); r->error_file = r->c->error_404_file; r->status = 404; return 1; } if (r->class != CLASS_FILE) close_rfd(r); switch (r->class) { case CLASS_FILE: return process_fd(r); case CLASS_SPECIAL: return process_special(r); case CLASS_EXTERNAL: return process_external(r); } log_d("unknown class!?"); r->status = 500; return 0; } static int parse_range_header(struct request *r, const char *s) { char *t; int suffix; uintmax_t u, v; if (strncasecmp(s, "bytes", 5)) return -1; s += 5; while (*s == ' ') ++s; if (*s != '=') return -1; do ++s; while (*s == ' '); suffix = *s == '-'; if (suffix) { do ++s; while (*s == ' '); if (*s == '-') return -1; } u = strtoumax(s, &t, 10); if (t == s) return -1; s = t; while (*s == ' ') ++s; if (suffix) { if (*s) return -1; r->range = -1; r->range_suffix = u; return 0; } if (*s != '-') return -1; do ++s; while (*s == ' '); if (*s == 0) { r->range = 1; r->range_floor = u; return 0; } if (*s == '-') return -1; v = strtoumax(s, &t, 10); if (t == s) return -1; s = t; while (*s == ' ') ++s; if (*s) return -1; if (v < u) return -1; r->range = 2; r->range_floor = u; r->range_ceiling = v; return 0; } static int parse_http_version(struct request *r) { const char *v; char *e; unsigned long ma, mi; v = r->version; if (v == 0) return 0; if (strncmp(v, "HTTP", 4)) return -1; v += 4; while (*v == ' ') ++v; if (*v != '/') return -1; do ++v; while (*v == ' '); if (*v == '-') return -1; ma = strtoul(v, &e, 10); if (e == v) return -1; v = e; while (*v == ' ') ++v; if (*v != '.') return -1; do ++v; while (*v == ' '); if (*v == '-') return -1; mi = strtoul(v, &e, 10); if (e == v || *e) return -1; if (ma == 0 || ma > INT_MAX || mi > INT_MAX) return -1; r->protocol_major = ma; r->protocol_minor = mi; return 0; } static const char *header_list_next(const char *s, size_t *lp) { int inquotedstring; char c, lastc; size_t l; while (*s == ',' || *s == ' ') ++s; l = 0; lastc = 0; inquotedstring = 0; while ((c = s[l]) != 0) { if (c == ',' && inquotedstring == 0) break; if (c == '"' && (inquotedstring == 0 || lastc != '\\')) inquotedstring = inquotedstring == 0; lastc = c; ++l; } while (l > 0 && s[l - 1] == ' ') --l; *lp = l; return s; } static void parse_connection_header(struct request *r, const char *s) { size_t l; while (1) { s = header_list_next(s, &l); if (l == 0) break; if (l == 10 && strncasecmp(s, "keep-alive", l) == 0) r->cn->keepalive = 1; else if (l == 5 && strncasecmp(s, "close", l) == 0) r->cn->keepalive = 0; s += l; } } static int parse_expect_header(struct request *r, const char *s) { size_t l; while (1) { s = header_list_next(s, &l); if (l == 0) break; if (l != 12 || strncasecmp(s, "100-continue", l)) return -1; r->send_continue = 1; s += l; } return 0; } static int process_headers(struct request *r) { char *l, *u, *s; time_t i; size_t n; unsigned long cl; do { l = getline(&r->cn->header_input, 0); if (l == 0) return -1; } while (*l == 0); r->method_s = l; u = strchr(l, ' '); if (u == 0) return -1; *u++ = 0; r->url = u; s = strrchr(u, 'H'); if (s == 0 || s == u || s[-1] != ' ') return -1; r->version = s; s[-1] = 0; s = strchr(u, '?'); if (s) { r->args = s + 1; *s = 0; } if (parse_http_version(r) == -1) { if (debug) log_d("parse_http_version failed for \"%s\"", r->version); r->status = 400; return 0; } if (r->protocol_major && r->protocol_minor) r->cn->keepalive = 1; n = 0; while ((l = getline(&r->cn->header_input, 1)) != 0) { s = strchr(l, ':'); if (s == 0) continue; *s++ = 0; while (*s == ' ') ++s; if (*s == 0) continue; if (!strcasecmp(l, "Connection")) { parse_connection_header(r, s); continue; } else if (!strcasecmp(l, "Keep-Alive")) continue; else if (!strcasecmp(l, "Expect")) { if (parse_expect_header(r, s) == -1) { if (debug) log_d("parse_expect_header failed for \"%s\"", s); r->status = 417; return 0; } continue; } else if (!strcasecmp(l, "Content-Type")) { r->in_content_type = s; continue; } else if (!strcasecmp(l, "Content-Length")) { r->in_content_length = s; continue; } else if (!strcasecmp(l, "Transfer-Encoding")) { r->in_transfer_encoding = s; continue; } if (n < tuning.num_headers) { r->headers[n].rh_name = l; r->headers[n++].rh_value = s; } if (!strcasecmp(l, "User-agent")) r->user_agent = s; else if (!strcasecmp(l, "Referer")) r->referer = s; else if (!strcasecmp(l, "Authorization")) r->authorization = s; else if (!strcasecmp(l, "Host")) { sanitize_host(s); r->host = s; } else if (!strcasecmp(l, "If-Modified-Since")) r->ims_s = s; else if (!strcasecmp(l, "If-Unmodified-Since")) r->ius_s = s; else if (!strcasecmp(l, "Range")) { if (r->range_s) { log_d("multiple Range headers"); r->status = 400; return 0; } else r->range_s = s; } else if (!strcasecmp(l, "If-Range")) r->if_range_s = s; } r->nheaders = n; s = r->method_s; if (strcmp(s, m_get) == 0) r->method = M_GET; else { if (strcmp(s, m_head) == 0) r->method = M_HEAD; else if (strcmp(s, m_post) == 0) r->method = M_POST; else { if (debug) log_d("method \"%s\" not implemented", s); r->status = 501; return 0; } } s = r->url; if (strlen(s) > STRLEN) { if (debug) log_d("url too long (%zu > %d)", strlen(s), STRLEN); r->status = 414; return 0; } if (*s != '/') { u = strchr(s, '/'); if (u == 0 || u[1] != '/' || u[2] == 0 || u[2] == '/') { if (debug) log_d("absoluteURI \"%s\" should contain a net_loc", r->url); r->status = 400; return 0; } u += 2; s = strchr(u, '/'); if (s == 0) strcpy(r->rhost, u); else { memcpy(r->rhost, u, s - u); r->rhost[s - u] = 0; } r->host = r->rhost; sanitize_host(r->host); } if (r->host == 0 && r->protocol_minor == 1) { if (debug) log_d("HTTP/1.1 request without Host"); r->status = 400; return 0; } if (r->host && r->host[0] == 0) { if (debug) log_d("empty Host header"); r->status = 400; return 0; } if (s == 0) strcpy(r->path, "/"); else if (unescape_url(s, r->path) == -1) { if (debug) log_d("unescape_url failed for \"%s\"", s); r->status = 400; return 0; } if (r->protocol_major > 1 || (r->protocol_major == 1 && r->protocol_minor > 1)) { r->status = 505; return 0; } if (r->in_transfer_encoding) { if (strcasecmp(r->in_transfer_encoding, "chunked")) { log_d("unimplemented transfer-coding \"%s\"", r->in_transfer_encoding); r->status = 501; return 0; } if (r->in_content_length) { log_d("ignoring Content-Length header from client"); r->in_content_length = 0; } } s = r->in_content_length; if (s) { if (*s == '-' || (cl = strtoul(s, &u, 10), u == s || *u || cl >= UINT_MAX)) { log_d("bad Content-Length from client: \"%s\"", s); r->status = 400; return 0; } r->in_mblen = cl; } if (r->method == M_GET) { s = r->ims_s; if (s) { i = timerfc(s); if (i != -1 && i <= current_time) r->ims = i; } s = r->ius_s; if (s) { i = timerfc(s); if (i != -1) r->ius = i; } s = r->if_range_s; if (s) { i = timerfc(s); if (i != -1) r->if_range = i; } s = r->range_s; if (s) { if (parse_range_header(r, s) == -1) log_d("ignoring Range header \"%s\"", s); } } else if (r->method == M_POST) { if (r->in_content_length == 0) { if (debug) log_d("POST: length required"); r->status = 411; return 0; } } if (r->send_continue) { if (r->protocol_minor == 0 && r->protocol_major == 1) { if (debug) log_d("suppressing '100 Continue' response (HTTP/1.0 client)"); r->send_continue = 0; } else if ((r->in_content_length && r->in_mblen) || r->in_transfer_encoding) { if (r->cn->header_input.end > r->cn->header_input.middle) { if (debug) log_d("suppressing '100 Continue' response (more input received)"); r->send_continue = 0; } } else { if (debug) log_d("suppressing '100 Continue' response (no body)"); r->send_continue = 0; } } return 1; } static const char *http_code_phrase(int status) { switch (status) { case 100: return "100 Continue"; case 200: return "200 OK"; case 204: return "204 No Content"; case 206: return "206 Partial Content"; case 302: return "302 Moved"; case 304: return "304 Not Modified"; case 400: return "400 Bad Request"; case 401: return "401 Not Authorized"; case 403: return "403 Forbidden"; case 404: return "404 Not Found"; case 405: return "405 Method Not Allowed"; case 411: return "411 Length Required"; case 412: return "412 Precondition Failed"; case 414: return "414 Request-URI Too Long"; case 416: return "416 Requested Range Not Satisfiable"; case 417: return "417 Expectation Failed"; case 501: return "501 Not Implemented"; case 503: return "503 Service Unavailable"; case 505: return "505 HTTP Version Not Supported"; default: return "500 Internal Server Error"; } } int pool_print(struct pool *p, const char *fmt, ...) { va_list ap; size_t n; int r; if (p->ceiling <= p->end) return -1; n = p->ceiling - p->end; va_start(ap, fmt); r = vsnprintf(p->end, n, fmt, ap); va_end(ap); if ((size_t) r >= n) return -1; p->end += r; return r; } static int prepare_reply(struct request *r) { struct pool *p; int send_message; char gbuf[40]; struct simple_list *h; const char *status_line; char *cl_start, *cl_end; if (r->forked) return 0; if (r->in_content_length || r->in_transfer_encoding) { if (debug) log_d("client sent request-body; turning off keepalive"); r->cn->keepalive = 0; } p = &r->cn->output; send_message = r->cn->rfd == -1 && r->method != M_HEAD && r->status != 204 && r->status != 304; status_line = http_code_phrase(r->status); cl_start = cl_end = 0; if (send_message) { r->num_content = 0; r->content_type = "text/html"; r->content_length = p->ceiling - p->floor; /* hack - this gives us enough room in the output to change it to something less */ } if (r->status >= 400) r->last_modified = 0; if (pool_print(p, "HTTP/1.1 %s\r\nServer: %s\r\nDate: %s\r\n", status_line, server_version, rfctime(current_time, gbuf)) == -1) return -1; switch (r->status) { case 206: if (pool_print(p, "Content-Range: bytes %ju-%ju/%ju\r\n", r->range_floor, r->range_ceiling, r->range_total) == -1) return -1; break; case 302: if (r->location) if (pool_print(p, "Location: %s\r\n", r->location) == -1) return -1; break; case 401: if (r->c && r->c->realm) if (pool_print(p, "WWW-Authenticate: Basic realm=\"%s\"\r\n", r->c->realm) == -1) return -1; break; case 405: if (pool_print(p, "Allow: GET, HEAD\r\n") == -1) return -1; break; case 416: if (pool_print(p, "Content-Range: bytes */%ju\r\n", r->range_total) == -1) return -1; break; } if (r->num_content >= 0) { if (pool_print(p, "Content-Type: %s\r\n", r->content_type) == -1) return -1; if (r->content_length >= 0) { if (pool_print(p, "Content-Length: ") == -1) return -1; cl_start = p->end; if (pool_print(p, "%jd", r->content_length) == -1) return -1; cl_end = p->end; if (pool_print(p, "\r\n") == -1) return -1; } if (r->last_modified) if (pool_print(p, "Last-Modified: %s\r\n", rfctime(r->last_modified, gbuf)) == -1) return -1; } if (r->cn->keepalive) { if (r->protocol_minor == 0) if (pool_print(p, "Connection: keep-alive\r\n") == -1) return -1; } else if (r->protocol_minor) if (pool_print(p, "Connection: close\r\n") == -1) return -1; if (r->c && r->status == 200) for (h = r->c->extra_headers; h; h = h->next) if (pool_print(p, "%s\r\n", h->name) == -1) return -1; if (pool_print(p, "\r\n") == -1) return -1; p->middle = p->end; if (send_message == 0) return 0; if (pool_print(p, "%s\n

%s

\n", status_line, status_line) == -1) return -1; switch (r->status) { case 302: if (pool_print(p, "This document has moved to URL %s.\n", r->location, r->location) == -1) return -1; break; case 401: if (pool_print(p, "You need proper authorization to use this resource.\n") == -1) return -1; break; case 400: case 405: case 501: case 505: if (pool_print(p, "Your request was not understood or not allowed by this server.\n") == -1) return -1; break; case 403: if (pool_print(p, "Access to this resource has been denied to you.\n") == -1) return -1; break; case 404: if (pool_print(p, "The resource requested could not be found on this server.\n") == -1) return -1; break; case 503: if (pool_print(p, "The server is temporarily busy.\n") == -1) return -1; break; } if (r->c && r->c->admin) if (pool_print(p, "

Please contact the site administrator at %s.\n", r->c->admin) == -1) return -1; r->content_length = p->end - p->middle; if (cl_start == 0) { log_d("cl_start is null!?!?"); return -1; } sprintf(cl_start, "%*jd", (int) (cl_end - cl_start), r->content_length); *cl_end = '\r'; return 0; } void init_request(struct request *r) { r->vs = 0; r->user_agent = 0; r->referer = 0; r->authorization = 0; r->host = 0; r->in_content_type = 0; r->in_content_length = 0; r->ims_s = 0; r->path[0] = 0; r->path_translated[0] = 0; r->path_args[0] = 0; r->num_content = -1; r->class = 0; r->content_length = -1; r->last_modified = 0; r->ims = 0; r->location = 0; r->method_s = 0; r->url = 0; r->args = 0; r->version = 0; r->protocol_major = 0; r->protocol_minor = 0; r->method = 0; r->status = 0; r->isindex = 0; r->c = 0; r->error_file = 0; r->user[0] = 0; r->location_length = 0; r->nheaders = 0; r->range_s = 0; r->if_range_s = 0; r->if_range = 0; r->range = 0; r->range_floor = 0; r->range_ceiling = 0; r->range_suffix = 0; r->range_total = 0; r->ius_s = 0; r->ius = 0; r->rhost[0] = 0; r->in_transfer_encoding = 0; r->in_mblen = 0; r->curdir[0] = 0; r->send_continue = 0; r->forked = 0; } int process_request(struct request *r) { int s, n; s = process_headers(r); switch (s) { case -1: return -1; case 0: break; default: process_path(r); if (r->status && r->error_file == 0) break; n = 0; do { if (r->error_file) { r->location_length = sprintf(r->path_translated, "%.*s", PATHLEN - 1, r->error_file); r->error_file = 0; } if (debug) log_d("process_request: %s", r->path_translated); s = process_path_translated(r); } while (++n < 3 && s == 1 && r->error_file); if (n == 3 && s == 1) { log_d("process_request: loop detected"); r->status = 500; } } if (r->status) { if (prepare_reply(r) == -1) { log_d("cannot prepare reply for client"); return -1; } } else if (r->send_continue) { if (debug) log_d("sending 100-continue"); pool_print(&r->cn->output, "HTTP/1.1 %s\r\n\r\n", http_code_phrase(100)); } if (debug) log_d("process_request finished (s=%d)", s); return s >= 0 ? 0 : -1; } int cgi_error(struct request *r) { int rv; if (debug) log_d("cgi_error"); r->status = 500; r->forked = 0; close_rfd(r); rv = prepare_reply(r); if (rv == -1) { log_d("prepare_reply failed in cgi_error"); close_connection(r->cn); return -1; } set_connection_state(r->cn, HC_WRITING); return rv; }