sacc

sacc(omys), simple console gopher client (mirror)
git clone https://git.parazyd.org/sacc
Log | Files | Refs | LICENSE

sacc.c (18263B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include <ctype.h>
      3 #include <errno.h>
      4 #include <fcntl.h>
      5 #include <limits.h>
      6 #include <locale.h>
      7 #include <netdb.h>
      8 #include <netinet/in.h>
      9 #include <signal.h>
     10 #include <stdarg.h>
     11 #include <stdio.h>
     12 #include <stdlib.h>
     13 #include <string.h>
     14 #include <unistd.h>
     15 #include <wchar.h>
     16 #include <sys/socket.h>
     17 #include <sys/stat.h>
     18 #include <sys/types.h>
     19 #include <sys/wait.h>
     20 
     21 #include "common.h"
     22 #include "tls.h"
     23 
     24 #include "config.h"
     25 
     26 static char *mainurl;
     27 static Item *mainentry;
     28 static int devnullfd;
     29 static int parent = 1;
     30 static int interactive;
     31 static int dotls = 0;
     32 static struct tls *tlsctx = NULL;
     33 
     34 static void (*diag)(char *fmt, ...);
     35 
     36 void
     37 stddiag(char *fmt, ...)
     38 {
     39 	va_list arg;
     40 
     41 	va_start(arg, fmt);
     42 	vfprintf(stderr, fmt, arg);
     43 	va_end(arg);
     44 	fputc('\n', stderr);
     45 }
     46 
     47 void
     48 die(const char *fmt, ...)
     49 {
     50 	va_list arg;
     51 
     52 	va_start(arg, fmt);
     53 	vfprintf(stderr, fmt, arg);
     54 	va_end(arg);
     55 	fputc('\n', stderr);
     56 
     57 	exit(1);
     58 }
     59 
     60 static ssize_t
     61 read_wrap(int s, void *buf, size_t bs)
     62 {
     63 	if (tlsctx) {
     64 		ssize_t r = TLS_WANT_POLLIN;
     65 		while (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT)
     66 			r = tls_read(tlsctx, buf, bs);
     67 		return r;
     68 	}
     69 	return read(s, buf, bs);
     70 }
     71 
     72 static ssize_t
     73 write_wrap(int fd, const void *buf, size_t nbyte)
     74 {
     75 	if (tlsctx) {
     76 		ssize_t r = TLS_WANT_POLLIN;
     77 		while (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT)
     78 			r = tls_write(tlsctx, buf, nbyte);
     79 		return r;
     80 	}
     81 	return write(fd, buf, nbyte);
     82 }
     83 
     84 static int
     85 close_wrap(int fd)
     86 {
     87 	if (tlsctx) {
     88 		tls_close(tlsctx);
     89 		tls_reset(tlsctx);
     90 	}
     91 	return close(fd);
     92 }
     93 
     94 #ifdef NEED_ASPRINTF
     95 int
     96 asprintf(char **s, const char *fmt, ...)
     97 {
     98 	va_list ap;
     99 	int n;
    100 
    101 	va_start(ap, fmt);
    102 	n = vsnprintf(NULL, 0, fmt, ap);
    103 	va_end(ap);
    104 
    105 	if (n == INT_MAX || !(*s = malloc(++n)))
    106 		return -1;
    107 
    108 	va_start(ap, fmt);
    109 	vsnprintf(*s, n, fmt, ap);
    110 	va_end(ap);
    111 
    112 	return n;
    113 }
    114 #endif /* NEED_ASPRINTF */
    115 
    116 #ifdef NEED_STRCASESTR
    117 char *
    118 strcasestr(const char *h, const char *n)
    119 {
    120 	size_t i;
    121 
    122 	if (!n[0])
    123 		return (char *)h;
    124 
    125 	for (; *h; ++h) {
    126 		for (i = 0; n[i] && tolower((unsigned char)n[i]) ==
    127 		            tolower((unsigned char)h[i]); ++i)
    128 			;
    129 		if (n[i] == '\0')
    130 			return (char *)h;
    131 	}
    132 
    133 	return NULL;
    134 }
    135 #endif /* NEED_STRCASESTR */
    136 
    137 /* print `len' columns of characters. */
    138 size_t
    139 mbsprint(const char *s, size_t len)
    140 {
    141 	wchar_t wc;
    142 	size_t col = 0, i, slen;
    143 	const char *p;
    144 	int rl, pl, w;
    145 
    146 	if (!len)
    147 		return col;
    148 
    149 	slen = strlen(s);
    150 	for (i = 0; i < slen; i += rl) {
    151 		rl = mbtowc(&wc, s + i, slen - i < 4 ? slen - i : 4);
    152 		if (rl == -1) {
    153 			/* reset state */
    154 			mbtowc(NULL, NULL, 0);
    155 			p = "\xef\xbf\xbd"; /* replacement character */
    156 			pl = 3;
    157 			rl = w = 1;
    158 		} else {
    159 			if ((w = wcwidth(wc)) == -1)
    160 				continue;
    161 			pl = rl;
    162 			p = s + i;
    163 		}
    164 		if (col + w > len || (col + w == len && s[i + rl])) {
    165 			fputs("\xe2\x80\xa6", stdout); /* ellipsis */
    166 			col++;
    167 			break;
    168 		}
    169 		fwrite(p, 1, pl, stdout);
    170 		col += w;
    171 	}
    172 	return col;
    173 }
    174 
    175 static void *
    176 xreallocarray(void *m, size_t n, size_t s)
    177 {
    178 	void *nm;
    179 
    180 	if (n == 0 || s == 0) {
    181 		free(m);
    182 		return NULL;
    183 	}
    184 	if (s && n > (size_t)-1/s)
    185 		die("realloc: overflow");
    186 	if (!(nm = realloc(m, n * s)))
    187 		die("realloc: %s", strerror(errno));
    188 
    189 	return nm;
    190 }
    191 
    192 static void *
    193 xmalloc(const size_t n)
    194 {
    195 	void *m = malloc(n);
    196 
    197 	if (!m)
    198 		die("malloc: %s", strerror(errno));
    199 
    200 	return m;
    201 }
    202 
    203 static void *
    204 xcalloc(size_t n)
    205 {
    206 	char *m = calloc(1, n);
    207 
    208 	if (!m)
    209 		die("calloc: %s", strerror(errno));
    210 
    211 	return m;
    212 }
    213 
    214 static char *
    215 xstrdup(const char *str)
    216 {
    217 	char *s;
    218 
    219 	if (!(s = strdup(str)))
    220 		die("strdup: %s", strerror(errno));
    221 
    222 	return s;
    223 }
    224 
    225 static void
    226 usage(void)
    227 {
    228 	die("usage: sacc URL");
    229 }
    230 
    231 static void
    232 clearitem(Item *item)
    233 {
    234 	Dir *dir;
    235 	Item *items;
    236 	char *tag;
    237 	size_t i;
    238 
    239 	if (!item)
    240 		return;
    241 
    242 	if (dir = item->dat) {
    243 		items = dir->items;
    244 		for (i = 0; i < dir->nitems; ++i)
    245 			clearitem(&items[i]);
    246 		free(items);
    247 		clear(&item->dat);
    248 	}
    249 
    250 	if (parent && (tag = item->tag) &&
    251 	    !strncmp(tag, tmpdir, strlen(tmpdir)))
    252 		unlink(tag);
    253 
    254 	clear(&item->tag);
    255 	clear(&item->raw);
    256 }
    257 
    258 const char *
    259 typedisplay(char t)
    260 {
    261 	switch (t) {
    262 	case '0':
    263 		return "Text+";
    264 	case '1':
    265 		return "Dir +";
    266 	case '2':
    267 		return "CSO |";
    268 	case '3':
    269 		return "Err |";
    270 	case '4':
    271 		return "Macf+";
    272 	case '5':
    273 		return "DOSf+";
    274 	case '6':
    275 		return "UUEf+";
    276 	case '7':
    277 		return "Find+";
    278 	case '8':
    279 		return "Tlnt+";
    280 	case '9':
    281 		return "Binf+";
    282 	case '+':
    283 		return "Mirr+";
    284 	case 'T':
    285 		return "IBMt|";
    286 	case 'g':
    287 		return "GIF +";
    288 	case 'I':
    289 		return "Img +";
    290 	case 'h':
    291 		return "HTML+";
    292 	case 'i':
    293 		return "    |";
    294 	default:
    295 		/* "Characters '0' through 'Z' are reserved." (ASCII) */
    296 		if (t >= '0' && t <= 'Z')
    297 			return "!   |";
    298 		else
    299 			return "UNKN|";
    300 	}
    301 }
    302 
    303 static void
    304 printdir(Item *item)
    305 {
    306 	Dir *dir;
    307 	Item *items;
    308 	size_t i, nitems;
    309 
    310 	if (!item || !(dir = item->dat))
    311 		return;
    312 
    313 	items = dir->items;
    314 	nitems = dir->nitems;
    315 
    316 	for (i = 0; i < nitems; ++i) {
    317 		printf("%s%s\n",
    318 		       typedisplay(items[i].type), items[i].username);
    319 	}
    320 }
    321 
    322 static void
    323 displaytextitem(Item *item)
    324 {
    325 	FILE *pagerin;
    326 	int pid, wpid;
    327 
    328 	uicleanup();
    329 	switch (pid = fork()) {
    330 	case -1:
    331 		diag("Couldn't fork.");
    332 		return;
    333 	case 0:
    334 		parent = 0;
    335 		if (!(pagerin = popen("$PAGER", "w")))
    336 			_exit(1);
    337 		fputs(item->raw, pagerin);
    338 		exit(pclose(pagerin));
    339 	default:
    340 		while ((wpid = wait(NULL)) >= 0 && wpid != pid)
    341 			;
    342 	}
    343 	uisetup();
    344 }
    345 
    346 static char *
    347 pickfield(char **raw, const char *sep)
    348 {
    349 	char c, *r, *f = *raw;
    350 
    351 	for (r = *raw; (c = *r) && !strchr(sep, c); ++r) {
    352 		if (c == '\n')
    353 			goto skipsep;
    354 	}
    355 
    356 	*r++ = '\0';
    357 skipsep:
    358 	*raw = r;
    359 
    360 	return f;
    361 }
    362 
    363 static char *
    364 invaliditem(char *raw)
    365 {
    366 	char c;
    367 	int tabs;
    368 
    369 	for (tabs = 0; (c = *raw) && c != '\n'; ++raw) {
    370 		if (c == '\t')
    371 			++tabs;
    372 	}
    373 	if (tabs < 3) {
    374 		*raw++ = '\0';
    375 		return raw;
    376 	}
    377 
    378 	return NULL;
    379 }
    380 
    381 static void
    382 molditem(Item *item, char **raw)
    383 {
    384 	char *next;
    385 
    386 	if (!*raw)
    387 		return;
    388 
    389 	if ((next = invaliditem(*raw))) {
    390 		item->username = *raw;
    391 		*raw = next;
    392 		return;
    393 	}
    394 
    395 	item->type = *raw[0]++;
    396 	item->username = pickfield(raw, "\t");
    397 	item->selector = pickfield(raw, "\t");
    398 	item->host = pickfield(raw, "\t");
    399 	item->port = pickfield(raw, "\t\r");
    400 	while (*raw[0] != '\n')
    401 		++*raw;
    402 	*raw[0]++ = '\0';
    403 }
    404 
    405 static Dir *
    406 molddiritem(char *raw)
    407 {
    408 	Item *item, *items = NULL;
    409 	char *s, *nl, *p;
    410 	Dir *dir;
    411 	size_t i, n, nitems;
    412 
    413 	for (s = nl = raw, nitems = 0; p = strchr(nl, '\n'); ++nitems) {
    414 		s = nl;
    415 		nl = p+1;
    416 	}
    417 	if (!nitems) {
    418 		diag("Couldn't parse dir item");
    419 		return NULL;
    420 	}
    421 
    422 	dir = xmalloc(sizeof(Dir));
    423 	items = xreallocarray(items, nitems, sizeof(Item));
    424 	memset(items, 0, nitems * sizeof(Item));
    425 
    426 	for (i = 0; i < nitems; ++i) {
    427 		item = &items[i];
    428 		molditem(item, &raw);
    429 		if (item->type == '+') {
    430 			for (n = i - 1; n < (size_t)-1; --n) {
    431 				if (items[n].type != '+') {
    432 					item->redtype = items[n].type;
    433 					break;
    434 				}
    435 			}
    436 		}
    437 	}
    438 
    439 	dir->items = items;
    440 	dir->nitems = nitems;
    441 	dir->printoff = dir->curline = 0;
    442 
    443 	return dir;
    444 }
    445 
    446 static char *
    447 getrawitem(int sock)
    448 {
    449 	char *raw, *buf;
    450 	size_t bn, bs;
    451 	ssize_t n;
    452 
    453 	raw = buf = NULL;
    454 	bn = bs = n = 0;
    455 
    456 	do {
    457 		bs -= n;
    458 		buf += n;
    459 
    460 		if (buf - raw >= 5) {
    461 			if (strncmp(buf-5, "\r\n.\r\n", 5) == 0) {
    462 				buf[-3] = '\0';
    463 				break;
    464 			}
    465 		} else if (buf - raw == 3 && strncmp(buf-3, ".\r\n", 3)) {
    466 			buf[-3] = '\0';
    467 			break;
    468 		}
    469 
    470 		if (bs < 1) {
    471 			raw = xreallocarray(raw, ++bn, BUFSIZ);
    472 			buf = raw + (bn-1) * BUFSIZ;
    473 			bs = BUFSIZ;
    474 		}
    475 	} while ((n = read_wrap(sock, buf, bs)) > 0);
    476 
    477 	*buf = '\0';
    478 
    479 	if (n < 0) {
    480 		diag("Can't read socket: %s",
    481 		     tlsctx ? tls_error(tlsctx) : strerror(errno));
    482 		clear(&raw);
    483 	}
    484 
    485 	return raw;
    486 }
    487 
    488 
    489 static int
    490 sendselector(int sock, const char *selector)
    491 {
    492 	char *msg, *p;
    493 	size_t ln;
    494 	ssize_t n;
    495 
    496 	ln = strlen(selector) + 3;
    497 	msg = p = xmalloc(ln);
    498 	snprintf(msg, ln--, "%s\r\n", selector);
    499 
    500 	while ((n = write_wrap(sock, p, ln)) > 0) {
    501 		ln -= n;
    502 		p += n;
    503 	}
    504 
    505 	free(msg);
    506 	if (n == -1)
    507 		diag("Can't send message: %s",
    508 		     tlsctx ? tls_error(tlsctx) : strerror(errno));
    509 
    510 	return n;
    511 }
    512 
    513 static int
    514 connectto(const char *host, const char *port)
    515 {
    516 	sigset_t set, oset;
    517 	static const struct addrinfo hints = {
    518 	    .ai_family = AF_UNSPEC,
    519 	    .ai_socktype = SOCK_STREAM,
    520 	    .ai_protocol = IPPROTO_TCP,
    521 	};
    522 	struct addrinfo *addrs, *addr;
    523 	int r, t = 0, sock = -1;
    524 
    525 	sigemptyset(&set);
    526 	sigaddset(&set, SIGWINCH);
    527 	sigprocmask(SIG_BLOCK, &set, &oset);
    528 
    529 	if (r = getaddrinfo(host, port, &hints, &addrs)) {
    530 		diag("Can't resolve hostname \"%s\": %s",
    531 		     host, gai_strerror(r));
    532 		goto err;
    533 	}
    534 
    535 	for (addr = addrs; addr; addr = addr->ai_next) {
    536 		if ((sock = socket(addr->ai_family, addr->ai_socktype,
    537 		                   addr->ai_protocol)) < 0)
    538 			continue;
    539 		if ((r = connect(sock, addr->ai_addr, addr->ai_addrlen)) < 0) {
    540 			close_wrap(sock);
    541 			continue;
    542 		}
    543 		if (dotls) {
    544 			tlsctx = tls_client();
    545 			t = tls_connect_socket(tlsctx, sock, host);
    546 		}
    547 		break;
    548 	}
    549 
    550 	freeaddrinfo(addrs);
    551 
    552 	if (sock < 0) {
    553 		diag("Can't open socket: %s", strerror(errno));
    554 		goto err;
    555 	}
    556 	if (r < 0) {
    557 		diag("Can't connect to: %s:%s: %s",
    558 		     host, port, strerror(errno));
    559 		goto err;
    560 	}
    561 	if (t < 0) {
    562 		diag("Can't init TLS : %s", tls_error(tlsctx));
    563 		goto err;
    564 	}
    565 
    566 	sigprocmask(SIG_SETMASK, &oset, NULL);
    567 	return sock;
    568 
    569 err:
    570 	sigprocmask(SIG_SETMASK, &oset, NULL);
    571 	return -1;
    572 }
    573 
    574 static int
    575 download(Item *item, int dest)
    576 {
    577 	char buf[BUFSIZ];
    578 	ssize_t r, w;
    579 	int src;
    580 
    581 	if (!item->tag) {
    582 		if ((src = connectto(item->host, item->port)) < 0 ||
    583 		    sendselector(src, item->selector) < 0)
    584 			return 0;
    585 	} else if ((src = open(item->tag, O_RDONLY)) < 0) {
    586 		printf("Can't open source file %s: %s",
    587 		       item->tag, strerror(errno));
    588 		errno = 0;
    589 		return 0;
    590 	}
    591 
    592 	w = 0;
    593 	while ((r = read_wrap(src, buf, BUFSIZ)) > 0) {
    594 		while ((w = write(dest, buf, r)) > 0)
    595 			r -= w;
    596 	}
    597 
    598 	if (r < 0 || w < 0) {
    599 		printf("Error downloading file %s: %s",
    600 		       item->selector, strerror(errno));
    601 		errno = 0;
    602 	}
    603 
    604 	close_wrap(src);
    605 
    606 	return (r == 0 && w == 0);
    607 }
    608 
    609 static void
    610 downloaditem(Item *item)
    611 {
    612 	char *file, *path, *tag;
    613 	mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP;
    614 	int dest;
    615 
    616 	if (file = strrchr(item->selector, '/'))
    617 		++file;
    618 	else
    619 		file = item->selector;
    620 
    621 	if (!(path = uiprompt("Download to [%s] (^D cancel): ", file)))
    622 		return;
    623 
    624 	if (!path[0])
    625 		path = xstrdup(file);
    626 
    627 	if (tag = item->tag) {
    628 		if (access(tag, R_OK) < 0) {
    629 			clear(&item->tag);
    630 		} else if (!strcmp(tag, path)) {
    631 			goto cleanup;
    632 		}
    633 	}
    634 
    635 	if ((dest = open(path, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0) {
    636 		diag("Can't open destination file %s: %s",
    637 		     path, strerror(errno));
    638 		errno = 0;
    639 		goto cleanup;
    640 	}
    641 
    642 	if (!download(item, dest))
    643 		goto cleanup;
    644 
    645 	if (item->tag)
    646 		goto cleanup;
    647 
    648 	item->tag = path;
    649 
    650 	return;
    651 cleanup:
    652 	free(path);
    653 	return;
    654 }
    655 
    656 static int
    657 fetchitem(Item *item)
    658 {
    659 	char *raw, *r;
    660 	int sock;
    661 
    662 	if ((sock = connectto(item->host, item->port)) < 0 ||
    663 	    sendselector(sock, item->selector) < 0)
    664 		return 0;
    665 	raw = getrawitem(sock);
    666 	close_wrap(sock);
    667 
    668 	if (raw == NULL || !*raw) {
    669 		diag("Empty response from server");
    670 		clear(&raw);
    671 	}
    672 
    673 	return ((item->raw = raw) != NULL);
    674 }
    675 
    676 static void
    677 plumb(char *url)
    678 {
    679 	switch (fork()) {
    680 	case -1:
    681 		diag("Couldn't fork.");
    682 		return;
    683 	case 0:
    684 		parent = 0;
    685 		dup2(devnullfd, 1);
    686 		dup2(devnullfd, 2);
    687 		if (execlp(plumber, plumber, url, NULL) < 0)
    688 			_exit(1);
    689 	}
    690 
    691 	diag("Plumbed \"%s\"", url);
    692 }
    693 
    694 static void
    695 plumbitem(Item *item)
    696 {
    697 	char *file, *path, *tag;
    698 	mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP;
    699 	int dest, plumbitem;
    700 
    701 	if (file = strrchr(item->selector, '/'))
    702 		++file;
    703 	else
    704 		file = item->selector;
    705 
    706 	path = uiprompt("Download %s to (^D cancel, <empty> plumb): ",
    707 	                file);
    708 	if (!path)
    709 		return;
    710 
    711 	if ((tag = item->tag) && access(tag, R_OK) < 0) {
    712 		clear(&item->tag);
    713 		tag = NULL;
    714 	}
    715 
    716 	plumbitem = path[0] ? 0 : 1;
    717 
    718 	if (!path[0]) {
    719 		clear(&path);
    720 		if (!tag) {
    721 			if (asprintf(&path, "%s/%s", tmpdir, file) < 0)
    722 				die("Can't generate tmpdir path: %s/%s: %s",
    723 				    tmpdir, file, strerror(errno));
    724 		}
    725 	}
    726 
    727 	if (path && (!tag || strcmp(tag, path))) {
    728 		if ((dest = open(path, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0) {
    729 			diag("Can't open destination file %s: %s",
    730 			     path, strerror(errno));
    731 			errno = 0;
    732 			goto cleanup;
    733 		}
    734 		if (!download(item, dest) || tag)
    735 			goto cleanup;
    736 	}
    737 
    738 	if (!tag)
    739 		item->tag = path;
    740 
    741 	if (plumbitem)
    742 		plumb(item->tag);
    743 
    744 	return;
    745 cleanup:
    746 	free(path);
    747 	return;
    748 }
    749 
    750 static int
    751 dig(Item *entry, Item *item)
    752 {
    753 	char *plumburi = NULL;
    754 	int t;
    755 
    756 	if (item->raw) /* already in cache */
    757 		return item->type;
    758 	if (!item->entry)
    759 		item->entry = entry ? entry : item;
    760 
    761 	t = item->redtype ? item->redtype : item->type;
    762 	switch (t) {
    763 	case 'h': /* fallthrough */
    764 		if (!strncmp(item->selector, "URL:", 4)) {
    765 			plumb(item->selector+4);
    766 			return 0;
    767 		}
    768 	case '0':
    769 		if (!fetchitem(item))
    770 			return 0;
    771 		break;
    772 	case '1':
    773 	case '7':
    774 		if (!fetchitem(item) || !(item->dat = molddiritem(item->raw)))
    775 			return 0;
    776 		break;
    777 	case '4':
    778 	case '5':
    779 	case '6':
    780 	case '9':
    781 		downloaditem(item);
    782 		return 0;
    783 	case '8':
    784 		if (asprintf(&plumburi, "telnet://%s%s%s:%s",
    785 		             item->selector, item->selector ? "@" : "",
    786 		             item->host, item->port) < 0)
    787 			return 0;
    788 		plumb(plumburi);
    789 		free(plumburi);
    790 		return 0;
    791 	case 'T':
    792 		if (asprintf(&plumburi, "tn3270://%s%s%s:%s",
    793 		             item->selector, item->selector ? "@" : "",
    794 		             item->host, item->port) < 0)
    795 			return 0;
    796 		plumb(plumburi);
    797 		free(plumburi);
    798 		return 0;
    799 	default:
    800 		if (t >= '0' && t <= 'Z') {
    801 			diag("Type %c (%s) not supported", t, typedisplay(t));
    802 			return 0;
    803 		}
    804 	case 'g':
    805 	case 'I':
    806 		plumbitem(item);
    807 	case 'i':
    808 		return 0;
    809 	}
    810 
    811 	return item->type;
    812 }
    813 
    814 static char *
    815 searchselector(Item *item)
    816 {
    817 	char *pexp, *exp, *tag, *selector = item->selector;
    818 	size_t n = strlen(selector);
    819 
    820 	if ((tag = item->tag) && !strncmp(tag, selector, n))
    821 		pexp = tag + n+1;
    822 	else
    823 		pexp = "";
    824 
    825 	if (!(exp = uiprompt("Enter search string (^D cancel) [%s]: ", pexp)))
    826 		return NULL;
    827 
    828 	if (exp[0] && strcmp(exp, pexp)) {
    829 		n += strlen(exp) + 2;
    830 		tag = xmalloc(n);
    831 		snprintf(tag, n, "%s\t%s", selector, exp);
    832 	}
    833 
    834 	free(exp);
    835 	return tag;
    836 }
    837 
    838 static int
    839 searchitem(Item *entry, Item *item)
    840 {
    841 	char *sel, *selector;
    842 
    843 	if (!(sel = searchselector(item)))
    844 		return 0;
    845 
    846 	if (sel != item->tag)
    847 		clearitem(item);
    848 	if (!item->dat) {
    849 		selector = item->selector;
    850 		item->selector = item->tag = sel;
    851 		dig(entry, item);
    852 		item->selector = selector;
    853 	}
    854 	return (item->dat != NULL);
    855 }
    856 
    857 static void
    858 printout(Item *hole)
    859 {
    860 	char t = 0;
    861 
    862 	if (!hole)
    863 		return;
    864 
    865 	switch (hole->redtype ? hole->redtype : (t = hole->type)) {
    866 	case '0':
    867 		if (dig(hole, hole))
    868 			fputs(hole->raw, stdout);
    869 		return;
    870 	case '1':
    871 	case '7':
    872 		if (dig(hole, hole))
    873 			printdir(hole);
    874 		return;
    875 	default:
    876 		if (t >= '0' && t <= 'Z') {
    877 			diag("Type %c (%s) not supported", t, typedisplay(t));
    878 			return;
    879 		}
    880 	case '4':
    881 	case '5':
    882 	case '6':
    883 	case '9':
    884 	case 'g':
    885 	case 'I':
    886 		download(hole, 1);
    887 	case '2':
    888 	case '3':
    889 	case '8':
    890 	case 'T':
    891 		return;
    892 	}
    893 }
    894 
    895 static void
    896 delve(Item *hole)
    897 {
    898 	Item *entry = NULL;
    899 
    900 	while (hole) {
    901 		switch (hole->redtype ? hole->redtype : hole->type) {
    902 		case 'h':
    903 		case '0':
    904 			if (dig(entry, hole))
    905 				displaytextitem(hole);
    906 			break;
    907 		case '1':
    908 		case '+':
    909 			if (dig(entry, hole) && hole->dat)
    910 				entry = hole;
    911 			break;
    912 		case '7':
    913 			if (searchitem(entry, hole))
    914 				entry = hole;
    915 			break;
    916 		case 0:
    917 			diag("Couldn't get %s:%s/%c%s", hole->host,
    918 			     hole->port, hole->type, hole->selector);
    919 			break;
    920 		case '4':
    921 		case '5':
    922 		case '6': /* TODO decode? */
    923 		case '8':
    924 		case '9':
    925 		case 'g':
    926 		case 'I':
    927 		case 'T':
    928 		default:
    929 			dig(entry, hole);
    930 			break;
    931 		}
    932 
    933 		if (!entry)
    934 			return;
    935 
    936 		do {
    937 			uidisplay(entry);
    938 			hole = uiselectitem(entry);
    939 		} while (hole == entry);
    940 	}
    941 }
    942 
    943 static Item *
    944 moldentry(char *url)
    945 {
    946 	Item *entry;
    947 	char *p, *host = url, *port = "70", *gopherpath = "1";
    948 	int parsed, ipv6;
    949 
    950 	if (p = strstr(url, "://")) {
    951 		if (!strncmp(url, "gopher", p - url))
    952 			dotls = 0;
    953 		else if (!strncmp(url, "gophers", p - url))
    954 			dotls = 1;
    955 		else
    956 			die("Protocol not supported: %.*s", p - url, url);
    957 		host = p + 3;
    958 	}
    959 
    960 	if (*host == '[') {
    961 		ipv6 = 1;
    962 		++host;
    963 	} else {
    964 		ipv6 = 0;
    965 	}
    966 
    967 	for (parsed = 0, p = host; !parsed && *p; ++p) {
    968 		switch (*p) {
    969 		case ']':
    970 			if (ipv6) {
    971 				*p = '\0';
    972 				ipv6 = 0;
    973 			}
    974 			continue;
    975 		case ':':
    976 			if (!ipv6) {
    977 				*p = '\0';
    978 				port = p+1;
    979 			}
    980 			continue;
    981 		case '/':
    982 			*p = '\0';
    983 			parsed = 1;
    984 			continue;
    985 		}
    986 	}
    987 
    988 	if (*host == '\0' || *port == '\0' || ipv6)
    989 		die("Can't parse url");
    990 
    991 	if (*p != '\0')
    992 		gopherpath = p;
    993 
    994 	entry = xcalloc(sizeof(Item));
    995 	entry->type = gopherpath[0];
    996 	entry->username = entry->selector = ++gopherpath;
    997 	if (entry->type == '7') {
    998 		if (p = strstr(gopherpath, "%09")) {
    999 			memmove(p+1, p+3, strlen(p+3)+1);
   1000 			*p = '\t';
   1001 		}
   1002 		if (p || (p = strchr(gopherpath, '\t'))) {
   1003 			asprintf(&entry->tag, "%s", gopherpath);
   1004 			*p = '\0';
   1005 		}
   1006 	}
   1007 	entry->host = host;
   1008 	entry->port = port;
   1009 	entry->entry = entry;
   1010 
   1011 	return entry;
   1012 }
   1013 
   1014 static void
   1015 cleanup(void)
   1016 {
   1017 	clearitem(mainentry);
   1018 	if (parent)
   1019 		rmdir(tmpdir);
   1020 	free(mainentry);
   1021 	free(mainurl);
   1022 	if (interactive)
   1023 		uicleanup();
   1024 }
   1025 
   1026 void
   1027 sighandler(int signo)
   1028 {
   1029 	exit(128 + signo);
   1030 }
   1031 
   1032 static void
   1033 setup(void)
   1034 {
   1035 	struct sigaction sa;
   1036 	int fd;
   1037 
   1038 	setlocale(LC_CTYPE, "");
   1039 	setenv("PAGER", "more", 0);
   1040 	atexit(cleanup);
   1041 	/* reopen stdin in case we're reading from a pipe */
   1042 	if ((fd = open("/dev/tty", O_RDONLY)) < 0)
   1043 		die("open: /dev/tty: %s", strerror(errno));
   1044 	if (dup2(fd, 0) < 0)
   1045 		die("dup2: /dev/tty, stdin: %s", strerror(errno));
   1046 	close(fd);
   1047 	if ((devnullfd = open("/dev/null", O_WRONLY)) < 0)
   1048 		die("open: /dev/null: %s", strerror(errno));
   1049 
   1050 	sigemptyset(&sa.sa_mask);
   1051 	sa.sa_flags = SA_RESTART;
   1052 	sa.sa_handler = sighandler;
   1053 	sigaction(SIGINT, &sa, NULL);
   1054 	sigaction(SIGHUP, &sa, NULL);
   1055 	sigaction(SIGTERM, &sa, NULL);
   1056 
   1057 	if (!mkdtemp(tmpdir))
   1058 		die("mkdir: %s: %s", tmpdir, strerror(errno));
   1059 	if(interactive = isatty(1)) {
   1060 		uisetup();
   1061 		sa.sa_handler = uisigwinch;
   1062 		sigaction(SIGWINCH, &sa, NULL);
   1063 	}
   1064 }
   1065 
   1066 int
   1067 main(int argc, char *argv[])
   1068 {
   1069 	if (argc != 2)
   1070 		usage();
   1071 
   1072 	setup();
   1073 
   1074 	mainurl = xstrdup(argv[1]);
   1075 
   1076 	mainentry = moldentry(mainurl);
   1077 	if (interactive) {
   1078 		diag = uistatus;
   1079 		delve(mainentry);
   1080 	} else {
   1081 		diag = stddiag;
   1082 		printout(mainentry);
   1083 	}
   1084 
   1085 	exit(0);
   1086 }