tsacc.c - sacc - [fork] customized build of sacc, the simple console gopher client
(HTM) git clone git://src.adamsgaard.dk/sacc
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) LICENSE
---
tsacc.c (18229B)
---
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 '8':
781 if (asprintf(&plumburi, "telnet://%s%s%s:%s",
782 item->selector, item->selector ? "@" : "",
783 item->host, item->port) < 0)
784 return 0;
785 plumb(plumburi);
786 free(plumburi);
787 return 0;
788 case 'T':
789 if (asprintf(&plumburi, "tn3270://%s%s%s:%s",
790 item->selector, item->selector ? "@" : "",
791 item->host, item->port) < 0)
792 return 0;
793 plumb(plumburi);
794 free(plumburi);
795 return 0;
796 default:
797 if (t >= '0' && t <= 'Z') {
798 diag("Type %c (%s) not supported", t, typedisplay(t));
799 return 0;
800 }
801 case 'g':
802 case '9':
803 case 'I':
804 plumbitem(item);
805 case 'i':
806 return 0;
807 }
808
809 return item->type;
810 }
811
812 static char *
813 searchselector(Item *item)
814 {
815 char *pexp, *exp, *tag, *selector = item->selector;
816 size_t n = strlen(selector);
817
818 if ((tag = item->tag) && !strncmp(tag, selector, n))
819 pexp = tag + n+1;
820 else
821 pexp = "";
822
823 if (!(exp = uiprompt("Enter search string (^D cancel) [%s]: ", pexp)))
824 return NULL;
825
826 if (exp[0] && strcmp(exp, pexp)) {
827 n += strlen(exp) + 2;
828 tag = xmalloc(n);
829 snprintf(tag, n, "%s\t%s", selector, exp);
830 }
831
832 free(exp);
833 return tag;
834 }
835
836 static int
837 searchitem(Item *entry, Item *item)
838 {
839 char *sel, *selector;
840
841 if (!(sel = searchselector(item)))
842 return 0;
843
844 if (sel != item->tag)
845 clearitem(item);
846 if (!item->dat) {
847 selector = item->selector;
848 item->selector = item->tag = sel;
849 dig(entry, item);
850 item->selector = selector;
851 }
852 return (item->dat != NULL);
853 }
854
855 static void
856 printout(Item *hole)
857 {
858 char t = 0;
859
860 if (!hole)
861 return;
862
863 switch (hole->redtype ? hole->redtype : (t = hole->type)) {
864 case '0':
865 if (dig(hole, hole))
866 fputs(hole->raw, stdout);
867 return;
868 case '1':
869 case '7':
870 if (dig(hole, hole))
871 printdir(hole);
872 return;
873 default:
874 if (t >= '0' && t <= 'Z') {
875 diag("Type %c (%s) not supported", t, typedisplay(t));
876 return;
877 }
878 case '4':
879 case '5':
880 case '6':
881 case '9':
882 case 'g':
883 case 'I':
884 download(hole, 1);
885 case '2':
886 case '3':
887 case '8':
888 case 'T':
889 return;
890 }
891 }
892
893 static void
894 delve(Item *hole)
895 {
896 Item *entry = NULL;
897
898 while (hole) {
899 switch (hole->redtype ? hole->redtype : hole->type) {
900 case 'h':
901 case '0':
902 if (dig(entry, hole))
903 displaytextitem(hole);
904 break;
905 case '1':
906 case '+':
907 if (dig(entry, hole) && hole->dat)
908 entry = hole;
909 break;
910 case '7':
911 if (searchitem(entry, hole))
912 entry = hole;
913 break;
914 case 0:
915 diag("Couldn't get %s:%s/%c%s", hole->host,
916 hole->port, hole->type, hole->selector);
917 break;
918 case '4':
919 case '5':
920 case '6': /* TODO decode? */
921 case '8':
922 case '9':
923 case 'g':
924 case 'I':
925 case 'T':
926 default:
927 dig(entry, hole);
928 break;
929 }
930
931 if (!entry)
932 return;
933
934 do {
935 uidisplay(entry);
936 hole = uiselectitem(entry);
937 } while (hole == entry);
938 }
939 }
940
941 static Item *
942 moldentry(char *url)
943 {
944 Item *entry;
945 char *p, *host = url, *port = "70", *gopherpath = "1";
946 int parsed, ipv6;
947
948 if (p = strstr(url, "://")) {
949 if (!strncmp(url, "gopher", p - url))
950 dotls = 0;
951 else if (!strncmp(url, "gophers", p - url))
952 dotls = 1;
953 else
954 die("Protocol not supported: %.*s", p - url, url);
955 host = p + 3;
956 }
957
958 if (*host == '[') {
959 ipv6 = 1;
960 ++host;
961 } else {
962 ipv6 = 0;
963 }
964
965 for (parsed = 0, p = host; !parsed && *p; ++p) {
966 switch (*p) {
967 case ']':
968 if (ipv6) {
969 *p = '\0';
970 ipv6 = 0;
971 }
972 continue;
973 case ':':
974 if (!ipv6) {
975 *p = '\0';
976 port = p+1;
977 }
978 continue;
979 case '/':
980 *p = '\0';
981 parsed = 1;
982 continue;
983 }
984 }
985
986 if (*host == '\0' || *port == '\0' || ipv6)
987 die("Can't parse url");
988
989 if (*p != '\0')
990 gopherpath = p;
991
992 entry = xcalloc(sizeof(Item));
993 entry->type = gopherpath[0];
994 entry->username = entry->selector = ++gopherpath;
995 if (entry->type == '7') {
996 if (p = strstr(gopherpath, "%09")) {
997 memmove(p+1, p+3, strlen(p+3)+1);
998 *p = '\t';
999 }
1000 if (p || (p = strchr(gopherpath, '\t'))) {
1001 asprintf(&entry->tag, "%s", gopherpath);
1002 *p = '\0';
1003 }
1004 }
1005 entry->host = host;
1006 entry->port = port;
1007 entry->entry = entry;
1008
1009 return entry;
1010 }
1011
1012 static void
1013 cleanup(void)
1014 {
1015 clearitem(mainentry);
1016 if (parent)
1017 rmdir(tmpdir);
1018 free(mainentry);
1019 free(mainurl);
1020 if (interactive)
1021 uicleanup();
1022 }
1023
1024 void
1025 sighandler(int signo)
1026 {
1027 exit(128 + signo);
1028 }
1029
1030 static void
1031 setup(void)
1032 {
1033 struct sigaction sa;
1034 int fd;
1035
1036 setlocale(LC_CTYPE, "");
1037 setenv("PAGER", "more", 0);
1038 atexit(cleanup);
1039 /* reopen stdin in case we're reading from a pipe */
1040 if ((fd = open("/dev/tty", O_RDONLY)) < 0)
1041 die("open: /dev/tty: %s", strerror(errno));
1042 if (dup2(fd, 0) < 0)
1043 die("dup2: /dev/tty, stdin: %s", strerror(errno));
1044 close(fd);
1045 if ((devnullfd = open("/dev/null", O_WRONLY)) < 0)
1046 die("open: /dev/null: %s", strerror(errno));
1047
1048 sigemptyset(&sa.sa_mask);
1049 sa.sa_flags = SA_RESTART;
1050 sa.sa_handler = sighandler;
1051 sigaction(SIGINT, &sa, NULL);
1052 sigaction(SIGHUP, &sa, NULL);
1053 sigaction(SIGTERM, &sa, NULL);
1054
1055 if (!mkdtemp(tmpdir))
1056 die("mkdir: %s: %s", tmpdir, strerror(errno));
1057 if(interactive = isatty(1)) {
1058 uisetup();
1059 sa.sa_handler = uisigwinch;
1060 sigaction(SIGWINCH, &sa, NULL);
1061 }
1062 }
1063
1064 int
1065 main(int argc, char *argv[])
1066 {
1067 if (argc != 2)
1068 usage();
1069
1070 setup();
1071
1072 mainurl = xstrdup(argv[1]);
1073
1074 mainentry = moldentry(mainurl);
1075 if (interactive) {
1076 diag = uistatus;
1077 delve(mainentry);
1078 } else {
1079 diag = stddiag;
1080 printout(mainentry);
1081 }
1082
1083 exit(0);
1084 }