spartserv

Log

Files

Refs

README

spartservPrivDrop.c (17750B)

     1 /*
     2 This server creates a socket listening to PORT and
     3 starts the event loop
     4 2 mimetypes are set depending on file extension:
     5 text/gemini   .gmi
     6 text/markdown .md .markdown
     7 */
     8 
     9 #define _GNU_SOURCE
    10 #include <string.h>
    11 #include <stdio.h>
    12 #include <sys/types.h>
    13 #include <sys/socket.h>
    14 #include <netinet/in.h>
    15 #include <stdlib.h>
    16 #include <unistd.h>
    17 #include <sys/stat.h>
    18 #include <stdbool.h>
    19 #include <ctype.h>
    20 #include <stdarg.h>
    21 #include <limits.h>
    22 #include <time.h>
    23 #include <fcntl.h> // access
    24 
    25 // inet_ntoa
    26 //already included #include <sys/socket.h>
    27 //already included #include <netinet/in.h>
    28 #include <arpa/inet.h>
    29 
    30 // privilege drop
    31 // already included #include <sys/types.h>
    32 #include <pwd.h>
    33 #include <grp.h>
    34 
    35 // seccomp bpf filters
    36 #include <sys/syscall.h>
    37 #include <errno.h>
    38 #include <stddef.h>
    39 #include <linux/seccomp.h>
    40 #include <linux/filter.h> // struct sock_filter
    41 #include <linux/audit.h>
    42 #include <sys/mman.h>
    43 #include <sys/prctl.h>
    44 
    45 // server configuration
    46 #define HOSTNAME "localhost"
    47 #define ROOT "/"
    48 #define PORT 300
    49 
    50 #define RUNAS "spartserv"
    51 #define CHROOT "."
    52 
    53 // ----------------- seccomp setup
    54 // This code is copied and modified from kore http server
    55 // https://kore.io
    56 /*
    57  * Copyright (c) 2013-2022 Joris Vink <joris@coders.se>
    58  *
    59  * Permission to use, copy, modify, and distribute this software for any
    60  * purpose with or without fee is hereby granted, provided that the above
    61  * copyright notice and this permission notice appear in all copies.
    62  *
    63  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
    64  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
    65  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
    66  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    67  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
    68  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    69  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
    70  */
    71 
    72 #if __BYTE_ORDER == __LITTLE_ENDIAN
    73 #define ARGS_LO_OFFSET                0
    74 #define ARGS_HI_OFFSET                sizeof(u_int32_t)
    75 #elif __BYTE_ORDER == __BIG_ENDIAN
    76 #define ARGS_LO_OFFSET                sizeof(u_int32_t)
    77 #define ARGS_HI_OFFSET                0
    78 #else
    79 #error "__BYTE_ORDER unknown"
    80 #endif
    81 
    82 // AUDIT_ARCH_X86_64 AUDIT_ARCH_ARM AUDIT_ARCH_AARCH64
    83 #define SECCOMP_AUDIT_ARCH AUDIT_ARCH_X86_64
    84 #define SECCOMP_KILL_POLICY                SECCOMP_RET_KILL
    85 
    86 /* Load field of seccomp_data into accumulator. */
    87 #define KORE_BPF_LOAD(_field, _off)                                \
    88     BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, _field) + _off)
    89 /* Compare the accumulator against a constant (==). */
    90 #define KORE_BPF_CMP(_k, _jt, _jf)                        \
    91     BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, _k, _jt, _jf)
    92 
    93 /* Return a constant from a BPF program. */
    94 #define KORE_BPF_RET(_retval)                                \
    95     BPF_STMT(BPF_RET+BPF_K, _retval)
    96 
    97 /* AND operation on the accumulator. */
    98 #define KORE_BPF_AND(_k)                                \
    99     BPF_STMT(BPF_ALU+BPF_AND+BPF_K, _k)
   100 
   101 /* The length of a filter. */
   102 #define KORE_FILTER_LEN(x)                (sizeof(x) / sizeof(x[0]))
   103 
   104 #define KORE_SYSCALL_FILTER(_name, _action)                \
   105     KORE_BPF_CMP(SYS_##_name, 0, 1),                        \
   106     KORE_BPF_RET(_action)
   107 
   108 #define KORE_SYSCALL_WITH_FLAG(_name, _arg, _flag, _action)        \
   109     KORE_BPF_CMP(SYS_##_name, 0, 8),                        \
   110     KORE_BPF_LOAD(args[(_arg)], ARGS_LO_OFFSET),        \
   111     KORE_BPF_AND(((_flag) & 0xffffffff)),                \
   112     KORE_BPF_CMP(((_flag) & 0xffffffff), 0, 4),                \
   113     KORE_BPF_LOAD(args[(_arg)], ARGS_HI_OFFSET),        \
   114     KORE_BPF_AND((((uint32_t)((uint64_t)(_flag) >> 32)) & 0xffffffff)),        \
   115     KORE_BPF_CMP((((uint32_t)((uint64_t)(_flag) >> 32)) & 0xffffffff), 0, 1),  \
   116     KORE_BPF_RET(_action),                                \
   117     KORE_BPF_LOAD(nr, 0)
   118 
   119 #define KORE_SYSCALL_DENY(_name, _errno)                \
   120     KORE_SYSCALL_FILTER(_name, SECCOMP_RET_ERRNO|(_errno))
   121 
   122 #define KORE_SYSCALL_DENY_WITH_FLAG(_name, _arg, _flag, _errno)        \
   123     KORE_SYSCALL_WITH_FLAG(_name, _arg, _flag, SECCOMP_RET_ERRNO|(_errno))
   124 
   125 #define KORE_SYSCALL_ALLOW(_name)                        \
   126     KORE_SYSCALL_FILTER(_name, SECCOMP_RET_ALLOW)
   127 
   128 
   129 /*
   130  * The bare minimum to be able to run kore. These are added last and can
   131  * be overwritten by a filter program that is added before hand.
   132  */
   133 static struct sock_filter filter_kore[] = {
   134         /* Deny these, but with EACCESS instead of dying. */
   135         KORE_SYSCALL_DENY(ioctl, EACCES),
   136 
   137         /* File related. */
   138         KORE_SYSCALL_ALLOW(read),
   139 #if defined(SYS_stat)
   140         KORE_SYSCALL_ALLOW(stat),
   141 #endif
   142 #if defined(SYS_lstat)
   143         KORE_SYSCALL_ALLOW(lstat),
   144 #endif
   145         KORE_SYSCALL_ALLOW(fstat),
   146         KORE_SYSCALL_ALLOW(write),
   147         KORE_SYSCALL_ALLOW(close),
   148         KORE_SYSCALL_ALLOW(openat),
   149 #if defined(SYS_access)
   150         KORE_SYSCALL_ALLOW(access),
   151 #endif
   152 #if defined(SYS_send)
   153         KORE_SYSCALL_ALLOW(send),
   154 #endif
   155         KORE_SYSCALL_ALLOW(sendto),
   156         KORE_SYSCALL_ALLOW(accept),
   157         KORE_SYSCALL_ALLOW(sendfile),
   158 #if defined(SYS_recv)
   159         KORE_SYSCALL_ALLOW(recv),
   160 #endif
   161         KORE_SYSCALL_ALLOW(recvfrom),
   162         KORE_SYSCALL_ALLOW(setsockopt),
   163     // for perror
   164 #if defined(SYS_dup)
   165         KORE_SYSCALL_ALLOW(dup),
   166 #endif
   167 #if defined(SYS_fcntl)
   168         KORE_SYSCALL_ALLOW(fcntl),
   169 #endif
   170 };
   171 
   172 /* bpf program prologue. */
   173 static struct sock_filter filter_prologue[] = {
   174         /* Load arch member into accumulator (A) (arch is __u32). */
   175         KORE_BPF_LOAD(arch, 0),
   176 
   177         /* Compare accumulator against constant, if false jump over kill. */
   178         KORE_BPF_CMP(SECCOMP_AUDIT_ARCH, 1, 0),
   179         KORE_BPF_RET(SECCOMP_RET_KILL),
   180 
   181         /* Load the system call number into the accumulator. */
   182         KORE_BPF_LOAD(nr, 0),
   183 };
   184 
   185 /* bpf program epilogue. */
   186 static struct sock_filter filter_epilogue[] = {
   187         /* Return hit if no system calls matched our list. */
   188         BPF_STMT(BPF_RET+BPF_K, SECCOMP_KILL_POLICY)
   189 };
   190 
   191 #define filter_prologue_len        KORE_FILTER_LEN(filter_prologue)
   192 #define filter_epilogue_len        KORE_FILTER_LEN(filter_epilogue)
   193 
   194 int kore_seccomp_tracing = 0;
   195 
   196 void kore_seccomp_enable(void) {
   197         struct sock_filter                *sf;
   198         struct sock_fprog                prog;
   199         size_t                                prog_len, off, i;
   200 
   201         /*
   202          * If kore_seccomp_tracing is turned on, set the default policy to
   203          * SECCOMP_RET_TRACE so we can log the system calls.
   204          */
   205         if (kore_seccomp_tracing) {
   206                 filter_epilogue[0].k = SECCOMP_RET_TRACE;
   207                 puts("seccomp tracing enabled");
   208         }
   209 
   210         /* Start with the prologue. */
   211         /* Finally add the epilogue. */
   212         prog_len = filter_prologue_len + KORE_FILTER_LEN(filter_kore) + filter_epilogue_len ;
   213 
   214         /* Build the entire bpf program now. */
   215         if ((sf = calloc(prog_len, sizeof(*sf))) == NULL) {
   216                 puts("calloc");
   217         exit(1);
   218     }
   219 
   220         off = 0;
   221         for (i = 0; i < filter_prologue_len; i++)
   222                 sf[off++] = filter_prologue[i];
   223 
   224         for (i = 0; i < KORE_FILTER_LEN(filter_kore); i++)
   225                 sf[off++] = filter_kore[i];
   226 
   227         for (i = 0; i < filter_epilogue_len; i++)
   228                 sf[off++] = filter_epilogue[i];
   229 
   230         /* Lock and load it. */
   231         if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
   232                 printf("prctl: %s\n", strerror(errno));
   233         exit(1);
   234     }
   235 
   236         prog.filter = sf;
   237         prog.len = prog_len;
   238 
   239         if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) {
   240                 printf("prctl: %s\n", strerror(errno));
   241         exit(1);
   242     }
   243 }
   244 
   245 // ----------------- seccomp end
   246 
   247 bool isDir(const char *path) {
   248     struct stat st;
   249 
   250     if (stat(path, &st) == -1) {
   251         return(false);
   252     }
   253 
   254     if (!S_ISDIR(st.st_mode)) {
   255         return(false);
   256     }
   257     return(true);
   258 }
   259 
   260 int main(int ac, char **av){
   261     int sock;
   262     struct sockaddr_in server;
   263     int mysock;
   264     char buf[4096];
   265     int rval;
   266 
   267     char root[PATH_MAX] = {0};
   268     if (ROOT[0] == '/') {
   269         realpath(ROOT, root);
   270     }
   271     else {
   272         // create absolute path from relative ROOT path
   273         char p[PATH_MAX] = {0};
   274         getcwd(p, PATH_MAX);
   275         strcat(p, "/");
   276         strcat(p, ROOT);
   277         realpath(p, root);
   278         strcat(root, "/");
   279     }
   280 
   281     size_t rootLen = strlen(root);
   282     size_t slash = 0;
   283 
   284     // count slashes at the end of root
   285     // to compare paths with memcmp correctly
   286     // since realpath removes the slashes at the
   287     // end of the path from client.
   288     while(root[rootLen-1-slash] == '/') {
   289         slash++;
   290     }
   291 
   292     sock = socket(AF_INET, SOCK_STREAM, 0);
   293     if (sock < 0){
   294         perror("Failed to create socket");
   295     }
   296 
   297     server.sin_family = AF_INET;
   298     server.sin_addr.s_addr = INADDR_ANY;
   299     server.sin_port = htons(PORT);
   300 
   301     /* setsockopt: Handy debugging trick that lets
   302      * us rerun the server immediately after we kill it;
   303      * otherwise we have to wait about 20 secs.
   304      * Eliminates "ERROR on binding: Address already in use" error.
   305      */
   306     int optval = 1;
   307     setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval , sizeof(int));
   308 
   309     if (bind(sock, (struct sockaddr *) &server, sizeof(server))){
   310         perror("bind failed");
   311         exit(1);
   312     }
   313 
   314     listen(sock, SOMAXCONN);
   315 
   316     struct passwd *pw = NULL;
   317     pw = getpwnam(RUNAS);
   318     if (!pw) {
   319         perror("get user info failed");
   320         exit(1);
   321     }
   322 
   323     if (chroot(CHROOT) == -1) {
   324         perror("cannot chroot");
   325         exit(1);
   326     }
   327 
   328     if (chdir("/") == -1) {
   329         perror("cannot chdir");
   330         exit(1);
   331     }
   332 
   333     if (setgroups(1, &pw->pw_gid) ||
   334             setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
   335             setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) {
   336         printf("cannot drop privileges");
   337         exit(1);
   338     }
   339 
   340     kore_seccomp_enable();
   341 
   342     printf("Serving "HOSTNAME":%d %s\n", PORT, root);
   343 
   344     // date for request print
   345     char date[50];
   346     do {
   347         // struct for printing client ip in terminal with inet_ntoa
   348         struct sockaddr_in addr;
   349         socklen_t len = sizeof(addr);
   350         mysock = accept(sock, &addr, &len);
   351         if (mysock == -1)
   352             perror("accept failed");
   353         else {
   354             // Set 10s timeouts for receive and send (SO_RCVTIMEO and SO_SNDTIMEO)
   355             struct timeval timeout;
   356             timeout.tv_sec = 10;
   357             timeout.tv_usec = 0;
   358             // get YMD HMS date
   359             time_t clk = time(NULL);
   360             struct tm *pClk = localtime(&clk);
   361             strftime(date, sizeof(date), "%Y-%m-%d:%H:%M:%S", pClk);
   362             if (setsockopt(mysock, SOL_SOCKET, SO_RCVTIMEO, (void *) &timeout, sizeof(timeout)) < 0) {
   363                     printf("%s %s ",date, inet_ntoa(addr.sin_addr));
   364                 perror("receive timeout failed");
   365                 close(mysock);
   366                 continue;
   367             }
   368             if (setsockopt(mysock, SOL_SOCKET, SO_SNDTIMEO, (void *) &timeout, sizeof(timeout)) < 0) {
   369                     printf("%s %s ",date, inet_ntoa(addr.sin_addr));
   370                 perror("send timeout failed");
   371                 close(mysock);
   372                 continue;
   373             }
   374 
   375             // new client
   376             memset(buf, 0, sizeof(buf));
   377             if ((rval = recv(mysock, buf, sizeof(buf), 0)) < 0) {
   378                     printf("%s %s ",date, inet_ntoa(addr.sin_addr));
   379                 perror("reading message");
   380                 close(mysock);
   381                 continue;
   382             }
   383             else if (rval == 0) {
   384                     printf("%s %s ",date, inet_ntoa(addr.sin_addr));
   385                 puts("Ending connection");
   386                 close(mysock);
   387                 continue;
   388             }
   389 
   390             printf("%s %s ",date, inet_ntoa(addr.sin_addr));
   391 
   392             // validate request then scan hostname path and content length
   393 
   394             // find request end
   395             char *reqEnd = memmem(buf, sizeof(buf), "\r\n", 2);
   396             if (!reqEnd || buf[0] == ' ') {
   397                 puts("4 Invalid request");
   398                 // add MSG_NOSIGNAL flag to ignore SIGPIPE when the client closes the socket early
   399                 send(mysock, "4 Invalid request\r\n", sizeof("4 Invalid request\r\n"), MSG_NOSIGNAL);
   400                 close(mysock);
   401                 continue;
   402             }
   403 
   404             // check ascii
   405             char *cursor = buf;
   406             bool isBad = false;
   407             while (cursor < reqEnd) {
   408                 if (*cursor < 32 || *cursor == 127) {
   409                     isBad = true;
   410                     break;
   411                 }
   412                 cursor++;
   413             }
   414             if (isBad) {
   415                 puts("4 Non ASCII");
   416                 send(mysock, "4 Non ASCII\r\n", sizeof("4 Non ASCII\r\n"), MSG_NOSIGNAL);
   417                 close(mysock);
   418                 continue;
   419             }
   420 
   421             // print request in terminal
   422             *reqEnd = 0;
   423             printf("%s ", buf);
   424             *reqEnd = '\r';
   425 
   426             // parse hostname, path and content_length in request
   427             char *hostname;
   428             char *path;
   429             char *content_length;
   430 
   431             hostname = buf;
   432 
   433             // hostname must match HOSTNAME
   434             // comment out this test to accept nay hostname
   435             int c = memcmp(hostname, HOSTNAME, strlen(HOSTNAME));
   436 
   437             if (c != 0) {
   438                 puts("4 Hostname");
   439                 send(mysock, "4 Hostname\r\n", sizeof("4 Hostname\r\n"), MSG_NOSIGNAL);
   440                 close(mysock);
   441                 continue;
   442             }
   443 
   444             // get path
   445             cursor = buf;
   446             while (*cursor != ' ' && cursor < reqEnd) {
   447                 cursor++;
   448             }
   449 
   450             cursor++;
   451             if (cursor >= reqEnd || *cursor == ' ') {
   452                 puts("4 Path");
   453                 send(mysock, "4 Path\r\n", sizeof("4 Path\r\n"), MSG_NOSIGNAL);
   454                 close(mysock);
   455                 continue;
   456             }
   457 
   458             path = cursor;
   459 
   460             // get content_length
   461             while (*cursor != ' ' && cursor < reqEnd) {
   462                 cursor++;
   463             }
   464 
   465             cursor++;
   466             if (cursor >= reqEnd || *cursor == ' ') {
   467                 puts("4 Length");
   468                 send(mysock, "4 Length\r\n", sizeof("4 Length\r\n"), MSG_NOSIGNAL);
   469                 close(mysock);
   470                 continue;
   471             }
   472 
   473             content_length = cursor;
   474             while(cursor < reqEnd) {
   475                 if (!isdigit(*cursor)) {
   476                     isBad = true;
   477                     break;
   478                 }
   479                 cursor++;
   480             }
   481 
   482             // the request must not have any content
   483             // content_length = 0
   484             if (isBad || reqEnd - content_length > 1 || *content_length != '0') {
   485                 puts("4 Length");
   486                 send(mysock, "4 Length\r\n", sizeof("4 Length\r\n"), MSG_NOSIGNAL);
   487                 close(mysock);
   488                 continue;
   489             }
   490 
   491             // replace SPC with 0 at the end of path
   492             *(content_length-1) = 0;
   493 
   494             // build server path
   495             char localPath[PATH_MAX] = {0};
   496             if (rootLen + strlen(path) >= PATH_MAX) {
   497                 puts("5 Path too long");
   498                 send(mysock, "5 Path too long\r\n", sizeof("5 Path too long\r\n"), MSG_NOSIGNAL);
   499                 close(mysock);
   500                 continue;
   501             }
   502             memcpy(localPath, root, rootLen);
   503             cursor = localPath + rootLen;
   504             memcpy(cursor, path, strlen(path));
   505 
   506             // check path
   507             if (access(localPath, R_OK) == -1) {
   508                 puts("4 Not found");
   509                 send(mysock, "4 Not found\r\n", sizeof("4 Not found\r\n"), MSG_NOSIGNAL);
   510                 close(mysock);
   511                 continue;
   512             }
   513             char realPath[PATH_MAX] = {0};
   514             realpath(localPath, realPath);
   515             if (memcmp(realPath, root, rootLen-slash) != 0) {
   516                 puts("4 Not found");
   517                 send(mysock, "4 Not found\r\n", sizeof("4 Not found\r\n"), MSG_NOSIGNAL);
   518                 close(mysock);
   519                 continue;
   520             }
   521 
   522             size_t pathLen = strlen(realPath);
   523             cursor = realPath + pathLen;
   524             if (isDir(realPath)) {
   525                 if (pathLen + strlen("/index.gmi") >= PATH_MAX) {
   526                     puts("5 Path too long");
   527                     send(mysock, "5 Path too long\r\n", sizeof("5 Path too long\r\n"), MSG_NOSIGNAL);
   528                     close(mysock);
   529                     continue;
   530                 }
   531                 memcpy(cursor, "/index.gmi", strlen("/index.gmi"));
   532                 cursor += strlen("/index.gmi");
   533             }
   534 
   535             FILE *f = fopen(realPath, "r");
   536             if (!f) {
   537                 puts("4 Page not found");
   538                 send(mysock, "4 Page not found\r\n", sizeof("4 Page not found\r\n"), MSG_NOSIGNAL);
   539                 close(mysock);
   540                 continue;
   541             }
   542 
   543             // request in buf is not needed anymore, reuse buf for response
   544 
   545             // check gemini extension
   546             char *mimetype = "application/octet-stream";
   547             if (strlen(realPath) > 4 && memcmp(cursor-2, "md", 2) == 0) {
   548                 mimetype = "text/markdown";
   549             }
   550             else if (strlen(realPath) > 5 && memcmp(cursor-3, "gmi", 3) == 0) {
   551                 mimetype = "text/gemini";
   552             }
   553             else if (strlen(realPath) > 10 && memcmp(cursor-2, "markdown", 8) == 0) {
   554                 mimetype = "text/markdown";
   555             }
   556 
   557             int len = sprintf(buf, "2 %s\r\n", mimetype);
   558             if (send(mysock, buf, len, MSG_NOSIGNAL) != -1) {
   559                 // print response in terminal
   560                 // remove \r\n
   561                 buf[len-2] = 0;
   562                 puts(buf);
   563 
   564                 // send file
   565                 size_t fsz;
   566                 while (fsz = fread(buf, 1, (size_t)sizeof(buf) , f)) {
   567                     ssize_t r = send(mysock, buf, fsz, MSG_NOSIGNAL);
   568                     if (r == -1) {
   569                         perror("write failed");
   570                         puts("closed socket");
   571                         break;
   572                     }
   573                 }
   574             }
   575             else {
   576                 perror("write failed");
   577             }
   578             fclose(f);
   579             close(mysock);
   580         }
   581     } while(1);
   582 
   583     puts("Server stopped.");
   584 }