int server_file(struct httpd *env, struct client *clt) { struct http_descriptor *desc = clt->clt_descreq; struct server_config *srv_conf = clt->clt_srv_conf; char path[PATH_MAX]; const char *stripped, *errstr = NULL; int ret = 500; if (srv_conf->flags & SRVFLAG_FCGI) return (server_fcgi(env, clt)); /* Request path is already canonicalized */ stripped = server_root_strip( desc->http_path_alias != NULL ? desc->http_path_alias : desc->http_path, srv_conf->strip); if ((size_t)snprintf(path, sizeof(path), "%s%s", srv_conf->root, stripped) >= sizeof(path)) { errstr = desc->http_path; goto abort; } /* Returns HTTP status code on error */ if ((ret = server_file_access(env, clt, path, sizeof(path))) > 0) { errstr = desc->http_path_alias != NULL ? desc->http_path_alias : desc->http_path; goto abort; } return (ret); abort: if (errstr == NULL) errstr = strerror(errno); server_abort_http(clt, ret, errstr); return (-1); }
int server_fcgi(struct httpd *env, struct client *clt) { struct server_fcgi_param param; struct server_config *srv_conf = clt->clt_srv_conf; struct http_descriptor *desc = clt->clt_descreq; struct fcgi_record_header *h; struct fcgi_begin_request_body *begin; char hbuf[HOST_NAME_MAX+1]; size_t scriptlen; int pathlen; int fd = -1, ret; const char *stripped, *p, *alias, *errstr = NULL; char *str, *script = NULL; if (srv_conf->socket[0] == ':') { struct sockaddr_storage ss; in_port_t port; p = srv_conf->socket + 1; port = strtonum(p, 0, 0xffff, &errstr); if (errstr != NULL) { log_warn("%s: strtonum %s, %s", __func__, p, errstr); goto fail; } memset(&ss, 0, sizeof(ss)); ss.ss_family = AF_INET; ((struct sockaddr_in *) &ss)->sin_addr.s_addr = htonl(INADDR_LOOPBACK); port = htons(port); if ((fd = server_socket_connect(&ss, port, srv_conf)) == -1) goto fail; } else { struct sockaddr_un sun; size_t len; if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) goto fail; memset(&sun, 0, sizeof(sun)); sun.sun_family = AF_UNIX; len = strlcpy(sun.sun_path, srv_conf->socket, sizeof(sun.sun_path)); if (len >= sizeof(sun.sun_path)) { errstr = "socket path to long"; goto fail; } sun.sun_len = len; if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) goto fail; } socket_set_blockmode(fd, BM_NONBLOCK); memset(hbuf, 0, sizeof(hbuf)); clt->clt_fcgi_state = FCGI_READ_HEADER; clt->clt_fcgi_toread = sizeof(struct fcgi_record_header); if (clt->clt_srvevb != NULL) evbuffer_free(clt->clt_srvevb); clt->clt_srvevb = evbuffer_new(); if (clt->clt_srvevb == NULL) { errstr = "failed to allocate evbuffer"; goto fail; } close(clt->clt_fd); clt->clt_fd = fd; if (clt->clt_srvbev != NULL) bufferevent_free(clt->clt_srvbev); clt->clt_srvbev_throttled = 0; clt->clt_srvbev = bufferevent_new(fd, server_fcgi_read, NULL, server_file_error, clt); if (clt->clt_srvbev == NULL) { errstr = "failed to allocate fcgi buffer event"; goto fail; } memset(¶m, 0, sizeof(param)); h = (struct fcgi_record_header *)¶m.buf; h->version = 1; h->type = FCGI_BEGIN_REQUEST; h->id = htons(1); h->content_len = htons(sizeof(struct fcgi_begin_request_body)); h->padding_len = 0; begin = (struct fcgi_begin_request_body *)¶m.buf[sizeof(struct fcgi_record_header)]; begin->role = htons(FCGI_RESPONDER); if (bufferevent_write(clt->clt_srvbev, ¶m.buf, sizeof(struct fcgi_record_header) + sizeof(struct fcgi_begin_request_body)) == -1) { errstr = "failed to write to evbuffer"; goto fail; } h->type = FCGI_PARAMS; h->content_len = param.total_len = 0; alias = desc->http_path_alias != NULL ? desc->http_path_alias : desc->http_path; stripped = server_root_strip(alias, srv_conf->strip); if ((pathlen = asprintf(&script, "%s%s", srv_conf->root, stripped)) == -1) { errstr = "failed to get script name"; goto fail; } scriptlen = path_info(script); /* * no part of root should show up in PATH_INFO. * therefore scriptlen should be >= strlen(root) */ if (scriptlen < strlen(srv_conf->root)) scriptlen = strlen(srv_conf->root); if ((int)scriptlen < pathlen) { if (fcgi_add_param(¶m, "PATH_INFO", script + scriptlen, clt) == -1) { errstr = "failed to encode param"; goto fail; } script[scriptlen] = '\0'; } else { /* RFC 3875 mandates that PATH_INFO is empty if not set */ if (fcgi_add_param(¶m, "PATH_INFO", "", clt) == -1) { errstr = "failed to encode param"; goto fail; } } /* * calculate length of http SCRIPT_NAME: * add length of stripped prefix, * subtract length of prepended local root */ scriptlen += (stripped - alias) - strlen(srv_conf->root); if ((str = strndup(alias, scriptlen)) == NULL) goto fail; ret = fcgi_add_param(¶m, "SCRIPT_NAME", str, clt); free(str); if (ret == -1) { errstr = "failed to encode param"; goto fail; } if (fcgi_add_param(¶m, "SCRIPT_FILENAME", script, clt) == -1) { errstr = "failed to encode param"; goto fail; } if (desc->http_query) if (fcgi_add_param(¶m, "QUERY_STRING", desc->http_query, clt) == -1) { errstr = "failed to encode param"; goto fail; } if (fcgi_add_param(¶m, "DOCUMENT_ROOT", srv_conf->root, clt) == -1) { errstr = "failed to encode param"; goto fail; } if (fcgi_add_param(¶m, "DOCUMENT_URI", alias, clt) == -1) { errstr = "failed to encode param"; goto fail; } if (fcgi_add_param(¶m, "GATEWAY_INTERFACE", "CGI/1.1", clt) == -1) { errstr = "failed to encode param"; goto fail; } if (srv_conf->flags & SRVFLAG_AUTH) { if (fcgi_add_param(¶m, "REMOTE_USER", clt->clt_remote_user, clt) == -1) { errstr = "failed to encode param"; goto fail; } } /* Add HTTP_* headers */ if (server_headers(clt, desc, server_fcgi_writeheader, ¶m) == -1) { errstr = "failed to encode param"; goto fail; } if (srv_conf->flags & SRVFLAG_TLS) if (fcgi_add_param(¶m, "HTTPS", "on", clt) == -1) { errstr = "failed to encode param"; goto fail; } (void)print_host(&clt->clt_ss, hbuf, sizeof(hbuf)); if (fcgi_add_param(¶m, "REMOTE_ADDR", hbuf, clt) == -1) { errstr = "failed to encode param"; goto fail; } (void)snprintf(hbuf, sizeof(hbuf), "%d", ntohs(clt->clt_port)); if (fcgi_add_param(¶m, "REMOTE_PORT", hbuf, clt) == -1) { errstr = "failed to encode param"; goto fail; } if (fcgi_add_param(¶m, "REQUEST_METHOD", server_httpmethod_byid(desc->http_method), clt) == -1) { errstr = "failed to encode param"; goto fail; } if (!desc->http_query) { if (fcgi_add_param(¶m, "REQUEST_URI", desc->http_path, clt) == -1) { errstr = "failed to encode param"; goto fail; } } else { if (asprintf(&str, "%s?%s", desc->http_path, desc->http_query) == -1) { errstr = "failed to encode param"; goto fail; } ret = fcgi_add_param(¶m, "REQUEST_URI", str, clt); free(str); if (ret == -1) { errstr = "failed to encode param"; goto fail; } } (void)print_host(&clt->clt_srv_ss, hbuf, sizeof(hbuf)); if (fcgi_add_param(¶m, "SERVER_ADDR", hbuf, clt) == -1) { errstr = "failed to encode param"; goto fail; } (void)snprintf(hbuf, sizeof(hbuf), "%d", ntohs(server_socket_getport(&clt->clt_srv_ss))); if (fcgi_add_param(¶m, "SERVER_PORT", hbuf, clt) == -1) { errstr = "failed to encode param"; goto fail; } if (fcgi_add_param(¶m, "SERVER_NAME", srv_conf->name, clt) == -1) { errstr = "failed to encode param"; goto fail; } if (fcgi_add_param(¶m, "SERVER_PROTOCOL", desc->http_version, clt) == -1) { errstr = "failed to encode param"; goto fail; } if (fcgi_add_param(¶m, "SERVER_SOFTWARE", HTTPD_SERVERNAME, clt) == -1) { errstr = "failed to encode param"; goto fail; } if (param.total_len != 0) { /* send last params record */ if (bufferevent_write(clt->clt_srvbev, ¶m.buf, sizeof(struct fcgi_record_header) + ntohs(h->content_len)) == -1) { errstr = "failed to write to client evbuffer"; goto fail; } } /* send "no more params" message */ h->content_len = 0; if (bufferevent_write(clt->clt_srvbev, ¶m.buf, sizeof(struct fcgi_record_header)) == -1) { errstr = "failed to write to client evbuffer"; goto fail; } bufferevent_settimeout(clt->clt_srvbev, srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec); bufferevent_enable(clt->clt_srvbev, EV_READ|EV_WRITE); if (clt->clt_toread != 0) { server_read_httpcontent(clt->clt_bev, clt); bufferevent_enable(clt->clt_bev, EV_READ); } else { bufferevent_disable(clt->clt_bev, EV_READ); fcgi_add_stdin(clt, NULL); } if (strcmp(desc->http_version, "HTTP/1.1") == 0) { clt->clt_fcgi_chunked = 1; } else { /* HTTP/1.0 does not support chunked encoding */ clt->clt_fcgi_chunked = 0; clt->clt_persist = 0; } clt->clt_fcgi_end = 0; clt->clt_done = 0; free(script); return (0); fail: free(script); if (errstr == NULL) errstr = strerror(errno); server_abort_http(clt, 500, errstr); return (-1); }
int server_file_index(struct httpd *env, struct client *clt, struct stat *st) { char path[PATH_MAX]; char tmstr[21]; struct http_descriptor *desc = clt->clt_descreq; struct server_config *srv_conf = clt->clt_srv_conf; struct dirent **namelist, *dp; int namesize, i, ret, fd = -1, namewidth, skip; int code = 500; struct evbuffer *evb = NULL; struct media_type *media; const char *stripped, *style; char *escapeduri, *escapedhtml, *escapedpath; struct tm tm; time_t t, dir_mtime; if ((ret = server_file_method(clt)) != 0) { code = ret; goto abort; } /* Request path is already canonicalized */ stripped = server_root_strip(desc->http_path, srv_conf->strip); if ((size_t)snprintf(path, sizeof(path), "%s%s", srv_conf->root, stripped) >= sizeof(path)) goto abort; /* Now open the file, should be readable or we have another problem */ if ((fd = open(path, O_RDONLY)) == -1) goto abort; /* Save last modification time */ dir_mtime = MINIMUM(time(NULL), st->st_mtim.tv_sec); if ((evb = evbuffer_new()) == NULL) goto abort; if ((namesize = scandir(path, &namelist, NULL, alphasort)) == -1) goto abort; /* Indicate failure but continue going through the list */ skip = 0; if ((escapedpath = escape_html(desc->http_path)) == NULL) goto fail; /* A CSS stylesheet allows minimal customization by the user */ style = "body { background-color: white; color: black; font-family: " "sans-serif; }\nhr { border: 0; border-bottom: 1px dashed; }\n"; /* Generate simple HTML index document */ if (evbuffer_add_printf(evb, "<!DOCTYPE html>\n" "<html>\n" "<head>\n" "<title>Index of %s</title>\n" "<style type=\"text/css\"><!--\n%s\n--></style>\n" "</head>\n" "<body>\n" "<h1>Index of %s</h1>\n" "<hr>\n<pre>\n", escapedpath, style, escapedpath) == -1) skip = 1; free(escapedpath); for (i = 0; i < namesize; i++) { dp = namelist[i]; if (skip || fstatat(fd, dp->d_name, st, 0) == -1) { free(dp); continue; } t = st->st_mtime; localtime_r(&t, &tm); strftime(tmstr, sizeof(tmstr), "%d-%h-%Y %R", &tm); namewidth = 51 - strlen(dp->d_name); if ((escapeduri = url_encode(dp->d_name)) == NULL) goto fail; if ((escapedhtml = escape_html(dp->d_name)) == NULL) goto fail; if (dp->d_name[0] == '.' && !(dp->d_name[1] == '.' && dp->d_name[2] == '\0')) { /* ignore hidden files starting with a dot */ } else if (S_ISDIR(st->st_mode)) { namewidth -= 1; /* trailing slash */ if (evbuffer_add_printf(evb, "<a href=\"%s%s/\">%s/</a>%*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, "<a href=\"%s%s\">%s</a>%*s%s%20llu\n", strchr(escapeduri, ':') != NULL ? "./" : "", escapeduri, escapedhtml, MAXIMUM(namewidth, 0), " ", tmstr, st->st_size) == -1) skip = 1; } free(escapeduri); free(escapedhtml); free(dp); } free(namelist); if (skip || evbuffer_add_printf(evb, "</pre>\n<hr>\n</body>\n</html>\n") == -1) goto abort; close(fd); fd = -1; media = media_find(env->sc_mediatypes, "index.html"); ret = server_response_http(clt, 200, media, EVBUFFER_LENGTH(evb), dir_mtime); switch (ret) { case -1: goto fail; case 0: /* Connection is already finished */ evbuffer_free(evb); 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 (fd != -1) close(fd); if (evb != NULL) evbuffer_free(evb); server_abort_http(clt, code, desc->http_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_request(struct httpd *env, struct client *clt, char *path, struct stat *st) { struct server_config *srv_conf = clt->clt_srv_conf; struct media_type *media; const char *errstr = NULL; int fd = -1, ret, code = 500; if ((ret = server_file_method(clt)) != 0) { code = ret; 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; media = media_find(env->sc_mediatypes, path); ret = server_response_http(clt, 200, media, st->st_size, MINIMUM(time(NULL), st->st_mtim.tv_sec)); switch (ret) { case -1: goto fail; case 0: /* Connection is already finished */ close(fd); goto done; default: break; } clt->clt_fd = fd; if (clt->clt_srvbev != NULL) bufferevent_free(clt->clt_srvbev); clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read, server_write, server_file_error, clt); if (clt->clt_srvbev == NULL) { errstr = "failed to allocate file buffer event"; goto fail; } /* Adjust read watermark to the socket output buffer size */ bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0, clt->clt_sndbufsiz); bufferevent_settimeout(clt->clt_srvbev, srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec); bufferevent_enable(clt->clt_srvbev, EV_READ); bufferevent_disable(clt->clt_bev, EV_READ); 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_partial_file_request(struct httpd *env, struct client *clt, char *path, struct stat *st, char *range_str) { struct server_config *srv_conf = clt->clt_srv_conf; struct http_descriptor *resp = clt->clt_descresp; struct http_descriptor *desc = clt->clt_descreq; struct media_type *media, multipart_media; struct range_data *r = &clt->clt_ranges; struct range *range; size_t content_length = 0; int code = 500, fd = -1, i, nranges, ret; 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 ((nranges = parse_ranges(clt, range_str, st->st_size)) < 1) { 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_config(env, srv_conf, path); r->range_media = media; if (nranges == 1) { range = &r->range[0]; (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; range = &r->range[0]; content_length += range->end - range->start + 1; } else { /* Add boundary, all parts will be handled by the callback */ arc4random_buf(&clt->clt_boundary, sizeof(clt->clt_boundary)); /* Calculate Content-Length of the complete multipart body */ for (i = 0; i < nranges; i++) { range = &r->range[i]; /* calculate Content-Length of the complete body */ if ((ret = snprintf(NULL, 0, "\r\n--%llu\r\n" "Content-Type: %s/%s\r\n" "Content-Range: bytes %lld-%lld/%lld\r\n\r\n", clt->clt_boundary, media->media_type, media->media_subtype, range->start, range->end, st->st_size)) < 0) goto abort; /* Add data length */ content_length += ret + range->end - range->start + 1; } if ((ret = snprintf(NULL, 0, "\r\n--%llu--\r\n", clt->clt_boundary)) < 0) goto abort; content_length += ret; /* 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=%llu", clt->clt_boundary); media = &multipart_media; } /* Start with first range */ r->range_toread = TOREAD_HTTP_RANGE; 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); goto done; default: break; } clt->clt_fd = fd; if (clt->clt_srvbev != NULL) bufferevent_free(clt->clt_srvbev); clt->clt_srvbev_throttled = 0; clt->clt_srvbev = bufferevent_new(clt->clt_fd, server_read_httprange, server_write, server_file_error, clt); if (clt->clt_srvbev == NULL) { errstr = "failed to allocate file buffer event"; goto fail; } /* Adjust read watermark to the socket output buffer size */ bufferevent_setwatermark(clt->clt_srvbev, EV_READ, 0, clt->clt_sndbufsiz); bufferevent_settimeout(clt->clt_srvbev, srv_conf->timeout.tv_sec, srv_conf->timeout.tv_sec); bufferevent_enable(clt->clt_srvbev, EV_READ); bufferevent_disable(clt->clt_bev, EV_READ); 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 (fd != -1) close(fd); if (errstr == NULL) errstr = strerror(errno); server_abort_http(clt, code, errstr); return (-1); }