💾 Archived View for gmi.noulin.net › gitRepositories › spartserv › file › spartservPrivDrop.c.gmi captured on 2024-09-29 at 01:17:18. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2024-08-18)

-=-=-=-=-=-=-

spartserv

Log

Files

Refs

README

spartservPrivDrop.c (19427B)

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