sacc

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

sacc.c (18973B)


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