💾 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
⬅️ Previous capture (2024-08-18)
-=-=-=-=-=-=-
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 }