From fbefc9a818a5ed612c0df918598057a8b551cb95 Mon Sep 17 00:00:00 2001 From: Reyk Floeter Date: Fri, 15 May 2015 20:13:20 +0200 Subject: sync --- httpd/config.c | 20 +++- httpd/httpd.8 | 7 +- httpd/httpd.conf.5 | 7 +- httpd/logger.c | 14 ++- httpd/parse.y | 4 +- httpd/server.c | 20 ++-- httpd/server_fcgi.c | 15 ++- httpd/server_file.c | 312 ++++++++++++++++++++++++++++++++++++++++++++++++++-- httpd/server_http.c | 18 ++- 9 files changed, 376 insertions(+), 41 deletions(-) diff --git a/httpd/config.c b/httpd/config.c index 2dc9159..7635c33 100644 --- a/httpd/config.c +++ b/httpd/config.c @@ -1,4 +1,4 @@ -/* $OpenBSD: config.c,v 1.36 2015/02/23 11:48:41 reyk Exp $ */ +/* $OpenBSD: config.c,v 1.37 2015/04/11 14:52:49 jsing Exp $ */ /* * Copyright (c) 2011 - 2015 Reyk Floeter @@ -212,12 +212,22 @@ config_setserver(struct httpd *env, struct server *srv) fd = -1; else if ((fd = dup(srv->srv_s)) == -1) return (-1); - proc_composev_imsg(ps, id, n, - IMSG_CFG_SERVER, fd, iov, c); + if (proc_composev_imsg(ps, id, n, + IMSG_CFG_SERVER, fd, iov, c) != 0) { + log_warn("%s: failed to compose " + "IMSG_CFG_SERVER imsg for `%s'", + __func__, srv->srv_conf.name); + return (-1); + } } } else { - proc_composev_imsg(ps, id, -1, IMSG_CFG_SERVER, -1, - iov, c); + if (proc_composev_imsg(ps, id, -1, IMSG_CFG_SERVER, -1, + iov, c) != 0) { + log_warn("%s: failed to compose " + "IMSG_CFG_SERVER imsg for `%s'", + __func__, srv->srv_conf.name); + return (-1); + } } } diff --git a/httpd/httpd.8 b/httpd/httpd.8 index eb35096..fcb3e42 100644 --- a/httpd/httpd.8 +++ b/httpd/httpd.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: httpd.8,v 1.50 2015/02/24 07:56:06 bentley Exp $ +.\" $OpenBSD: httpd.8,v 1.51 2015/03/26 19:16:57 jmc Exp $ .\" .\" Copyright (c) 2014 Reyk Floeter .\" @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: February 24 2015 $ +.Dd $Mdocdate: March 26 2015 $ .Dt HTTPD 8 .Os .Sh NAME @@ -85,7 +85,8 @@ Default access log file. Default error log file. .El .Sh SEE ALSO -.Xr httpd.conf 5 +.Xr httpd.conf 5 , +.Xr slowcgi 8 .Sh HISTORY The .Nm diff --git a/httpd/httpd.conf.5 b/httpd/httpd.conf.5 index 578e490..26d6798 100644 --- a/httpd/httpd.conf.5 +++ b/httpd/httpd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: httpd.conf.5,v 1.56 2015/03/11 21:52:20 reyk Exp $ +.\" $OpenBSD: httpd.conf.5,v 1.57 2015/03/26 19:16:57 jmc Exp $ .\" .\" Copyright (c) 2014, 2015 Reyk Floeter .\" @@ -14,7 +14,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: March 11 2015 $ +.Dd $Mdocdate: March 26 2015 $ .Dt HTTPD.CONF 5 .Os .Sh NAME @@ -524,7 +524,8 @@ include "/etc/nginx/mime.types" .Ed .Sh SEE ALSO .Xr htpasswd 1 , -.Xr httpd 8 +.Xr httpd 8 , +.Xr slowcgi 8 .Sh AUTHORS .An -nosplit The diff --git a/httpd/logger.c b/httpd/logger.c index b4e4404..d03d954 100644 --- a/httpd/logger.c +++ b/httpd/logger.c @@ -1,4 +1,4 @@ -/* $OpenBSD: logger.c,v 1.11 2015/02/08 00:00:59 reyk Exp $ */ +/* $OpenBSD: logger.c,v 1.12 2015/04/11 14:52:49 jsing Exp $ */ /* * Copyright (c) 2014 Reyk Floeter @@ -118,12 +118,20 @@ logger_open_file(const char *name) iov[1].iov_base = log->log_name; iov[1].iov_len = strlen(log->log_name) + 1; - proc_composev_imsg(env->sc_ps, PROC_PARENT, -1, IMSG_LOG_OPEN, -1, - iov, 2); + if (proc_composev_imsg(env->sc_ps, PROC_PARENT, -1, IMSG_LOG_OPEN, -1, + iov, 2) != 0) { + log_warn("%s: failed to compose IMSG_LOG_OPEN imsg", __func__); + goto err; + } TAILQ_INSERT_TAIL(&log_files, log, log_entry); return (log); + +err: + free(log); + + return (NULL); } int diff --git a/httpd/parse.y b/httpd/parse.y index 50e51ab..0aae421 100644 --- a/httpd/parse.y +++ b/httpd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.66 2015/03/09 15:46:45 reyk Exp $ */ +/* $OpenBSD: parse.y,v 1.67 2015/04/01 04:51:15 jsg Exp $ */ /* * Copyright (c) 2007 - 2015 Reyk Floeter @@ -1907,6 +1907,8 @@ server_inherit(struct server *src, const char *name, fatal("out of memory"); dst->srv_conf.tls_cert = NULL; dst->srv_conf.tls_key = NULL; + dst->srv_conf.tls_cert_len = 0; + dst->srv_conf.tls_key_len = 0; if (src->srv_conf.return_uri != NULL && (dst->srv_conf.return_uri = diff --git a/httpd/server.c b/httpd/server.c index 281d3ee..ca67a47 100644 --- a/httpd/server.c +++ b/httpd/server.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server.c,v 1.61 2015/03/15 22:08:45 florian Exp $ */ +/* $OpenBSD: server.c,v 1.63 2015/04/23 16:59:28 florian Exp $ */ /* * Copyright (c) 2006 - 2015 Reyk Floeter @@ -852,6 +852,11 @@ server_accept(int fd, short event, void *arg) if ((clt = calloc(1, sizeof(*clt))) == NULL) goto err; + /* Pre-allocate log buffer */ + clt->clt_log = evbuffer_new(); + if (clt->clt_log == NULL) + goto err; + clt->clt_s = s; clt->clt_fd = -1; clt->clt_toread = TOREAD_UNLIMITED; @@ -899,13 +904,6 @@ server_accept(int fd, short event, void *arg) return; } - /* Pre-allocate log buffer */ - clt->clt_log = evbuffer_new(); - if (clt->clt_log == NULL) { - server_close(clt, "failed to allocate log buffer"); - return; - } - if (srv->srv_conf.flags & SRVFLAG_TLS) { event_again(&clt->clt_ev, clt->clt_s, EV_TIMEOUT|EV_READ, server_accept_tls, &clt->clt_tv_start, @@ -1010,7 +1008,11 @@ server_sendlog(struct server_config *srv_conf, int cmd, const char *emsg, ...) iov[1].iov_base = msg; iov[1].iov_len = strlen(msg) + 1; - proc_composev_imsg(env->sc_ps, PROC_LOGGER, -1, cmd, -1, iov, 2); + if (proc_composev_imsg(env->sc_ps, PROC_LOGGER, -1, cmd, -1, iov, + 2) != 0) { + log_warn("%s: failed to compose imsg", __func__); + return; + } } void diff --git a/httpd/server_fcgi.c b/httpd/server_fcgi.c index 33603a0..d0a8800 100644 --- a/httpd/server_fcgi.c +++ b/httpd/server_fcgi.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server_fcgi.c,v 1.52 2015/02/23 19:22:43 chrisz Exp $ */ +/* $OpenBSD: server_fcgi.c,v 1.53 2015/03/26 09:01:51 florian Exp $ */ /* * Copyright (c) 2014 Florian Obser @@ -652,10 +652,21 @@ server_fcgi_writeheader(struct client *clt, struct kv *hdr, void *arg) return (-1); } + /* + * RFC 7230 defines a header field-name as a "token" and a "token" + * is defined as one or more characters for which isalpha or + * isdigit is true plus a list of additional characters. + * According to RFC 3875 a CGI environment variable is created + * by converting all letters to upper case and replacing '-' + * with '_'. + */ for (p = name; *p != '\0'; p++) { if (isalpha((unsigned char)*p)) *p = toupper((unsigned char)*p); - else + else if (!(*p == '!' || *p == '#' || *p == '$' || *p == '%' || + *p == '&' || *p == '\'' || *p == '*' || *p == '+' || + *p == '.' || *p == '^' || *p == '_' || *p == '`' || + *p == '|' || *p == '~' || isdigit((unsigned char)*p))) *p = '_'; } diff --git a/httpd/server_file.c b/httpd/server_file.c index f697504..52e50ff 100644 --- a/httpd/server_file.c +++ b/httpd/server_file.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server_file.c,v 1.51 2015/02/12 10:05:29 reyk Exp $ */ +/* $OpenBSD: server_file.c,v 1.54 2015/05/05 11:10:13 florian Exp $ */ /* * Copyright (c) 2006 - 2015 Reyk Floeter @@ -36,12 +36,27 @@ #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) #define MAXIMUM(a, b) (((a) > (b)) ? (a) : (b)) - -int server_file_access(struct httpd *, struct client *, char *, size_t); -int server_file_request(struct httpd *, struct client *, char *, - struct stat *); -int server_file_index(struct httpd *, struct client *, struct stat *); -int server_file_method(struct client *); +#define MAX_RANGES 4 + +struct range { + off_t start; + off_t end; +}; + +int server_file_access(struct httpd *, struct client *, + char *, size_t); +int server_file_request(struct httpd *, struct client *, + char *, struct stat *); +int server_partial_file_request(struct httpd *, struct client *, + char *, struct stat *, char *); +int server_file_index(struct httpd *, struct client *, + struct stat *); +int server_file_modified_since(struct http_descriptor *, + struct stat *); +int server_file_method(struct client *); +int parse_range_spec(char *, size_t, struct range *); +struct range *parse_range(char *, size_t, int *); +int buffer_add_range(int, struct evbuffer *, struct range *); int server_file_access(struct httpd *env, struct client *clt, @@ -50,6 +65,7 @@ server_file_access(struct httpd *env, struct client *clt, struct http_descriptor *desc = clt->clt_descreq; struct server_config *srv_conf = clt->clt_srv_conf; struct stat st; + struct kv *r, key; char *newpath; int ret; @@ -123,7 +139,13 @@ server_file_access(struct httpd *env, struct client *clt, goto fail; } - return (server_file_request(env, clt, path, &st)); + key.kv_key = "Range"; + r = kv_find(&desc->http_headers, &key); + if (r != NULL) + return (server_partial_file_request(env, clt, path, &st, + r->kv_value)); + else + return (server_file_request(env, clt, path, &st)); fail: switch (errno) { @@ -209,6 +231,9 @@ server_file_request(struct httpd *env, struct client *clt, char *path, goto abort; } + if ((ret = server_file_modified_since(clt->clt_descreq, st)) != -1) + return ret; + /* Now open the file, should be readable or we have another problem */ if ((fd = open(path, O_RDONLY)) == -1) goto abort; @@ -261,6 +286,143 @@ server_file_request(struct httpd *env, struct client *clt, char *path, return (-1); } +int +server_partial_file_request(struct httpd *env, struct client *clt, char *path, + struct stat *st, char *range_str) +{ + struct http_descriptor *resp = clt->clt_descresp; + struct http_descriptor *desc = clt->clt_descreq; + struct media_type *media, multipart_media; + struct range *range; + struct evbuffer *evb = NULL; + size_t content_length; + int code = 500, fd = -1, i, nranges, ret; + uint32_t boundary; + char content_range[64]; + const char *errstr = NULL; + + /* Ignore range request for methods other than GET */ + if (desc->http_method != HTTP_METHOD_GET) + return server_file_request(env, clt, path, st); + + if ((range = parse_range(range_str, st->st_size, &nranges)) == NULL) { + code = 416; + (void)snprintf(content_range, sizeof(content_range), + "bytes */%lld", st->st_size); + errstr = content_range; + goto abort; + } + + /* Now open the file, should be readable or we have another problem */ + if ((fd = open(path, O_RDONLY)) == -1) + goto abort; + + media = media_find(env->sc_mediatypes, path); + if ((evb = evbuffer_new()) == NULL) { + errstr = "failed to allocate file buffer"; + goto abort; + } + + if (nranges == 1) { + (void)snprintf(content_range, sizeof(content_range), + "bytes %lld-%lld/%lld", range->start, range->end, + st->st_size); + if (kv_add(&resp->http_headers, "Content-Range", + content_range) == NULL) + goto abort; + + content_length = range->end - range->start + 1; + if (buffer_add_range(fd, evb, range) == 0) + goto abort; + + } else { + content_length = 0; + boundary = arc4random(); + /* Generate a multipart payload of byteranges */ + while (nranges--) { + if ((i = evbuffer_add_printf(evb, "\r\n--%ud\r\n", + boundary)) == -1) + goto abort; + + content_length += i; + if ((i = evbuffer_add_printf(evb, + "Content-Type: %s/%s\r\n", + media == NULL ? "application" : media->media_type, + media == NULL ? + "octet-stream" : media->media_subtype)) == -1) + goto abort; + + content_length += i; + if ((i = evbuffer_add_printf(evb, + "Content-Range: bytes %lld-%lld/%lld\r\n\r\n", + range->start, range->end, st->st_size)) == -1) + goto abort; + + content_length += i; + if (buffer_add_range(fd, evb, range) == 0) + goto abort; + + content_length += range->end - range->start + 1; + range++; + } + + if ((i = evbuffer_add_printf(evb, "\r\n--%ud--\r\n", + boundary)) == -1) + goto abort; + + content_length += i; + + /* prepare multipart/byteranges media type */ + (void)strlcpy(multipart_media.media_type, "multipart", + sizeof(multipart_media.media_type)); + (void)snprintf(multipart_media.media_subtype, + sizeof(multipart_media.media_subtype), + "byteranges; boundary=%ud", boundary); + media = &multipart_media; + } + + ret = server_response_http(clt, 206, media, content_length, + MINIMUM(time(NULL), st->st_mtim.tv_sec)); + switch (ret) { + case -1: + goto fail; + case 0: + /* Connection is already finished */ + close(fd); + evbuffer_free(evb); + evb = NULL; + goto done; + default: + break; + } + + if (server_bufferevent_write_buffer(clt, evb) == -1) + goto fail; + + evbuffer_free(evb); + evb = NULL; + + bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE); + if (clt->clt_persist) + clt->clt_toread = TOREAD_HTTP_HEADER; + else + clt->clt_toread = TOREAD_HTTP_NONE; + clt->clt_done = 0; + + done: + server_reset_http(clt); + return (0); + fail: + bufferevent_disable(clt->clt_bev, EV_READ|EV_WRITE); + bufferevent_free(clt->clt_bev); + clt->clt_bev = NULL; + abort: + if (errstr == NULL) + errstr = strerror(errno); + server_abort_http(clt, code, errstr); + return (-1); +} + int server_file_index(struct httpd *env, struct client *clt, struct stat *st) { @@ -352,13 +514,15 @@ server_file_index(struct httpd *env, struct client *clt, struct stat *st) } else if (S_ISDIR(st->st_mode)) { namewidth -= 1; /* trailing slash */ if (evbuffer_add_printf(evb, - "%s/%*s%s%20s\n", + "%s/%*s%s%20s\n", + strchr(escapeduri, ':') != NULL ? "./" : "", escapeduri, escapedhtml, MAXIMUM(namewidth, 0), " ", tmstr, "-") == -1) skip = 1; } else if (S_ISREG(st->st_mode)) { if (evbuffer_add_printf(evb, - "%s%*s%s%20llu\n", + "%s%*s%s%20llu\n", + strchr(escapeduri, ':') != NULL ? "./" : "", escapeduri, escapedhtml, MAXIMUM(namewidth, 0), " ", tmstr, st->st_size) == -1) @@ -467,3 +631,131 @@ server_file_error(struct bufferevent *bev, short error, void *arg) server_close(clt, "unknown event error"); return; } + +int +server_file_modified_since(struct http_descriptor * desc, struct stat * st) +{ + struct kv key, *since; + struct tm tm; + + memset(&tm, 0, sizeof(struct tm)); + + key.kv_key = "If-Modified-Since"; + if ((since = kv_find(&desc->http_headers, &key)) != NULL && + since->kv_value != NULL) { + if (strptime(since->kv_value, "%a, %d %h %Y %T %Z", &tm) != + NULL && timegm(&tm) >= st->st_mtim.tv_sec) { + return (304); + } + } + + return (-1); +} + +struct range * +parse_range(char *str, size_t file_sz, int *nranges) +{ + static struct range ranges[MAX_RANGES]; + int i = 0; + char *p, *q; + + /* Extract range unit */ + if ((p = strchr(str, '=')) == NULL) + return (NULL); + + *p++ = '\0'; + /* Check if it's a bytes range spec */ + if (strcmp(str, "bytes") != 0) + return (NULL); + + while ((q = strchr(p, ',')) != NULL) { + *q++ = '\0'; + + /* Extract start and end positions */ + if (parse_range_spec(p, file_sz, &ranges[i]) == 0) + continue; + + i++; + if (i == MAX_RANGES) + return (NULL); + + p = q; + } + + if (parse_range_spec(p, file_sz, &ranges[i]) != 0) + i++; + + *nranges = i; + return (i ? ranges : NULL); +} + +int +parse_range_spec(char *str, size_t size, struct range *r) +{ + size_t start_str_len, end_str_len; + char *p, *start_str, *end_str; + const char *errstr; + + if ((p = strchr(str, '-')) == NULL) + return (0); + + *p++ = '\0'; + start_str = str; + end_str = p; + start_str_len = strlen(start_str); + end_str_len = strlen(end_str); + + /* Either 'start' or 'end' is optional but not both */ + if ((start_str_len == 0) && (end_str_len == 0)) + return (0); + + if (end_str_len) { + r->end = strtonum(end_str, 0, LLONG_MAX, &errstr); + if (errstr) + return (0); + + if ((size_t)r->end >= size) + r->end = size - 1; + } else + r->end = size - 1; + + if (start_str_len) { + r->start = strtonum(start_str, 0, LLONG_MAX, &errstr); + if (errstr) + return (0); + + if ((size_t)r->start >= size) + return (0); + } else { + r->start = size - r->end; + r->end = size - 1; + } + + if (r->end < r->start) + return (0); + + return (1); +} + +int +buffer_add_range(int fd, struct evbuffer *evb, struct range *range) +{ + char buf[BUFSIZ]; + size_t n, range_sz; + ssize_t nread; + + if (lseek(fd, range->start, SEEK_SET) == -1) + return (0); + + range_sz = range->end - range->start + 1; + while (range_sz) { + n = MINIMUM(range_sz, sizeof(buf)); + if ((nread = read(fd, buf, n)) == -1) + return (0); + + evbuffer_add(evb, buf, nread); + range_sz -= nread; + } + + return (1); +} diff --git a/httpd/server_http.c b/httpd/server_http.c index b63fc22..7b65a5e 100644 --- a/httpd/server_http.c +++ b/httpd/server_http.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server_http.c,v 1.75 2015/02/23 18:43:18 reyk Exp $ */ +/* $OpenBSD: server_http.c,v 1.79 2015/05/03 18:39:58 florian Exp $ */ /* * Copyright (c) 2006 - 2015 Reyk Floeter @@ -225,7 +225,8 @@ server_read_http(struct bufferevent *bev, void *arg) goto done; } - while (!clt->clt_done && (line = evbuffer_readline(src)) != NULL) { + while (!clt->clt_done && (line = + evbuffer_readln(src, NULL, EVBUFFER_EOL_CRLF_STRICT)) != NULL) { linelen = strlen(line); /* @@ -536,7 +537,7 @@ server_read_httpchunks(struct bufferevent *bev, void *arg) } switch (clt->clt_toread) { case TOREAD_HTTP_CHUNK_LENGTH: - line = evbuffer_readline(src); + line = evbuffer_readln(src, NULL, EVBUFFER_EOL_CRLF_STRICT); if (line == NULL) { /* Ignore empty line, continue */ bufferevent_enable(bev, EV_READ); @@ -571,7 +572,7 @@ server_read_httpchunks(struct bufferevent *bev, void *arg) break; case TOREAD_HTTP_CHUNK_TRAILER: /* Last chunk is 0 bytes followed by trailer and empty line */ - line = evbuffer_readline(src); + line = evbuffer_readln(src, NULL, EVBUFFER_EOL_CRLF_STRICT); if (line == NULL) { /* Ignore empty line, continue */ bufferevent_enable(bev, EV_READ); @@ -591,7 +592,7 @@ server_read_httpchunks(struct bufferevent *bev, void *arg) break; case 0: /* Chunk is terminated by an empty newline */ - line = evbuffer_readline(src); + line = evbuffer_readln(src, NULL, EVBUFFER_EOL_CRLF_STRICT); if (line != NULL) free(line); if (server_bufferevent_print(clt, "\r\n") == -1) @@ -787,6 +788,13 @@ server_abort_http(struct client *clt, u_int code, const char *msg) extraheader = NULL; } break; + case 416: + if (asprintf(&extraheader, + "Content-Range: %s\r\n", msg) == -1) { + code = 500; + extraheader = NULL; + } + break; default: /* * Do not send details of the error. Traditionally, -- cgit v1.2.3-54-g00ecf