Blob


1 /*
2 * Copyright (c) 2020 Omar Polo <op@omarpolo.com>
3 *
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.
7 *
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.
15 */
17 #include <sys/socket.h>
18 #include <sys/stat.h>
20 #include <netinet/in.h>
22 #include <assert.h>
23 #include <err.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <poll.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <tls.h>
31 #include <unistd.h>
33 #ifndef __OpenBSD__
34 # define pledge(a, b) 0
35 # define unveil(a, b) 0
36 #endif /* __OpenBSD__ */
38 #ifndef INFTIM
39 # define INFTIM -1
40 #endif /* INFTIM */
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 */
45 #define PATHBUF 2048
47 #define FILEBUF 1024
49 #define SUCCESS 20
50 #define NOT_FOUND 51
51 #define BAD_REQUEST 59
53 #ifndef MAX_USERS
54 #define MAX_USERS 64
55 #endif
57 enum {
58 S_OPEN,
59 S_INITIALIZING,
60 S_SENDING,
61 S_CLOSING,
62 };
64 struct client {
65 struct tls *ctx;
66 int state;
67 int code;
68 const char *meta;
69 int fd;
70 };
72 int dirfd;
74 char *url_after_proto(char*);
75 char *url_start_of_request(char*);
76 int url_trim(char*);
77 void adjust_path(char*);
78 int path_isdir(char*);
80 int start_reply(struct pollfd*, struct client*, int, const char*);
81 int isdir(int);
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);
87 int make_soket(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*);
94 char *
95 url_after_proto(char *url)
96 {
97 char *s;
98 const char *proto = "gemini";
99 const char *marker = "://";
101 if ((s = strstr(url, marker)) == NULL)
102 return url;
104 /* not a gemini:// URL */
106 if (s - strlen(proto) < url)
107 return NULL;
108 /* TODO: */
109 /* if (strcmp(s - strlen(proto), proto)) */
110 /* return NULL; */
112 /* a valid gemini:// URL */
113 return s + strlen(marker);
116 char *
117 url_start_of_request(char *url)
119 char *s, *t;
121 if ((s = url_after_proto(url)) == NULL)
122 return NULL;
124 if ((t = strstr(s, "/")) == NULL)
125 return s + strlen(s);
126 return t;
129 int
130 url_trim(char *url)
132 const char *e = "\r\n";
133 char *s;
135 if ((s = strstr(url, e)) == NULL)
136 return 0;
137 s[0] = '\0';
138 s[1] = '\0';
140 if (s[2] != '\0') {
141 fprintf(stderr, "the request was longer than 1024 bytes\n");
142 return 0;
145 return 1;
148 void
149 adjust_path(char *path)
151 char *s;
152 size_t len;
154 /* /.. -> / */
155 len = strlen(path);
156 if (len >= 3) {
157 if (!strcmp(&path[len-3], "/..")) {
158 path[len-2] = '\0';
162 /* if the path is only `..` trim out and exit */
163 if (!strcmp(path, "..")) {
164 path[0] = '\0';
165 return;
168 /* remove every ../ in the path */
169 while (1) {
170 if ((s = strstr(path, "../")) == NULL)
171 return;
172 memmove(s, s+3, strlen(s)+1); /* copy also the \0 */
176 int
177 path_isdir(char *path)
179 if (*path == '\0')
180 return 1;
181 return path[strlen(path)-1] == '/';
184 int
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 */
188 int len;
189 int ret;
191 client->code = code;
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;
200 return 0;
203 if (ret == TLS_WANT_POLLOUT) {
204 pfd->events = POLLOUT;
205 return 0;
208 return 1;
211 int
212 isdir(int fd)
214 struct stat sb;
216 if (fstat(fd, &sb) == -1) {
217 warn("fstat");
218 return 1; /* we'll probably fail later on anyway */
221 return S_ISDIR(sb.st_mode);
224 void
225 send_file(char *path, struct pollfd *fds, struct client *client)
227 char fpath[PATHBUF];
228 char buf[FILEBUF];
229 size_t i;
230 ssize_t t, w;
232 if (client->fd == -1) {
233 assert(path != NULL);
235 bzero(fpath, sizeof(fpath));
237 if (*path != '.')
238 fpath[0] = '.';
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"))
244 return;
245 goodbye(fds, client);
246 return;
249 if (isdir(client->fd)) {
250 warnx("%s is a directory, trying %s/index.gmi", fpath, fpath);
251 close(client->fd);
252 client->fd = -1;
253 send_dir(fpath, fds, client);
254 return;
257 /* assume it's a text/gemini file */
258 if (!start_reply(fds, client, SUCCESS, "text/gemini"))
259 return;
262 while (1) {
263 w = read(client->fd, buf, sizeof(buf));
264 if (w == -1)
265 warn("read");
266 if (w == 0 || w == -1) {
267 goodbye(fds, client);
268 return;
271 t = w;
272 i = 0;
274 while (w > 0) {
275 t = tls_write(client->ctx, buf+i, w);
276 switch (t) {
277 case -1:
278 warnx("tls_write: %s", tls_error(client->ctx));
279 goodbye(fds, client);
280 return;
282 case TLS_WANT_POLLIN:
283 case TLS_WANT_POLLOUT:
284 fds->events = (t == TLS_WANT_POLLIN)
285 ? POLLIN
286 : POLLOUT;
287 return;
289 default:
290 w -= t;
291 i += t;
292 break;
298 void
299 send_dir(char *path, struct pollfd *fds, struct client *client)
301 char fpath[PATHBUF];
302 size_t len;
304 bzero(fpath, PATHBUF);
306 if (*path != '.')
307 fpath[0] = '.';
309 /* this cannot fail since sizeof(fpath) > maxlen of path */
310 strlcat(fpath, path, PATHBUF);
311 len = strlen(fpath);
313 /* add a trailing / in case. */
314 if (fpath[len-1] != '/') {
315 fpath[len] = '/';
318 strlcat(fpath, "index.gmi", sizeof(fpath));
320 send_file(fpath, fds, client);
323 void
324 handle(struct pollfd *fds, struct client *client)
326 char buf[GEMINI_URL_LEN];
327 char *path;
329 switch (client->state) {
330 case S_OPEN:
331 bzero(buf, GEMINI_URL_LEN);
332 switch (tls_read(client->ctx, buf, sizeof(buf)-1)) {
333 case -1:
334 warnx("tls_read: %s", tls_error(client->ctx));
335 goodbye(fds, client);
336 return;
338 case TLS_WANT_POLLIN:
339 fds->events = POLLIN;
340 return;
342 case TLS_WANT_POLLOUT:
343 fds->events = POLLOUT;
344 return;
347 if (!url_trim(buf)) {
348 if (!start_reply(fds, client, BAD_REQUEST, "bad request"))
349 return;
350 goodbye(fds, client);
351 return;
354 if ((path = url_start_of_request(buf)) == NULL) {
355 if (!start_reply(fds, client, BAD_REQUEST, "bad request"))
356 return;
357 goodbye(fds, client);
358 return;
361 adjust_path(path);
362 fprintf(stderr, "requested path: %s\n", path);
364 if (path_isdir(path))
365 send_dir(path, fds, client);
366 else
367 send_file(path, fds, client);
368 break;
370 case S_INITIALIZING:
371 if (!start_reply(fds, client, client->code, client->meta))
372 return;
374 if (client->code != SUCCESS) {
375 /* we don't need a body */
376 goodbye(fds, client);
377 return;
380 client->state = S_SENDING;
382 /* fallthrough */
384 case S_SENDING:
385 send_file(NULL, fds, client);
386 break;
388 case S_CLOSING:
389 goodbye(fds, client);
390 break;
392 default:
393 /* unreachable */
394 abort();
398 void
399 mark_nonblock(int fd)
401 int flags;
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)");
409 int
410 make_socket(int port)
412 int sock, v;
413 struct sockaddr_in addr;
415 if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
416 err(1, "socket");
418 v = 1;
419 if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &v, sizeof(v)) == -1)
420 err(1, "setsockopt(SO_REUSEADDR)");
422 v = 1;
423 if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &v, sizeof(v)) == -1)
424 err(1, "setsockopt(SO_REUSEPORT)");
426 mark_nonblock(sock);
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)
434 err(1, "bind");
436 if (listen(sock, 16) == -1)
437 err(1, "listen");
439 return sock;
442 void
443 do_accept(int sock, struct tls *ctx, struct pollfd *fds, struct client *clients)
445 int i, fd;
446 struct sockaddr_in addr;
447 socklen_t len;
449 len = sizeof(addr);
450 if ((fd = accept(sock, (struct sockaddr*)&addr, &len)) == -1) {
451 if (errno == EWOULDBLOCK)
452 return;
453 err(1, "accept");
456 mark_nonblock(fd);
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! */
464 fds[i].fd = fd;
465 fds[i].events = POLLIN;
467 clients[i].state = S_OPEN;
468 clients[i].fd = -1;
470 return;
474 printf("too much clients. goodbye bro!\n");
475 close(fd);
478 void
479 goodbye(struct pollfd *pfd, struct client *c)
481 ssize_t ret;
483 c->state = S_CLOSING;
485 ret = tls_close(c->ctx);
486 if (ret == TLS_WANT_POLLIN) {
487 pfd->events = POLLIN;
488 return;
490 if (ret == TLS_WANT_POLLOUT) {
491 pfd->events = POLLOUT;
492 return;
495 tls_free(c->ctx);
496 c->ctx = NULL;
498 if (c->fd != -1)
499 close(c->fd);
501 close(pfd->fd);
502 pfd->fd = -1;
505 void
506 loop(struct tls *ctx, int sock)
508 int i, todo;
509 struct client clients[MAX_USERS];
510 struct pollfd fds[MAX_USERS];
512 for (i = 0; i < MAX_USERS; ++i) {
513 fds[i].fd = -1;
514 fds[i].events = POLLIN;
515 bzero(&clients[i], sizeof(struct client));
518 fds[0].fd = sock;
520 for (;;) {
521 if ((todo = poll(fds, MAX_USERS, INFTIM)) == -1)
522 err(1, "poll");
524 for (i = 0; i < MAX_USERS; i++) {
525 assert(i < MAX_USERS);
527 if (fds[i].revents == 0)
528 continue;
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]);
535 continue;
538 todo--;
540 if (i == 0) { /* new client */
541 do_accept(sock, ctx, fds, clients);
542 continue;
545 handle(&fds[i], &clients[i]);
551 void
552 usage(const char *me)
554 fprintf(stderr,
555 "USAGE: %s [-h] [-c cert.pem] [-d docs] [-k key.pem]\n",
556 me);
559 int
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;
565 int sock, ch;
567 while ((ch = getopt(argc, argv, "c:d:hk:")) != -1) {
568 switch (ch) {
569 case 'c':
570 cert = optarg;
571 break;
573 case 'd':
574 dir = optarg;
575 break;
577 case 'h':
578 usage(*argv);
579 return 0;
581 case 'k':
582 key = optarg;
583 break;
585 default:
586 usage(*argv);
587 return 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)
616 err(1, "unveil");
618 if (pledge("stdio rpath inet", "") == -1)
619 err(1, "pledge");
621 loop(ctx, sock);
623 close(sock);
624 tls_free(ctx);
625 tls_config_free(conf);