sacc

sacc (saccomys): simple gopher client.
Log | Files | Refs | LICENSE

sacc.c (19147B)


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