Blob


1 /*
2 * Copyright (c) 2021 Matthias Schmidt <xhr@giessen.ccc.de>
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 #define _GNU_SOURCE
19 #include <sys/file.h>
20 #include <sys/queue.h>
21 #include <sys/socket.h>
22 #include <sys/syslog.h>
23 #include <sys/types.h>
24 #include <sys/wait.h>
26 #include <arpa/inet.h>
28 #include <net/if.h>
29 #include <netinet/in.h>
31 #include <openssl/err.h>
32 #include <openssl/ssl.h>
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <limits.h>
37 #include <netdb.h>
38 #include <pthread.h>
39 #include <pwd.h>
40 #include <signal.h>
41 #include <syslog.h>
42 #include <stdarg.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
48 #if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) &&\
49 !defined(__DragonFly__)
50 #include <grp.h>
51 #endif /* __BSD__ */
53 #include "log.h"
54 #include "twind.h"
56 #define PID_BUF_SIZE 100
57 #define TWIND_USER "_twind"
58 #define _PATH_TWIND_CHROOT "/var/twind"
59 #define _PATH_TWIND_CERT "/etc/twind/twind.cert.pem"
60 #define _PATH_TWIND_KEY "/etc/twind/twind.key.pem"
61 #define _PATH_TWIND_PID_CHROOT "/var/twind/twind.pid"
62 #define _PATH_TWIND_PID "twind.pid"
64 static void organize_termination(void);
65 static void open_sockets(int[2], int);
66 void* main_request_handler(void*);
67 void receive_gemini_request(SSL*, char *);
68 int handle_incoming_connections(int, int, SSL_CTX *);
69 void fork_main_process(int[2], SSL_CTX *);
70 SSL_CTX* initialize_tls_context(void);
71 int open_pid_file(void);
72 static void drop_root(void);
74 #if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) &&\
75 !defined(__DragonFly__)
76 void setproctitle(const char *, ...);
77 void setproctitle(const char *fmt, ...) {}
78 #endif /* __BSD__ */
80 static void
81 usage(void)
82 {
83 extern char *__progname;
85 fprintf(stderr, "usage: %s [-dfv] [-p port]\n", __progname);
86 exit(-1);
87 }
89 static void
90 signal_handler(int signal)
91 {
92 switch (signal) {
93 case SIGINT:
94 case SIGTERM:
95 organize_termination();
96 break;
97 default:
98 fatalx("Unknown signal");
99 }
102 int
103 main(int argc, char *argv[])
105 SSL_CTX *sslctx = NULL;
106 int ch, fg_flag = 0, debug_flag = 0, verbose_flag = 0;
107 int tcpsock[2] = { -1, -1 }, port = 1965;
109 log_init(1, LOG_DAEMON); /* Log to stderr until daemonized. */
110 log_setverbose(1);
112 while ((ch = getopt(argc, argv, "dfp:vV")) != -1) {
113 switch(ch) {
114 case 'd':
115 debug_flag = 1;
116 break;
117 case 'f':
118 fg_flag = 1;
119 break;
120 case 'p':
121 port = atoi(optarg);
122 break;
123 case 'v':
124 verbose_flag = 1;
125 break;
126 case 'V':
127 fprintf(stderr, "Version %s\n", VERSION);
128 exit(-1);
129 default:
130 usage();
131 break;
135 argc -= optind;
136 argv += optind;
138 if (geteuid())
139 fatalx("need root privileges");
141 open_pid_file();
143 if (signal(SIGINT, signal_handler) == SIG_ERR)
144 fatalx("signal");
145 if (signal(SIGTERM, signal_handler) == SIG_ERR)
146 fatalx("signal");
148 open_sockets(tcpsock, port);
150 sslctx = initialize_tls_context();
152 drop_root();
154 log_init(debug_flag, LOG_DAEMON);
155 log_setverbose(verbose_flag);
157 #ifdef __OpenBSD__
158 if (pledge("stdio inet dns proc rpath", NULL) == -1)
159 fatalx("pledge");
160 #endif /* __OpenBSD__ */
162 fork_main_process(tcpsock, sslctx);
164 if (!fg_flag)
165 if (daemon(0, 0) == -1)
166 fatalx("daemonizing failed");
168 organize_termination();
170 return 0;
173 static void
174 organize_termination(void)
176 pid_t sub_pid;
178 log_debug("waiting for sub processes to terminate");
179 for (;;) {
180 sub_pid = wait(NULL);
181 if (sub_pid == -1) {
182 if (errno == ECHILD) {
183 /* All sub processes are terminated */
184 log_debug("twind turns to dust");
185 exit(0);
186 } else {
187 fatalx("wait");
193 SSL_CTX*
194 initialize_tls_context(void)
196 SSL_CTX *sslctx;
198 SSL_load_error_strings();
199 OpenSSL_add_all_algorithms();
201 sslctx = SSL_CTX_new(TLS_method());
202 if (sslctx == NULL)
203 fatalx("Cannot initialize TLS CTX structure");
205 SSL_CTX_set_ecdh_auto(sslctx, 1);
207 /* Gemini requires TLSv1.2 minimum */
208 if (SSL_CTX_set_min_proto_version(sslctx, TLS1_2_VERSION) != 1)
209 fatalx("Cannot set minimum TLS version");
211 if (SSL_CTX_use_certificate_file(sslctx, _PATH_TWIND_CERT, SSL_FILETYPE_PEM)
212 != 1)
213 fatalx("Cannot load TLS certificate %s", _PATH_TWIND_CERT);
215 if (SSL_CTX_use_PrivateKey_file(sslctx, _PATH_TWIND_KEY, SSL_FILETYPE_PEM)
216 != 1)
217 fatalx("Cannot load TLS private key %s", _PATH_TWIND_KEY);
219 return sslctx;
222 int
223 handle_incoming_connections(int counter, int tcpsock, SSL_CTX *sslctx)
225 struct sockaddr_storage addr;
226 struct sockaddr_in clientaddr;
227 struct sockaddr_in6 clientaddr6;
228 char str[INET6_ADDRSTRLEN];
229 pthread_t thread_id;
230 SSL *ssl_peer;
231 socklen_t len = sizeof(addr);
232 int ret, ssl_err;
234 #ifdef __OpenBSD__
235 /* We can get rid of proc pledge here */
236 if (pledge("stdio inet dns rpath", NULL) == -1)
237 fatalx("pledge");
238 #endif /* __OpenBSD__ */
240 while (1) {
241 ret = accept(tcpsock, (struct sockaddr *)&addr, &len);
242 if (ret < 0)
243 fatalx("Error when accepting connection");
245 getpeername(ret, (struct sockaddr *)&clientaddr, &len);
246 if (clientaddr.sin_family == AF_INET) {
247 if (inet_ntop(AF_INET, &clientaddr.sin_addr, str, sizeof(str)))
248 log_info("Connection from %s on Port %d",
249 str, ntohs(clientaddr.sin_port));
250 } else if (clientaddr.sin_family == AF_INET6) {
251 getpeername(ret, (struct sockaddr *)&clientaddr6, &len);
252 if (inet_ntop(AF_INET6, &clientaddr6.sin6_addr, str, sizeof(str)))
253 log_info("Connection from %s on Port %d",
254 str, ntohs(clientaddr6.sin6_port));
257 if ((ssl_peer = SSL_new(sslctx)) == NULL) {
258 log_warn("Creating new TLS structure failed");
259 close(ret);
260 continue;
263 if (SSL_set_fd(ssl_peer, ret) == 0) {
264 log_warn("TLS cannot set file descriptor");
265 SSL_free(ssl_peer);
266 close(ret);
267 continue;
270 ssl_err = SSL_accept(ssl_peer);
271 if (ssl_err < 0) {
272 ERR_print_errors_fp(stderr);
273 log_warn("Fatal TLS error. Cannot accept TLS connection");
274 SSL_shutdown(ssl_peer);
275 SSL_free(ssl_peer);
276 close(ret);
277 continue;
278 } else if (ssl_err == 0) {
279 log_warn("TLS handshake not successful");
280 SSL_shutdown(ssl_peer);
281 SSL_free(ssl_peer);
282 close(ret);
283 continue;
286 log_debug("SSL connection using %s\n", SSL_get_cipher (ssl_peer));
288 if (pthread_create(&thread_id, NULL, main_request_handler, ((void*)ssl_peer))
289 != 0) {
290 log_warn("Cannot create handling thread");
291 return -1;
294 pthread_join(thread_id, NULL);
296 SSL_shutdown(ssl_peer);
297 SSL_free(ssl_peer);
298 close(ret);
301 return 0;
304 void
305 fork_main_process(int tcpsock[2], SSL_CTX *sslctx)
307 pid_t pid;
308 int i;
310 /* Fork two main handler processes, one for IPv4, one for IPv6 */
311 for (i=0; i < 2; i++) {
312 if (tcpsock[i] == -1)
313 continue;
314 switch (pid = fork()) {
315 case -1:
316 fatalx("Cannot fork() main IPv%d handler process", i == 0 ? 4 : 6);
317 case 0:
318 log_debug("Main IPv%d handling process started: %d", i == 0 ? 4 : 6,
319 getpid());
320 setproctitle("v%d %s", i == 0 ? 4 : 6, "handler");
321 handle_incoming_connections(i, tcpsock[i], sslctx);
322 exit(0);
327 void *
328 main_request_handler(void *argp)
330 SSL *ssl_peer = (SSL*)argp;
331 char finalpath[MAXREQLEN];
332 char temp[MAXREQLEN];
333 char request[MAXREQLEN];
334 char *ext = NULL;
335 char *mime = NULL;
336 int ret;
338 memset(finalpath, 0, sizeof(finalpath));
339 memset(request, 0, sizeof(request));
340 memset(temp, 0, sizeof(temp));
342 receive_gemini_request(ssl_peer, request);
344 ret = get_path_from_request(request, finalpath);
345 if (ret == -1) { /* Malformed request */
346 send_non_success_response(ssl_peer, STATUS_BAD_REQUEST);
347 return NULL;
348 } else if (ret == -2) { /* 404 */
349 send_non_success_response(ssl_peer, STATUS_NOT_FOUND);
350 return NULL;
353 if ((ext = get_file_extension(finalpath)) == NULL) {
354 log_debug("Cannot get file extension from %s", finalpath);
355 } else {
356 if ((mime = get_mime_type(ext)) == NULL)
357 log_debug("Cannot get MIME type for %s", ext);
360 if (send_response(ssl_peer, STATUS_SUCCESS, finalpath, mime) < 0) {
361 log_warn("Sending response to client failed");
362 return NULL;
365 free(ext);
366 free(mime);
368 return NULL;
371 /*
372 * Gemini requests are a single CRLF-terminated line with the following structure:
374 * <URL><CR><LF>
376 * <URL> is a UTF-8 encoded absolute URL, including a scheme, of maximum length
377 * 1024 bytes.
378 */
379 void
380 receive_gemini_request(SSL *ssl_peer, char* request_buf)
382 if (SSL_read(ssl_peer, request_buf, MAXREQLEN) <= 0) {
383 log_warn("initial recv failed");
386 return;
389 static void
390 open_sockets(int tcpsock[2], int port)
392 struct sockaddr_in addr4;
393 struct sockaddr_in6 addr6;
394 struct sockaddr *addr;
395 socklen_t len;
396 int opt = 1;
398 memset(&addr4, 0, sizeof(addr4));
399 addr4.sin_family = AF_INET;
400 addr4.sin_port = htons(port);
401 addr4.sin_addr.s_addr = INADDR_ANY;
402 addr = (struct sockaddr*)&addr4;
403 len = sizeof(addr4);
405 if ((tcpsock[0] = socket(AF_INET, SOCK_STREAM, 0)) != -1) {
406 if (setsockopt(tcpsock[0], SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))
407 == -1)
408 log_warn("setting SO_REUSEADDR on socket");
409 if (bind(tcpsock[0], addr, len) == -1) {
410 close(tcpsock[0]);
411 tcpsock[0] = -1;
413 if (listen(tcpsock[0], 5) == -1) {
414 close(tcpsock[0]);
415 tcpsock[0] = -1;
419 memset(&addr6, 0, sizeof(addr6));
420 addr6.sin6_family = AF_INET6;
421 addr6.sin6_port = htons(port);
422 addr6.sin6_addr = in6addr_any;
423 addr = (struct sockaddr*)&addr6;
424 len = sizeof(addr6);
426 if ((tcpsock[1] = socket(AF_INET6, SOCK_STREAM, 0)) != -1) {
427 if (setsockopt(tcpsock[1], SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))
428 == -1)
429 log_warn("setting SO_REUSEADDR on socket");
430 if (bind(tcpsock[1], addr, len) == -1) {
431 close(tcpsock[1]);
432 tcpsock[1] = -1;
434 if (listen(tcpsock[1], 5) == -1) {
435 close(tcpsock[1]);
436 tcpsock[1] = -1;
440 if (tcpsock[0] == -1 && tcpsock[1] == -1) {
441 fatalx("Cannot bind to 0.0.0.0 or :: on Port 1965");
445 int
446 open_pid_file(void)
448 char buf[PID_BUF_SIZE];
449 char pid_path[MAXREQLEN];
450 int fd;
452 snprintf(pid_path, MAXREQLEN, "%s/%s",
453 _PATH_TWIND_CHROOT, _PATH_TWIND_PID);
454 if ((fd = open(pid_path, O_CREAT|O_RDWR, 0600)) == -1)
455 fatalx("Cannot open PID file");
457 if (flock(fd, LOCK_EX|LOCK_NB) == -1)
458 fatalx("Cannot get lock on PID file. Another instance running?");
460 /*
461 * We need to truncate the file since the new PID could be shorter than
462 * an old one in the file.
463 */
464 if (ftruncate(fd, 0) == -1)
465 fatalx("Cannot truncate PID file");
467 snprintf(buf, PID_BUF_SIZE, "%ld\n", (long) getpid());
468 if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf))
469 fatalx("Cannot write PID file");
471 return fd;
474 static void
475 drop_root(void)
477 struct passwd *pw;
479 if (!(pw = getpwnam(TWIND_USER)))
480 fatalx("Cannot find user entry for %s", TWIND_USER);
482 if (!pw->pw_uid)
483 fatalx("Cannot get UID entry for %s", TWIND_USER);
485 #ifdef __OpenBSD__
486 if (unveil(_PATH_TWIND_CERT, "r") == -1)
487 fatalx("unveil");
488 if (unveil(_PATH_TWIND_KEY, "r") == -1)
489 fatalx("unveil");
490 if (unveil(_PATH_TWIND_CHROOT, "r") == -1)
491 fatalx("unveil");
492 if (unveil(_PATH_TWIND_PID_CHROOT, "r") == -1)
493 fatalx("unveil");
494 if (unveil(NULL, NULL) == -1)
495 fatalx("unveil");
496 #endif /* __OpenBSD__ */
498 if (chroot(_PATH_TWIND_CHROOT) == -1)
499 fatalx("chroot() to %s failed", _PATH_TWIND_CHROOT);
500 if (chdir("/") == -1)
501 fatalx("chdir() failed");
503 if (setgroups(1, &pw->pw_gid) == -1)
504 fatalx("Cannot set group access list");
505 if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1)
506 fatalx("Cannot set GUID to %d", pw->pw_gid);
507 if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
508 fatalx("Cannot set UID to %d", pw->pw_uid);