spartserv

Log

Files

Refs

README

spartservudp.c (10105B)

     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/types.h>
    18 #include <sys/stat.h>
    19 #include <stdbool.h>
    20 #include <ctype.h>
    21 #include <stdarg.h>
    22 #include <limits.h>
    23 #include <time.h>
    24 #include <fcntl.h> // access
    25 
    26 // inet_ntoa
    27 //already included #include <sys/socket.h>
    28 //already included #include <netinet/in.h>
    29 #include <arpa/inet.h>
    30 
    31 
    32 // server configuration
    33 // TODO set HOSTNAME
    34 #define HOSTNAME ""
    35 //#define HOSTNAME "192.168.1.2"
    36 #define ROOT "."
    37 #define PORT 3000
    38 
    39 bool isDir(const char *path) {
    40     struct stat st;
    41 
    42     if (stat(path, &st) == -1) {
    43         return(false);
    44     }
    45 
    46     if (!S_ISDIR(st.st_mode)) {
    47         return(false);
    48     }
    49     return(true);
    50 }
    51 
    52 int main(int ac, char **av){
    53     int sock;
    54     struct sockaddr_in server;
    55     int mysock;
    56     char buf[63000];
    57     int rval;
    58 
    59     char root[PATH_MAX] = {0};
    60     if (ROOT[0] == '/') {
    61         realpath(ROOT, root);
    62     }
    63     else {
    64         // create absolute path from relative ROOT path
    65         char p[PATH_MAX] = {0};
    66         getcwd(p, PATH_MAX);
    67         strcat(p, "/");
    68         strcat(p, ROOT);
    69         realpath(p, root);
    70         strcat(root, "/");
    71     }
    72 
    73     size_t rootLen = strlen(root);
    74     size_t slash = 0;
    75 
    76     // count slashes at the end of root
    77     // to compare paths with memcmp correctly
    78     // since realpath removes the slashes at the
    79     // end of the path from client.
    80     while(root[rootLen-1-slash] == '/') {
    81         slash++;
    82     }
    83 
    84     sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    85     if (sock < 0){
    86         perror("Failed to create socket");
    87     }
    88 
    89     server.sin_family = AF_INET;
    90     server.sin_addr.s_addr = INADDR_ANY;
    91     server.sin_port = htons(PORT);
    92 
    93     /* setsockopt: Handy debugging trick that lets
    94      * us rerun the server immediately after we kill it;
    95      * otherwise we have to wait about 20 secs.
    96      * Eliminates "ERROR on binding: Address already in use" error.
    97      */
    98     int optval = 1;
    99     setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval , sizeof(int));
   100 
   101     if (bind(sock, (struct sockaddr *) &server, sizeof(server))){
   102         perror("bind failed");
   103         exit(1);
   104     }
   105     listen(sock, SOMAXCONN);
   106 
   107     printf("Serving "HOSTNAME":%d %s\n", PORT, root);
   108 
   109     // date for request print
   110     char date[50];
   111     do {
   112         // struct for printing client ip in terminal with inet_ntoa
   113         struct sockaddr_in addr;
   114         socklen_t len = sizeof(addr);
   115         memset(buf, 0, sizeof(buf));
   116         ssize_t r = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *) &addr, &len);
   117         if (r == -1)
   118             perror("recvfrom failed");
   119         else {
   120             // new client
   121             // get YMD HMS date
   122             time_t clk = time(NULL);
   123             struct tm *pClk = localtime(&clk);
   124             strftime(date, sizeof(date), "%Y-%m-%d:%H:%M:%S", pClk);
   125             printf("%s %s ",date, inet_ntoa(addr.sin_addr));
   126 
   127             int mysock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
   128             if (mysock < 0){
   129                 perror("Failed to create socket");
   130             }
   131             addr.sin_port = htons(PORT);
   132 
   133             // validate request then scan hostname path and content length
   134 
   135             // find request end
   136             char *reqEnd = memmem(buf, sizeof(buf), "\r\n", 2);
   137             if (!reqEnd || buf[0] == ' ') {
   138                 puts("4 Invalid request");
   139                 // add MSG_NOSIGNAL flag to ignore SIGPIPE when the client closes the socket early
   140                 sendto(mysock, "4 Invalid request\r\n", sizeof("4 Invalid request\r\n"), 0, (const struct sockaddr *)&addr, sizeof(addr));
   141                 close(mysock);
   142                 continue;
   143             }
   144 
   145             // check ascii
   146             char *cursor = buf;
   147             bool isBad = false;
   148             while (cursor < reqEnd) {
   149                 if (*cursor < 32 || *cursor == 127) {
   150                     isBad = true;
   151                     break;
   152                 }
   153                 cursor++;
   154             }
   155             if (isBad) {
   156                 puts("4 Non ASCII");
   157                 sendto(mysock, "4 Non ASCII\r\n", sizeof("4 Non ASCII\r\n"), 0, (const struct sockaddr *)&addr, sizeof(addr));
   158                 close(mysock);
   159                 continue;
   160             }
   161 
   162             // print request in terminal
   163             *reqEnd = 0;
   164             printf("%s ", buf);
   165             *reqEnd = '\r';
   166 
   167             // parse hostname, path and content_length in request
   168             char *hostname;
   169             char *path;
   170             char *content_length;
   171 
   172             hostname = buf;
   173 
   174             // hostname must match HOSTNAME
   175             // comment out this test to accept nay hostname
   176             int c = memcmp(hostname, HOSTNAME, strlen(HOSTNAME));
   177 
   178             if (c != 0) {
   179                 puts("4 Hostname");
   180                 sendto(mysock, "4 Hostname\r\n", sizeof("4 Hostname\r\n"), 0, (const struct sockaddr *)&addr, sizeof(addr));
   181                 close(mysock);
   182                 continue;
   183             }
   184 
   185             // get path
   186             cursor = buf;
   187             while (*cursor != ' ' && cursor < reqEnd) {
   188                 cursor++;
   189             }
   190 
   191             cursor++;
   192             if (cursor >= reqEnd || *cursor == ' ') {
   193                 puts("4 Path");
   194                 sendto(mysock, "4 Path\r\n", sizeof("4 Path\r\n"), 0, (const struct sockaddr *)&addr, sizeof(addr));
   195                 close(mysock);
   196                 continue;
   197             }
   198 
   199             path = cursor;
   200 
   201             // get content_length
   202             while (*cursor != ' ' && cursor < reqEnd) {
   203                 cursor++;
   204             }
   205 
   206             cursor++;
   207             if (cursor >= reqEnd || *cursor == ' ') {
   208                 puts("4 Length");
   209                 sendto(mysock, "4 Length\r\n", sizeof("4 Length\r\n"), 0, (const struct sockaddr *)&addr, sizeof(addr));
   210                 close(mysock);
   211                 continue;
   212             }
   213 
   214             content_length = cursor;
   215             while(cursor < reqEnd) {
   216                 if (!isdigit(*cursor)) {
   217                     isBad = true;
   218                     break;
   219                 }
   220                 cursor++;
   221             }
   222 
   223             // the request must not have any content
   224             // content_length = 0
   225             if (isBad || reqEnd - content_length > 1 || *content_length != '0') {
   226                 puts("4 Length");
   227                 sendto(mysock, "4 Length\r\n", sizeof("4 Length\r\n"), 0, (const struct sockaddr *)&addr, sizeof(addr));
   228                 close(mysock);
   229                 continue;
   230             }
   231 
   232             // replace SPC with 0 at the end of path
   233             *(content_length-1) = 0;
   234 
   235             // build server path
   236             char localPath[PATH_MAX] = {0};
   237             if (rootLen + strlen(path) >= PATH_MAX) {
   238                 puts("5 Path too long");
   239                 sendto(mysock, "5 Path too long\r\n", sizeof("5 Path too long\r\n"), 0, (const struct sockaddr *)&addr, sizeof(addr));
   240                 close(mysock);
   241                 continue;
   242             }
   243             memcpy(localPath, root, rootLen);
   244             cursor = localPath + rootLen;
   245             memcpy(cursor, path, strlen(path));
   246 
   247             // check path
   248             if (access(localPath, R_OK) == -1) {
   249                 puts("4 Not found");
   250                 sendto(mysock, "4 Not found\r\n", sizeof("4 Not found\r\n"), 0, (const struct sockaddr *)&addr, sizeof(addr));
   251                 close(mysock);
   252                 continue;
   253             }
   254             char realPath[PATH_MAX] = {0};
   255             realpath(localPath, realPath);
   256             if (memcmp(realPath, root, rootLen-slash) != 0) {
   257                 puts("4 Not found");
   258                 sendto(mysock, "4 Not found\r\n", sizeof("4 Not found\r\n"), 0, (const struct sockaddr *)&addr, sizeof(addr));
   259                 close(mysock);
   260                 continue;
   261             }
   262 
   263             size_t pathLen = strlen(realPath);
   264             cursor = realPath + pathLen;
   265             if (isDir(realPath)) {
   266                 if (pathLen + strlen("/index.gmi") >= PATH_MAX) {
   267                     puts("5 Path too long");
   268                     sendto(mysock, "5 Path too long\r\n", sizeof("5 Path too long\r\n"), 0, (const struct sockaddr *)&addr, sizeof(addr));
   269                     close(mysock);
   270                     continue;
   271                 }
   272                 memcpy(cursor, "/index.gmi", strlen("/index.gmi"));
   273                 cursor += strlen("/index.gmi");
   274             }
   275 
   276             FILE *f = fopen(realPath, "r");
   277             if (!f) {
   278                 puts("4 Page not found");
   279                 sendto(mysock, "4 Page not found\r\n", sizeof("4 Page not found\r\n"), 0, (const struct sockaddr *)&addr, sizeof(addr));
   280                 close(mysock);
   281                 continue;
   282             }
   283 
   284             // request in buf is not needed anymore, reuse buf for response
   285 
   286             // check gemini extension
   287             char *mimetype = "application/octet-stream";
   288             if (strlen(realPath) > 4 && memcmp(cursor-2, "md", 2) == 0) {
   289                 mimetype = "text/markdown";
   290             }
   291             else if (strlen(realPath) > 5 && memcmp(cursor-3, "gmi", 3) == 0) {
   292                 mimetype = "text/gemini";
   293             }
   294             else if (strlen(realPath) > 10 && memcmp(cursor-2, "markdown", 8) == 0) {
   295                 mimetype = "text/markdown";
   296             }
   297 
   298             int len = sprintf(buf, "2 %s\r\n", mimetype);
   299             // print response in terminal
   300             // remove \r\n
   301             buf[len-2] = 0;
   302             puts(buf);
   303             buf[len-2] = '\r';
   304             len    += fread(buf, 1, (size_t)sizeof(buf) , f);
   305             if (sendto(mysock, buf, len, 0, (const struct sockaddr *)&addr, sizeof(addr)) == -1) {
   306                 perror("write failed");
   307             }
   308             fclose(f);
   309             close(mysock);
   310         }
   311     } while(1);
   312 
   313     puts("Server stopped.");
   314 }