💾 Archived View for gmi.noulin.net › gitRepositories › spartserv › file › spartservudp.c.gmi captured on 2023-11-04 at 13:02:10. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
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 }