Commit Diff


commit - 61f8d630c81fb92d771cf26496b25b5157c776ca
commit + 0be51733ef271183e8164b8de1871cba634c1508
blob - 95981c51cac77d80586ed0a6310b3780a5228695
blob + e9cccf08d188ef5c69acd727bb7c01297d2ba323
--- gmid.c
+++ gmid.c
@@ -89,6 +89,56 @@ logs(int priority, struct client *c,
 	free(fmted);
 
 	va_end(ap);
+}
+
+/* strchr, but with a bound */
+static char *
+gmid_strnchr(char *s, int c, size_t len)
+{
+	size_t i;
+
+	for (i = 0; i < len; ++i)
+		if (s[i] == c)
+			return &s[i];
+	return NULL;
+}
+
+void
+log_request(struct client *c, char *meta, size_t l)
+{
+	char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV], b[GEMINI_URL_LEN];
+	char *t;
+	size_t len;
+	int ec;
+
+	len = sizeof(c->addr);
+	ec = getnameinfo((struct sockaddr*)&c->addr, len,
+	    hbuf, sizeof(hbuf),
+	    sbuf, sizeof(sbuf),
+	    NI_NUMERICHOST | NI_NUMERICSERV);
+	if (ec != 0)
+		fatal("getnameinfo: %s", gai_strerror(ec));
+
+	/* serialize the IRI */
+	strlcpy(b, c->iri.schema, sizeof(b));
+	strlcat(b, "://", sizeof(b));
+	strlcat(b, c->iri.host, sizeof(b));
+	strlcat(b, "/", sizeof(b));
+	strlcat(b, c->iri.path, sizeof(b)); /* TODO: sanitize UTF8 */
+	if (*c->iri.query != '\0') {	    /* TODO: sanitize UTF8 */
+		strlcat(b, "?", sizeof(b));
+		strlcat(b, c->iri.query, sizeof(b));
+	}
+
+	if ((t = gmid_strnchr(meta, '\r', l)) == NULL)
+		t = meta + len;
+
+	if (conf.foreground)
+		fprintf(stderr, "%s:%s GET %s %.*s\n", hbuf, sbuf, b,
+		    (int)(t - meta), meta);
+	else
+		syslog(LOG_INFO | LOG_DAEMON, "%s:%s GET %s %.*s",
+		    hbuf, sbuf, b, (int)(t - meta), meta);
 }
 
 void
blob - 5c659a233a0fb227b3b364507fb648e8ec6872ed
blob + cd38c51e0113052d04747cc2eee316bab79d26d7
--- gmid.h
+++ gmid.h
@@ -38,6 +38,7 @@
 #define PATHBUF		2048
 
 #define SUCCESS		20
+#define TEMP_REDIRECT	30
 #define TEMP_FAILURE	40
 #define NOT_FOUND	51
 #define PROXY_REFUSED	53
@@ -88,6 +89,22 @@ struct conf {
 extern struct conf conf;
 extern int exfd;
 
+struct iri {
+	char		*schema;
+	char		*host;
+	char		*port;
+	uint16_t	 port_no;
+	char		*path;
+	char		*query;
+	char		*fragment;
+};
+
+struct parser {
+	char		*iri;
+	struct iri	*parsed;
+	const char	*err;
+};
+
 enum {
 	S_HANDSHAKE,
 	S_OPEN,
@@ -98,6 +115,8 @@ enum {
 
 struct client {
 	struct tls	*ctx;
+	char		 req[GEMINI_URL_LEN];
+	struct iri	 iri;
 	int		 state;
 	int		 code;
 	const char	*meta;
@@ -110,22 +129,6 @@ struct client {
 	struct vhost	*host;	/* host she's talking to */
 };
 
-struct iri {
-	char		*schema;
-	char		*host;
-	char		*port;
-	uint16_t	 port_no;
-	char		*path;
-	char		*query;
-	char		*fragment;
-};
-
-struct parser {
-	char		*iri;
-	struct iri	*parsed;
-	const char	*err;
-};
-
 enum {
 	FILE_EXISTS,
 	FILE_EXECUTABLE,
@@ -141,6 +144,7 @@ void fatal(const char*, ...);
 
 __attribute__((format (printf, 3, 4)))
 void logs(int, struct client*, const char*, ...);
+void log_request(struct client*, char*, size_t);
 
 void		 sig_handler(int);
 int		 starts_with(const char*, const char*);
@@ -169,15 +173,15 @@ const char	*mime(struct vhost*, const char*);
 
 /* server.c */
 int		 check_path(struct client*, const char*, int*);
-int		 open_file(char*, char*, struct pollfd*, struct client*);
+int		 open_file(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		 send_file(struct pollfd*, struct client*);
+void		 send_dir(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 - 20a058ad0500a088ed521e2c26135c6e9ad5f339
blob + 728a679525556324ffcb2efedd570fc32b40630a
--- server.c
+++ server.c
@@ -54,41 +54,43 @@ check_path(struct client *c, const char *path, int *fd
 }
 
 int
-open_file(char *fpath, char *query, struct pollfd *fds, struct client *c)
+open_file(struct pollfd *fds, struct client *c)
 {
-	switch (check_path(c, fpath, &c->fd)) {
+	switch (check_path(c, c->iri.path, &c->fd)) {
 	case FILE_EXECUTABLE:
-		if (c->host->cgi != NULL && starts_with(fpath, c->host->cgi))
-			return start_cgi(fpath, "", query, fds, c);
+		if (c->host->cgi != NULL && starts_with(c->iri.path, c->host->cgi))
+			return start_cgi(c->iri.path, "", c->iri.query, fds, c);
 
 		/* fallthrough */
 
 	case FILE_EXISTS:
 		if ((c->len = filesize(c->fd)) == -1) {
-			LOGE(c, "failed to get file size for %s", fpath);
+			LOGE(c, "failed to get file size for %s", c->iri.path);
 			goodbye(fds, c);
 			return 0;
 		}
 
 		if ((c->buf = mmap(NULL, c->len, PROT_READ, MAP_PRIVATE,
 			    c->fd, 0)) == MAP_FAILED) {
-			LOGW(c, "mmap: %s: %s", fpath, strerror(errno));
+			LOGW(c, "mmap: %s: %s", c->iri.path, strerror(errno));
 			goodbye(fds, c);
 			return 0;
 		}
 		c->i = c->buf;
-		return start_reply(fds, c, SUCCESS, mime(c->host, fpath));
+		if (!start_reply(fds, c, SUCCESS, mime(c->host, c->iri.path)))
+			return 0;
+		send_file(fds, c);
+		return 0;
 
 	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);
+		send_dir(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 (c->host->cgi != NULL && starts_with(c->iri.path, c->host->cgi))
+			return check_for_cgi(c->iri.path, c->iri.query, fds, c);
 
 		if (!start_reply(fds, c, NOT_FOUND, "not found"))
 			return 0;
@@ -203,13 +205,12 @@ hostnotfound:
 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));
+	bzero(c->req, sizeof(c->req));
+	bzero(&c->iri, sizeof(c->iri));
 
-	switch (tls_read(c->ctx, buf, sizeof(buf)-1)) {
+	switch (tls_read(c->ctx, c->req, sizeof(c->req)-1)) {
 	case -1:
 		LOGE(c, "tls_read: %s", tls_error(c->ctx));
 		goodbye(fds, c);
@@ -224,33 +225,29 @@ handle_open_conn(struct pollfd *fds, struct client *c)
 		return;
 	}
 
-	if (!trim_req_iri(buf) || !parse_iri(buf, &iri, &parse_err)) {
+	if (!trim_req_iri(c->req) || !parse_iri(c->req, &c->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) {
+	/* XXX: we should check that the SNI matches the requested host */
+	if (strcmp(c->iri.schema, "gemini") || c->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);
+	open_file(fds, c);
 }
 
 int
 start_reply(struct pollfd *pfd, struct client *c, int code, const char *meta)
 {
 	char buf[1030];		/* status + ' ' + max reply len + \r\n\0 */
-	int len;
+	size_t len;
 
 	c->code = code;
 	c->meta = meta;
@@ -264,7 +261,7 @@ start_reply(struct pollfd *pfd, struct client *c, int 
 	}
 
 	len = strlcat(buf, "\r\n", sizeof(buf));
-	assert(len < (int)sizeof(buf));
+	assert(len < sizeof(buf));
 
 	switch (tls_write(c->ctx, buf, len)) {
 	case TLS_WANT_POLLIN:
@@ -274,6 +271,7 @@ start_reply(struct pollfd *pfd, struct client *c, int 
 		pfd->events = POLLOUT;
 		return 0;
 	default:
+		log_request(c, buf, sizeof(buf));
 		return 1;
 	}
 }
@@ -323,6 +321,7 @@ start_cgi(const char *spath, const char *relpath, cons
 	c->child = 1;
 	c->state = S_SENDING;
 	cgi_poll_on_child(fds, c);
+	c->code = -1;
 	/* handle_cgi(fds, c); */
 	return 0;
 
@@ -332,16 +331,10 @@ err:
 }
 
 void
-send_file(char *path, char *query, struct pollfd *fds, struct client *c)
+send_file(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) {
@@ -370,28 +363,56 @@ send_file(char *path, char *query, struct pollfd *fds,
 }
 
 void
-send_dir(char *path, struct pollfd *fds, struct client *client)
+send_dir(struct pollfd *fds, struct client *c)
 {
-	char fpath[PATHBUF];
 	size_t len;
 
-	bzero(fpath, PATHBUF);
+	/* 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.gmi
+	 *  - foo/index.gmi is a directory.
+	 *
+	 * It's an unlikely case, but can happen.  We then redirect
+	 * to foo/index.gmi
+	 */
+	if (c->iri.path == c->sbuf) {
+		if (!start_reply(fds, c, TEMP_REDIRECT, c->sbuf))
+			return;
+		goodbye(fds, c);
+		return;
+	}
 
-	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] = '/';
+	len = strlen(c->iri.path);
+	if (len > 0 && c->iri.path[len-1] != '/') {
+		/* redirect to url with the trailing / */
+		strlcpy(c->sbuf, c->iri.path, sizeof(c->sbuf));
+		strlcat(c->sbuf, "/", sizeof(c->sbuf));
+		if (!start_reply(fds, c, TEMP_REDIRECT, c->sbuf))
+			return;
+		goodbye(fds, c);
+		return;
 	}
 
-	strlcat(fpath, "index.gmi", sizeof(fpath));
+        strlcpy(c->sbuf, c->iri.path, sizeof(c->sbuf));
+	if (len != 0)
+		strlcat(c->sbuf, "/", sizeof(c->sbuf));
+	len = strlcat(c->sbuf, "index.gmi", sizeof(c->sbuf));
 
-	send_file(fpath, NULL, fds, client);
+	if (len >= sizeof(c->sbuf)) {
+		if (!start_reply(fds, c, TEMP_FAILURE, "internal server error"))
+			return;
+		goodbye(fds, c);
+		return;
+	}
+
+	close(c->fd);
+	c->iri.path = c->sbuf;
+	open_file(fds, c);
 }
 
 void
@@ -445,6 +466,13 @@ handle_cgi(struct pollfd *fds, struct client *c)
 			}
 			c->len = r;
 			c->off = 0;
+
+			/* XXX: if we haven't still read a whole
+			 * reply line, we should go back to poll! */
+			if (c->code == -1) {
+				c->code = 0;
+				log_request(c, c->sbuf, sizeof(c->sbuf));
+			}
 		}
 
 		while (c->len > 0) {
@@ -571,7 +599,7 @@ handle(struct pollfd *fds, struct client *client)
 		if (client->child)
 			handle_cgi(fds, client);
 		else
-			send_file(NULL, NULL, fds, client);
+			send_file(fds, client);
 		break;
 
 	case S_CLOSING: