commit - 5f564d23e9ea96fd6565ee4099fba6c31b71567e
commit + d3a08f4d172687d4e3e60e7faaa8830bb7f9f9db
blob - 4d6ae12143b2610a5d740e9e462e7c21dd25279f
blob + 10a141fc7c4652916ed4bbf9b46a753283a39bcd
--- Makefile
+++ Makefile
y.tab.c: parse.y
${YACC} -b y -d parse.y
-OBJS = gmid.o iri.o utf8.o lex.yy.o y.tab.o ex.o cgi.o sandbox.o
+OBJS = gmid.o iri.o utf8.o lex.yy.o y.tab.o ex.o server.o sandbox.o
gmid: ${OBJS}
${CC} ${OBJS} -o gmid ${LDFLAGS}
-o gmid
strip gmid
-TAGS: gmid.c iri.c utf8.c ex.c cgi.c sandbox.c
- -etags gmid.c iri.c utf8.c ex.c cgi.c sandbox.c || true
+TAGS: gmid.c iri.c utf8.c ex.c server.c sandbox.c
+ -etags gmid.c iri.c utf8.c ex.c server.c sandbox.c || true
clean:
rm -f *.o lex.yy.c y.tab.c y.tab.h y.output gmid iri_test
blob - bb1254b240dfceced27ad814dcf9570fda4af6c9
blob + dad0d299000bee04694d47e700b9e2b413f16c4c
--- gmid.c
+++ gmid.c
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include <sys/mman.h>
-#include <sys/stat.h>
-
-#include <assert.h>
#include <err.h>
#include <errno.h>
+
#include <fcntl.h>
#include <limits.h>
#include <netdb.h>
struct vhost hosts[HOSTSLEN];
-int connected_clients;
int goterror;
int exfd;
{NULL, NULL}
};
-__attribute__ ((format (printf, 1, 2)))
-__attribute__ ((__noreturn__))
-static inline void
+void
fatal(const char *fmt, ...)
{
va_list ap;
return 1;
}
-int
-start_reply(struct pollfd *pfd, struct client *client, int code, const char *reason)
-{
- char buf[1030]; /* status + ' ' + max reply len + \r\n\0 */
- int len;
-
- client->code = code;
- client->meta = reason;
- client->state = S_INITIALIZING;
-
- len = snprintf(buf, sizeof(buf), "%d %s\r\n", code, reason);
- assert(len < (int)sizeof(buf));
-
- switch (tls_write(client->ctx, buf, len)) {
- case TLS_WANT_POLLIN:
- pfd->events = POLLIN;
- return 0;
- case TLS_WANT_POLLOUT:
- pfd->events = POLLOUT;
- return 0;
- default:
- return 1;
- }
-}
-
ssize_t
filesize(int fd)
{
return def;
}
-int
-check_path(struct client *c, const char *path, int *fd)
+char *
+absolutify_path(const char *path)
{
- struct stat sb;
+ char *wd, *r;
- assert(path != NULL);
- if ((*fd = openat(c->host->dirfd, *path ? path : ".",
- O_RDONLY | O_NOFOLLOW | O_CLOEXEC)) == -1) {
- return FILE_MISSING;
- }
+ if (*path == '/')
+ return strdup(path);
- if (fstat(*fd, &sb) == -1) {
- LOGN(c, "failed stat for %s: %s", path, strerror(errno));
- return FILE_MISSING;
- }
+ wd = getcwd(NULL, 0);
+ if (asprintf(&r, "%s/%s", wd, path) == -1)
+ err(1, "asprintf");
+ free(wd);
+ return r;
+}
- if (S_ISDIR(sb.st_mode))
- return FILE_DIRECTORY;
-
- if (sb.st_mode & S_IXUSR)
- return FILE_EXECUTABLE;
-
- return FILE_EXISTS;
+void
+yyerror(const char *msg)
+{
+ goterror = 1;
+ fprintf(stderr, "%d: %s\n", yylineno, msg);
}
int
-open_file(char *fpath, char *query, struct pollfd *fds, struct client *c)
+parse_portno(const char *p)
{
- switch (check_path(c, fpath, &c->fd)) {
- case FILE_EXECUTABLE:
- if (c->host->cgi != NULL && starts_with(fpath, c->host->cgi))
- return start_cgi(fpath, "", query, fds, c);
+ char *ep;
+ long lval;
- /* fallthrough */
-
- case FILE_EXISTS:
- if ((c->len = filesize(c->fd)) == -1) {
- LOGE(c, "failed to get file size for %s", fpath);
- goodbye(fds, c);
- return 0;
- }
-
- if ((c->buf = mmap(NULL, c->len, PROT_READ, MAP_PRIVATE,
- c->fd, 0)) == MAP_FAILED) {
- warn("mmap: %s", fpath);
- goodbye(fds, c);
- return 0;
- }
- c->i = c->buf;
- return start_reply(fds, c, SUCCESS, mime(fpath));
-
- case FILE_DIRECTORY:
- LOGD(c, "%s is a directory, trying %s/index.gmi", fpath, fpath);
- close(c->fd);
- c->fd = -1;
- send_dir(fpath, fds, c);
- return 0;
-
- case FILE_MISSING:
- if (c->host->cgi != NULL && starts_with(fpath, c->host->cgi))
- return check_for_cgi(fpath, query, fds, c);
-
- if (!start_reply(fds, c, NOT_FOUND, "not found"))
- return 0;
- goodbye(fds, c);
- return 0;
-
- default:
- /* unreachable */
- abort();
- }
+ errno = 0;
+ lval = strtol(p, &ep, 10);
+ if (p[0] == '\0' || *ep != '\0')
+ errx(1, "not a number: %s", p);
+ if (lval < 0 || lval > UINT16_MAX)
+ errx(1, "port number out of range for domain %s: %ld", p, lval);
+ return lval;
}
void
-send_file(char *path, char *query, struct pollfd *fds, struct client *c)
+parse_conf(const char *path)
{
- ssize_t ret, len;
-
- if (c->fd == -1) {
- if (!open_file(path, query, fds, c))
- return;
- c->state = S_SENDING;
- }
+ if ((yyin = fopen(path, "r")) == NULL)
+ err(1, "cannot open config %s", path);
+ yyparse();
+ fclose(yyin);
- len = (c->buf + c->len) - c->i;
-
- while (len > 0) {
- switch (ret = tls_write(c->ctx, c->i, len)) {
- case -1:
- LOGE(c, "tls_write: %s", tls_error(c->ctx));
- goodbye(fds, c);
- return;
-
- case TLS_WANT_POLLIN:
- fds->events = POLLIN;
- return;
-
- case TLS_WANT_POLLOUT:
- fds->events = POLLOUT;
- return;
-
- default:
- c->i += ret;
- len -= ret;
- break;
- }
- }
-
- goodbye(fds, c);
+ if (goterror)
+ exit(1);
}
void
-send_dir(char *path, struct pollfd *fds, struct client *client)
+load_vhosts(struct tls_config *tlsconf)
{
- char fpath[PATHBUF];
- size_t len;
-
- bzero(fpath, PATHBUF);
-
- if (path[0] != '.')
- fpath[0] = '.';
-
- /* this cannot fail since sizeof(fpath) > maxlen of path */
- strlcat(fpath, path, PATHBUF);
- len = strlen(fpath);
-
- /* add a trailing / in case. */
- if (fpath[len-1] != '/') {
- fpath[len] = '/';
- }
-
- strlcat(fpath, "index.gmi", sizeof(fpath));
-
- send_file(fpath, NULL, fds, client);
-}
-
-void
-handle_handshake(struct pollfd *fds, struct client *c)
-{
struct vhost *h;
- const char *servname;
- switch (tls_handshake(c->ctx)) {
- case 0: /* success */
- case -1: /* already handshaked */
- break;
- case TLS_WANT_POLLIN:
- fds->events = POLLIN;
- return;
- case TLS_WANT_POLLOUT:
- fds->events = POLLOUT;
- return;
- default:
- /* unreachable */
- abort();
- }
+ /* we need to set something, then we can add how many key we want */
+ if (tls_config_set_keypair_file(tlsconf, hosts->cert, hosts->key))
+ errx(1, "tls_config_set_keypair_file failed");
- servname = tls_conn_servername(c->ctx);
- if (servname == NULL)
- goto hostnotfound;
-
for (h = hosts; h->domain != NULL; ++h) {
- if (!strcmp(h->domain, servname) || !strcmp(h->domain, "*"))
- break;
- }
-
- if (h->domain != NULL) {
- c->state = S_OPEN;
- c->host = h;
- handle_open_conn(fds, c);
- return;
- }
-
-hostnotfound:
- /* XXX: check the correct response */
- if (!start_reply(fds, c, BAD_REQUEST, "Wrong host or missing SNI"))
- return;
- goodbye(fds, c);
-}
-
-void
-handle_open_conn(struct pollfd *fds, struct client *c)
-{
- char buf[GEMINI_URL_LEN];
- const char *parse_err = "invalid request";
- struct iri iri;
-
- bzero(buf, sizeof(buf));
-
- switch (tls_read(c->ctx, buf, sizeof(buf)-1)) {
- case -1:
- LOGE(c, "tls_read: %s", tls_error(c->ctx));
- goodbye(fds, c);
- return;
-
- case TLS_WANT_POLLIN:
- fds->events = POLLIN;
- return;
-
- case TLS_WANT_POLLOUT:
- fds->events = POLLOUT;
- return;
- }
-
- if (!trim_req_iri(buf) || !parse_iri(buf, &iri, &parse_err)) {
- if (!start_reply(fds, c, BAD_REQUEST, parse_err))
- return;
- goodbye(fds, c);
- return;
- }
+ if (tls_config_add_keypair_file(tlsconf, h->cert, h->key) == -1)
+ errx(1, "failed to load the keypair (%s, %s)",
+ h->cert, h->key);
- if (strcmp(iri.schema, "gemini") || iri.port_no != conf.port) {
- if (!start_reply(fds, c, PROXY_REFUSED, "won't proxy request"))
- return;
- goodbye(fds, c);
- return;
+ if ((h->dirfd = open(h->dir, O_RDONLY | O_DIRECTORY)) == -1)
+ err(1, "open %s for domain %s", h->dir, h->domain);
}
-
- LOGI(c, "GET %s%s%s",
- *iri.path ? iri.path : "/",
- *iri.query ? "?" : "",
- *iri.query ? iri.query : "");
-
- send_file(iri.path, iri.query, fds, c);
}
-void
-handle(struct pollfd *fds, struct client *client)
-{
- switch (client->state) {
- case S_HANDSHAKE:
- handle_handshake(fds, client);
- break;
-
- case S_OPEN:
- handle_open_conn(fds, client);
- break;
-
- case S_INITIALIZING:
- if (!start_reply(fds, client, client->code, client->meta))
- return;
-
- if (client->code != SUCCESS) {
- /* we don't need a body */
- goodbye(fds, client);
- return;
- }
-
- client->state = S_SENDING;
-
- /* fallthrough */
-
- case S_SENDING:
- if (client->child)
- handle_cgi(fds, client);
- else
- send_file(NULL, NULL, fds, client);
- break;
-
- case S_CLOSING:
- goodbye(fds, client);
- break;
-
- default:
- /* unreachable */
- abort();
- }
-}
-
-void
-mark_nonblock(int fd)
-{
- int flags;
-
- if ((flags = fcntl(fd, F_GETFL)) == -1)
- fatal("fcntl(F_GETFL): %s", strerror(errno));
- if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
- fatal("fcntl(F_SETFL): %s", strerror(errno));
-}
-
int
make_socket(int port, int family)
{
return sock;
}
-void
-do_accept(int sock, struct tls *ctx, struct pollfd *fds, struct client *clients)
-{
- int i, fd;
- struct sockaddr_storage addr;
- socklen_t len;
-
- len = sizeof(addr);
- if ((fd = accept(sock, (struct sockaddr*)&addr, &len)) == -1) {
- if (errno == EWOULDBLOCK)
- return;
- fatal("accept: %s", strerror(errno));
- }
-
- mark_nonblock(fd);
-
- for (i = 0; i < MAX_USERS; ++i) {
- if (fds[i].fd == -1) {
- bzero(&clients[i], sizeof(struct client));
- if (tls_accept_socket(ctx, &clients[i].ctx, fd) == -1)
- break; /* goodbye fd! */
-
- fds[i].fd = fd;
- fds[i].events = POLLIN;
-
- clients[i].state = S_HANDSHAKE;
- clients[i].fd = -1;
- clients[i].child = 0;
- clients[i].waiting_on_child = 0;
- clients[i].buf = MAP_FAILED;
- clients[i].af = AF_INET;
- clients[i].addr = addr;
-
- connected_clients++;
- return;
- }
- }
-
- close(fd);
-}
-
-void
-goodbye(struct pollfd *pfd, struct client *c)
-{
- c->state = S_CLOSING;
-
- switch (tls_close(c->ctx)) {
- case TLS_WANT_POLLIN:
- pfd->events = POLLIN;
- return;
- case TLS_WANT_POLLOUT:
- pfd->events = POLLOUT;
- return;
- }
-
- connected_clients--;
-
- tls_free(c->ctx);
- c->ctx = NULL;
-
- if (c->buf != MAP_FAILED)
- munmap(c->buf, c->len);
-
- if (c->fd != -1)
- close(c->fd);
-
- close(pfd->fd);
- pfd->fd = -1;
-}
-
-void
-loop(struct tls *ctx, int sock4, int sock6)
-{
- int i;
- struct client clients[MAX_USERS];
- struct pollfd fds[MAX_USERS];
-
- for (i = 0; i < MAX_USERS; ++i) {
- fds[i].fd = -1;
- fds[i].events = POLLIN;
- bzero(&clients[i], sizeof(struct client));
- }
-
- fds[0].fd = sock4;
- fds[1].fd = sock6;
-
- for (;;) {
- if (poll(fds, MAX_USERS, INFTIM) == -1) {
- if (errno == EINTR) {
- warnx("connected clients: %d",
- connected_clients);
- continue;
- }
- fatal("poll: %s", strerror(errno));
- }
-
- for (i = 0; i < MAX_USERS; i++) {
- if (fds[i].revents == 0)
- continue;
-
- if (fds[i].revents & (POLLERR|POLLNVAL))
- fatal("bad fd %d: %s", fds[i].fd,
- strerror(errno));
-
- if (fds[i].revents & POLLHUP) {
- /* fds[i] may be the fd of the stdin
- * of a cgi script that has exited. */
- if (!clients[i].waiting_on_child) {
- goodbye(&fds[i], &clients[i]);
- continue;
- }
- }
-
- if (fds[i].fd == sock4)
- do_accept(sock4, ctx, fds, clients);
- else if (fds[i].fd == sock6)
- do_accept(sock6, ctx, fds, clients);
- else
- handle(&fds[i], &clients[i]);
- }
- }
-}
-
-char *
-absolutify_path(const char *path)
-{
- char *wd, *r;
-
- if (*path == '/')
- return strdup(path);
-
- wd = getcwd(NULL, 0);
- if (asprintf(&r, "%s/%s", wd, path) == -1)
- err(1, "asprintf");
- free(wd);
- return r;
-}
-
-void
-yyerror(const char *msg)
-{
- goterror = 1;
- fprintf(stderr, "%d: %s\n", yylineno, msg);
-}
-
int
-parse_portno(const char *p)
-{
- char *ep;
- long lval;
-
- errno = 0;
- lval = strtol(p, &ep, 10);
- if (p[0] == '\0' || *ep != '\0')
- errx(1, "not a number: %s", p);
- if (lval < 0 || lval > UINT16_MAX)
- errx(1, "port number out of range for domain %s: %ld", p, lval);
- return lval;
-}
-
-void
-parse_conf(const char *path)
-{
- if ((yyin = fopen(path, "r")) == NULL)
- err(1, "cannot open config %s", path);
- yyparse();
- fclose(yyin);
-
- if (goterror)
- exit(1);
-}
-
-void
-load_vhosts(struct tls_config *tlsconf)
-{
- struct vhost *h;
-
- /* we need to set something, then we can add how many key we want */
- if (tls_config_set_keypair_file(tlsconf, hosts->cert, hosts->key))
- errx(1, "tls_config_set_keypair_file failed");
-
- for (h = hosts; h->domain != NULL; ++h) {
- if (tls_config_add_keypair_file(tlsconf, h->cert, h->key) == -1)
- errx(1, "failed to load the keypair (%s, %s)",
- h->cert, h->key);
-
- if ((h->dirfd = open(h->dir, O_RDONLY | O_DIRECTORY)) == -1)
- err(1, "open %s for domain %s", h->dir, h->domain);
- }
-}
-
-int
listener_main()
{
int sock4, sock6;
conf.ipv6 = 0;
conf.protos = TLS_PROTOCOL_TLSv1_2 | TLS_PROTOCOL_TLSv1_3;
- connected_clients = 0;
-
while ((ch = getopt(argc, argv, "6C:c:d:fhK:np:x:")) != -1) {
switch (ch) {
case '6':
blob - ed2a6f023dd1d1231cc6c03d473e21739014bd55
blob + 39bf27ee95a1d47e668c03d704cf299fdb5974a9
--- gmid.h
+++ gmid.h
};
/* gmid.c */
-__attribute__ ((format (printf, 3, 4))) void logs(int, struct client*, const char*, ...);
+__attribute__((format (printf, 1, 2)))
+__attribute__((__noreturn__))
+void fatal(const char*, ...);
+
+__attribute__((format (printf, 3, 4)))
+void logs(int, struct client*, const char*, ...);
+
void sig_handler(int);
int starts_with(const char*, const char*);
-
-int start_reply(struct pollfd*, struct client*, int, const char*);
ssize_t filesize(int);
const char *path_ext(const char*);
const char *mime(const char*);
-int check_path(struct client*, const char*, int*);
-int open_file(char*, char*, struct pollfd*, struct client*);
-void send_file(char*, char*, struct pollfd*, struct client*);
-void send_dir(char*, struct pollfd*, struct client*);
-void handle_handshake(struct pollfd*, struct client*);
-void handle_open_conn(struct pollfd*, struct client*);
-void handle(struct pollfd*, struct client*);
-
-void mark_nonblock(int);
-int make_soket(int);
-void do_accept(int, struct tls*, struct pollfd*, struct client*);
-void goodbye(struct pollfd*, struct client*);
-void loop(struct tls*, int, int);
-
char *absolutify_path(const char*);
void yyerror(const char*);
int parse_portno(const char*);
void parse_conf(const char*);
void load_vhosts(struct tls_config*);
-
+int make_soket(int);
int listener_main();
-
void usage(const char*);
/* provided by lex/yacc */
extern int yyparse(void);
extern int yylex(void);
+/* server.c */
+int check_path(struct client*, const char*, int*);
+int open_file(char*, char*, struct pollfd*, struct client*);
+int check_for_cgi(char *, char*, struct pollfd*, struct client*);
+void mark_nonblock(int);
+void handle_handshake(struct pollfd*, struct client*);
+void handle_open_conn(struct pollfd*, struct client*);
+int start_reply(struct pollfd*, struct client*, int, const char*);
+int start_cgi(const char*, const char*, const char*, struct pollfd*, struct client*);
+void send_file(char*, char*, struct pollfd*, struct client*);
+void send_dir(char*, 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*);
+void goodbye(struct pollfd*, struct client*);
+void do_accept(int, struct tls*, struct pollfd*, struct client*);
+void handle(struct pollfd*, struct client*);
+void loop(struct tls*, int, int);
+
/* ex.c */
int send_string(int, const char*);
int recv_string(int, char**);
int recv_fd(int);
int executor_main(int);
-/* cgi.c */
-int check_for_cgi(char *, char*, struct pollfd*, struct client*);
-int start_cgi(const char*, const char*, const char*, 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*);
-
/* sandbox.c */
void sandbox();
blob - /dev/null
blob + ab3cd5db9de9be6294579e0bd8d34f3c29b84796 (mode 644)
--- /dev/null
+++ server.c
+/*
+ * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#include <netdb.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include "gmid.h"
+
+int connected_clients;
+
+int
+check_path(struct client *c, const char *path, int *fd)
+{
+ struct stat sb;
+
+ assert(path != NULL);
+ if ((*fd = openat(c->host->dirfd, *path ? path : ".",
+ O_RDONLY | O_NOFOLLOW | O_CLOEXEC)) == -1) {
+ return FILE_MISSING;
+ }
+
+ if (fstat(*fd, &sb) == -1) {
+ LOGN(c, "failed stat for %s: %s", path, strerror(errno));
+ return FILE_MISSING;
+ }
+
+ if (S_ISDIR(sb.st_mode))
+ return FILE_DIRECTORY;
+
+ if (sb.st_mode & S_IXUSR)
+ return FILE_EXECUTABLE;
+
+ return FILE_EXISTS;
+}
+
+int
+open_file(char *fpath, char *query, struct pollfd *fds, struct client *c)
+{
+ switch (check_path(c, fpath, &c->fd)) {
+ case FILE_EXECUTABLE:
+ if (c->host->cgi != NULL && starts_with(fpath, c->host->cgi))
+ return start_cgi(fpath, "", query, fds, c);
+
+ /* fallthrough */
+
+ case FILE_EXISTS:
+ if ((c->len = filesize(c->fd)) == -1) {
+ LOGE(c, "failed to get file size for %s", fpath);
+ goodbye(fds, c);
+ return 0;
+ }
+
+ if ((c->buf = mmap(NULL, c->len, PROT_READ, MAP_PRIVATE,
+ c->fd, 0)) == MAP_FAILED) {
+ warn("mmap: %s", fpath);
+ goodbye(fds, c);
+ return 0;
+ }
+ c->i = c->buf;
+ return start_reply(fds, c, SUCCESS, mime(fpath));
+
+ case FILE_DIRECTORY:
+ LOGD(c, "%s is a directory, trying %s/index.gmi", fpath, fpath);
+ close(c->fd);
+ c->fd = -1;
+ send_dir(fpath, fds, c);
+ return 0;
+
+ case FILE_MISSING:
+ if (c->host->cgi != NULL && starts_with(fpath, c->host->cgi))
+ return check_for_cgi(fpath, query, fds, c);
+
+ if (!start_reply(fds, c, NOT_FOUND, "not found"))
+ return 0;
+ goodbye(fds, c);
+ return 0;
+
+ default:
+ /* unreachable */
+ abort();
+ }
+}
+
+
+/*
+ * the inverse of this algorithm, i.e. starting from the start of the
+ * path + strlen(cgi), and checking if each component, should be
+ * faster. But it's tedious to write. This does the opposite: starts
+ * from the end and strip one component at a time, until either an
+ * executable is found or we emptied the path.
+ */
+int
+check_for_cgi(char *path, char *query, struct pollfd *fds, struct client *c)
+{
+ char *end;
+ end = strchr(path, '\0');
+
+ /* NB: assume CGI is enabled and path matches cgi */
+
+ while (end > path) {
+ /* go up one level. UNIX paths are simple and POSIX
+ * dirname, with its ambiguities on if the given path
+ * is changed or not, gives me headaches. */
+ while (*end != '/')
+ end--;
+ *end = '\0';
+
+ switch (check_path(c, path, &c->fd)) {
+ case FILE_EXECUTABLE:
+ return start_cgi(path, end+1, query, fds,c);
+ case FILE_MISSING:
+ break;
+ default:
+ goto err;
+ }
+
+ *end = '/';
+ end--;
+ }
+
+err:
+ if (!start_reply(fds, c, NOT_FOUND, "not found"))
+ return 0;
+ goodbye(fds, c);
+ return 0;
+}
+
+void
+mark_nonblock(int fd)
+{
+ int flags;
+
+ if ((flags = fcntl(fd, F_GETFL)) == -1)
+ fatal("fcntl(F_GETFL): %s", strerror(errno));
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
+ fatal("fcntl(F_SETFL): %s", strerror(errno));
+}
+
+void
+handle_handshake(struct pollfd *fds, struct client *c)
+{
+ struct vhost *h;
+ const char *servname;
+
+ switch (tls_handshake(c->ctx)) {
+ case 0: /* success */
+ case -1: /* already handshaked */
+ break;
+ case TLS_WANT_POLLIN:
+ fds->events = POLLIN;
+ return;
+ case TLS_WANT_POLLOUT:
+ fds->events = POLLOUT;
+ return;
+ default:
+ /* unreachable */
+ abort();
+ }
+
+ servname = tls_conn_servername(c->ctx);
+ if (servname == NULL)
+ goto hostnotfound;
+
+ for (h = hosts; h->domain != NULL; ++h) {
+ if (!strcmp(h->domain, servname) || !strcmp(h->domain, "*"))
+ break;
+ }
+
+ if (h->domain != NULL) {
+ c->state = S_OPEN;
+ c->host = h;
+ handle_open_conn(fds, c);
+ return;
+ }
+
+hostnotfound:
+ /* XXX: check the correct response */
+ if (!start_reply(fds, c, BAD_REQUEST, "Wrong host or missing SNI"))
+ return;
+ goodbye(fds, c);
+}
+
+void
+handle_open_conn(struct pollfd *fds, struct client *c)
+{
+ char buf[GEMINI_URL_LEN];
+ const char *parse_err = "invalid request";
+ struct iri iri;
+
+ bzero(buf, sizeof(buf));
+
+ switch (tls_read(c->ctx, buf, sizeof(buf)-1)) {
+ case -1:
+ LOGE(c, "tls_read: %s", tls_error(c->ctx));
+ goodbye(fds, c);
+ return;
+
+ case TLS_WANT_POLLIN:
+ fds->events = POLLIN;
+ return;
+
+ case TLS_WANT_POLLOUT:
+ fds->events = POLLOUT;
+ return;
+ }
+
+ if (!trim_req_iri(buf) || !parse_iri(buf, &iri, &parse_err)) {
+ if (!start_reply(fds, c, BAD_REQUEST, parse_err))
+ return;
+ goodbye(fds, c);
+ return;
+ }
+
+ if (strcmp(iri.schema, "gemini") || iri.port_no != conf.port) {
+ if (!start_reply(fds, c, PROXY_REFUSED, "won't proxy request"))
+ return;
+ goodbye(fds, c);
+ return;
+ }
+
+ LOGI(c, "GET %s%s%s",
+ *iri.path ? iri.path : "/",
+ *iri.query ? "?" : "",
+ *iri.query ? iri.query : "");
+
+ send_file(iri.path, iri.query, fds, c);
+}
+
+int
+start_reply(struct pollfd *pfd, struct client *client, int code, const char *reason)
+{
+ char buf[1030]; /* status + ' ' + max reply len + \r\n\0 */
+ int len;
+
+ client->code = code;
+ client->meta = reason;
+ client->state = S_INITIALIZING;
+
+ len = snprintf(buf, sizeof(buf), "%d %s\r\n", code, reason);
+ assert(len < (int)sizeof(buf));
+
+ switch (tls_write(client->ctx, buf, len)) {
+ case TLS_WANT_POLLIN:
+ pfd->events = POLLIN;
+ return 0;
+ case TLS_WANT_POLLOUT:
+ pfd->events = POLLOUT;
+ return 0;
+ default:
+ return 1;
+ }
+}
+
+int
+start_cgi(const char *spath, const char *relpath, const char *query,
+ struct pollfd *fds, struct client *c)
+{
+ char addr[NI_MAXHOST];
+ const char *ruser, *cissuer, *chash;
+ int e;
+
+ e = getnameinfo((struct sockaddr*)&c->addr, sizeof(c->addr),
+ addr, sizeof(addr),
+ NULL, 0,
+ NI_NUMERICHOST);
+ if (e != 0)
+ goto err;
+
+ if (tls_peer_cert_provided(c->ctx)) {
+ ruser = tls_peer_cert_subject(c->ctx);
+ cissuer = tls_peer_cert_issuer(c->ctx);
+ chash = tls_peer_cert_hash(c->ctx);
+ } else {
+ ruser = NULL;
+ cissuer = NULL;
+ chash = NULL;
+ }
+
+ if (!send_string(exfd, spath)
+ || !send_string(exfd, relpath)
+ || !send_string(exfd, query)
+ || !send_string(exfd, addr)
+ || !send_string(exfd, ruser)
+ || !send_string(exfd, cissuer)
+ || !send_string(exfd, chash)
+ || !send_vhost(exfd, c->host))
+ goto err;
+
+ close(c->fd);
+ if ((c->fd = recv_fd(exfd)) == -1) {
+ if (!start_reply(fds, c, TEMP_FAILURE, "internal server error"))
+ return 0;
+ goodbye(fds, c);
+ return 0;
+ }
+ c->child = 1;
+ c->state = S_SENDING;
+ cgi_poll_on_child(fds, c);
+ /* handle_cgi(fds, c); */
+ return 0;
+
+err:
+ /* fatal("cannot talk to the executor process: %s", strerror(errno)); */
+ err(1, "cannot talk to the executor process");
+}
+
+void
+send_file(char *path, char *query, struct pollfd *fds, struct client *c)
+{
+ ssize_t ret, len;
+
+ if (c->fd == -1) {
+ if (!open_file(path, query, fds, c))
+ return;
+ c->state = S_SENDING;
+ }
+
+ len = (c->buf + c->len) - c->i;
+
+ while (len > 0) {
+ switch (ret = tls_write(c->ctx, c->i, len)) {
+ case -1:
+ LOGE(c, "tls_write: %s", tls_error(c->ctx));
+ goodbye(fds, c);
+ return;
+
+ case TLS_WANT_POLLIN:
+ fds->events = POLLIN;
+ return;
+
+ case TLS_WANT_POLLOUT:
+ fds->events = POLLOUT;
+ return;
+
+ default:
+ c->i += ret;
+ len -= ret;
+ break;
+ }
+ }
+
+ goodbye(fds, c);
+}
+
+void
+send_dir(char *path, struct pollfd *fds, struct client *client)
+{
+ char fpath[PATHBUF];
+ size_t len;
+
+ bzero(fpath, PATHBUF);
+
+ if (path[0] != '.')
+ fpath[0] = '.';
+
+ /* this cannot fail since sizeof(fpath) > maxlen of path */
+ strlcat(fpath, path, PATHBUF);
+ len = strlen(fpath);
+
+ /* add a trailing / in case. */
+ if (fpath[len-1] != '/') {
+ fpath[len] = '/';
+ }
+
+ strlcat(fpath, "index.gmi", sizeof(fpath));
+
+ send_file(fpath, NULL, fds, client);
+}
+
+void
+cgi_poll_on_child(struct pollfd *fds, struct client *c)
+{
+ int fd;
+
+ if (c->waiting_on_child)
+ return;
+ c->waiting_on_child = 1;
+
+ fds->events = POLLIN;
+
+ fd = fds->fd;
+ fds->fd = c->fd;
+ c->fd = fd;
+}
+
+void
+cgi_poll_on_client(struct pollfd *fds, struct client *c)
+{
+ int fd;
+
+ if (!c->waiting_on_child)
+ return;
+ c->waiting_on_child = 0;
+
+ fd = fds->fd;
+ fds->fd = c->fd;
+ c->fd = fd;
+}
+
+void
+handle_cgi(struct pollfd *fds, struct client *c)
+{
+ ssize_t r;
+
+ /* ensure c->fd is the child and fds->fd the client */
+ cgi_poll_on_client(fds, c);
+
+ while (1) {
+ if (c->len == 0) {
+ if ((r = read(c->fd, c->sbuf, sizeof(c->sbuf))) == 0)
+ goto end;
+ if (r == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ cgi_poll_on_child(fds, c);
+ return;
+ }
+ goto end;
+ }
+ c->len = r;
+ c->off = 0;
+ }
+
+ 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:
+ goodbye(fds, c);
+}
+
+void
+goodbye(struct pollfd *pfd, struct client *c)
+{
+ c->state = S_CLOSING;
+
+ switch (tls_close(c->ctx)) {
+ case TLS_WANT_POLLIN:
+ pfd->events = POLLIN;
+ return;
+ case TLS_WANT_POLLOUT:
+ pfd->events = POLLOUT;
+ return;
+ }
+
+ connected_clients--;
+
+ tls_free(c->ctx);
+ c->ctx = NULL;
+
+ if (c->buf != MAP_FAILED)
+ munmap(c->buf, c->len);
+
+ if (c->fd != -1)
+ close(c->fd);
+
+ close(pfd->fd);
+ pfd->fd = -1;
+}
+
+void
+do_accept(int sock, struct tls *ctx, struct pollfd *fds, struct client *clients)
+{
+ int i, fd;
+ struct sockaddr_storage addr;
+ socklen_t len;
+
+ len = sizeof(addr);
+ if ((fd = accept(sock, (struct sockaddr*)&addr, &len)) == -1) {
+ if (errno == EWOULDBLOCK)
+ return;
+ fatal("accept: %s", strerror(errno));
+ }
+
+ mark_nonblock(fd);
+
+ for (i = 0; i < MAX_USERS; ++i) {
+ if (fds[i].fd == -1) {
+ bzero(&clients[i], sizeof(struct client));
+ if (tls_accept_socket(ctx, &clients[i].ctx, fd) == -1)
+ break; /* goodbye fd! */
+
+ fds[i].fd = fd;
+ fds[i].events = POLLIN;
+
+ clients[i].state = S_HANDSHAKE;
+ clients[i].fd = -1;
+ clients[i].child = 0;
+ clients[i].waiting_on_child = 0;
+ clients[i].buf = MAP_FAILED;
+ clients[i].af = AF_INET;
+ clients[i].addr = addr;
+
+ connected_clients++;
+ return;
+ }
+ }
+
+ close(fd);
+}
+
+void
+handle(struct pollfd *fds, struct client *client)
+{
+ switch (client->state) {
+ case S_HANDSHAKE:
+ handle_handshake(fds, client);
+ break;
+
+ case S_OPEN:
+ handle_open_conn(fds, client);
+ break;
+
+ case S_INITIALIZING:
+ if (!start_reply(fds, client, client->code, client->meta))
+ return;
+
+ if (client->code != SUCCESS) {
+ /* we don't need a body */
+ goodbye(fds, client);
+ return;
+ }
+
+ client->state = S_SENDING;
+
+ /* fallthrough */
+
+ case S_SENDING:
+ if (client->child)
+ handle_cgi(fds, client);
+ else
+ send_file(NULL, NULL, fds, client);
+ break;
+
+ case S_CLOSING:
+ goodbye(fds, client);
+ break;
+
+ default:
+ /* unreachable */
+ abort();
+ }
+}
+
+void
+loop(struct tls *ctx, int sock4, int sock6)
+{
+ int i;
+ struct client clients[MAX_USERS];
+ struct pollfd fds[MAX_USERS];
+
+ connected_clients = 0;
+
+ for (i = 0; i < MAX_USERS; ++i) {
+ fds[i].fd = -1;
+ fds[i].events = POLLIN;
+ bzero(&clients[i], sizeof(struct client));
+ }
+
+ fds[0].fd = sock4;
+ fds[1].fd = sock6;
+
+ for (;;) {
+ if (poll(fds, MAX_USERS, INFTIM) == -1) {
+ if (errno == EINTR) {
+ warnx("connected clients: %d",
+ connected_clients);
+ continue;
+ }
+ fatal("poll: %s", strerror(errno));
+ }
+
+ for (i = 0; i < MAX_USERS; i++) {
+ if (fds[i].revents == 0)
+ continue;
+
+ if (fds[i].revents & (POLLERR|POLLNVAL))
+ fatal("bad fd %d: %s", fds[i].fd,
+ strerror(errno));
+
+ if (fds[i].revents & POLLHUP) {
+ /* fds[i] may be the fd of the stdin
+ * of a cgi script that has exited. */
+ if (!clients[i].waiting_on_child) {
+ goodbye(&fds[i], &clients[i]);
+ continue;
+ }
+ }
+
+ if (fds[i].fd == sock4)
+ do_accept(sock4, ctx, fds, clients);
+ else if (fds[i].fd == sock6)
+ do_accept(sock6, ctx, fds, clients);
+ else
+ handle(&fds[i], &clients[i]);
+ }
+ }
+}