2 * Copyright (c) 2020 Omar Polo <op@omarpolo.com>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 #include <sys/socket.h>
20 #include <netinet/in.h>
34 # define pledge(a, b) 0
35 # define unveil(a, b) 0
36 #endif /* __OpenBSD__ */
42 #define GEMINI_URL_LEN (1024+3) /* URL max len + \r\n + \0 */
44 /* large enough to hold a copy of a gemini URL and still have extra room */
51 #define BAD_REQUEST 59
74 char *url_after_proto(char*);
75 char *url_start_of_request(char*);
77 void adjust_path(char*);
78 int path_isdir(char*);
80 int start_reply(struct pollfd*, struct client*, int, const char*);
82 void send_file(char*, struct pollfd*, struct client*);
83 void send_dir(char*, struct pollfd*, struct client*);
84 void handle(struct pollfd*, struct client*);
86 void mark_nonblock(int);
88 void do_accept(int, struct tls*, struct pollfd*, struct client*);
89 void goodbye(struct pollfd*, struct client*);
90 void loop(struct tls*, int);
92 void usage(const char*);
95 url_after_proto(char *url)
98 const char *proto = "gemini";
99 const char *marker = "://";
101 if ((s = strstr(url, marker)) == NULL)
104 /* not a gemini:// URL */
106 if (s - strlen(proto) < url)
109 /* if (strcmp(s - strlen(proto), proto)) */
112 /* a valid gemini:// URL */
113 return s + strlen(marker);
117 url_start_of_request(char *url)
121 if ((s = url_after_proto(url)) == NULL)
124 if ((t = strstr(s, "/")) == NULL)
125 return s + strlen(s);
132 const char *e = "\r\n";
135 if ((s = strstr(url, e)) == NULL)
141 fprintf(stderr, "the request was longer than 1024 bytes\n");
149 adjust_path(char *path)
157 if (!strcmp(&path[len-3], "/..")) {
162 /* if the path is only `..` trim out and exit */
163 if (!strcmp(path, "..")) {
168 /* remove every ../ in the path */
170 if ((s = strstr(path, "../")) == NULL)
172 memmove(s, s+3, strlen(s)+1); /* copy also the \0 */
177 path_isdir(char *path)
181 return path[strlen(path)-1] == '/';
185 start_reply(struct pollfd *pfd, struct client *client, int code, const char *reason)
187 char buf[1030] = {0}; /* status + ' ' + max reply len + \r\n\0 */
192 client->meta = reason;
193 client->state = S_INITIALIZING;
195 len = snprintf(buf, sizeof(buf), "%d %s\r\n", code, reason);
196 assert(len < (int)sizeof(buf));
197 ret = tls_write(client->ctx, buf, len);
198 if (ret == TLS_WANT_POLLIN) {
199 pfd->events = POLLIN;
203 if (ret == TLS_WANT_POLLOUT) {
204 pfd->events = POLLOUT;
216 if (fstat(fd, &sb) == -1) {
218 return 1; /* we'll probably fail later on anyway */
221 return S_ISDIR(sb.st_mode);
225 send_file(char *path, struct pollfd *fds, struct client *client)
232 if (client->fd == -1) {
233 assert(path != NULL);
235 bzero(fpath, sizeof(fpath));
239 strlcat(fpath, path, PATHBUF);
241 if ((client->fd = openat(dirfd, fpath, O_RDONLY | O_NOFOLLOW)) == -1) {
242 warn("open: %s", fpath);
243 if (!start_reply(fds, client, NOT_FOUND, "not found"))
245 goodbye(fds, client);
249 if (isdir(client->fd)) {
250 warnx("%s is a directory, trying %s/index.gmi", fpath, fpath);
253 send_dir(fpath, fds, client);
257 /* assume it's a text/gemini file */
258 if (!start_reply(fds, client, SUCCESS, "text/gemini"))
263 w = read(client->fd, buf, sizeof(buf));
266 if (w == 0 || w == -1) {
267 goodbye(fds, client);
275 t = tls_write(client->ctx, buf+i, w);
278 warnx("tls_write: %s", tls_error(client->ctx));
279 goodbye(fds, client);
282 case TLS_WANT_POLLIN:
283 case TLS_WANT_POLLOUT:
284 fds->events = (t == TLS_WANT_POLLIN)
299 send_dir(char *path, struct pollfd *fds, struct client *client)
304 bzero(fpath, PATHBUF);
309 /* this cannot fail since sizeof(fpath) > maxlen of path */
310 strlcat(fpath, path, PATHBUF);
313 /* add a trailing / in case. */
314 if (fpath[len-1] != '/') {
318 strlcat(fpath, "index.gmi", sizeof(fpath));
320 send_file(fpath, fds, client);
324 handle(struct pollfd *fds, struct client *client)
326 char buf[GEMINI_URL_LEN];
329 switch (client->state) {
331 bzero(buf, GEMINI_URL_LEN);
332 switch (tls_read(client->ctx, buf, sizeof(buf)-1)) {
334 warnx("tls_read: %s", tls_error(client->ctx));
335 goodbye(fds, client);
338 case TLS_WANT_POLLIN:
339 fds->events = POLLIN;
342 case TLS_WANT_POLLOUT:
343 fds->events = POLLOUT;
347 if (!url_trim(buf)) {
348 if (!start_reply(fds, client, BAD_REQUEST, "bad request"))
350 goodbye(fds, client);
354 if ((path = url_start_of_request(buf)) == NULL) {
355 if (!start_reply(fds, client, BAD_REQUEST, "bad request"))
357 goodbye(fds, client);
362 fprintf(stderr, "requested path: %s\n", path);
364 if (path_isdir(path))
365 send_dir(path, fds, client);
367 send_file(path, fds, client);
371 if (!start_reply(fds, client, client->code, client->meta))
374 if (client->code != SUCCESS) {
375 /* we don't need a body */
376 goodbye(fds, client);
380 client->state = S_SENDING;
385 send_file(NULL, fds, client);
389 goodbye(fds, client);
399 mark_nonblock(int fd)
403 if ((flags = fcntl(fd, F_GETFL)) == -1)
404 err(1, "fcntl(F_GETFL)");
405 if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
406 err(1, "fcntl(F_SETFL)");
410 make_socket(int port)
413 struct sockaddr_in addr;
415 if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
419 if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v)) == -1)
420 err(1, "setsockopt(SO_REUSEADDR)");
423 if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &v, sizeof(v)) == -1)
424 err(1, "setsockopt(SO_REUSEPORT)");
428 bzero(&addr, sizeof(addr));
429 addr.sin_family = AF_INET;
430 addr.sin_port = htons(port);
431 addr.sin_addr.s_addr = INADDR_ANY;
433 if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1)
436 if (listen(sock, 16) == -1)
443 do_accept(int sock, struct tls *ctx, struct pollfd *fds, struct client *clients)
446 struct sockaddr_in addr;
450 if ((fd = accept(sock, (struct sockaddr*)&addr, &len)) == -1) {
451 if (errno == EWOULDBLOCK)
458 for (i = 0; i < MAX_USERS; ++i) {
459 if (fds[i].fd == -1) {
460 bzero(&clients[i], sizeof(struct client));
461 if (tls_accept_socket(ctx, &clients[i].ctx, fd) == -1)
462 break; /* goodbye fd! */
465 fds[i].events = POLLIN;
467 clients[i].state = S_OPEN;
474 printf("too much clients. goodbye bro!\n");
479 goodbye(struct pollfd *pfd, struct client *c)
483 c->state = S_CLOSING;
485 ret = tls_close(c->ctx);
486 if (ret == TLS_WANT_POLLIN) {
487 pfd->events = POLLIN;
490 if (ret == TLS_WANT_POLLOUT) {
491 pfd->events = POLLOUT;
506 loop(struct tls *ctx, int sock)
509 struct client clients[MAX_USERS];
510 struct pollfd fds[MAX_USERS];
512 for (i = 0; i < MAX_USERS; ++i) {
514 fds[i].events = POLLIN;
515 bzero(&clients[i], sizeof(struct client));
521 if ((todo = poll(fds, MAX_USERS, INFTIM)) == -1)
524 for (i = 0; i < MAX_USERS; i++) {
525 assert(i < MAX_USERS);
527 if (fds[i].revents == 0)
530 if (fds[i].revents & (POLLERR|POLLNVAL))
531 err(1, "bad fd %d", fds[i].fd);
533 if (fds[i].revents & POLLHUP) {
534 goodbye(&fds[i], &clients[i]);
540 if (i == 0) { /* new client */
541 do_accept(sock, ctx, fds, clients);
545 handle(&fds[i], &clients[i]);
552 usage(const char *me)
555 "USAGE: %s [-h] [-c cert.pem] [-d docs] [-k key.pem]\n",
560 main(int argc, char **argv)
562 const char *cert = "cert.pem", *key = "key.pem", *dir = "docs";
563 struct tls *ctx = NULL;
564 struct tls_config *conf;
567 while ((ch = getopt(argc, argv, "c:d:hk:")) != -1) {
591 if ((conf = tls_config_new()) == NULL)
592 err(1, "tls_config_new");
594 if (tls_config_set_protocols(conf,
595 TLS_PROTOCOL_TLSv1_2 | TLS_PROTOCOL_TLSv1_3) == -1)
596 err(1, "tls_config_set_protocols");
598 if (tls_config_set_cert_file(conf, cert) == -1)
599 err(1, "tls_config_set_cert_file: %s", cert);
601 if (tls_config_set_key_file(conf, key) == -1)
602 err(1, "tls_config_set_key_file: %s", key);
604 if ((ctx = tls_server()) == NULL)
605 err(1, "tls_server");
607 if (tls_configure(ctx, conf) == -1)
608 errx(1, "tls_configure: %s", tls_error(ctx));
610 sock = make_socket(1965);
612 if ((dirfd = open(dir, O_RDONLY | O_DIRECTORY)) == -1)
613 err(1, "open: %s", dir);
615 if (unveil(dir, "r") == -1)
618 if (pledge("stdio rpath inet", "") == -1)
625 tls_config_free(conf);