commit - 61f8d630c81fb92d771cf26496b25b5157c776ca
commit + 0be51733ef271183e8164b8de1871cba634c1508
blob - 95981c51cac77d80586ed0a6310b3780a5228695
blob + e9cccf08d188ef5c69acd727bb7c01297d2ba323
--- gmid.c
+++ gmid.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
#define PATHBUF 2048
#define SUCCESS 20
+#define TEMP_REDIRECT 30
#define TEMP_FAILURE 40
#define NOT_FOUND 51
#define PROXY_REFUSED 53
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,
struct client {
struct tls *ctx;
+ char req[GEMINI_URL_LEN];
+ struct iri iri;
int state;
int code;
const char *meta;
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,
__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*);
/* 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
}
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;
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);
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;
}
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:
pfd->events = POLLOUT;
return 0;
default:
+ log_request(c, buf, sizeof(buf));
return 1;
}
}
c->child = 1;
c->state = S_SENDING;
cgi_poll_on_child(fds, c);
+ c->code = -1;
/* handle_cgi(fds, c); */
return 0;
}
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) {
}
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
}
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) {
if (client->child)
handle_cgi(fds, client);
else
- send_file(NULL, NULL, fds, client);
+ send_file(fds, client);
break;
case S_CLOSING: