geomyidae

A small C-based gopherd. (gopher://bitreich.org/1/scm/geomyidae)
git clone git://r-36.net/geomyidae
Log | Files | Refs | README | LICENSE

ind.c (12847B)


      1 /*
      2  * Copy me if you can.
      3  * by 20h
      4  */
      5 
      6 #ifdef __linux__
      7 	#define _GNU_SOURCE
      8 #endif
      9 
     10 #include <libgen.h>
     11 #include <unistd.h>
     12 #include <stdarg.h>
     13 #include <string.h>
     14 #include <memory.h>
     15 #include <fcntl.h>
     16 #include <stdio.h>
     17 #include <stdlib.h>
     18 #include <stdint.h>
     19 #include <time.h>
     20 #include <netdb.h>
     21 #include <sys/socket.h>
     22 #include <sys/stat.h>
     23 #include <netinet/in.h>
     24 #include <netinet/tcp.h>
     25 #include <arpa/inet.h>
     26 #include <sys/ioctl.h>
     27 #include <limits.h>
     28 #include <errno.h>
     29 
     30 #define PAGE_SHIFT 12
     31 #define PAGE_SIZE (1UL << PAGE_SHIFT)
     32 #define BLOCK_SIZE ((PAGE_SIZE * 16) - 1)
     33 
     34 #include "arg.h"
     35 #include "ind.h"
     36 #include "handlr.h"
     37 
     38 /*
     39  * Be careful, to look at handlerequest(), in case you add any executing
     40  * handler, so nocgi will be valuable.
     41  *
     42  * All files are handled as binary, without a following ".\r\n". Proper
     43  * encoding lines with beginning "." would be a really slow function, not
     44  * adding any feature to gopher. Clients can check for the types
     45  * requested and assume ".\r\n" or leave it out.
     46  *
     47  * Geomyidae only adds ".\r\n" in all kind of menus, like dir listings
     48  * or dcgi files. There the case of some maybe future "." item type needs
     49  * to be handled, if really used.
     50  */
     51 
     52 #include "filetypes.h"
     53 
     54 int
     55 pendingbytes(int sock)
     56 {
     57 	int pending, rval;
     58 
     59 	pending = 0;
     60 	rval = 0;
     61 #if defined(TIOCOUTQ) && !defined(__OpenBSD__)
     62 	rval = ioctl(sock, TIOCOUTQ, &pending);
     63 #else
     64 #ifdef SIOCOUTQ
     65 	rval = ioctl(sock, SIOCOUTQ, &pending);
     66 #endif
     67 #endif
     68 
     69 	if (rval != 0)
     70 		return 0;
     71 
     72 	return pending;
     73 }
     74 
     75 void
     76 waitforpendingbytes(int sock)
     77 {
     78 	int npending = 0, opending = 0;
     79 	useconds_t trytime = 10;
     80 
     81 	/*
     82 	 * Wait until there is nothing pending or the connection stalled
     83 	 * (nothing was sent) for around 40 seconds. Beware, trytime is
     84 	 * an exponential wait.
     85 	 */
     86 	while ((npending = pendingbytes(sock)) > 0 && trytime < 20000000) {
     87 		if (opending != 0) {
     88 			if (opending != npending) {
     89 				trytime = 10;
     90 			} else {
     91 				/*
     92 				 * Exponentially increase the usleep
     93 				 * waiting time to not waste CPU
     94 				 * resources.
     95 				 */
     96 				trytime += trytime;
     97 			}
     98 		}
     99 		opending = npending;
    100 
    101 		usleep(trytime);
    102 	}
    103 }
    104 
    105 #ifdef __linux__
    106 int
    107 xsplice(int fd, int sock)
    108 {
    109 	int pipefd[2], ret = 0;
    110 	ssize_t nread, nwritten;
    111 	off_t in_offset = 0;
    112 
    113 	if (pipe(pipefd) < 0)
    114 		return -1;
    115 
    116 	do {
    117 		nread = splice(fd, &in_offset, pipefd[1], NULL,
    118 			BLOCK_SIZE, SPLICE_F_MOVE | SPLICE_F_MORE);
    119 
    120 		if (nread <= 0) {
    121 			ret = nread < 0 ? -1 : 0;
    122 			goto out;
    123 		}
    124 
    125 		nwritten  = splice(pipefd[0], NULL, sock, NULL, BLOCK_SIZE,
    126 				SPLICE_F_MOVE | SPLICE_F_MORE);
    127 		if (nwritten < 0) {
    128 			ret = -1;
    129 			goto out;
    130 		}
    131 	} while (nwritten > 0);
    132 
    133 out:
    134 	close(pipefd[0]);
    135 	close(pipefd[1]);
    136 
    137 	return ret;
    138 }
    139 #endif
    140 
    141 int
    142 xsendfile(int fd, int sock)
    143 {
    144 	struct stat st;
    145 	char *sendb, *sendi;
    146 	size_t bufsiz = BUFSIZ;
    147 	int len, sent, optval;
    148 
    149 #ifdef splice
    150 	return xsplice(fd, sock);
    151 #endif
    152 
    153 	USED(optval);
    154 
    155 	/*
    156 	 * The story of xsendfile.
    157 	 *
    158 	 * Once upon a time, here you saw a big #ifdef switch source of
    159 	 * many ways how to send files with special functions on
    160 	 * different operating systems. All of this was removed, because
    161 	 * operating systems and kernels got better over time,
    162 	 * simplifying what you need and reducing corner cases.
    163 	 *
    164 	 * For example Linux sendfile(2) sounds nice and faster, but
    165 	 * the function is different on every OS and slower to the now
    166 	 * used approach of read(2) and write(2).
    167 	 *
    168 	 * If you ever consider changing this to some "faster" approach,
    169 	 * consider benchmarks on all platforms.
    170 	 */
    171 
    172 	if (fstat(fd, &st) >= 0) {
    173 		if ((bufsiz = st.st_blksize) < BUFSIZ)
    174 			bufsiz = BUFSIZ;
    175 	}
    176 
    177 	sendb = xmalloc(bufsiz);
    178 	while ((len = read(fd, sendb, bufsiz)) > 0) {
    179 		sendi = sendb;
    180 		while (len > 0) {
    181 			if ((sent = write(sock, sendi, len)) < 0) {
    182 				free(sendb);
    183 				return -1;
    184 			}
    185 			len -= sent;
    186 			sendi += sent;
    187 		}
    188 	}
    189 	free(sendb);
    190 
    191 	return 0;
    192 }
    193 
    194 void *
    195 xcalloc(size_t nmemb, size_t size)
    196 {
    197 	void *p;
    198 
    199 	if (!(p = calloc(nmemb, size))) {
    200 		perror("calloc");
    201 		exit(1);
    202 	}
    203 
    204 	return p;
    205 }
    206 
    207 void *
    208 xmalloc(size_t size)
    209 {
    210 	void *p;
    211 
    212 	if (!(p = malloc(size))) {
    213 		perror("malloc");
    214 		exit(1);
    215 	}
    216 
    217 	return p;
    218 }
    219 
    220 void *
    221 xrealloc(void *ptr, size_t size)
    222 {
    223 	if (!(ptr = realloc(ptr, size))) {
    224 		perror("realloc");
    225 		exit(1);
    226 	}
    227 
    228 	return ptr;
    229 }
    230 
    231 char *
    232 xstrdup(const char *str)
    233 {
    234 	char *ret;
    235 
    236 	if (!(ret = strdup(str))) {
    237 		perror("strdup");
    238 		exit(1);
    239 	}
    240 
    241 	return ret;
    242 }
    243 
    244 filetype *
    245 gettype(char *filename)
    246 {
    247 	char *end;
    248 	int i;
    249 
    250 	end = strrchr(filename, '.');
    251 	if (end == NULL)
    252 		return &type[0];
    253 	end++;
    254 
    255 	for (i = 0; type[i].end != NULL; i++)
    256 		if (!strcasecmp(end, type[i].end))
    257 			return &type[i];
    258 
    259 	return &type[0];
    260 }
    261 
    262 void
    263 gph_freeelem(gphelem *e)
    264 {
    265 	if (e != NULL) {
    266 		if (e->e != NULL) {
    267 			for (;e->num > 0; e->num--)
    268 				if (e->e[e->num - 1] != NULL)
    269 					free(e->e[e->num - 1]);
    270 			free(e->e);
    271 		}
    272 		free(e);
    273 	}
    274 	return;
    275 }
    276 
    277 void
    278 gph_freeindex(gphindex *i)
    279 {
    280 	if (i != NULL) {
    281 		if (i->n != NULL) {
    282 			for (;i->num > 0; i->num--)
    283 				gph_freeelem(i->n[i->num - 1]);
    284 			free(i->n);
    285 		}
    286 		free(i);
    287 	}
    288 
    289 	return;
    290 }
    291 
    292 void
    293 gph_addelem(gphelem *e, char *s)
    294 {
    295 	e->num++;
    296 	e->e = xrealloc(e->e, sizeof(char *) * e->num);
    297 	e->e[e->num - 1] = xstrdup(s);
    298 
    299 	return;
    300 }
    301 
    302 gphelem *
    303 gph_getadv(char *str)
    304 {
    305 	char *b, *e, *o, *bo;
    306 	gphelem *ret;
    307 
    308 	ret = xcalloc(1, sizeof(gphelem));
    309 
    310 	if (strchr(str, '\t')) {
    311 		gph_addelem(ret, "i");
    312 		gph_addelem(ret, "Happy helping ☃ here: You tried to "
    313 			"output a spurious TAB character. This will "
    314 			"break gopher. Please review your scripts. "
    315 			"Have a nice day!");
    316 		gph_addelem(ret, "Err");
    317 		gph_addelem(ret, "server");
    318 		gph_addelem(ret, "port");
    319 
    320 		return ret;
    321 	}
    322 
    323 	/* Check for escape sequence. */
    324 	if (str[0] == '[' && str[1] != '|') {
    325 		o = xstrdup(str);
    326 		b = o + 1;
    327 		bo = b;
    328 		while ((e = strchr(bo, '|')) != NULL) {
    329 			if (e != bo && e[-1] == '\\') {
    330 				memmove(&e[-1], e, strlen(e));
    331 				bo = e;
    332 				continue;
    333 			}
    334 			*e = '\0';
    335 			e++;
    336 			gph_addelem(ret, b);
    337 			b = e;
    338 			bo = b;
    339 		}
    340 
    341 		e = strchr(b, ']');
    342 		if (e != NULL) {
    343 			*e = '\0';
    344 			gph_addelem(ret, b);
    345 		}
    346 		free(o);
    347 
    348 		if (ret->e != NULL && ret->e[0] != NULL &&
    349 				ret->e[0][0] != '\0' && ret->num == 5) {
    350 			return ret;
    351 		}
    352 
    353 		/* Invalid entry: Give back the whole line. */
    354 		gph_freeelem(ret);
    355 		ret = xcalloc(1, sizeof(gphelem));
    356 	}
    357 
    358 	gph_addelem(ret, "i");
    359 	/* Jump over escape sequence. */
    360 	if (str[0] == '[' && str[1] == '|')
    361 		str += 2;
    362 	gph_addelem(ret, str);
    363 	gph_addelem(ret, "Err");
    364 	gph_addelem(ret, "server");
    365 	gph_addelem(ret, "port");
    366 
    367 	return ret;
    368 }
    369 
    370 void
    371 gph_addindex(gphindex *idx, gphelem *el)
    372 {
    373 	idx->num++;
    374 	idx->n = xrealloc(idx->n, sizeof(gphelem *) * idx->num);
    375 	idx->n[idx->num - 1] = el;
    376 
    377 	return;
    378 }
    379 
    380 gphindex *
    381 gph_scanfile(char *fname)
    382 {
    383 	char *ln = NULL;
    384 	size_t linesiz = 0;
    385 	ssize_t n;
    386 	FILE *fp;
    387 	gphindex *ret;
    388 	gphelem *el;
    389 
    390 	if (!(fp = fopen(fname, "r")))
    391 		return NULL;
    392 
    393 	ret = xcalloc(1, sizeof(gphindex));
    394 
    395 	while ((n = getline(&ln, &linesiz, fp)) > 0) {
    396 		if (ln[n - 1] == '\n')
    397 			ln[--n] = '\0';
    398 		el = gph_getadv(ln);
    399 		if (el == NULL)
    400 			continue;
    401 
    402 		gph_addindex(ret, el);
    403 	}
    404 	if (ferror(fp))
    405 		perror("getline");
    406 	free(ln);
    407 	fclose(fp);
    408 
    409 	if (ret->n == NULL) {
    410 		free(ret);
    411 		return NULL;
    412 	}
    413 
    414 	return ret;
    415 }
    416 
    417 int
    418 gph_printelem(int fd, gphelem *el, char *file, char *base, char *addr, char *port)
    419 {
    420 	char *path, *p, *argbase, buf[PATH_MAX+1], *argp, *realbase, *rpath;
    421 	int len, blen;
    422 
    423 	if (!strcmp(el->e[3], "server")) {
    424 		free(el->e[3]);
    425 		el->e[3] = xstrdup(addr);
    426 	}
    427 	if (!strcmp(el->e[4], "port")) {
    428 		free(el->e[4]);
    429 		el->e[4] = xstrdup(port);
    430 	}
    431 
    432 	/*
    433 	 * Ignore if the path is from base, if it might be some h type with
    434 	 * some URL and ignore various types that have different semantics,
    435 	 * do not point to some file or directory.
    436 	 */
    437 	if ((el->e[2][0] != '\0'
    438 	    && el->e[2][0] != '/' /* Absolute Request. */
    439 	    && el->e[0][0] != 'i' /* Informational item. */
    440 	    && el->e[0][0] != '2' /* CSO server */
    441 	    && el->e[0][0] != '3' /* Error */
    442 	    && el->e[0][0] != '8' /* Telnet */
    443 	    && el->e[0][0] != 'w' /* Selector is direct URI. */
    444 	    && el->e[0][0] != 'T') && /* tn3270 */
    445 	    !(el->e[0][0] == 'h' && !strncmp(el->e[2], "URL:", 4))) {
    446 		path = file + strlen(base);
    447 
    448 		/* Strip off original gph file name. */
    449 		if ((p = strrchr(path, '/'))) {
    450 			len = strlen(path) - strlen(basename(path));
    451 		} else {
    452 			len = strlen(path);
    453 		}
    454 
    455 		/* Strip off arguments for realpath. */
    456 		argbase = strchr(el->e[2], '?');
    457 		if (argbase != NULL) {
    458 			blen = argbase - el->e[2];
    459 		} else {
    460 			blen = strlen(el->e[2]);
    461 		}
    462 
    463 		/*
    464 		 * Print everything together. Realpath will resolve it.
    465 		 */
    466 		snprintf(buf, sizeof(buf), "%s%.*s%.*s", base, len,
    467 			path, blen, el->e[2]);
    468 
    469 		if ((rpath = realpath(buf, NULL)) &&
    470 				(realbase = realpath(*base? base : "/", NULL)) &&
    471 				!strncmp(realbase, rpath, strlen(realbase))) {
    472 			p = rpath + (*base? strlen(realbase) : 0);
    473 
    474 			/*
    475 			 * Do not forget to re-add arguments which were
    476 			 * stripped off.
    477 			 */
    478 			argp = smprintf("%s%s", *p? p : "/", argbase? argbase : "");
    479 
    480 			free(el->e[2]);
    481 			el->e[2] = argp;
    482 			free(realbase);
    483 		}
    484 		if (rpath != NULL)
    485 			free(rpath);
    486 	}
    487 
    488 	if (dprintf(fd, "%.1s%s\t%s\t%s\t%s\r\n", el->e[0], el->e[1], el->e[2],
    489 			el->e[3], el->e[4]) < 0) {
    490 		perror("printgphelem: dprintf");
    491 		return -1;
    492 	}
    493 	return 0;
    494 }
    495 
    496 char *
    497 smprintf(char *fmt, ...)
    498 {
    499         va_list fmtargs;
    500         char *ret;
    501         int size;
    502 
    503         va_start(fmtargs, fmt);
    504         size = vsnprintf(NULL, 0, fmt, fmtargs);
    505         va_end(fmtargs);
    506 
    507         ret = xcalloc(1, ++size);
    508         va_start(fmtargs, fmt);
    509         vsnprintf(ret, size, fmt, fmtargs);
    510         va_end(fmtargs);
    511 
    512         return ret;
    513 }
    514 
    515 char *
    516 reverselookup(char *host)
    517 {
    518 	struct in_addr hoststr;
    519 	struct hostent *client;
    520 	char *rethost;
    521 
    522 	rethost = NULL;
    523 
    524 	if (inet_pton(AF_INET, host, &hoststr)) {
    525 		client = gethostbyaddr((const void *)&hoststr,
    526 				sizeof(hoststr), AF_INET);
    527 		if (client != NULL)
    528 			rethost = xstrdup(client->h_name);
    529 	}
    530 
    531 	if (rethost == NULL)
    532 		rethost = xstrdup(host);
    533 
    534 	return rethost;
    535 }
    536 
    537 void
    538 setcgienviron(char *file, char *path, char *port, char *base, char *args,
    539 		char *sear, char *ohost, char *chost, char *bhost, int istls,
    540 		char *sel, char *traverse)
    541 {
    542 	/*
    543 	 * TODO: Clean environment from possible unsafe environment variables.
    544 	 *       But then it is the responsibility of the script writer.
    545 	 */
    546 	unsetenv("AUTH_TYPE");
    547 	unsetenv("CONTENT_LENGTH");
    548 	unsetenv("CONTENT_TYPE");
    549 	setenv("GATEWAY_INTERFACE", "CGI/1.1", 1);
    550 	/* TODO: Separate, if run like rest.dcgi. */
    551 	setenv("PATH_INFO", path+strlen(base), 1);
    552 	setenv("PATH_TRANSLATED", path, 1);
    553 
    554 	setenv("QUERY_STRING", args, 1);
    555 	setenv("SELECTOR", sel, 1);
    556 	setenv("REQUEST", sel, 1);
    557 	setenv("TRAVERSAL", traverse, 1);
    558 
    559 	setenv("REMOTE_ADDR", chost, 1);
    560 	/*
    561 	 * Don't do a reverse lookup on every call. Only do when needed, in
    562 	 * the script. The RFC allows us to set the IP to the value.
    563 	 */
    564 	setenv("REMOTE_HOST", chost, 1);
    565 	/* Please do not implement identd here. */
    566 	unsetenv("REMOTE_IDENT");
    567 	unsetenv("REMOTE_USER");
    568 	/* Make PHP happy. */
    569 	setenv("REDIRECT_STATUS", "", 1);
    570 	/*
    571 	 * Only GET is possible in gopher. POST emulation would be really
    572 	 * ugly.
    573 	 */
    574 	setenv("REQUEST_METHOD", "GET", 1);
    575 	setenv("SCRIPT_NAME", file, 1);
    576 	setenv("SERVER_NAME", ohost, 1);
    577 	setenv("SERVER_PORT", port, 1);
    578 	setenv("SERVER_LISTEN_NAME", bhost, 1);
    579 	if (istls) {
    580 		setenv("SERVER_PROTOCOL", "gophers/1.0", 1);
    581 	} else {
    582 		setenv("SERVER_PROTOCOL", "gopher/1.0", 1);
    583 	}
    584 	setenv("SERVER_SOFTWARE", "geomyidae", 1);
    585 
    586 	setenv("X_GOPHER_SEARCH", sear, 1);
    587 	/* legacy compatibility */
    588 	setenv("SEARCHREQUEST", sear, 1);
    589 
    590 	if (istls) {
    591 		setenv("GOPHERS", "on", 1);
    592 		setenv("HTTPS", "on", 1);
    593 	} else {
    594 		unsetenv("GOPHERS");
    595 		unsetenv("HTTPS");
    596 	}
    597 
    598 }
    599 
    600 char *
    601 humansize(off_t n)
    602 {
    603 	static char buf[16];
    604 	const char postfixes[] = "BKMGTPE";
    605 	double size;
    606 	int i = 0;
    607 
    608 	for (size = n; size >= 1024 && i < strlen(postfixes); i++)
    609 		size /= 1024;
    610 
    611 	if (!i) {
    612 		snprintf(buf, sizeof(buf), "%ju%c", (uintmax_t)n,
    613 				postfixes[i]);
    614 	} else {
    615 		snprintf(buf, sizeof(buf), "%.1f%c", size, postfixes[i]);
    616 	}
    617 
    618 	return buf;
    619 }
    620 
    621 char *
    622 humantime(const time_t *clock)
    623 {
    624 	static char buf[32];
    625 	struct tm *tm;
    626 
    627 	tm = localtime(clock);
    628 	strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M %Z", tm);
    629 
    630 	return buf;
    631 }
    632 
    633 void
    634 lingersock(int sock)
    635 {
    636 	struct linger lingerie;
    637 	int j;
    638 
    639 	/*
    640 	 * On close only wait for at maximum 60 seconds for all data to be
    641 	 * transmitted before forcefully closing the connection.
    642 	 */
    643 	lingerie.l_onoff = 1;
    644 	lingerie.l_linger = 60;
    645 	setsockopt(sock, SOL_SOCKET, SO_LINGER,
    646 			&lingerie, sizeof(lingerie));
    647 
    648 	/*
    649 	 * Force explicit flush of buffers using TCP_NODELAY.
    650 	 */
    651 	j = 1;
    652 	setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &j, sizeof(int));
    653 	waitforpendingbytes(sock);
    654 	j = 0;
    655 	setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &j, sizeof(int));
    656 
    657 	return;
    658 }
    659