t* gopherproxy-c customized URI git clone git://git.codevoid.de/gopherproxy-c-sdk DIR Log DIR Files DIR Refs DIR README DIR LICENSE --- tgopherproxy.c (14459B) --- 1 #define _WITH_DPRINTF 2 3 #include <sys/socket.h> 4 #include <sys/time.h> 5 #include <sys/types.h> 6 7 #include <ctype.h> 8 #include <errno.h> 9 #include <netdb.h> 10 #include <stdarg.h> 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <string.h> 14 #include <unistd.h> 15 #include "config.h" 16 17 #define MAX_RESPONSETIMEOUT 15 /* timeout in seconds */ 18 #define MAX_RESPONSESIZ 8589934592 /* max download size in bytes */ 19 20 #ifndef USE_PLEDGE 21 #define pledge(a,b) 0 22 #endif 23 24 struct uri { 25 char host[256]; 26 char port[8]; 27 char path[1024]; 28 }; 29 30 struct visited { 31 int _type; 32 char username[1024]; 33 char path[1024]; 34 char server[256]; 35 char port[8]; 36 }; 37 38 int headerset = 0, isdir = 0; 39 40 void 41 die(int code, const char *fmt, ...) 42 { 43 va_list ap; 44 45 if (!headerset) { 46 switch (code) { 47 case 400: 48 fputs("Status: 400 Bad Request\r\n", stdout); 49 break; 50 case 403: 51 fputs("Status: 403 Permission Denied\r\n", stdout); 52 break; 53 default: 54 fputs("Status: 500 Internal Server Error\r\n", stdout); 55 break; 56 } 57 fputs("Content-Type: text/plain; charset=utf-8\r\n\r\n", stdout); 58 } 59 60 va_start(ap, fmt); 61 vfprintf(stderr, fmt, ap); 62 va_end(ap); 63 64 va_start(ap, fmt); 65 vfprintf(stdout, fmt, ap); 66 va_end(ap); 67 68 if (isdir) 69 fputs("</pre>\n</body>\n</html>\n", stdout); 70 71 exit(1); 72 } 73 74 /* Escape characters below as HTML 2.0 / XML 1.0. */ 75 void 76 xmlencode(const char *s) 77 { 78 for (; *s; s++) { 79 switch(*s) { 80 case '<': fputs("<", stdout); break; 81 case '>': fputs(">", stdout); break; 82 case '\'': fputs("'", stdout); break; 83 case '&': fputs("&", stdout); break; 84 case '"': fputs(""", stdout); break; 85 default: putchar(*s); 86 } 87 } 88 } 89 90 int 91 dial(const char *host, const char *port) 92 { 93 struct addrinfo hints, *res, *res0; 94 int error, save_errno, s; 95 const char *cause = NULL; 96 struct timeval timeout = { 97 .tv_sec = MAX_RESPONSETIMEOUT, 98 .tv_usec = 0, 99 }; 100 101 memset(&hints, 0, sizeof(hints)); 102 hints.ai_family = AF_UNSPEC; 103 hints.ai_socktype = SOCK_STREAM; 104 hints.ai_flags = AI_NUMERICSERV; /* numeric port only */ 105 if ((error = getaddrinfo(host, port, &hints, &res0))) 106 die(500, "%s: %s: %s:%s\n", __func__, gai_strerror(error), host, port); 107 s = -1; 108 for (res = res0; res; res = res->ai_next) { 109 s = socket(res->ai_family, res->ai_socktype, 110 res->ai_protocol); 111 if (s == -1) { 112 cause = "socket"; 113 continue; 114 } 115 116 if (setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) == -1) 117 die(500, "%s: setsockopt: %s\n", __func__, strerror(errno)); 118 if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) == -1) 119 die(500, "%s: setsockopt: %s\n", __func__, strerror(errno)); 120 121 if (connect(s, res->ai_addr, res->ai_addrlen) == -1) { 122 cause = "connect"; 123 save_errno = errno; 124 close(s); 125 errno = save_errno; 126 s = -1; 127 continue; 128 } 129 break; 130 } 131 if (s == -1) 132 die(500, "%s: %s: %s:%s\n", __func__, cause, host, port); 133 freeaddrinfo(res0); 134 135 return s; 136 } 137 138 int 139 isblacklisted(const char *host, const char *port, const char *path) 140 { 141 char *p; 142 143 if (!allow_all_ports && (strcmp(port, "70") && strcmp(port, "7070"))) 144 return 1; 145 if (!allow_tor && ((p = strstr(host, ".onion")) && strlen(p) == strlen(".onion"))) 146 return 1; 147 return 0; 148 } 149 150 char * 151 typestr(int c) 152 { 153 switch (c) { 154 case '0': return " TEXT"; 155 case '1': return " DIR"; 156 case '2': return " CSO"; 157 case '3': return " ERR"; 158 case '4': return " MAC"; 159 case '5': return " DOS"; 160 case '6': return " UUENC"; 161 case '7': return " SEND"; 162 case '8': return "TELNET"; 163 case '9': return " BIN"; 164 case 'g': return " GIF"; 165 case 'h': return " URI"; /* non-standard */ 166 case 's': return " SND"; /* non-standard */ 167 case '+': return "MIRROR"; 168 case 'I': return " IMG"; 169 case 'T': return "TN3270"; 170 default: 171 /* "Characters '0' through 'Z' are reserved." (ASCII) */ 172 if (c >= '0' && c <= 'Z') 173 return "RESERV"; 174 else 175 return " "; 176 } 177 } 178 179 void 180 servefile(const char *server, const char *port, const char *path) 181 { 182 char buf[1024]; 183 int r, w, fd; 184 size_t totalsiz = 0; 185 186 fd = dial(server, port); 187 188 if (pledge("stdio", NULL) == -1) 189 die(500, "pledge: %s\n", strerror(errno)); 190 191 if ((w = dprintf(fd, "%s\r\n", path)) == -1) 192 die(500, "dprintf: %s\n", strerror(errno)); 193 194 while ((r = read(fd, buf, sizeof(buf))) > 0) { 195 /* too big total response */ 196 totalsiz += r; 197 if (totalsiz > MAX_RESPONSESIZ) { 198 dprintf(1, "--- transfer too big, truncated ---\n"); 199 break; 200 } 201 202 if ((w = write(1, buf, r)) == -1) 203 die(500, "write: %s\n", strerror(errno)); 204 } 205 if (r == -1) 206 die(500, "read: %s\n", strerror(errno)); 207 close(fd); 208 } 209 210 void 211 servedir(const char *server, const char *port, const char *path, const char *param) 212 { 213 struct visited v; 214 FILE *fp; 215 char line[1024], uri[1024]; 216 size_t totalsiz, linenr; 217 ssize_t n; 218 int fd, r, i, len; 219 220 fd = dial(server, port); 221 222 if (pledge("stdio", NULL) == -1) 223 die(500, "pledge: %s\n", strerror(errno)); 224 225 if (param[0]) 226 r = dprintf(fd, "%s\t%s\r\n", path, param); 227 else 228 r = dprintf(fd, "%s\r\n", path); 229 if (r == -1) 230 die(500, "write: %s\n", strerror(errno)); 231 232 if (!(fp = fdopen(fd, "rb+"))) 233 die(500, "fdopen: %s\n", strerror(errno)); 234 235 totalsiz = 0; 236 for (linenr = 1; fgets(line, sizeof(line), fp); linenr++) { 237 n = strcspn(line, "\n"); 238 if (line[n] != '\n') 239 die(500, "%s:%s %s:%d: line too long\n", 240 server, port, path, linenr); 241 if (n && line[n] == '\n') 242 line[n] = '\0'; 243 if (n && line[n - 1] == '\r') 244 line[--n] = '\0'; 245 if (n == 1 && line[0] == '.') 246 break; 247 248 /* too big total response */ 249 totalsiz += n; 250 if (totalsiz > MAX_RESPONSESIZ) { 251 dprintf(1, "--- transfer too big, truncated ---\n"); 252 break; 253 } 254 255 memset(&v, 0, sizeof(v)); 256 257 v._type = line[0]; 258 259 /* "username" */ 260 i = 1; 261 len = strcspn(line + i, "\t"); 262 if (len + 1 < sizeof(v.username)) { 263 memcpy(v.username, line + i, len); 264 v.username[len] = '\0'; 265 } else { 266 die(500, "%s:%s %s:%d: username field too long\n", 267 server, port, path, linenr); 268 } 269 if (line[i + len] == '\t') 270 i += len + 1; 271 else 272 die(500, "%s:%s %s:%d: invalid line / field count\n", 273 server, port, path, linenr); 274 275 /* selector / path */ 276 len = strcspn(line + i, "\t"); 277 if (len + 1 < sizeof(v.path)) { 278 memcpy(v.path, line + i, len); 279 v.path[len] = '\0'; 280 } else { 281 die(500, "%s:%s %s:%d: path field too long\n", 282 server, port, path, linenr); 283 } 284 if (line[i + len] == '\t') 285 i += len + 1; 286 else 287 die(500, "%s:%s %s:%d: invalid line / field count\n", 288 server, port, path, linenr); 289 290 /* server */ 291 len = strcspn(line + i, "\t"); 292 if (len + 1 < sizeof(v.server)) { 293 memcpy(v.server, line + i, len); 294 v.server[len] = '\0'; 295 } else { 296 die(500, "%s:%s %s:%d: server field too long\n", 297 server, port, path, linenr); 298 } 299 if (line[i + len] == '\t') 300 i += len + 1; 301 else 302 die(500, "%s:%s %s:%d: invalid line / field count\n", 303 server, port, path, linenr); 304 305 /* port */ 306 len = strcspn(line + i, "\t"); 307 if (len + 1 < sizeof(v.port)) { 308 memcpy(v.port, line + i, len); 309 v.port[len] = '\0'; 310 } else { 311 die(500, "%s:%s %s:%d: port field too long\n", 312 server, port, path, linenr); 313 } 314 315 if (!strcmp(v.port, "70")) 316 if(!strcmp(v.server, hostname)) { 317 snprintf(uri, sizeof(uri), "/%c%s", 318 v._type, v.path); 319 } else { 320 snprintf(uri, sizeof(uri), "%s/%c%s", 321 v.server, v._type, v.path); 322 } 323 else 324 snprintf(uri, sizeof(uri), "%s:%s/%c%s", 325 v.server, v.port, v._type, v.path); 326 327 switch (v._type) { 328 case 'i': /* info */ 329 case '3': /* error */ 330 fputs(typestr(v._type), stdout); 331 fputs(" ", stdout); 332 xmlencode(v.username); 333 break; 334 case '7': /* search */ 335 fputs("</pre><form method=\"get\" action=\"\"><pre>", stdout); 336 fputs(typestr(v._type), stdout); 337 fputs(" <input type=\"hidden\" name=\"q\" value=\"", stdout); 338 xmlencode(uri); 339 fputs("\" /><input type=\"search\" placeholder=\"", stdout); 340 xmlencode(v.username); 341 fputs( 342 "\" name=\"p\" value=\"\" size=\"72\" />" 343 "<input type=\"submit\" value=\"Send\" /></pre></form><pre>", stdout); 344 break; 345 case '8': /* telnet */ 346 case 'T': /* tn3270 */ 347 fputs(typestr(v._type), stdout); 348 printf(" <a href=\"%s://", v._type == '8' ? "telnet" : "tn3270"); 349 if (v.path[0]) { 350 xmlencode(v.path); 351 fputs("@", stdout); 352 } 353 xmlencode(v.server); 354 fputs(":", stdout); 355 xmlencode(v.port); 356 fputs("\">", stdout); 357 xmlencode(v.username); 358 fputs("</a>", stdout); 359 break; 360 default: /* other */ 361 fputs(typestr(v._type), stdout); 362 fputs(" <a href=\"", stdout); 363 if (v._type == 'h' && !strncmp(v.path, "URL:", sizeof("URL:") - 1)) { 364 xmlencode(v.path + sizeof("URL:") - 1); 365 } else { 366 if(enable_rewrite == 0) { 367 fputs("?q=", stdout); 368 } 369 if(strncmp(uri, "/", sizeof("/") -1)) { 370 if(enable_rewrite == 1) { 371 fputs("/?q=", stdout); 372 } 373 fputs("gopher://", stdout); 374 } 375 xmlencode(uri); 376 } 377 fputs("\">", stdout); 378 xmlencode(v.username); 379 fputs("</a>", stdout); 380 381 } 382 putchar('\n'); 383 } 384 if (ferror(fp)) 385 die(500, "fgets: %s\n", strerror(errno)); 386 fclose(fp); 387 } 388 389 int 390 hexdigit(int c) 391 { 392 if (c >= '0' && c <= '9') 393 return c - '0'; 394 else if (c >= 'A' && c <= 'F') 395 return c - 'A' + 10; 396 else if (c >= 'a' && c <= 'f') 397 return c - 'a' + 10; 398 399 return 0; 400 } 401 402 /* decode until NUL separator or end of "key". */ 403 int 404 decodeparam(char *buf, size_t bufsiz, const char *s) 405 { 406 size_t i; 407 408 if (!bufsiz) 409 return -1; 410 411 for (i = 0; *s && *s != '&'; s++) { 412 if (i + 3 >= bufsiz) 413 return -1; 414 switch (*s) { 415 case '%': 416 if (!isxdigit(*(s+1)) || !isxdigit(*(s+2))) 417 return -1; 418 buf[i++] = hexdigit(*(s+1)) * 16 + hexdigit(*(s+2)); 419 s += 2; 420 break; 421 case '+': 422 buf[i++] = ' '; 423 break; 424 default: 425 buf[i++] = *s; 426 break; 427 } 428 } 429 buf[i] = '\0'; 430 431 return i; 432 } 433 434 char * 435 getparam(const char *query, const char *s) 436 { 437 const char *p; 438 size_t len; 439 440 len = strlen(s); 441 for (p = query; (p = strstr(p, s)); p += len) { 442 if (p[len] == '=' && (p == query || p[-1] == '&')) 443 return (char *)p + len + 1; 444 } 445 446 return NULL; 447 } 448 449 int 450 checkparam(const char *s) 451 { 452 for (; *s; s++) 453 if (iscntrl(*s)) 454 return 0; 455 return 1; 456 } 457 458 int 459 parseuri(const char *str, struct uri *u) 460 { 461 const char *s, *e; 462 463 memset(u, 0, sizeof(struct uri)); 464 465 s = str; 466 467 /* IPv6 */ 468 if (*s == '[') { 469 s++; 470 e = strchr(s, ']'); 471 if (!e || e - s + 1 >= sizeof(u->host)) 472 return 0; 473 memcpy(u->host, s, e - s); 474 u->host[e - s] = '\0'; 475 e++; 476 } else { 477 e = &s[strcspn(s, ":/")]; 478 if (e - s + 1 >= sizeof(u->host)) 479 return 0; 480 memcpy(u->host, s, e - s); 481 u->host[e - s] = '\0'; 482 } 483 484 if (*e == ':') { 485 s = e + 1; 486 e = &s[strcspn(s, "/")]; 487 488 if (e - s + 1 >= sizeof(u->port)) 489 return 0; 490 memcpy(u->port, s, e - s); 491 u->port[e - s] = '\0'; 492 } 493 if (*e && *e != '/') 494 return 0; /* invalid path */ 495 496 s = e; 497 e = s + strlen(s); 498 499 if (e - s + 1 >= sizeof(u->path)) 500 return 0; 501 memcpy(u->path, s, e - s); 502 u->path[e - s] = '\0'; 503 504 return 1; 505 } 506 507 int 508 main(void) 509 { 510 struct uri u; 511 const char *p, *qs, *path, *uri = ""; 512 char query[1024] = "", param[1024] = ""; 513 int _type = '1'; 514 515 if (pledge("stdio inet dns", NULL) == -1) 516 die(500, "pledge: %s\n", strerror(errno)); 517 518 if (!(qs = getenv("QUERY_STRING"))) 519 qs = ""; 520 if ((p = getparam(qs, "q")) || (p = getparam(qs,("%E2%98%BA")))) { 521 if (decodeparam(query, sizeof(query), p) == -1 || 522 !checkparam(query)) 523 die(400, "Invalid parameter: q\n"); 524 } 525 if ((p = getparam(qs, "p"))) { 526 if (decodeparam(param, sizeof(param), p) == -1 || 527 !checkparam(param)) 528 die(400, "Invalid parameter: p\n"); 529 } 530 531 path = "/"; 532 if (query[0]) { 533 if (!strncmp(query, "gopher://", sizeof("gopher://") - 1)) 534 uri = query + sizeof("gopher://") - 1; 535 else 536 uri = query; 537 538 if (!parseuri(uri, &u)) 539 die(400, "Invalid uri: %s\n", uri); 540 if (u.host[0] == '\0') 541 strcpy(u.host, hostname); 542 543 if (u.path[0] == '\0') 544 memcpy(u.path, "/", 2); 545 if (u.port[0] == '\0') 546 memcpy(u.port, "70", 3); 547 548 path = u.path; 549 if (path[0] == '/') { 550 path++; 551 if (*path) { 552 _type = *path; 553 path++; 554 } 555 } else { 556 path = ""; 557 } 558 559 if (isblacklisted(u.host, u.port, path)) 560 die(403, "%s:%s %s: blacklisted\n", u.host, u.port, path); 561 562 headerset = 1; 563 switch (_type) { 564 case '1': 565 case '7': 566 break; /* handled below */ 567 case '0': 568 dprintf(1, "Content-Type: text/plain; charset=utf-8\r\n\r\n"); 569 servefile(u.host, u.port, path); 570 return 0; 571 case 'g': 572 dprintf(1, "Content-Type: image/gif\r\n\r\n"); 573 servefile(u.host, u.port, path); 574 return 0; 575 case 'I': 576 /* try to set Content-Type based on extension */ 577 if ((p = strrchr(path, '.'))) { 578 p++; 579 if (!strcasecmp("png", p)) 580 dprintf(1, "Content-Type: image/png\r\n"); 581 else if (!strcasecmp("jpg", p) || !strcasecmp("jpeg", p)) 582 dprintf(1, "Content-Type: image/jpeg\r\n"); 583 else if (!strcasecmp("gif", p)) 584 dprintf(1, "Content-Type: image/gif\r\n"); 585 } 586 write(1, "\r\n", 2); 587 servefile(u.host, u.port, path); 588 return 0; 589 case '9': 590 /* try to detect filename */ 591 if ((p = strrchr(path, '/'))) 592 dprintf(1, "Content-Disposition: attachment; filename=\"%s\"\r\n", p + 1); 593 dprintf(1, "Content-Type: application/octet-stream\r\n\r\n"); 594 servefile(u.host, u.port, path); 595 return 0; 596 default: 597 write(1, "\r\n", 2); 598 servefile(u.host, u.port, path); 599 return 0; 600 } 601 } 602 603 headerset = isdir = 1; 604 fputs( 605 "Content-Type: text/html; charset=utf-8\r\n" 606 "\r\n" 607 "<!DOCTYPE html>\n" 608 "<html dir=\"ltr\">\n" 609 "<head>\n" 610 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n" 611 "<title>", stdout); 612 fputs("gopher://codevoid.de", stdout); 613 if (strrchr(path, '/')) { 614 xmlencode(query); 615 } 616 fputs( 617 "</title>\n" 618 "<style type=\"text/css\">a { text-decoration: none; } " 619 "a:hover { text-decoration: underline; }</style>\n" 620 "<meta name=\"robots\" content=\"noindex, nofollow\" />\n" 621 "<meta name=\"robots\" content=\"none\" />\n" 622 "<meta content=\"width=device-width\" name=\"viewport\" />\n" 623 "</head>\n" 624 "<body>\n", stdout); 625 626 if(!disable_urlbar) { 627 fputs("<form method=\"get\" action=\"\"><pre>" 628 " URI: <input type=\"search\" name=\"q\" value=\"", stdout); 629 xmlencode(uri); 630 fputs( 631 "\" placeholder=\"URI...\" size=\"72\" autofocus=\"autofocus\" class=\"search\" />" 632 "<input type=\"submit\" value=\"Go for it!\" /></pre>" 633 "</form>\n", stdout); 634 } 635 fputs("<pre>\n", stdout); 636 637 if (query[0]) { 638 if (_type != '7') 639 param[0] = '\0'; 640 servedir(u.host, u.port, path, param); 641 } 642 643 fputs("</pre>\n</body>\n</html>\n", stdout); 644 645 return 0; 646 }