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("&lt;", stdout);   break;
           81                 case '>':  fputs("&gt;", stdout);   break;
           82                 case '\'': fputs("&#39;", stdout);  break;
           83                 case '&':  fputs("&amp;", stdout);  break;
           84                 case '"':  fputs("&quot;", 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 }