URI: 
       sacc.c - sacc - sacc(omys), simple console gopher client
  HTML git clone git://bitreich.org/sacc/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/sacc/
   DIR Log
   DIR Files
   DIR Refs
   DIR Tags
   DIR 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 }