URI: 
       ind.c - geomyidae - A small C-based gopherd.
  HTML git clone git://bitreich.org/geomyidae/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/geomyidae/
   DIR Log
   DIR Files
   DIR Refs
   DIR Tags
   DIR README
   DIR LICENSE
       ---
       ind.c (13717B)
       ---
            1 /*
            2  * Copy me if you can.
            3  * by 20h
            4  */
            5 
            6 #ifdef __linux__
            7         #define _GNU_SOURCE
            8 #endif
            9 
           10 #include <libgen.h>
           11 #include <unistd.h>
           12 #include <stdarg.h>
           13 #include <string.h>
           14 #include <memory.h>
           15 #include <fcntl.h>
           16 #include <stdio.h>
           17 #include <stdlib.h>
           18 #include <stdint.h>
           19 #include <time.h>
           20 #include <netdb.h>
           21 #include <sys/socket.h>
           22 #include <sys/stat.h>
           23 #include <netinet/in.h>
           24 #include <netinet/tcp.h>
           25 #include <arpa/inet.h>
           26 #include <sys/ioctl.h>
           27 #include <limits.h>
           28 #include <errno.h>
           29 
           30 #define PAGE_SHIFT 12
           31 #define PAGE_SIZE (1UL << PAGE_SHIFT)
           32 #define BLOCK_SIZE ((PAGE_SIZE * 16) - 1)
           33 
           34 #include "arg.h"
           35 #include "ind.h"
           36 #include "handlr.h"
           37 
           38 /*
           39  * Be careful, to look at handlerequest(), in case you add any executing
           40  * handler, so nocgi will be valuable.
           41  *
           42  * All files are handled as binary, without a following ".\r\n". Proper
           43  * encoding lines with beginning "." would be a really slow function, not
           44  * adding any feature to gopher. Clients can check for the types
           45  * requested and assume ".\r\n" or leave it out.
           46  *
           47  * Geomyidae only adds ".\r\n" in all kind of menus, like dir listings
           48  * or dcgi files. There the case of some maybe future "." item type needs
           49  * to be handled, if really used.
           50  */
           51 
           52 #include "filetypes.h"
           53 
           54 int
           55 pendingbytes(int sock)
           56 {
           57         int pending, rval;
           58 
           59         pending = 0;
           60         rval = 0;
           61 #if defined(TIOCOUTQ) && !defined(__OpenBSD__)
           62         rval = ioctl(sock, TIOCOUTQ, &pending);
           63 #else
           64 #ifdef SIOCOUTQ
           65         rval = ioctl(sock, SIOCOUTQ, &pending);
           66 #endif
           67 #endif
           68 
           69         if (rval != 0)
           70                 return 0;
           71 
           72         return pending;
           73 }
           74 
           75 void
           76 waitforpendingbytes(int sock)
           77 {
           78         int npending = 0, opending = 0;
           79         useconds_t trytime = 10;
           80 
           81         /*
           82          * Wait until there is nothing pending or the connection stalled
           83          * (nothing was sent) for around 40 seconds. Beware, trytime is
           84          * an exponential wait.
           85          */
           86         while ((npending = pendingbytes(sock)) > 0 && trytime < 20000000) {
           87                 if (opending != 0) {
           88                         if (opending != npending) {
           89                                 trytime = 10;
           90                         } else {
           91                                 /*
           92                                  * Exponentially increase the usleep
           93                                  * waiting time to not waste CPU
           94                                  * resources.
           95                                  */
           96                                 trytime += trytime;
           97                         }
           98                 }
           99                 opending = npending;
          100 
          101                 usleep(trytime);
          102         }
          103 }
          104 
          105 #ifdef __linux__
          106 int
          107 xsplice(int fd, int sock)
          108 {
          109         int pipefd[2], ret = 0;
          110         ssize_t nread, nwritten;
          111         off_t in_offset = 0;
          112 
          113         if (pipe(pipefd) < 0)
          114                 return -1;
          115 
          116         do {
          117                 nread = splice(fd, &in_offset, pipefd[1], NULL,
          118                         BLOCK_SIZE, SPLICE_F_MOVE | SPLICE_F_MORE);
          119 
          120                 if (nread <= 0) {
          121                         ret = nread < 0 ? -1 : 0;
          122                         goto out;
          123                 }
          124 
          125                 nwritten  = splice(pipefd[0], NULL, sock, NULL, BLOCK_SIZE,
          126                                 SPLICE_F_MOVE | SPLICE_F_MORE);
          127                 if (nwritten < 0) {
          128                         ret = -1;
          129                         goto out;
          130                 }
          131         } while (nwritten > 0);
          132 
          133 out:
          134         close(pipefd[0]);
          135         close(pipefd[1]);
          136 
          137         return ret;
          138 }
          139 #endif
          140 
          141 int
          142 xsendfile(int fd, int sock)
          143 {
          144         struct stat st;
          145         char *sendb, *sendi;
          146         size_t bufsiz = BUFSIZ;
          147         int len, sent, optval;
          148 
          149 #ifdef splice
          150         return xsplice(fd, sock);
          151 #endif
          152 
          153         USED(optval);
          154 
          155         /*
          156          * The story of xsendfile.
          157          *
          158          * Once upon a time, here you saw a big #ifdef switch source of
          159          * many ways how to send files with special functions on
          160          * different operating systems. All of this was removed, because
          161          * operating systems and kernels got better over time,
          162          * simplifying what you need and reducing corner cases.
          163          *
          164          * For example Linux sendfile(2) sounds nice and faster, but
          165          * the function is different on every OS and slower to the now
          166          * used approach of read(2) and write(2).
          167          *
          168          * If you ever consider changing this to some "faster" approach,
          169          * consider benchmarks on all platforms.
          170          */
          171 
          172         if (fstat(fd, &st) >= 0) {
          173                 if ((bufsiz = st.st_blksize) < BUFSIZ)
          174                         bufsiz = BUFSIZ;
          175         }
          176 
          177         sendb = xmalloc(bufsiz);
          178         while ((len = read(fd, sendb, bufsiz)) > 0) {
          179                 sendi = sendb;
          180                 while (len > 0) {
          181                         if ((sent = write(sock, sendi, len)) < 0) {
          182                                 free(sendb);
          183                                 return -1;
          184                         }
          185                         len -= sent;
          186                         sendi += sent;
          187                 }
          188         }
          189         free(sendb);
          190 
          191         return 0;
          192 }
          193 
          194 void *
          195 xcalloc(size_t nmemb, size_t size)
          196 {
          197         void *p;
          198 
          199         if (!(p = calloc(nmemb, size))) {
          200                 perror("calloc");
          201                 exit(1);
          202         }
          203 
          204         return p;
          205 }
          206 
          207 void *
          208 xmalloc(size_t size)
          209 {
          210         void *p;
          211 
          212         if (!(p = malloc(size))) {
          213                 perror("malloc");
          214                 exit(1);
          215         }
          216 
          217         return p;
          218 }
          219 
          220 void *
          221 xrealloc(void *ptr, size_t size)
          222 {
          223         if (!(ptr = realloc(ptr, size))) {
          224                 perror("realloc");
          225                 exit(1);
          226         }
          227 
          228         return ptr;
          229 }
          230 
          231 char *
          232 xstrdup(const char *str)
          233 {
          234         char *ret;
          235 
          236         if (!(ret = strdup(str))) {
          237                 perror("strdup");
          238                 exit(1);
          239         }
          240 
          241         return ret;
          242 }
          243 
          244 filetype *
          245 gettype(char *filename)
          246 {
          247         char *end;
          248         int i;
          249 
          250         end = strrchr(filename, '.');
          251         if (end == NULL)
          252                 return &type[0];
          253         end++;
          254 
          255         for (i = 0; type[i].end != NULL; i++)
          256                 if (!strcasecmp(end, type[i].end))
          257                         return &type[i];
          258 
          259         return &type[0];
          260 }
          261 
          262 void
          263 gph_freeelem(gphelem *e)
          264 {
          265         if (e != NULL) {
          266                 if (e->e != NULL) {
          267                         for (;e->num > 0; e->num--)
          268                                 if (e->e[e->num - 1] != NULL)
          269                                         free(e->e[e->num - 1]);
          270                         free(e->e);
          271                 }
          272                 free(e);
          273         }
          274         return;
          275 }
          276 
          277 void
          278 gph_freeindex(gphindex *i)
          279 {
          280         if (i != NULL) {
          281                 if (i->n != NULL) {
          282                         for (;i->num > 0; i->num--)
          283                                 gph_freeelem(i->n[i->num - 1]);
          284                         free(i->n);
          285                 }
          286                 free(i);
          287         }
          288 
          289         return;
          290 }
          291 
          292 void
          293 gph_addelem(gphelem *e, char *s)
          294 {
          295         e->num++;
          296         e->e = xrealloc(e->e, sizeof(char *) * e->num);
          297         e->e[e->num - 1] = xstrdup(s);
          298 
          299         return;
          300 }
          301 
          302 gphelem *
          303 gph_getadv(char *str)
          304 {
          305         char *b, *e, *o, *bo;
          306         gphelem *ret;
          307 
          308         ret = xcalloc(1, sizeof(gphelem));
          309 
          310         if (strchr(str, '\t')) {
          311                 gph_addelem(ret, "i");
          312                 gph_addelem(ret, "Happy helping ☃ here: You tried to "
          313                         "output a spurious TAB character. This will "
          314                         "break gopher. Please review your scripts. "
          315                         "Have a nice day!");
          316                 gph_addelem(ret, "Err");
          317                 gph_addelem(ret, "server");
          318                 gph_addelem(ret, "port");
          319 
          320                 return ret;
          321         }
          322 
          323         /* Check for escape sequence. */
          324         if (str[0] == '[' && str[1] != '|') {
          325                 o = xstrdup(str);
          326                 b = o + 1;
          327                 bo = b;
          328                 while ((e = strchr(bo, '|')) != NULL) {
          329                         if (e != bo && e[-1] == '\\') {
          330                                 memmove(&e[-1], e, strlen(e));
          331                                 bo = e;
          332                                 continue;
          333                         }
          334                         *e = '\0';
          335                         e++;
          336                         gph_addelem(ret, b);
          337                         b = e;
          338                         bo = b;
          339                 }
          340 
          341                 e = strchr(b, ']');
          342                 if (e != NULL) {
          343                         *e = '\0';
          344                         gph_addelem(ret, b);
          345                 }
          346                 free(o);
          347 
          348                 if (ret->e != NULL && ret->e[0] != NULL &&
          349                                 ret->e[0][0] != '\0' && ret->num == 5) {
          350                         return ret;
          351                 }
          352 
          353                 /* Invalid entry: Give back the whole line. */
          354                 gph_freeelem(ret);
          355                 ret = xcalloc(1, sizeof(gphelem));
          356         }
          357 
          358         gph_addelem(ret, "i");
          359         /* Jump over escape sequence. */
          360         if (str[0] == '[' && str[1] == '|')
          361                 str += 2;
          362         gph_addelem(ret, str);
          363         gph_addelem(ret, "Err");
          364         gph_addelem(ret, "server");
          365         gph_addelem(ret, "port");
          366 
          367         return ret;
          368 }
          369 
          370 void
          371 gph_addindex(gphindex *idx, gphelem *el)
          372 {
          373         idx->num++;
          374         idx->n = xrealloc(idx->n, sizeof(gphelem *) * idx->num);
          375         idx->n[idx->num - 1] = el;
          376 
          377         return;
          378 }
          379 
          380 gphindex *
          381 gph_scanfile(char *fname)
          382 {
          383         char *ln = NULL;
          384         size_t linesiz = 0;
          385         ssize_t n;
          386         FILE *fp;
          387         gphindex *ret;
          388         gphelem *el;
          389 
          390         if (!(fp = fopen(fname, "r")))
          391                 return NULL;
          392 
          393         ret = xcalloc(1, sizeof(gphindex));
          394 
          395         while ((n = getline(&ln, &linesiz, fp)) > 0) {
          396                 if (ln[n - 1] == '\n')
          397                         ln[--n] = '\0';
          398                 el = gph_getadv(ln);
          399                 if (el == NULL)
          400                         continue;
          401 
          402                 gph_addindex(ret, el);
          403         }
          404         if (ferror(fp))
          405                 perror("getline");
          406         free(ln);
          407         fclose(fp);
          408 
          409         if (ret->n == NULL) {
          410                 free(ret);
          411                 return NULL;
          412         }
          413 
          414         return ret;
          415 }
          416 
          417 int
          418 gph_printelem(int fd, gphelem *el, char *file, char *base, char *addr, char *port)
          419 {
          420         char *path, *p, *argbase, buf[PATH_MAX+1], *argp, *realbase, *rpath;
          421         int len, blen;
          422 
          423         if (!strcmp(el->e[3], "server")) {
          424                 free(el->e[3]);
          425                 el->e[3] = xstrdup(addr);
          426         }
          427         if (!strcmp(el->e[4], "port")) {
          428                 free(el->e[4]);
          429                 el->e[4] = xstrdup(port);
          430         }
          431 
          432         /*
          433          * Ignore if the path is from base, if it might be some h type with
          434          * some URL and ignore various types that have different semantics,
          435          * do not point to some file or directory.
          436          */
          437         if ((el->e[2][0] != '\0'
          438             && el->e[2][0] != '/' /* Absolute Request. */
          439             && el->e[0][0] != 'i' /* Informational item. */
          440             && el->e[0][0] != '2' /* CSO server */
          441             && el->e[0][0] != '3' /* Error */
          442             && el->e[0][0] != '8' /* Telnet */
          443             && el->e[0][0] != 'w' /* Selector is direct URI. */
          444             && el->e[0][0] != 'T') && /* tn3270 */
          445             !(el->e[0][0] == 'h' && !strncmp(el->e[2], "URL:", 4))) {
          446                 path = file + strlen(base);
          447 
          448                 /* Strip off original gph file name. */
          449                 if ((p = strrchr(path, '/'))) {
          450                         len = strlen(path) - strlen(basename(path));
          451                 } else {
          452                         len = strlen(path);
          453                 }
          454 
          455                 /* Strip off arguments for realpath. */
          456                 argbase = strchr(el->e[2], '?');
          457                 if (argbase != NULL) {
          458                         blen = argbase - el->e[2];
          459                 } else {
          460                         blen = strlen(el->e[2]);
          461                 }
          462 
          463                 /*
          464                  * Print everything together. Realpath will resolve it.
          465                  */
          466                 snprintf(buf, sizeof(buf), "%s%.*s%.*s", base, len,
          467                         path, blen, el->e[2]);
          468 
          469                 if ((rpath = realpath(buf, NULL)) &&
          470                                 (realbase = realpath(*base? base : "/", NULL)) &&
          471                                 !strncmp(realbase, rpath, strlen(realbase))) {
          472                         p = rpath + (*base? strlen(realbase) : 0);
          473 
          474                         /*
          475                          * Do not forget to re-add arguments which were
          476                          * stripped off.
          477                          */
          478                         argp = smprintf("%s%s", *p? p : "/", argbase? argbase : "");
          479 
          480                         free(el->e[2]);
          481                         el->e[2] = argp;
          482                         free(realbase);
          483                 }
          484                 if (rpath != NULL)
          485                         free(rpath);
          486         }
          487 
          488         if (dprintf(fd, "%.1s%s\t%s\t%s\t%s\r\n", el->e[0], el->e[1], el->e[2],
          489                         el->e[3], el->e[4]) < 0) {
          490                 perror("printgphelem: dprintf");
          491                 return -1;
          492         }
          493         return 0;
          494 }
          495 
          496 char *
          497 smprintf(char *fmt, ...)
          498 {
          499         va_list fmtargs;
          500         char *ret;
          501         int size;
          502 
          503         va_start(fmtargs, fmt);
          504         size = vsnprintf(NULL, 0, fmt, fmtargs);
          505         va_end(fmtargs);
          506 
          507         ret = xcalloc(1, ++size);
          508         va_start(fmtargs, fmt);
          509         vsnprintf(ret, size, fmt, fmtargs);
          510         va_end(fmtargs);
          511 
          512         return ret;
          513 }
          514 
          515 char *
          516 reverselookup(char *host)
          517 {
          518         struct in_addr hoststr;
          519         struct hostent *client;
          520         char *rethost;
          521 
          522         rethost = NULL;
          523 
          524         if (inet_pton(AF_INET, host, &hoststr)) {
          525                 client = gethostbyaddr((const void *)&hoststr,
          526                                 sizeof(hoststr), AF_INET);
          527                 if (client != NULL)
          528                         rethost = xstrdup(client->h_name);
          529         }
          530 
          531         if (rethost == NULL)
          532                 rethost = xstrdup(host);
          533 
          534         return rethost;
          535 }
          536 
          537 void
          538 setcgienviron(char *file, char *path, char *port, char *base, char *args,
          539                 char *sear, char *ohost, char *chost, char *bhost, int istls,
          540                 char *sel, char *traverse)
          541 {
          542         /*
          543          * <@__20h__> satan, why did you add so much uncertainties
          544          *   into RFC3875?
          545          * <annna> Foolish mortal, I added uncertainties to RFC3875 to
          546          *   ensure that humans would forever be plagued by the complexity
          547          *   of web application management.
          548          */
          549 
          550         /* RFC3875 4.1.1 */
          551         setenv("AUTH_TYPE", "", 1);
          552         /* RFC3875 4.1.2 */
          553         unsetenv("CONTENT_LENGTH");
          554         /* RFC3875 4.1.3 */
          555         unsetenv("CONTENT_TYPE");
          556         /* RFC3875 4.1.4 */
          557         setenv("GATEWAY_INTERFACE", "CGI/1.1", 1);
          558         /* RFC3875 4.1.5 */
          559         setenv("PATH_INFO", traverse, 1);
          560         /* RFC3875 4.1.6 */
          561         setenv("PATH_TRANSLATED", path, 1);
          562         /* RFC3875 4.1.7 */
          563         setenv("QUERY_STRING", args, 1);
          564         /* RFC3875 4.1.8 */
          565         setenv("REMOTE_ADDR", chost, 1);
          566         /* RFC3875 4.1.9 */
          567         setenv("REMOTE_HOST", chost, 1);
          568         /* RFC3875 4.1.10 */
          569         unsetenv("REMOTE_IDENT");
          570         /* RFC3875 4.1.11 */
          571         unsetenv("REMOTE_USER");
          572         /* RFC3875 4.1.12 */
          573         setenv("REQUEST_METHOD", "GET", 1);
          574         /* RFC3875 4.1.13 */
          575         setenv("SCRIPT_NAME", path+strlen(base), 1);
          576         /* RFC3875 4.1.14 */
          577         setenv("SERVER_NAME", ohost, 1);
          578         /* RFC3875 4.1.15 */
          579         setenv("SERVER_PORT", port, 1);
          580         /* RFC3875 4.1.16 */
          581         setenv("SERVER_PROTOCOL", "gopher/1.0", 1);
          582         /* RFC3875 4.1.17 */
          583         setenv("SERVER_SOFTWARE", "geomyidae", 1);
          584 
          585         /* GOPHER-specific variables */
          586         /* This is allowed by RFC3875 */
          587         setenv("GOPHER_SELECTOR", sel, 1);
          588         setenv("GOPHER_REQUEST", sel, 1);
          589         setenv("GOPHER_SEARCH", sear, 1);
          590         setenv("GOPHER_SCRIPT_FILENAME", path, 1);
          591         setenv("GOPHER_DOCUMENT_ROOT", base, 1);
          592 
          593         /* legacy compatibility */
          594         /* Gopher stuff should begin with GOPHER_ by RFC3875 */
          595         /* See: https://boston.conman.org/2020/01/06.1 */
          596         setenv("SCRIPT_FILENAME", path, 1);
          597         /* Bucktooth compatibility */
          598         setenv("SELECTOR", sel, 1);
          599         /* Motsognir compatibility */
          600         setenv("QUERY_STRING_SEARCH", sear, 1);
          601         setenv("QUERY_STRING_URL", sear, 1);
          602         /* Gophernicus */
          603         setenv("SEARCHREQUEST", sear, 1);
          604         /*
          605          * TODO Do we need those? Does anyone use those?
          606          * COLUMNS, DOCUMENT_ROOT, GOPHER_CHARSET, GOPHER_FILETYPE,
          607          * GOPHER_REFERER, HTTP_ACCEPT_CHARSET, HTTP_REFERER, LOCAL_ADDR,
          608          * PATH, REQUEST, SERVER_ARCH, SERVER_CODENAME,
          609          * SERVER_DESCRIPTION, SERVER_TLS_PORT, SERVER_VERSION,
          610          * SESSION_ID, TLS
          611          */
          612 
          613         /* Make PHP happy. */
          614         setenv("REDIRECT_STATUS", "", 1);
          615         setenv("SERVER_LISTEN_NAME", bhost, 1);
          616         if (istls) {
          617                 setenv("GOPHERS", "on", 1);
          618                 setenv("HTTPS", "on", 1);
          619         } else {
          620                 unsetenv("GOPHERS");
          621                 unsetenv("HTTPS");
          622         }
          623 }
          624 
          625 char *
          626 humansize(off_t n)
          627 {
          628         static char buf[16];
          629         const char postfixes[] = "BKMGTPE";
          630         double size;
          631         int i = 0;
          632 
          633         for (size = n; size >= 1024 && i < strlen(postfixes); i++)
          634                 size /= 1024;
          635 
          636         if (!i) {
          637                 snprintf(buf, sizeof(buf), "%ju%c", (uintmax_t)n,
          638                                 postfixes[i]);
          639         } else {
          640                 snprintf(buf, sizeof(buf), "%.1f%c", size, postfixes[i]);
          641         }
          642 
          643         return buf;
          644 }
          645 
          646 char *
          647 humantime(const time_t *clock)
          648 {
          649         static char buf[32];
          650         struct tm *tm;
          651 
          652         tm = localtime(clock);
          653         strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M %Z", tm);
          654 
          655         return buf;
          656 }
          657 
          658 void
          659 lingersock(int sock)
          660 {
          661         struct linger lingerie;
          662         int j;
          663 
          664         /*
          665          * On close only wait for at maximum 60 seconds for all data to be
          666          * transmitted before forcefully closing the connection.
          667          */
          668         lingerie.l_onoff = 1;
          669         lingerie.l_linger = 60;
          670         setsockopt(sock, SOL_SOCKET, SO_LINGER,
          671                         &lingerie, sizeof(lingerie));
          672 
          673         /*
          674          * Force explicit flush of buffers using TCP_NODELAY.
          675          */
          676         j = 1;
          677         setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &j, sizeof(int));
          678         waitforpendingbytes(sock);
          679         j = 0;
          680         setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &j, sizeof(int));
          681 
          682         return;
          683 }
          684