tskrtt

Simple libev based gopher server
git clone https://git.inz.fi/tskrtt/
Log | Files | Refs | README

main.c (7281B)


      1 #ifdef USE_CHROOT
      2 #define _DEFAULT_SOURCE
      3 #define _BSD_SOURCE
      4 #endif
      5 #define _POSIX_C_SOURCE 200809L
      6 
      7 #include <netinet/in.h>
      8 #include <sys/socket.h>
      9 
     10 #include <fcntl.h>
     11 #include <grp.h>
     12 #include <limits.h>
     13 #include <netdb.h>
     14 #include <pwd.h>
     15 #include <stdarg.h>
     16 #include <stdbool.h>
     17 #include <stdio.h>
     18 #include <stdlib.h>
     19 #include <string.h>
     20 #include <time.h>
     21 #include <unistd.h>
     22 
     23 #include <ev.h>
     24 
     25 #ifdef USE_TLS
     26 #include <tls.h>
     27 #endif
     28 
     29 #include "arg.h"
     30 #include "client.h"
     31 #include "common.h"
     32 
     33 char dfl_hostname[128];
     34 const char dfl_port[] = "70";
     35 const char dfl_gopherroot[] = "/var/gopher";
     36 
     37 const char *hostname = dfl_hostname;
     38 const char *gopherroot = dfl_gopherroot;
     39 const char *oport = NULL;
     40 
     41 char *argv0;
     42 int logfd = -1;
     43 
     44 struct listener {
     45 	ev_io watcher;
     46 	int fd;
     47 #ifdef USE_TLS
     48 	struct tls *tlsctx;
     49 #endif
     50 };
     51 
     52 struct listener listen_watcher;
     53 ev_timer timeout_watcher;
     54 
     55 static void logprintf(const char *fmt, ...)
     56 {
     57 	va_list args;
     58 
     59 	if (logfd < 0)
     60 		return;
     61 
     62 	va_start(args, fmt);
     63 	vdprintf(logfd, fmt, args);
     64 	va_end(args);
     65 }
     66 
     67 void accesslog(struct client *c, const char *resource, const char *qs, const char *ss)
     68 {
     69 	char tbuf[64] = "";
     70 	char abuf[INET6_ADDRSTRLEN];
     71 
     72 	if (logfd < 0)
     73 		return;
     74 
     75 	getnameinfo((struct sockaddr *)&c->addr, c->addrlen, abuf, sizeof(abuf), NULL, 0, NI_NUMERICHOST);
     76 	strftime(tbuf, sizeof(tbuf), "%d/%b/%Y:%H:%M:%S %z", localtime(&(time_t){ time(NULL) }));
     77 
     78 	logprintf("%s - - [%s] \"%s\" \"%s\" \"%s\"\n",
     79 	    abuf, tbuf, resource, qs ? qs : "", ss ? ss : "");
     80 }
     81 
     82 char *cleanup_path(char *path, char **basename, size_t *pathlen)
     83 {
     84 	size_t parts[512];
     85 	size_t np = 0;
     86 	size_t w = 0;
     87 	size_t r = 0;
     88 
     89 	while (path[r] == '/' && r < *pathlen)
     90 		r++;
     91 
     92 	if (r == *pathlen) {
     93 		*pathlen = 0;
     94 		if (basename)
     95 			*basename = path;
     96 		return path;
     97 	}
     98 
     99 	while (r < *pathlen) {
    100 		if (!path[r])
    101 			return NULL;
    102 		if (path[r] == '/') {
    103 			if (w)
    104 				path[w++] = '/';
    105 			do {
    106 				r++;
    107 			} while (path[r] == '/');
    108 			continue;
    109 		} else if (path[r] == '.') {
    110 			if (r + 1 == *pathlen || path[r + 1] == '/') {
    111 				for (r++; r < *pathlen && path[r] == '/'; r++);
    112 				continue;
    113 			} else if (path[r + 1] == '.') {
    114 				if (r + 2 == *pathlen || path[r + 2] == '/') {
    115 					if (!np)
    116 						return NULL;
    117 					w = parts[--np];
    118 					if (r + 2 == *pathlen && w)
    119 						w--;
    120 					for (r += 2; r < *pathlen && path[r] == '/'; r++);
    121 					continue;
    122 				}
    123 			}
    124 		}
    125 		parts[np++] = w;
    126 		while (r < *pathlen && path[r] && path[r] != '/')
    127 			path[w++] = path[r++];
    128 	}
    129 
    130 	if (basename) {
    131 		if (np)
    132 			*basename = path + parts[np - 1];
    133 		else
    134 			*basename = path;
    135 	}
    136 
    137 	if (w && path[w - 1] == '/')
    138 		w--;
    139 	*pathlen = w;
    140 	return path;
    141 }
    142 
    143 static void listen_cb(EV_P_ ev_io *w, int revents)
    144 {
    145 	struct sockaddr_storage addr;
    146 	socklen_t addrlen = sizeof(addr);
    147 	struct listener *l = (struct listener *)w;
    148 	int fd;
    149 	struct client *c;
    150 
    151 	(void)revents;
    152 
    153 	fd = accept(l->fd, (struct sockaddr *)&addr, &addrlen);
    154 
    155 	if (fd < 0)
    156 		return;
    157 
    158 	c = client_new(EV_A_ fd, (struct sockaddr *)&addr, addrlen
    159 #ifdef USE_TLS
    160 			, listen_watcher.tlsctx
    161 #endif
    162 			);
    163 	if (!c) {
    164 		close(fd);
    165 		return;
    166 	}
    167 }
    168 
    169 static void usage(void)
    170 {
    171 	fprintf(stderr,
    172 		"usage: %s [-46d] "
    173 #ifdef USE_TLS
    174 		"[-t key cert] "
    175 #endif
    176 		"[-l logfile] [-b rootdir] [-p port] [-o outport] "
    177 		"[-u user] [-g group] [-h host] [-i listen address]\n",
    178 		argv0);
    179 	exit(1);
    180 }
    181 
    182 static void croak(const char *s)
    183 {
    184 	perror(s);
    185 	exit(1);
    186 }
    187 
    188 int main (int argc, char *argv[])
    189 {
    190 #ifdef USE_TLS
    191 	struct tls_config *tlscfg;
    192 	const char *keyfile = NULL;
    193 	const char *certfile = NULL;
    194 #endif
    195 	char gopherrootbuf[PATH_MAX];
    196 	struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_flags = AI_PASSIVE, .ai_socktype = SOCK_STREAM };
    197 	struct addrinfo *addrs;
    198 	struct addrinfo *ai;
    199 #if EV_MULTIPLICITY
    200 	EV_P = EV_DEFAULT;
    201 #endif
    202 	const char *bindto = NULL;
    203 	const char *port = dfl_port;
    204 	const char *user = NULL;
    205 	const char *group = NULL;
    206 	const char *logfile = NULL;
    207 	int lfd = -1;
    208 	bool dofork = true;
    209 #ifdef USE_CHROOT
    210 	bool dochroot = false;
    211 #endif
    212 	uid_t uid;
    213 	gid_t gid;
    214 
    215 	signal(SIGPIPE, SIG_IGN);
    216 
    217 	ARGBEGIN {
    218 		case '4':
    219 			hints.ai_family = AF_INET;
    220 			break;
    221 		case '6':
    222 			hints.ai_family = AF_INET6;
    223 			break;
    224 #ifdef USE_CHROOT
    225 		case 'c':
    226 			dochroot = true;
    227 			break;
    228 #endif
    229 		case 'd':
    230 			dofork = false;
    231 			break;
    232 #ifdef USE_TLS
    233 		case 't':
    234 			keyfile = EARGF(usage());
    235 			certfile = EARGF(usage());
    236 			break;
    237 #endif
    238 		case 'h':
    239 			hostname = EARGF(usage());
    240 			break;
    241 		case 'i':
    242 			bindto = EARGF(usage());
    243 			break;
    244 		case 'b':
    245 			gopherroot = EARGF(usage());
    246 			break;
    247 		case 'p':
    248 			port = EARGF(usage());
    249 			break;
    250 		case 'P':
    251 			oport = EARGF(usage());
    252 			break;
    253 		case 'u':
    254 			user = EARGF(usage());
    255 			break;
    256 		case 'g':
    257 			group = EARGF(usage());
    258 			break;
    259 		case 'l':
    260 			logfile = EARGF(usage());
    261 			break;
    262 		default:
    263 			usage();
    264 			break;
    265 	} ARGEND;
    266 
    267 	if (!oport)
    268 		oport = port;
    269 
    270 	if (hostname == dfl_hostname) {
    271 		if (bindto)
    272 			hostname = bindto;
    273 		else
    274 			gethostname(dfl_hostname, sizeof(dfl_hostname));
    275 	}
    276 
    277 	if (getaddrinfo(bindto, port, &hints, &addrs))
    278 		croak("Resolving bind address failed");
    279 
    280 	for (ai = addrs; ai; ai = ai->ai_next) {
    281 		lfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
    282 
    283 		if (lfd < 0)
    284 			continue;
    285 
    286 		setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));
    287 
    288 		if (!bind(lfd, ai->ai_addr, ai->ai_addrlen))
    289 			break;
    290 
    291 		close(lfd);
    292 	}
    293 
    294 	if (logfile && (logfd = open(logfile, O_WRONLY | O_APPEND | O_CREAT, 0644)) < 0)
    295 		croak("Log opening failed");
    296 
    297 	if (!ai)
    298 		croak("Bind failed");
    299 
    300 	freeaddrinfo(addrs);
    301 
    302 	if (listen(lfd, 10))
    303 		croak("Listen failed");
    304 
    305 #ifdef USE_TLS
    306 	if (keyfile && certfile) {
    307 		tls_init();
    308 		listen_watcher.tlsctx = tls_server();
    309 		if (!(tlscfg = tls_config_new()) ||
    310 		    tls_config_set_key_file(tlscfg, keyfile) ||
    311 		    tls_config_set_cert_file(tlscfg, certfile) ||
    312 		    tls_configure(listen_watcher.tlsctx, tlscfg))
    313 			croak("TLS configuration error");
    314 		tls_config_free(tlscfg);
    315 	} else
    316 		listen_watcher.tlsctx = NULL;
    317 #endif
    318 
    319 	if (*gopherroot != '/' && getcwd(gopherrootbuf, sizeof(gopherrootbuf))) {
    320 		size_t l = strlen(gopherrootbuf);
    321 		int ll = snprintf(gopherrootbuf + l, sizeof(gopherrootbuf) - l, "/%s", gopherroot);
    322 		if ((l += ll - 1) < sizeof(gopherrootbuf) - 1 && cleanup_path(gopherrootbuf + 1, NULL, &l)) {
    323 			gopherrootbuf[l + 1] = '\0';
    324 			gopherroot = gopherrootbuf;
    325 		}
    326 	}
    327 
    328 	if (user) {
    329 		struct passwd *u = getpwnam(user);
    330 		if (!u)
    331 			croak("No such user");
    332 		uid = u->pw_uid;
    333 	}
    334 
    335 	if (group) {
    336 		struct group *g = getgrnam(group);
    337 		if (!g)
    338 			croak("No such group");
    339 		gid = g->gr_gid;
    340 	}
    341 
    342 #ifdef USE_CHROOT
    343 	if (dochroot) {
    344 		if (chroot(gopherroot))
    345 			croak("chroot failed");
    346 		gopherroot = strcpy(gopherrootbuf, "/");
    347 	}
    348 #endif
    349 
    350 	if (group && setgid(gid))
    351 		croak("setgid failed");
    352 	if (user && setuid(uid))
    353 		croak("setuid failed");
    354 
    355 	if (dofork) {
    356 		if (fork()) {
    357 			close(lfd);
    358 			return 0;
    359 		}
    360 		setsid();
    361 		if (fork()) {
    362 			close(lfd);
    363 			return 0;
    364 		}
    365 		close(STDIN_FILENO);
    366 		close(STDOUT_FILENO);
    367 		close(STDERR_FILENO);
    368 	}
    369 
    370 	listen_watcher.fd = lfd;
    371 	ev_io_init(&listen_watcher.watcher, listen_cb, lfd, EV_READ);
    372 	ev_io_start(EV_A_ &listen_watcher.watcher);
    373 
    374 	ev_run(EV_A_ 0);
    375 
    376 #ifdef USE_TLS
    377 	if (listen_watcher.tlsctx)
    378 		tls_close(listen_watcher.tlsctx);
    379 #endif
    380 	close(listen_watcher.fd);
    381 
    382 	return 0;
    383 }