2 * Copyright (c) 2021 Matthias Schmidt <xhr@giessen.ccc.de>
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.
20 #include <sys/queue.h>
21 #include <sys/socket.h>
22 #include <sys/syslog.h>
23 #include <sys/types.h>
26 #include <arpa/inet.h>
29 #include <netinet/in.h>
31 #include <openssl/err.h>
32 #include <openssl/ssl.h>
48 #if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) &&\
49 !defined(__DragonFly__)
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, ...) {}
83 extern char *__progname;
85 fprintf(stderr, "usage: %s [-dfv] [-p port]\n", __progname);
90 signal_handler(int signal)
95 organize_termination();
98 fatalx("Unknown signal");
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. */
112 while ((ch = getopt(argc, argv, "dfp:vV")) != -1) {
127 fprintf(stderr, "Version %s\n", VERSION);
139 fatalx("need root privileges");
143 if (signal(SIGINT, signal_handler) == SIG_ERR)
145 if (signal(SIGTERM, signal_handler) == SIG_ERR)
148 open_sockets(tcpsock, port);
150 sslctx = initialize_tls_context();
154 log_init(debug_flag, LOG_DAEMON);
155 log_setverbose(verbose_flag);
158 if (pledge("stdio inet dns proc rpath", NULL) == -1)
160 #endif /* __OpenBSD__ */
162 fork_main_process(tcpsock, sslctx);
165 if (daemon(0, 0) == -1)
166 fatalx("daemonizing failed");
168 organize_termination();
174 organize_termination(void)
178 log_debug("waiting for sub processes to terminate");
180 sub_pid = wait(NULL);
182 if (errno == ECHILD) {
183 /* All sub processes are terminated */
184 log_debug("twind turns to dust");
194 initialize_tls_context(void)
198 SSL_load_error_strings();
199 OpenSSL_add_all_algorithms();
201 sslctx = SSL_CTX_new(TLS_method());
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)
213 fatalx("Cannot load TLS certificate %s", _PATH_TWIND_CERT);
215 if (SSL_CTX_use_PrivateKey_file(sslctx, _PATH_TWIND_KEY, SSL_FILETYPE_PEM)
217 fatalx("Cannot load TLS private key %s", _PATH_TWIND_KEY);
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];
231 socklen_t len = sizeof(addr);
235 /* We can get rid of proc pledge here */
236 if (pledge("stdio inet dns rpath", NULL) == -1)
238 #endif /* __OpenBSD__ */
241 ret = accept(tcpsock, (struct sockaddr *)&addr, &len);
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");
263 if (SSL_set_fd(ssl_peer, ret) == 0) {
264 log_warn("TLS cannot set file descriptor");
270 ssl_err = SSL_accept(ssl_peer);
272 ERR_print_errors_fp(stderr);
273 log_warn("Fatal TLS error. Cannot accept TLS connection");
274 SSL_shutdown(ssl_peer);
278 } else if (ssl_err == 0) {
279 log_warn("TLS handshake not successful");
280 SSL_shutdown(ssl_peer);
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))
290 log_warn("Cannot create handling thread");
294 pthread_join(thread_id, NULL);
296 SSL_shutdown(ssl_peer);
305 fork_main_process(int tcpsock[2], SSL_CTX *sslctx)
310 /* Fork two main handler processes, one for IPv4, one for IPv6 */
311 for (i=0; i < 2; i++) {
312 if (tcpsock[i] == -1)
314 switch (pid = fork()) {
316 fatalx("Cannot fork() main IPv%d handler process", i == 0 ? 4 : 6);
318 log_debug("Main IPv%d handling process started: %d", i == 0 ? 4 : 6,
320 setproctitle("v%d %s", i == 0 ? 4 : 6, "handler");
321 handle_incoming_connections(i, tcpsock[i], sslctx);
328 main_request_handler(void *argp)
330 SSL *ssl_peer = (SSL*)argp;
331 char finalpath[MAXREQLEN];
332 char temp[MAXREQLEN];
333 char request[MAXREQLEN];
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);
348 } else if (ret == -2) { /* 404 */
349 send_non_success_response(ssl_peer, STATUS_NOT_FOUND);
353 if ((ext = get_file_extension(finalpath)) == NULL) {
354 log_debug("Cannot get file extension from %s", finalpath);
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");
372 * Gemini requests are a single CRLF-terminated line with the following structure:
376 * <URL> is a UTF-8 encoded absolute URL, including a scheme, of maximum length
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");
390 open_sockets(int tcpsock[2], int port)
392 struct sockaddr_in addr4;
393 struct sockaddr_in6 addr6;
394 struct sockaddr *addr;
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;
405 if ((tcpsock[0] = socket(AF_INET, SOCK_STREAM, 0)) != -1) {
406 if (setsockopt(tcpsock[0], SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))
408 log_warn("setting SO_REUSEADDR on socket");
409 if (bind(tcpsock[0], addr, len) == -1) {
413 if (listen(tcpsock[0], 5) == -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;
426 if ((tcpsock[1] = socket(AF_INET6, SOCK_STREAM, 0)) != -1) {
427 if (setsockopt(tcpsock[1], SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))
429 log_warn("setting SO_REUSEADDR on socket");
430 if (bind(tcpsock[1], addr, len) == -1) {
434 if (listen(tcpsock[1], 5) == -1) {
440 if (tcpsock[0] == -1 && tcpsock[1] == -1) {
441 fatalx("Cannot bind to 0.0.0.0 or :: on Port 1965");
448 char buf[PID_BUF_SIZE];
449 char pid_path[MAXREQLEN];
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?");
461 * We need to truncate the file since the new PID could be shorter than
462 * an old one in the file.
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");
479 if (!(pw = getpwnam(TWIND_USER)))
480 fatalx("Cannot find user entry for %s", TWIND_USER);
483 fatalx("Cannot get UID entry for %s", TWIND_USER);
486 if (unveil(_PATH_TWIND_CERT, "r") == -1)
488 if (unveil(_PATH_TWIND_KEY, "r") == -1)
490 if (unveil(_PATH_TWIND_CHROOT, "r") == -1)
492 if (unveil(_PATH_TWIND_PID_CHROOT, "r") == -1)
494 if (unveil(NULL, NULL) == -1)
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);