commit 252908e6bb335c42249a3d5fe6ecaa4daf5a3e3e from: Omar Polo date: Sun Jan 24 18:53:26 2021 UTC added support for location blocks commit - c8b74339185123feebb6164b91f500f1930e45ff commit + 252908e6bb335c42249a3d5fe6ecaa4daf5a3e3e blob - b7f05157b697387c34a2aa1d1ea441f66636fa41 blob + 1626a63f07560366fb17f7f63161ce309d4aeaf2 --- ChangeLog +++ ChangeLog @@ -1,5 +1,7 @@ 2021-01-24 Omar Polo + * server.c (open_dir): add directory listing (disabled by default) + * parse.y (vhost): added support for location blocks * server.c (send_dir): make the directory index customizable blob - 70ece216eb65b5deaaf34bce45113e2314463af6 blob + ad0aeba97f7232a1d19b9386ae486e802cb90884 --- gmid.1 +++ gmid.1 @@ -181,6 +181,9 @@ parameter will be added in the response. Set the directory index file. If not specified, it defaults to .Pa index.gmi +.It Ic auto Ic index Ar bool +If no index file is found, automatically generate a directory listing. +It's disabled by default. .It Ic location Pa path Brq ... Specify server configuration rules for a specific location. The blob - 5bccefcb33564b967270e493acfb8d9daa978382 blob + 3e1ed338c0fcbfc4f686ebd27bc3d48414985053 --- gmid.c +++ gmid.c @@ -156,6 +156,9 @@ starts_with(const char *str, const char *prefix) { size_t i; + if (prefix == NULL) + return 0; + for (i = 0; prefix[i] != '\0'; ++i) if (str[i] != prefix[i]) return 0; blob - 139338e0f1048ebcbffaa6b2200d1cf1cc2f7647 blob + 2364e7c7bc0cbda5e0eeb965b11ca858c6c21db1 --- gmid.h +++ gmid.h @@ -17,10 +17,13 @@ #ifndef GMID_H #define GMID_H +#include +#include + #include #include -#include +#include #include #include #include @@ -62,6 +65,7 @@ struct location { char *lang; char *default_mime; char *index; + int auto_index; /* 0 auto, -1 off, 1 on */ }; struct vhost { @@ -119,6 +123,7 @@ enum { S_OPEN, S_INITIALIZING, S_SENDING_FILE, + S_SENDING_DIR, S_SENDING_CGI, S_CLOSING, }; @@ -134,6 +139,7 @@ struct client { char sbuf[1024]; /* static buffer */ void *buf, *i; /* mmap buffer */ ssize_t len, off; /* mmap/static buffer */ + DIR *dir; struct sockaddr_storage addr; struct vhost *host; /* host she's talking to */ }; @@ -185,8 +191,10 @@ const char *mime(struct vhost*, const char*); const char *vhost_lang(struct vhost*, const char*); const char *vhost_default_mime(struct vhost*, const char*); const char *vhost_index(struct vhost*, const char*); +int vhost_auto_index(struct vhost*, const char*); int check_path(struct client*, const char*, int*); void open_file(struct pollfd*, struct client*); +void load_file(struct pollfd*, struct client*); void check_for_cgi(char *, char*, struct pollfd*, struct client*); void mark_nonblock(int); void handle_handshake(struct pollfd*, struct client*); @@ -194,7 +202,10 @@ void handle_open_conn(struct pollfd*, struct client* void start_reply(struct pollfd*, struct client*, int, const char*); void start_cgi(const char*, const char*, const char*, struct pollfd*, struct client*); void send_file(struct pollfd*, struct client*); -void send_dir(struct pollfd*, struct client*); +void open_dir(struct pollfd*, struct client*); +void redirect_canonical_dir(struct pollfd*, struct client*); +int read_next_dir_entry(struct client*); +void send_directory_listing(struct pollfd*, struct client*); void cgi_poll_on_child(struct pollfd*, struct client*); void cgi_poll_on_client(struct pollfd*, struct client*); void handle_cgi(struct pollfd*, struct client*); blob - b785796f7bf9fb2390aa06179bed58b0cd242d52 blob + 240f7c466e7c1551d21d5664d4aaab6974aba9d3 --- lex.l +++ lex.l @@ -67,6 +67,7 @@ root return TROOT; cgi return TCGI; lang return TLANG; index return TINDEX; +auto return TAUTO; [{}] return *yytext; blob - e7883a9d1705124c010c75d6c852d3090d06c8a2 blob + 5e7cb216241e588d84ac594ed7b7a2ef4a4671cc --- parse.y +++ parse.y @@ -46,7 +46,7 @@ extern void yyerror(const char*); } %token TDAEMON TIPV6 TPORT TPROTOCOLS TMIME TDEFAULT TTYPE TSERVER -%token TLOCATION TCERT TKEY TROOT TCGI TLANG +%token TLOCATION TCERT TKEY TROOT TCGI TLANG TINDEX TAUTO %token TERR %token TSTRING @@ -138,4 +138,5 @@ locopt : TDEFAULT TTYPE TSTRING { free(loc->index); loc->index = $2; } + | TAUTO TINDEX TBOOL { loc->auto_index = $3 ? 1 : -1; } ; blob - 8cf2afb1ee11f9c4d717dd411e42e23e37d4c7fb blob + ca87701f35435c53dcc1f64a02f7f06b21ff1509 --- regress/runtime +++ regress/runtime @@ -199,9 +199,25 @@ run eq "$(head /dir/hello)" "20 text/plain" "Unexpected head for /" echo OK GET /dir/hello with location and default type -eq "$(head /dir/)" "20 text/plain" "Unexpected head for /dir" +eq "$(head /dir/)" "20 text/plain" "Unexpected head for /dir/" eq "$(get /dir/|tail -1)" 'echo "# hello world"' "Unexpected body for /dir/" echo OK GET /dir/ with location and custom index check "should be running" quit + +config '' 'location "/dir/" { auto index on }' +checkconf +run + +eq "$(head /)" "20 text/gemini" "Unexpected head for /" +eq "$(get /)" "# hello world$ln" "Unexpected body for /" +echo OK GET / with auto index + +eq "$(head /dir)" "30 /dir/" "Unexpected head for /dir" +eq "$(head /dir/)" "20 text/gemini" "Unexpected head for /dir/" +eq "$(get /dir/|wc -l|xargs)" "3" "Unexpected body for /dir/" +echo OK GET /dir/ with auto index on + +check "should be running" +quit blob - d9bf46fbb00a0a25bea51cbaf4e8403f1302fd9c blob + 490bd2c97b6aea7a9f2efc48f9236d0a8bf82dcb --- sample.conf +++ sample.conf @@ -19,9 +19,25 @@ server "it.example.com" { key "/path/to/key.pem" root "/var/gemini/example.com" - # enable CGI scripts in /cgi-bin/ + # enable CGI scripts in /cgi-bin/ cgi "/cgi-bin/" - # optional - lang "it" + # optional + lang "it" } + +# a server block with a location +server "foo.com" { + cert "..." + key "..." + root "..." + + location "/it/" { + lang "it" + } + + location "/files" { + lang "en" + auto index on + } +} blob - 48a7701556fc2f134906f2d8c9ec6f7c73ca42b2 blob + 3c6b6e73a05d1b1b260837946773df372ee609e3 --- server.c +++ server.c @@ -78,10 +78,27 @@ vhost_index(struct vhost *v, const char *path) } int +vhost_auto_index(struct vhost *v, const char *path) +{ + struct location *loc; + int auto_index = 0; + + for (loc = v->locations; loc->match != NULL; ++loc) { + if (!fnmatch(loc->match, path, 0)) { + if (loc->auto_index) + auto_index = loc->auto_index; + } + } + + return auto_index == 1; +} + +int check_path(struct client *c, const char *path, int *fd) { struct stat sb; const char *p; + int flags; assert(path != NULL); @@ -94,9 +111,10 @@ check_path(struct client *c, const char *path, int *fd else p = path; - if ((*fd = openat(c->host->dirfd, p, O_RDONLY | O_NOFOLLOW)) == -1) { + flags = O_RDONLY | O_NOFOLLOW; + + if (*fd == -1 && (*fd = openat(c->host->dirfd, p, flags)) == -1) return FILE_MISSING; - } if (fstat(*fd, &sb) == -1) { LOGN(c, "failed stat for %s: %s", path, strerror(errno)); @@ -117,7 +135,7 @@ open_file(struct pollfd *fds, struct client *c) { switch (check_path(c, c->iri.path, &c->fd)) { case FILE_EXECUTABLE: - if (c->host->cgi != NULL && starts_with(c->iri.path, c->host->cgi)) { + if (starts_with(c->iri.path, c->host->cgi)) { start_cgi(c->iri.path, "", c->iri.query, fds, c); return; } @@ -125,27 +143,11 @@ open_file(struct pollfd *fds, struct client *c) /* fallthrough */ case FILE_EXISTS: - if ((c->len = filesize(c->fd)) == -1) { - LOGE(c, "failed to get file size for %s", c->iri.path); - close_conn(fds, c); - return; - } - - if ((c->buf = mmap(NULL, c->len, PROT_READ, MAP_PRIVATE, - c->fd, 0)) == MAP_FAILED) { - LOGW(c, "mmap: %s: %s", c->iri.path, strerror(errno)); - close_conn(fds, c); - return; - } - c->i = c->buf; - c->next = S_SENDING_FILE; - start_reply(fds, c, SUCCESS, mime(c->host, c->iri.path)); + load_file(fds, c); return; case FILE_DIRECTORY: - close(c->fd); - c->fd = -1; - send_dir(fds, c); + open_dir(fds, c); return; case FILE_MISSING: @@ -162,7 +164,26 @@ open_file(struct pollfd *fds, struct client *c) } } +void +load_file(struct pollfd *fds, struct client *c) +{ + if ((c->len = filesize(c->fd)) == -1) { + LOGE(c, "failed to get file size for %s", c->iri.path); + start_reply(fds, c, TEMP_FAILURE, "internal server error"); + return; + } + if ((c->buf = mmap(NULL, c->len, PROT_READ, MAP_PRIVATE, + c->fd, 0)) == MAP_FAILED) { + LOGW(c, "mmap: %s: %s", c->iri.path, strerror(errno)); + start_reply(fds, c, TEMP_FAILURE, "internal server error"); + return; + } + c->i = c->buf; + c->next = S_SENDING_FILE; + start_reply(fds, c, SUCCESS, mime(c->host, c->iri.path)); +} + /* * the inverse of this algorithm, i.e. starting from the start of the * path + strlen(cgi), and checking if each component, should be @@ -434,51 +455,159 @@ send_file(struct pollfd *fds, struct client *c) } void -send_dir(struct pollfd *fds, struct client *c) +open_dir(struct pollfd *fds, struct client *c) { size_t len; - - /* guard against a re-entrant call: open_file -> send_dir -> - * open_file -> send_dir. This can happen only if: - * - * - user requested a dir, say foo/ - * - we try to serve foo/$INDEX - * - foo/$INDEX is a directory. - */ - if (c->iri.path == c->sbuf) { - start_reply(fds, c, TEMP_REDIRECT, c->sbuf); - return; - } + int dirfd; + char *before_file; - strlcpy(c->sbuf, "/", sizeof(c->sbuf)); - len = strlen(c->iri.path); - if (len > 0 && c->iri.path[len-1] != '/') { - /* redirect to url with the trailing / */ - strlcat(c->sbuf, c->iri.path, sizeof(c->sbuf)); - strlcat(c->sbuf, "/", sizeof(c->sbuf)); - start_reply(fds, c, TEMP_REDIRECT, c->sbuf); + if (len > 0 && !ends_with(c->iri.path, "/")) { + redirect_canonical_dir(fds, c); return; } + strlcpy(c->sbuf, "/", sizeof(c->sbuf)); strlcat(c->sbuf, c->iri.path, sizeof(c->sbuf)); - if (!ends_with(c->sbuf, "/")) strlcat(c->sbuf, "/", sizeof(c->sbuf)); - + before_file = strchr(c->sbuf, '\0'); len = strlcat(c->sbuf, vhost_index(c->host, c->iri.path), sizeof(c->sbuf)); + if (len >= sizeof(c->sbuf)) { + start_reply(fds, c, TEMP_FAILURE, "internal server error"); + return; + } + c->iri.path = c->sbuf; + + /* close later unless we have to generate the dir listing */ + dirfd = c->fd; + c->fd = -1; + + switch (check_path(c, c->iri.path, &c->fd)) { + case FILE_EXECUTABLE: + if (starts_with(c->iri.path, c->host->cgi)) { + start_cgi(c->iri.path, "", c->iri.query, fds, c); + break; + } + + /* fallthrough */ + + case FILE_EXISTS: + load_file(fds, c); + break; + + case FILE_DIRECTORY: + start_reply(fds, c, TEMP_REDIRECT, c->sbuf); + break; + + case FILE_MISSING: + *before_file = '\0'; + + if (!vhost_auto_index(c->host, c->iri.path)) { + start_reply(fds, c, NOT_FOUND, "not found"); + break; + } + + c->fd = dirfd; + c->next = S_SENDING_DIR; + + if ((c->dir = fdopendir(c->fd)) == NULL) { + LOGE(c, "can't fdopendir(%d) (vhost:%s) %s: %s", + c->fd, c->host->domain, c->iri.path, strerror(errno)); + start_reply(fds, c, TEMP_FAILURE, "internal server error"); + return; + } + c->off = 0; + + start_reply(fds, c, SUCCESS, "text/gemini"); + return; + + default: + /* unreachable */ + abort(); + } + + close(dirfd); +} + +void +redirect_canonical_dir(struct pollfd *fds, struct client *c) +{ + size_t len; + + strlcpy(c->sbuf, "/", sizeof(c->sbuf)); + strlcat(c->sbuf, c->iri.path, sizeof(c->sbuf)); + len = strlcat(c->sbuf, "/", sizeof(c->sbuf)); + if (len >= sizeof(c->sbuf)) { start_reply(fds, c, TEMP_FAILURE, "internal server error"); return; } - close(c->fd); - c->iri.path = c->sbuf; - open_file(fds, c); + start_reply(fds, c, TEMP_REDIRECT, c->sbuf); } +int +read_next_dir_entry(struct client *c) +{ + struct dirent *d; + + do { + errno = 0; + if ((d = readdir(c->dir)) == NULL) { + if (errno != 0) + LOGE(c, "readdir: %s", strerror(errno)); + return 0; + } + } while (!strcmp(d->d_name, ".")); + + /* XXX: url escape */ + snprintf(c->sbuf, sizeof(c->sbuf), "=> %s %s\n", + d->d_name, d->d_name); + c->len = strlen(c->sbuf); + c->off = 0; + + return 1; +} + +void +send_directory_listing(struct pollfd *fds, struct client *c) +{ + ssize_t r; + + while (1) { + if (c->len == 0) { + if (!read_next_dir_entry(c)) + goto end; + } + + while (c->len > 0) { + switch (r = tls_write(c->ctx, c->sbuf + c->off, c->len)) { + case -1: + goto end; + + case TLS_WANT_POLLOUT: + fds->events = POLLOUT; + return; + + case TLS_WANT_POLLIN: + fds->events = POLLIN; + return; + + default: + c->off += r; + c->len -= r; + break; + } + } + } + +end: + close_conn(fds, c); +} + void cgi_poll_on_child(struct pollfd *fds, struct client *c) { @@ -624,6 +753,9 @@ close_conn(struct pollfd *pfd, struct client *c) if (c->fd != -1) close(c->fd); + if (c->dir != NULL) + closedir(c->dir); + close(pfd->fd); pfd->fd = -1; } @@ -658,6 +790,7 @@ do_accept(int sock, struct tls *ctx, struct pollfd *fd clients[i].fd = -1; clients[i].waiting_on_child = 0; clients[i].buf = MAP_FAILED; + clients[i].dir = NULL; clients[i].addr = addr; connected_clients++; @@ -688,6 +821,10 @@ handle(struct pollfd *fds, struct client *client) send_file(fds, client); break; + case S_SENDING_DIR: + send_directory_listing(fds, client); + break; + case S_SENDING_CGI: handle_cgi(fds, client); break;