#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "httpd.h" /* ---------------------------------------------------------------------- */ void read_request(struct REQUEST *req, int pipelined) { int rc; char *h; restart: #ifdef USE_SSL if (with_ssl) rc = ssl_read(req, req->hreq + req->hdata, MAX_HEADER - req->hdata); else #endif rc = read(req->fd, req->hreq + req->hdata, MAX_HEADER - req->hdata); switch (rc) { case -1: if (errno == EAGAIN) { if (pipelined) break; /* check if there is already a full request */ else return; } if (errno == EINTR) goto restart; xperror(LOG_INFO,"read",req->peerhost); /* fall through */ case 0: req->state = STATE_CLOSE; return; default: req->hdata += rc; req->hreq[req->hdata] = 0; } /* check if this looks like a http request after the first few bytes... */ if (req->hdata < 5) return; if (strncmp(req->hreq,"GET ",4) != 0 && strncmp(req->hreq,"PUT ",4) != 0 && strncmp(req->hreq,"HEAD ",5) != 0 && strncmp(req->hreq,"POST ",5) != 0) { mkerror(req,400,0); return; } /* header complete ?? */ if (NULL != (h = strstr(req->hreq,"\r\n\r\n")) || NULL != (h = strstr(req->hreq,"\n\n"))) { if (*h == '\r') { h += 4; *(h-2) = 0; } else { h += 2; *(h-1) = 0; } req->lreq = h - req->hreq; req->state = STATE_PARSE_HEADER; return; } if (req->hdata == MAX_HEADER) { /* oops: buffer full, but found no complete request ... */ mkerror(req,400,0); return; } return; } /* ---------------------------------------------------------------------- */ #if 0 static time_t parse_date(char *line) { static char *m[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; char month[4]; struct tm tm; int i; line = strchr(line,' '); /* skip weekday */ if (NULL == line) return -1; line++; /* first: RFC 1123 date ... */ if (6 != sscanf(line,"%2d %3s %4d %2d:%2d:%2d GMT", &tm.tm_mday,month,&tm.tm_year, &tm.tm_hour,&tm.tm_min,&tm.tm_sec)) /* second: RFC 1036 date ... */ if (6 != sscanf(line,"%2d-%3s-%2d %2d:%2d:%2d GMT", &tm.tm_mday,month,&tm.tm_year, &tm.tm_hour,&tm.tm_min,&tm.tm_sec)) /* third: asctime() format */ if (6 != sscanf(line,"%3s %2d %2d:%2d:%2d %4d", month,&tm.tm_mday, &tm.tm_hour,&tm.tm_min,&tm.tm_sec, &tm.tm_year)) /* none worked :-( */ return -1; for (i = 0; i <= 11; i++) if (0 == strcmp(month,m[i])) break; tm.tm_mon = i; if (tm.tm_year > 1900) tm.tm_year -= 1900; return mktime(&tm); } #endif static off_t parse_off_t(char *str, int *pos) { off_t value = 0; while (isdigit(str[*pos])) { value *= 10; value += str[*pos] - '0'; (*pos)++; } return value; } static int parse_ranges(struct REQUEST *req) { char *h,*line = req->range_hdr; int i,off; for (h = line, req->ranges=1; *h != '\n' && *h != '\0'; h++) if (*h == ',') req->ranges++; if (debug) fprintf(stderr,"%03d: %d ranges:",req->fd,req->ranges); req->r_start = malloc(req->ranges*sizeof(off_t)); req->r_end = malloc(req->ranges*sizeof(off_t)); req->r_head = malloc((req->ranges+1)*BR_HEADER); req->r_hlen = malloc((req->ranges+1)*sizeof(int)); if (NULL == req->r_start || NULL == req->r_end || NULL == req->r_head || NULL == req->r_hlen) { if (req->r_start) free(req->r_start); if (req->r_end) free(req->r_end); if (req->r_head) free(req->r_head); if (req->r_hlen) free(req->r_hlen); if (debug) fprintf(stderr,"oom\n"); return 500; } for (i = 0, off=0; i < req->ranges; i++) { if (line[off] == '-') { off++; if (!isdigit(line[off])) goto parse_error; req->r_start[i] = req->bst.st_size - parse_off_t(line,&off); req->r_end[i] = req->bst.st_size; } else { if (!isdigit(line[off])) goto parse_error; req->r_start[i] = parse_off_t(line,&off); if (line[off] != '-') goto parse_error; off++; if (isdigit(line[off])) req->r_end[i] = parse_off_t(line,&off) +1; else req->r_end[i] = req->bst.st_size; } off++; /* skip "," */ /* ranges ok? */ if (debug) fprintf(stderr," %d-%d", (int)(req->r_start[i]), (int)(req->r_end[i])); if (req->r_start[i] > req->r_end[i] || req->r_end[i] > req->bst.st_size) goto parse_error; } if (debug) fprintf(stderr," ok\n"); return 0; parse_error: req->ranges = 0; if (debug) fprintf(stderr," range error\n"); return 400; } static int unhex(unsigned char c) { if (c < '@') return c - '0'; return (c & 0x0f) + 9; } /* handle %hex quoting, also split path / querystring */ static void unquote(unsigned char *path, unsigned char *qs, unsigned char *src) { int q; unsigned char *dst; q=0; dst = path; while (src[0] != 0) { if (!q && *src == '?') { q = 1; *dst = 0; dst = qs; src++; continue; } if (q && *src == '+') { *dst = ' '; } else if ((*src == '%') && isxdigit(src[1]) && isxdigit(src[2])) { *dst = (unhex(src[1]) << 4) | unhex(src[2]); src += 2; } else { *dst = *src; } dst++; src++; } *dst = 0; } /* delete unneeded path elements */ static void fixpath(char *path) { char *dst = path; char *src = path; for (;*src;) { if (0 == strncmp(src,"//",2)) { src++; continue; } if (0 == strncmp(src,"/./",3)) { src+=2; continue; } *(dst++) = *(src++); } *dst = 0; } static int base64_table[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, }; static void decode_base64(unsigned char *dest, unsigned char *src, int maxlen) { int a,b,d; for (a=0, b=0, d=0; *src != 0 && d < maxlen; src++) { if (*src >= 128 || -1 == base64_table[*src]) break; a = (a<<6) | base64_table[*src]; b += 6; if (b >= 8) { b -= 8; dest[d++] = (a >> b) & 0xff; } } dest[d] = 0; } static int sanity_checks(struct REQUEST *req) { int i; /* path: must start with a '/' */ if (req->path[0] != '/') { mkerror(req,400,0); return -1; } /* path: must not contain "/../" */ if (strstr(req->path,"/../")) { mkerror(req,403,1); return -1; } if (req->hostname[0] == '\0') /* no hostname specified */ return 0; /* validate hostname */ for (i = 0; req->hostname[i] != '\0'; i++) { switch (req->hostname[i]) { case 'A' ... 'Z': req->hostname[i] += 32; /* lowercase */ case 'a' ... 'z': case '0' ... '9': case '-': /* these are fine as-is */ break; case '.': /* some extra checks */ if (0 == i) { /* don't allow a dot as first character */ mkerror(req,400,0); return -1; } if ('.' == req->hostname[i-1]) { /* don't allow two dots in sequence */ mkerror(req,400,0); return -1; } break; default: /* invalid character */ mkerror(req,400,0); return -1; } } return 0; } void parse_request(struct REQUEST *req) { char filename[MAX_PATH+1], proto[MAX_MISC+1], *h; int port, rc, len; struct passwd *pw=NULL; if (debug > 2) fprintf(stderr,"%s\n",req->hreq); /* parse request. Hehe, scanf is powerfull :-) */ if (4 != sscanf(req->hreq, "%" S(MAX_MISC) "[A-Z] " "%" S(MAX_PATH) "[^ \t\r\n] HTTP/%d.%d", req->type, filename, &(req->major),&(req->minor))) { mkerror(req,400,0); return; } if (filename[0] == '/') { strncpy(req->uri,filename,sizeof(req->uri)-1); } else { port = 0; *proto = 0; if (4 != sscanf(filename, "%" S(MAX_MISC) "[a-zA-Z]://" "%" S(MAX_HOST) "[a-zA-Z0-9.-]:%d" "%" S(MAX_PATH) "[^ \t\r\n]", proto, req->hostname, &port, req->uri) && 3 != sscanf(filename, "%" S(MAX_MISC) "[a-zA-Z]://" "%" S(MAX_HOST) "[a-zA-Z0-9.-]" "%" S(MAX_PATH) "[^ \t\r\n]", proto, req->hostname, req->uri)) { mkerror(req,400,0); return; } if (*proto != 0 && 0 != strcasecmp(proto,"http")) { mkerror(req,400,0); return; } } unquote(req->path,req->query,req->uri); fixpath(req->path); if (debug) fprintf(stderr,"%03d: %s \"%s\" HTTP/%d.%d\n", req->fd, req->type, req->path, req->major, req->minor); if (0 != strcmp(req->type,"GET") && 0 != strcmp(req->type,"HEAD")) { mkerror(req,501,0); return; } if (0 == strcmp(req->type,"HEAD")) { req->head_only = 1; } /* parse header lines */ req->keep_alive = req->minor; for (h = req->hreq; h - req->hreq < req->lreq;) { h = strchr(h,'\n'); if (NULL == h) break; h++; h[-2] = 0; h[-1] = 0; list_add(&req->header,h,0); if (0 == strncasecmp(h,"Connection: ",12)) { req->keep_alive = (0 == strncasecmp(h+12,"Keep-Alive",10)); } else if (0 == strncasecmp(h,"Host: ",6)) { if (2 != sscanf(h+6,"%" S(MAX_HOST) "[a-zA-Z0-9.-]:%d", req->hostname,&port)) sscanf(h+6,"%" S(MAX_HOST) "[a-zA-Z0-9.-]", req->hostname); } else if (0 == strncasecmp(h,"If-Modified-Since: ",19)) { req->if_modified = h+19; } else if (0 == strncasecmp(h,"If-Unmodified-Since: ",21)) { req->if_unmodified = h+21; } else if (0 == strncasecmp(h,"If-Range: ",10)) { req->if_range = h+10; } else if (0 == strncasecmp(h,"Authorization: Basic ",21)) { decode_base64(req->auth,h+21,sizeof(req->auth)-1); if (debug) fprintf(stderr,"%03d: auth: %s\n",req->fd,req->auth); } else if (0 == strncasecmp(h,"Range: bytes=",13)) { /* parsing must be done after fstat, we need the file size for the boundary checks */ req->range_hdr = h+13; } } if (debug) { if (req->if_modified) fprintf(stderr,"%03d: if-modified-since: \"%s\"\n", req->fd, req->if_modified); if (req->if_unmodified) fprintf(stderr,"%03d: if-unmodified-since: \"%s\"\n", req->fd, req->if_unmodified); if (req->if_range) fprintf(stderr,"%03d: if-range: \"%s\"\n", req->fd, req->if_range); } /* take care about the hostname */ if (virtualhosts) { if (req->hostname[0] == 0) { if (req->minor > 0) { /* HTTP/1.1 clients MUST specify a hostname */ mkerror(req,400,0); return; } strncpy(req->hostname,server_host,sizeof(req->hostname)-1); } } else { if (req->hostname[0] == '\0' || canonicalhost) strncpy(req->hostname,server_host,sizeof(req->hostname)-1); } /* checks */ if (0 != sanity_checks(req)) return; /* check basic auth */ if (NULL != userpass && 0 != strcmp(userpass,req->auth)) { mkerror(req,401,1); return; } /* is CGI ? */ if (NULL != cgipath && 0 == strncmp(req->path,cgipath,strlen(cgipath))) { cgi_request(req); return; } /* build filename */ if (userdir && '~' == req->path[1]) { /* expand user directories, i.e. /~user/path/file => $HOME/public_html/path/file */ h = strchr(req->path+2,'/'); if (NULL == h) { mkerror(req,404,1); return; } *h = 0; pw = getpwnam(req->path+2); *h = '/'; if (NULL == pw) { mkerror(req,404,1); return; } len = snprintf(filename, sizeof(filename)-1, "%s/%s/%s", pw->pw_dir, userdir, h+1); } else { len = snprintf(filename, sizeof(filename)-1, "%s%s%s%s", do_chroot ? "" : doc_root, virtualhosts ? "/" : "", virtualhosts ? req->hostname : "", req->path); } h = filename +len -1; if (*h == '/') { /* looks like the client asks for a directory */ if (indexhtml) { /* check for index file */ strncpy(h+1, indexhtml, sizeof(filename) -len -1); if (-1 != (req->bfd = open(filename,O_RDONLY))) { /* ok, we have one */ close_on_exec(req->bfd); goto regular_file; } else { if (errno == ENOENT) { /* no such file or directory => listing */ h[1] = '\0'; } else { mkerror(req,403,1); return; } } } if (no_listing) { mkerror(req,403,1); return; }; if (-1 == stat(filename,&(req->bst))) { if (errno == EACCES) { mkerror(req,403,1); } else { mkerror(req,404,1); } return; } strftime(req->mtime, sizeof(req->mtime), RFC1123, gmtime(&req->bst.st_mtime)); req->mime = "text/html; charset=utf-8"; req->dir = get_dir(req,filename); if (NULL == req->body) { /* We arrive here if opendir failed, probably due to -EPERM * It does exist (see the stat() call above) */ mkerror(req,403,1); return; } else if (NULL != req->if_modified && 0 == strcmp(req->if_modified, req->mtime)) { /* 304 not modified */ mkheader(req,304); req->head_only = 1; } else { /* 200 OK */ mkheader(req,200); } return; } /* it is /probably/ a regular file */ if (-1 == (req->bfd = open(filename,O_RDONLY))) { if (errno == EACCES) { mkerror(req,403,1); } else { mkerror(req,404,1); } return; } regular_file: fstat(req->bfd,&(req->bst)); if (req->range_hdr) if (0 != (rc = parse_ranges(req))) { mkerror(req,rc,1); return; } if (!S_ISREG(req->bst.st_mode)) { /* /not/ a regular file */ close(req->bfd); req->bfd = -1; if (S_ISDIR(req->bst.st_mode)) { /* oops: a directory without trailing slash */ strcat(req->path,"/"); mkredirect(req); } else { /* anything else is'nt allowed here */ mkerror(req,403,1); } return; } /* it is /really/ a regular file */ req->mime = get_mime(filename); strftime(req->mtime, sizeof(req->mtime), RFC1123, gmtime(&req->bst.st_mtime)); if (NULL != req->if_range && 0 != strcmp(req->if_range, req->mtime)) /* mtime mismatch -> no ranges */ req->ranges = 0; if (NULL != req->if_unmodified && 0 != strcmp(req->if_unmodified, req->mtime)) { /* 412 precondition failed */ mkerror(req,412,1); } else if (NULL != req->if_modified && 0 == strcmp(req->if_modified, req->mtime)) { /* 304 not modified */ mkheader(req,304); req->head_only = 1; } else if (req->ranges > 0) { /* send byte range(s) */ mkheader(req,206); } else { /* normal */ mkheader(req,200); } return; }