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


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