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