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 }