sacc.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 }