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 (11959B)


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