sacc

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

sacc.c (16553B)


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