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); }