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