💾 Archived View for gmi.noulin.net › gitRepositories › spartserv › file › spartservPrivDrop.c.gmi captured on 2023-01-29 at 13:30:34. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
spartservPrivDrop.c (17358B)
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 }; 164 165 /* bpf program prologue. */ 166 static struct sock_filter filter_prologue[] = { 167 /* Load arch member into accumulator (A) (arch is __u32). */ 168 KORE_BPF_LOAD(arch, 0), 169 170 /* Compare accumulator against constant, if false jump over kill. */ 171 KORE_BPF_CMP(SECCOMP_AUDIT_ARCH, 1, 0), 172 KORE_BPF_RET(SECCOMP_RET_KILL), 173 174 /* Load the system call number into the accumulator. */ 175 KORE_BPF_LOAD(nr, 0), 176 }; 177 178 /* bpf program epilogue. */ 179 static struct sock_filter filter_epilogue[] = { 180 /* Return hit if no system calls matched our list. */ 181 BPF_STMT(BPF_RET+BPF_K, SECCOMP_KILL_POLICY) 182 }; 183 184 #define filter_prologue_len KORE_FILTER_LEN(filter_prologue) 185 #define filter_epilogue_len KORE_FILTER_LEN(filter_epilogue) 186 187 int kore_seccomp_tracing = 0; 188 189 void kore_seccomp_enable(void) { 190 struct sock_filter *sf; 191 struct sock_fprog prog; 192 size_t prog_len, off, i; 193 194 /* 195 * If kore_seccomp_tracing is turned on, set the default policy to 196 * SECCOMP_RET_TRACE so we can log the system calls. 197 */ 198 if (kore_seccomp_tracing) { 199 filter_epilogue[0].k = SECCOMP_RET_TRACE; 200 puts("seccomp tracing enabled"); 201 } 202 203 /* Start with the prologue. */ 204 /* Finally add the epilogue. */ 205 prog_len = filter_prologue_len + KORE_FILTER_LEN(filter_kore) + filter_epilogue_len ; 206 207 /* Build the entire bpf program now. */ 208 if ((sf = calloc(prog_len, sizeof(*sf))) == NULL) { 209 puts("calloc"); 210 exit(1); 211 } 212 213 off = 0; 214 for (i = 0; i < filter_prologue_len; i++) 215 sf[off++] = filter_prologue[i]; 216 217 for (i = 0; i < KORE_FILTER_LEN(filter_kore); i++) 218 sf[off++] = filter_kore[i]; 219 220 for (i = 0; i < filter_epilogue_len; i++) 221 sf[off++] = filter_epilogue[i]; 222 223 /* Lock and load it. */ 224 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) { 225 printf("prctl: %s\n", strerror(errno)); 226 exit(1); 227 } 228 229 prog.filter = sf; 230 prog.len = prog_len; 231 232 if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) { 233 printf("prctl: %s\n", strerror(errno)); 234 exit(1); 235 } 236 } 237 238 // ----------------- seccomp end 239 240 bool isDir(const char *path) { 241 struct stat st; 242 243 if (stat(path, &st) == -1) { 244 return(false); 245 } 246 247 if (!S_ISDIR(st.st_mode)) { 248 return(false); 249 } 250 return(true); 251 } 252 253 int main(int ac, char **av){ 254 int sock; 255 struct sockaddr_in server; 256 int mysock; 257 char buf[4096]; 258 int rval; 259 260 char root[PATH_MAX] = {0}; 261 if (ROOT[0] == '/') { 262 realpath(ROOT, root); 263 } 264 else { 265 // create absolute path from relative ROOT path 266 char p[PATH_MAX] = {0}; 267 getcwd(p, PATH_MAX); 268 strcat(p, "/"); 269 strcat(p, ROOT); 270 realpath(p, root); 271 strcat(root, "/"); 272 } 273 274 size_t rootLen = strlen(root); 275 size_t slash = 0; 276 277 // count slashes at the end of root 278 // to compare paths with memcmp correctly 279 // since realpath removes the slashes at the 280 // end of the path from client. 281 while(root[rootLen-1-slash] == '/') { 282 slash++; 283 } 284 285 sock = socket(AF_INET, SOCK_STREAM, 0); 286 if (sock < 0){ 287 perror("Failed to create socket"); 288 } 289 290 server.sin_family = AF_INET; 291 server.sin_addr.s_addr = INADDR_ANY; 292 server.sin_port = htons(PORT); 293 294 /* setsockopt: Handy debugging trick that lets 295 * us rerun the server immediately after we kill it; 296 * otherwise we have to wait about 20 secs. 297 * Eliminates "ERROR on binding: Address already in use" error. 298 */ 299 int optval = 1; 300 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval , sizeof(int)); 301 302 if (bind(sock, (struct sockaddr *) &server, sizeof(server))){ 303 perror("bind failed"); 304 exit(1); 305 } 306 307 listen(sock, SOMAXCONN); 308 309 struct passwd *pw = NULL; 310 pw = getpwnam(RUNAS); 311 if (!pw) { 312 perror("get user info failed"); 313 exit(1); 314 } 315 316 if (chroot(CHROOT) == -1) { 317 perror("cannot chroot"); 318 exit(1); 319 } 320 321 if (chdir("/") == -1) { 322 perror("cannot chdir"); 323 exit(1); 324 } 325 326 if (setgroups(1, &pw->pw_gid) || 327 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 328 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) { 329 printf("cannot drop privileges"); 330 exit(1); 331 } 332 333 kore_seccomp_enable(); 334 335 printf("Serving "HOSTNAME":%d %s\n", PORT, root); 336 337 // date for request print 338 char date[50]; 339 do { 340 // struct for printing client ip in terminal with inet_ntoa 341 struct sockaddr_in addr; 342 socklen_t len = sizeof(addr); 343 mysock = accept(sock, &addr, &len); 344 if (mysock == -1) 345 perror("accept failed"); 346 else { 347 // Set 10s timeouts for receive and send (SO_RCVTIMEO and SO_SNDTIMEO) 348 struct timeval timeout; 349 timeout.tv_sec = 10; 350 timeout.tv_usec = 0; 351 if (setsockopt(mysock, SOL_SOCKET, SO_RCVTIMEO, (void *) &timeout, sizeof(timeout)) < 0) { 352 perror("receive timeout failed"); 353 close(mysock); 354 continue; 355 } 356 if (setsockopt(mysock, SOL_SOCKET, SO_SNDTIMEO, (void *) &timeout, sizeof(timeout)) < 0) { 357 perror("send timeout failed"); 358 close(mysock); 359 continue; 360 } 361 362 // new client 363 memset(buf, 0, sizeof(buf)); 364 if ((rval = recv(mysock, buf, sizeof(buf), 0)) < 0) { 365 perror("reading message"); 366 close(mysock); 367 continue; 368 } 369 else if (rval == 0) { 370 puts("Ending connection"); 371 close(mysock); 372 continue; 373 } 374 375 // get YMD HMS date 376 time_t clk = time(NULL); 377 struct tm *pClk = localtime(&clk); 378 strftime(date, sizeof(date), "%Y-%m-%d:%H:%M:%S", pClk); 379 printf("%s %s ",date, inet_ntoa(addr.sin_addr)); 380 381 // validate request then scan hostname path and content length 382 383 // find request end 384 char *reqEnd = memmem(buf, sizeof(buf), "\r\n", 2); 385 if (!reqEnd || buf[0] == ' ') { 386 puts("4 Invalid request"); 387 // add MSG_NOSIGNAL flag to ignore SIGPIPE when the client closes the socket early 388 send(mysock, "4 Invalid request\r\n", sizeof("4 Invalid request\r\n"), MSG_NOSIGNAL); 389 close(mysock); 390 continue; 391 } 392 393 // check ascii 394 char *cursor = buf; 395 bool isBad = false; 396 while (cursor < reqEnd) { 397 if (*cursor < 32 || *cursor == 127) { 398 isBad = true; 399 break; 400 } 401 cursor++; 402 } 403 if (isBad) { 404 puts("4 Non ASCII"); 405 send(mysock, "4 Non ASCII\r\n", sizeof("4 Non ASCII\r\n"), MSG_NOSIGNAL); 406 close(mysock); 407 continue; 408 } 409 410 // print request in terminal 411 *reqEnd = 0; 412 printf("%s ", buf); 413 *reqEnd = '\r'; 414 415 // parse hostname, path and content_length in request 416 char *hostname; 417 char *path; 418 char *content_length; 419 420 hostname = buf; 421 422 // hostname must match HOSTNAME 423 // comment out this test to accept nay hostname 424 int c = memcmp(hostname, HOSTNAME, strlen(HOSTNAME)); 425 426 if (c != 0) { 427 puts("4 Hostname"); 428 send(mysock, "4 Hostname\r\n", sizeof("4 Hostname\r\n"), MSG_NOSIGNAL); 429 close(mysock); 430 continue; 431 } 432 433 // get path 434 cursor = buf; 435 while (*cursor != ' ' && cursor < reqEnd) { 436 cursor++; 437 } 438 439 cursor++; 440 if (cursor >= reqEnd || *cursor == ' ') { 441 puts("4 Path"); 442 send(mysock, "4 Path\r\n", sizeof("4 Path\r\n"), MSG_NOSIGNAL); 443 close(mysock); 444 continue; 445 } 446 447 path = cursor; 448 449 // get content_length 450 while (*cursor != ' ' && cursor < reqEnd) { 451 cursor++; 452 } 453 454 cursor++; 455 if (cursor >= reqEnd || *cursor == ' ') { 456 puts("4 Length"); 457 send(mysock, "4 Length\r\n", sizeof("4 Length\r\n"), MSG_NOSIGNAL); 458 close(mysock); 459 continue; 460 } 461 462 content_length = cursor; 463 while(cursor < reqEnd) { 464 if (!isdigit(*cursor)) { 465 isBad = true; 466 break; 467 } 468 cursor++; 469 } 470 471 // the request must not have any content 472 // content_length = 0 473 if (isBad || reqEnd - content_length > 1 || *content_length != '0') { 474 puts("4 Length"); 475 send(mysock, "4 Length\r\n", sizeof("4 Length\r\n"), MSG_NOSIGNAL); 476 close(mysock); 477 continue; 478 } 479 480 // replace SPC with 0 at the end of path 481 *(content_length-1) = 0; 482 483 // build server path 484 char localPath[PATH_MAX] = {0}; 485 if (rootLen + strlen(path) >= PATH_MAX) { 486 puts("5 Path too long"); 487 send(mysock, "5 Path too long\r\n", sizeof("5 Path too long\r\n"), MSG_NOSIGNAL); 488 close(mysock); 489 continue; 490 } 491 memcpy(localPath, root, rootLen); 492 cursor = localPath + rootLen; 493 memcpy(cursor, path, strlen(path)); 494 495 // check path 496 if (access(localPath, R_OK) == -1) { 497 puts("4 Not found"); 498 send(mysock, "4 Not found\r\n", sizeof("4 Not found\r\n"), MSG_NOSIGNAL); 499 close(mysock); 500 continue; 501 } 502 char realPath[PATH_MAX] = {0}; 503 realpath(localPath, realPath); 504 if (memcmp(realPath, root, rootLen-slash) != 0) { 505 puts("4 Not found"); 506 send(mysock, "4 Not found\r\n", sizeof("4 Not found\r\n"), MSG_NOSIGNAL); 507 close(mysock); 508 continue; 509 } 510 511 size_t pathLen = strlen(realPath); 512 cursor = realPath + pathLen; 513 if (isDir(realPath)) { 514 if (pathLen + strlen("/index.gmi") >= PATH_MAX) { 515 puts("5 Path too long"); 516 send(mysock, "5 Path too long\r\n", sizeof("5 Path too long\r\n"), MSG_NOSIGNAL); 517 close(mysock); 518 continue; 519 } 520 memcpy(cursor, "/index.gmi", strlen("/index.gmi")); 521 cursor += strlen("/index.gmi"); 522 } 523 524 FILE *f = fopen(realPath, "r"); 525 if (!f) { 526 puts("4 Page not found"); 527 send(mysock, "4 Page not found\r\n", sizeof("4 Page not found\r\n"), MSG_NOSIGNAL); 528 close(mysock); 529 continue; 530 } 531 532 // request in buf is not needed anymore, reuse buf for response 533 534 // check gemini extension 535 char *mimetype = "application/octet-stream"; 536 if (strlen(realPath) > 4 && memcmp(cursor-2, "md", 2) == 0) { 537 mimetype = "text/markdown"; 538 } 539 else if (strlen(realPath) > 5 && memcmp(cursor-3, "gmi", 3) == 0) { 540 mimetype = "text/gemini"; 541 } 542 else if (strlen(realPath) > 10 && memcmp(cursor-2, "markdown", 8) == 0) { 543 mimetype = "text/markdown"; 544 } 545 546 int len = sprintf(buf, "2 %s\r\n", mimetype); 547 if (send(mysock, buf, len, MSG_NOSIGNAL) != -1) { 548 // print response in terminal 549 // remove \r\n 550 buf[len-2] = 0; 551 puts(buf); 552 553 // send file 554 size_t fsz; 555 while (fsz = fread(buf, 1, (size_t)sizeof(buf) , f)) { 556 ssize_t r = send(mysock, buf, fsz, MSG_NOSIGNAL); 557 if (r == -1) { 558 perror("write failed"); 559 puts("closed socket"); 560 break; 561 } 562 } 563 } 564 else { 565 perror("write failed"); 566 } 567 fclose(f); 568 close(mysock); 569 } 570 } while(1); 571 572 puts("Server stopped."); 573 }