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_LOGS "/var/twind/logs"
60 #define _PATH_TWIND_CERT "/etc/twind/twind.cert.pem"
61 #define _PATH_TWIND_KEY "/etc/twind/twind.key.pem"
62 #define _PATH_TWIND_PID_CHROOT "/var/twind/twind.pid"
63 #define _PATH_TWIND_PID "twind.pid"
65 static void organize_termination(void);
66 static void open_sockets(int[2], int);
67 void *get_in_addr(struct sockaddr *);
68 void* main_request_handler(void*);
69 int receive_gemini_request(SSL*, char *);
70 int handle_incoming_connections(int, int, SSL_CTX *);
71 void fork_main_process(int[2], SSL_CTX *);
72 SSL_CTX* initialize_tls_context(void);
73 int open_pid_file(void);
74 static void drop_root(void);
76 #if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) &&\
77 !defined(__DragonFly__)
78 void setproctitle(const char *, ...);
79 void setproctitle(const char *fmt, ...) {}
85 extern char *__progname;
87 fprintf(stderr, "usage: %s [-dfv] [-p port]\n", __progname);
92 signal_handler(int signal)
100 organize_termination();
103 fatalx("Unknown signal");
108 main(int argc, char *argv[])
110 SSL_CTX *sslctx = NULL;
111 int ch, fg_flag = 0, debug_flag = 0, verbose_flag = 0;
112 int tcpsock[2] = { -1, -1 }, port = 1965;
114 log_init(1, LOG_DAEMON); /* Log to stderr until daemonized. */
117 while ((ch = getopt(argc, argv, "dfp:vV")) != -1) {
132 fprintf(stderr, "Version %s\n", VERSION);
144 fatalx("need root privileges");
148 if (signal(SIGINT, signal_handler) == SIG_ERR)
150 if (signal(SIGTERM, signal_handler) == SIG_ERR)
152 if (signal(SIGHUP, signal_handler) == SIG_ERR)
155 open_sockets(tcpsock, port);
157 sslctx = initialize_tls_context();
161 log_init(debug_flag, LOG_DAEMON);
162 log_setverbose(verbose_flag);
166 if (pledge("stdio inet dns proc rpath wpath cpath", NULL) == -1)
168 #endif /* __OpenBSD__ */
171 if (daemon(0, 0) == -1)
172 fatalx("daemonizing failed");
176 fork_main_process(tcpsock, sslctx);
178 organize_termination();
184 organize_termination(void)
191 if (pledge("stdio", NULL) == -1)
193 #endif /* __OpenBSD__ */
195 log_debug("waiting for sub processes to terminate");
197 sub_pid = wait(NULL);
199 if (errno == ECHILD) {
200 /* All sub processes are terminated */
202 log_debug("twind turns to dust");
209 log_debug("Child %d terminated", sub_pid);
214 initialize_tls_context(void)
218 SSL_load_error_strings();
219 OpenSSL_add_all_algorithms();
221 sslctx = SSL_CTX_new(TLS_method());
223 fatalx("Cannot initialize TLS CTX structure");
225 SSL_CTX_set_ecdh_auto(sslctx, 1);
227 /* Gemini requires TLSv1.2 minimum */
228 if (SSL_CTX_set_min_proto_version(sslctx, TLS1_2_VERSION) != 1)
229 fatalx("Cannot set minimum TLS version");
231 if (SSL_CTX_use_certificate_file(sslctx, _PATH_TWIND_CERT, SSL_FILETYPE_PEM)
233 fatalx("Cannot load TLS certificate %s", _PATH_TWIND_CERT);
235 if (SSL_CTX_use_PrivateKey_file(sslctx, _PATH_TWIND_KEY, SSL_FILETYPE_PEM)
237 fatalx("Cannot load TLS private key %s", _PATH_TWIND_KEY);
243 get_in_addr(struct sockaddr *sa)
245 if (sa->sa_family == AF_INET)
246 return &(((struct sockaddr_in*)sa)->sin_addr);
248 return &(((struct sockaddr_in6*)sa)->sin6_addr);
252 handle_incoming_connections(int counter, int tcpsock, SSL_CTX *sslctx)
254 struct sockaddr_storage addr;
255 struct client_connection *cc;
256 char str[INET6_ADDRSTRLEN];
258 socklen_t len = sizeof(addr);
262 /* We can get rid of proc pledge here */
263 if (pledge("stdio inet dns rpath wpath cpath", NULL) == -1)
265 #endif /* __OpenBSD__ */
267 memset(str, 0, sizeof(str));
270 if (reload_log_files == 1) {
271 reload_log_files = 0;
272 log_debug("Re-open log files");
277 ret = accept(tcpsock, (struct sockaddr *)&addr, &len);
279 fatalx("Error when accepting connection");
281 cc = xmalloc(sizeof(struct client_connection));
283 inet_ntop(addr.ss_family, get_in_addr((struct sockaddr *)&addr), str, sizeof(str));
284 strlcpy(cc->client_addr, str, INET6_ADDRSTRLEN);
285 log_info("Connection from %s", cc->client_addr);
287 if ((cc->ssl_peer = SSL_new(sslctx)) == NULL) {
288 log_warn("Creating new TLS structure failed");
294 if (SSL_set_fd(cc->ssl_peer, ret) == 0) {
295 log_warn("TLS cannot set file descriptor");
296 SSL_free(cc->ssl_peer);
302 ssl_err = SSL_accept(cc->ssl_peer);
304 ERR_print_errors_fp(stderr);
305 log_warn("Fatal TLS error. Cannot accept TLS connection");
306 SSL_shutdown(cc->ssl_peer);
307 SSL_free(cc->ssl_peer);
311 } else if (ssl_err == 0) {
312 log_warn("TLS handshake not successful");
313 SSL_shutdown(cc->ssl_peer);
314 SSL_free(cc->ssl_peer);
320 log_debug("SSL connection using %s", SSL_get_cipher(cc->ssl_peer));
322 if (pthread_create(&thread_id, NULL, main_request_handler, ((void*)cc))
324 log_warn("Cannot create handling thread");
325 SSL_shutdown(cc->ssl_peer);
326 SSL_free(cc->ssl_peer);
332 if (pthread_join(thread_id, NULL) != 0) {
333 log_warn("Error while joining thread");
334 SSL_shutdown(cc->ssl_peer);
335 SSL_free(cc->ssl_peer);
341 SSL_shutdown(cc->ssl_peer);
342 SSL_free(cc->ssl_peer);
351 fork_main_process(int tcpsock[2], SSL_CTX *sslctx)
356 setproctitle("main");
358 /* Fork two main handler processes, one for IPv4, one for IPv6 */
359 for (i=0; i < 2; i++) {
360 if (tcpsock[i] == -1)
362 switch (pid = fork()) {
364 fatalx("Cannot fork() main IPv%d handler process", i == 0 ? 4 : 6);
366 log_debug("Main IPv%d handling process started: %d", i == 0 ? 4 : 6,
368 setproctitle("v%d %s", i == 0 ? 4 : 6, "handler");
369 handle_incoming_connections(i, tcpsock[i], sslctx);
376 main_request_handler(void *argp)
378 struct client_connection *cc = (struct client_connection *)argp;
379 char finalpath[MAXREQLEN];
380 char temp[MAXREQLEN];
381 char request[MAXREQLEN];
386 memset(finalpath, 0, sizeof(finalpath));
387 memset(request, 0, sizeof(request));
388 memset(temp, 0, sizeof(temp));
390 if (receive_gemini_request(cc->ssl_peer, request) < 0) {
391 log_warn("Receiving initial request failed");
395 ret = get_path_from_request(request, finalpath);
396 if (ret == -1) { /* Malformed request */
397 log_error(cc, "Malformed request");
398 send_non_success_response(cc->ssl_peer, STATUS_BAD_REQUEST);
400 } else if (ret == -2) { /* 404 */
401 log_error(cc, "Request file not found");
402 send_non_success_response(cc->ssl_peer, STATUS_NOT_FOUND);
406 if ((ext = get_file_extension(finalpath)) == NULL) {
407 log_debug("Cannot get file extension from %s", finalpath);
409 if ((mime = get_mime_type(ext)) == NULL)
410 log_debug("Cannot get MIME type for %s", ext);
413 log_access(cc, finalpath);
415 if (send_response(cc->ssl_peer, STATUS_SUCCESS, finalpath, mime) < 0) {
416 log_warn("Sending response to client failed");
427 * Gemini requests are a single CRLF-terminated line with the following structure:
431 * <URL> is a UTF-8 encoded absolute URL, including a scheme, of maximum length
435 receive_gemini_request(SSL *ssl_peer, char* request_buf)
437 if (SSL_read(ssl_peer, request_buf, MAXREQLEN) <= 0)
444 open_sockets(int tcpsock[2], int port)
446 struct sockaddr_in addr4;
447 struct sockaddr_in6 addr6;
448 struct sockaddr *addr;
452 memset(&addr4, 0, sizeof(addr4));
453 addr4.sin_family = AF_INET;
454 addr4.sin_port = htons(port);
455 addr4.sin_addr.s_addr = INADDR_ANY;
456 addr = (struct sockaddr*)&addr4;
459 if ((tcpsock[0] = socket(AF_INET, SOCK_STREAM, 0)) != -1) {
460 if (setsockopt(tcpsock[0], SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))
462 log_warn("setting SO_REUSEADDR on socket");
463 if (bind(tcpsock[0], addr, len) == -1) {
467 if (listen(tcpsock[0], 5) == -1) {
473 memset(&addr6, 0, sizeof(addr6));
474 addr6.sin6_family = AF_INET6;
475 addr6.sin6_port = htons(port);
476 addr6.sin6_addr = in6addr_any;
477 addr = (struct sockaddr*)&addr6;
480 if ((tcpsock[1] = socket(AF_INET6, SOCK_STREAM, 0)) != -1) {
481 if (setsockopt(tcpsock[1], SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))
483 log_warn("setting SO_REUSEADDR on socket");
484 if (bind(tcpsock[1], addr, len) == -1) {
488 if (listen(tcpsock[1], 5) == -1) {
494 if (tcpsock[0] == -1 && tcpsock[1] == -1) {
495 fatalx("Cannot bind to 0.0.0.0 or :: on Port 1965");
502 char buf[PID_BUF_SIZE];
503 char pid_path[MAXREQLEN];
506 snprintf(pid_path, MAXREQLEN, "%s/%s",
507 _PATH_TWIND_CHROOT, _PATH_TWIND_PID);
508 if ((fd = open(pid_path, O_CREAT|O_RDWR, 0600)) == -1)
509 fatalx("Cannot open PID file");
511 if (flock(fd, LOCK_EX|LOCK_NB) == -1)
512 fatalx("Cannot get lock on PID file. Another instance running?");
515 * We need to truncate the file since the new PID could be shorter than
516 * an old one in the file.
518 if (ftruncate(fd, 0) == -1)
519 fatalx("Cannot truncate PID file");
521 snprintf(buf, PID_BUF_SIZE, "%ld\n", (long) getpid());
522 if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf))
523 fatalx("Cannot write PID file");
533 if (!(pw = getpwnam(TWIND_USER)))
534 fatalx("Cannot find user entry for %s", TWIND_USER);
537 fatalx("Cannot get UID entry for %s", TWIND_USER);
540 if (unveil(_PATH_TWIND_CERT, "r") == -1)
542 if (unveil(_PATH_TWIND_KEY, "r") == -1)
544 if (unveil(_PATH_TWIND_CHROOT, "r") == -1)
546 if (unveil(_PATH_TWIND_PID_CHROOT, "r") == -1)
548 if (unveil(_PATH_TWIND_LOGS, "cw") == -1)
550 if (unveil(NULL, NULL) == -1)
552 #endif /* __OpenBSD__ */
554 if (chroot(_PATH_TWIND_CHROOT) == -1)
555 fatalx("chroot() to %s failed", _PATH_TWIND_CHROOT);
556 if (chdir("/") == -1)
557 fatalx("chdir() failed");
559 if (setgroups(1, &pw->pw_gid) == -1)
560 fatalx("Cannot set group access list");
561 if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1)
562 fatalx("Cannot set GUID to %d", pw->pw_gid);
563 if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
564 fatalx("Cannot set UID to %d", pw->pw_uid);