sfakeroot.c (21214B)
1 /* sfakeroot: manipulate files faking root privileges 2 * 3 * Copyright © 2020 - 2025 Richard Ipsum 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, version 3 of the License. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 * 17 */ 18 19 #include <syslog.h> 20 #include <stdarg.h> 21 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <stdbool.h> 25 #include <limits.h> 26 #include <dirent.h> 27 #include <unistd.h> 28 #include <string.h> 29 #include <errno.h> 30 #include <arpa/inet.h> 31 #include <sys/types.h> 32 #include <sys/socket.h> 33 #include <sys/wait.h> 34 #include <sys/un.h> 35 #include <signal.h> 36 37 #include "sfakeroot_inline.h" 38 #include "sfakeroot.h" 39 40 #include <sys/syscall.h> 41 42 #define FALLBACK_SHELL "/bin/sh" 43 #define SONAME "libsfakeroot.so" 44 45 #ifdef __linux__ 46 #define SESSION_FILE_LINE_FMT "%lu,%u,%u,%u\n" 47 #else 48 #define SESSION_FILE_LINE_FMT "%llu,%u,%u,%u\n" 49 #endif 50 51 static char *argv0; 52 53 struct sfakeroot_list { 54 struct sfakeroot_ent *first; 55 struct sfakeroot_ent *last; 56 }; 57 58 struct sfakeroot_list ents; 59 60 struct cmdline_options { 61 bool require_session_file; 62 char *session_file; 63 }; 64 65 struct cmdline_arguments { 66 struct cmdline_options options; 67 char **args; 68 size_t args_len; 69 }; 70 71 static struct sfakeroot_ent *lookupent(struct sfakeroot_list *list, ino_t st_ino) 72 { 73 for (struct sfakeroot_ent *e = list->first; e != NULL; e = e->next) { 74 debug("checking st_ino %llu\n", e->st.st_ino); 75 if (e->st.st_ino == st_ino) { 76 return e; 77 } 78 } 79 80 debug("lookupent returning NULL\n"); 81 return NULL; 82 } 83 84 static struct sfakeroot_ent *addent(struct sfakeroot_list *list, 85 const struct stat *s, bool stale) 86 { 87 struct sfakeroot_ent *ent; 88 89 ent = malloc(sizeof (*ent)); 90 if (ent == NULL) { 91 return NULL; 92 } 93 94 debug("add ent: %llu has mode %o\n", s->st_ino, s->st_mode); 95 96 ent->stale = stale; 97 ent->next = NULL; 98 ent->st = *s; 99 100 if (list->first == NULL) { 101 list->first = ent; 102 goto out; 103 } 104 105 if (list->last != NULL) { 106 list->last->next = ent; 107 } 108 109 out: 110 list->last = ent; 111 return ent; 112 } 113 114 static void handle_stat(struct sfakeroot_msg *m) 115 { 116 struct sfakeroot_ent *ent; 117 int errno_sv = 0; 118 int cwdfd; 119 ino_t inode; 120 DIR *cwd = opendir("."); 121 122 if (cwd == NULL) { 123 m->retcode = -1; 124 return; 125 } 126 cwdfd = dirfd(cwd); 127 if (chdir(m->working_dir) == -1) { 128 closedir(cwd); 129 m->retcode = -1; 130 return; 131 } 132 133 switch (m->type) { 134 case SFAKEROOT_MSG_FSTAT: 135 syslog(LOG_WARNING, "handle_fstat fd: %d working dir: %s\n", m->fd, m->working_dir); 136 m->retcode = fstat(m->fd, &m->st); 137 inode = m->st.st_ino; 138 errno_sv = errno; 139 break; 140 case SFAKEROOT_MSG_STAT: 141 syslog(LOG_WARNING, "handle_stat path: %s, working dir: %s\n", m->path, m->working_dir); 142 m->retcode = stat(m->path, &m->st); 143 inode = m->st.st_ino; 144 errno_sv = errno; 145 break; 146 case SFAKEROOT_MSG_LSTAT: 147 syslog(LOG_WARNING, "handle_lstat path: %s, working dir: %s\n", m->path, m->working_dir); 148 m->retcode = lstat(m->path, &m->st); 149 inode = m->st.st_ino; 150 errno_sv = errno; 151 break; 152 case SFAKEROOT_MSG_FSTATAT: 153 m->retcode = fstatat(m->fd, m->path, &m->st, m->flag); 154 inode = m->st.st_ino; 155 errno_sv = errno; 156 break; 157 #if USE_STATX 158 case SFAKEROOT_MSG_STATX: 159 syslog(LOG_WARNING, "handle_statx path: %s, working dir: %s\n", m->path, m->working_dir); 160 m->retcode = statx(m->fd, m->path, m->flag, m->mask, &m->statxbuf); 161 inode = m->statxbuf.stx_ino; 162 errno_sv = errno; 163 break; 164 #endif 165 default: 166 debug("non stat message in handle_stat\n"); 167 goto cleanup; 168 } 169 170 if (m->retcode != 0) { 171 m->reterrno = errno_sv; 172 goto cleanup; 173 } 174 175 if ((ent = lookupent(&ents, inode)) != NULL) { 176 debug("lookupent returned ent for inode %d, has st_mode: %o\n", inode, ent->st.st_mode); 177 if (ent->stale) { 178 /* update the entry with the new stat data */ 179 #if USE_STATX 180 m->statxbuf.stx_uid = ent->st.st_uid; 181 m->statxbuf.stx_gid = ent->st.st_gid; 182 m->statxbuf.stx_mode = ent->st.st_mode; 183 #endif 184 m->st.st_uid = ent->st.st_uid; 185 m->st.st_gid = ent->st.st_gid; 186 m->st.st_mode = ent->st.st_mode; 187 ent->st = m->st; 188 ent->stale = false; 189 } 190 else { 191 #if USE_STATX 192 m->statxbuf.stx_uid = ent->st.st_uid; 193 m->statxbuf.stx_gid = ent->st.st_gid; 194 m->statxbuf.stx_mode = ent->st.st_mode; 195 #endif 196 m->st = ent->st; 197 } 198 if ((int) m->st.st_uid == -1) { 199 m->st.st_uid = 0; 200 #if USE_STATX 201 m->statxbuf.stx_uid = 0; 202 #endif 203 } 204 if ((int) m->st.st_gid == -1) { 205 m->st.st_gid = 0; 206 #if USE_STATX 207 m->statxbuf.stx_gid = 0; 208 #endif 209 } 210 211 goto cleanup; 212 } 213 214 m->st.st_uid = 0; 215 m->st.st_gid = 0; 216 #if USE_STATX 217 m->statxbuf.stx_uid = 0; 218 m->statxbuf.stx_gid = 0; 219 #endif 220 221 cleanup: 222 fchdir(cwdfd); 223 closedir(cwd); 224 } 225 226 static void handle_perms_change(struct sfakeroot_msg *m) 227 { 228 struct stat s = {0}; 229 int errno_sv; 230 int cwdfd; 231 struct sfakeroot_ent *ent; 232 DIR *cwd = opendir("."); 233 234 if (cwd == NULL) { 235 m->retcode = -1; 236 return; 237 } 238 cwdfd = dirfd(cwd); 239 if (chdir(m->working_dir) == -1) { 240 closedir(cwd); 241 m->retcode = -1; 242 return; 243 } 244 245 debug("handle_perms_change: m->path: %s, m->working_dir: %s\n", 246 m->path, m->working_dir); 247 248 switch (m->type) { 249 case SFAKEROOT_MSG_LCHOWN: 250 m->retcode = lstat(m->path, &s); 251 errno_sv = errno; 252 break; 253 case SFAKEROOT_MSG_CHOWN: 254 case SFAKEROOT_MSG_CHMOD: 255 m->retcode = stat(m->path, &s); 256 errno_sv = errno; 257 break; 258 case SFAKEROOT_MSG_FCHOWNAT: 259 m->retcode = fstatat(m->fd, m->path, &s, m->flag); 260 errno_sv = errno; 261 break; 262 case SFAKEROOT_MSG_FCHOWN: 263 m->retcode = fstat(m->fd, &s); 264 errno_sv = errno; 265 break; 266 default: 267 debug("non chown/chmod message in handle_perms_change\n"); 268 goto cleanup; 269 } 270 271 if (m->retcode == -1) { 272 debug("stat returned error\n"); 273 m->reterrno = errno_sv; 274 goto cleanup; 275 } 276 277 /* path exists */ 278 switch (m->type) { 279 case SFAKEROOT_MSG_LCHOWN: 280 case SFAKEROOT_MSG_CHOWN: 281 case SFAKEROOT_MSG_FCHOWN: 282 case SFAKEROOT_MSG_FCHOWNAT: 283 s.st_uid = m->uid; 284 s.st_gid = m->gid; 285 break; 286 case SFAKEROOT_MSG_CHMOD: 287 debug("setting mode %o\n", m->mode); 288 s.st_mode = (s.st_mode & S_IFMT) | m->mode; 289 break; 290 default: 291 debug("non chown/chmod message in handle_perms_change\n"); 292 goto cleanup; 293 } 294 295 if ((ent = lookupent(&ents, s.st_ino)) != NULL) { 296 uid_t u = ent->st.st_uid; 297 gid_t g = ent->st.st_gid; 298 mode_t md = ent->st.st_mode; 299 ent->st = s; 300 ent->st.st_uid = ((int) m->uid == -1) ? u : m->uid; 301 ent->st.st_gid = ((int) m->gid == -1) ? g : m->gid; 302 ent->st.st_mode = m->mode ? m->mode : md; 303 m->retcode = 0; 304 goto cleanup; 305 } 306 307 s.st_uid = m->uid; 308 s.st_gid = m->gid; 309 310 if (addent(&ents, &s, false) == NULL) { 311 m->reterrno = ENOMEM; 312 m->retcode = -1; 313 goto cleanup; 314 } 315 316 m->retcode = 0; 317 318 cleanup: 319 fchdir(cwdfd); 320 closedir(cwd); 321 } 322 323 /* listen for new connections */ 324 static int sfakeroot_create_listener(const char *session_socket_path) 325 { 326 static struct sockaddr_un sa = {.sun_family = AF_UNIX}; 327 socklen_t sock_namelen; 328 int sock; 329 330 sock = socket(AF_UNIX, SOCK_STREAM, 0); 331 if (sock == -1) { 332 fprintf(stderr, "%s: socket: %s\n", argv0, strerror(errno)); 333 exit(1); 334 } 335 336 strlcpy(sa.sun_path, session_socket_path, sizeof (sa.sun_path)); 337 if (unlink(session_socket_path) == -1 && errno != ENOENT) { 338 fprintf(stderr, "%s: unlink \"%s\" failed: %s\n", 339 argv0, sa.sun_path, strerror(errno)); 340 exit(1); 341 } 342 343 sock_namelen = strlen(sa.sun_path) + 1 + sizeof (sa.sun_family); 344 if (bind(sock, (struct sockaddr *) &sa, sock_namelen) == -1) { 345 fprintf(stderr, "%s: bind: %s\n", argv0, strerror(errno)); 346 exit(1); 347 } 348 349 if (listen(sock, 21) == -1) { 350 fprintf(stderr, "%s: listen: %s\n", argv0, strerror(errno)); 351 exit(1); 352 } 353 354 return sock; 355 } 356 357 static int write_session_to_file(const char *save_path) 358 { 359 FILE *f = fopen(save_path, "w"); 360 361 debug("write session to `%s'\n", save_path); 362 363 if (f == NULL) { 364 goto error; 365 } 366 367 for (struct sfakeroot_ent *p = ents.first; p != NULL; p = p->next) { 368 struct stat *s = &p->st; 369 if (fprintf(f, SESSION_FILE_LINE_FMT, s->st_ino, s->st_uid, s->st_gid, s->st_mode) < 0) { 370 goto error; 371 } 372 debug(SESSION_FILE_LINE_FMT, s->st_ino, s->st_uid, s->st_gid, s->st_mode); 373 } 374 375 if (fclose(f) != 0) { 376 goto error; 377 } 378 379 return 0; 380 381 error: 382 fprintf(stderr, "%s: error saving session to `%s': %s\n", 383 argv0, save_path, strerror(errno)); 384 return -1; 385 } 386 387 static int sfakeroot_finish_session(const struct cmdline_arguments *args) 388 { 389 if (args->options.session_file != NULL) { 390 if (write_session_to_file(args->options.session_file) == -1) { 391 return -1; 392 } 393 } 394 395 return 0; 396 } 397 398 int estrtol(const char *s, long *out) 399 { 400 char *e; 401 402 long n = strtol(s, &e, 10); 403 404 if (*s == '\0' || *e != '\0') { 405 fprintf(stderr, "%s: estrtol: `%s' not a number\n", argv0, s); 406 return -1; 407 } 408 409 if ((errno == ERANGE && (n == LONG_MIN || n == LONG_MAX))) { 410 fprintf(stderr, "%s: estrtol: `%s' out of range\n", argv0, s); 411 return -1; 412 } 413 414 *out = n; 415 return 0; 416 } 417 418 static int sfakeroot_load_session_from_file(const struct cmdline_arguments *args) 419 { 420 char *line = NULL, *session_filepath; 421 size_t n = 0; 422 ssize_t count; 423 FILE *f; 424 425 session_filepath = args->options.session_file; 426 f = fopen(session_filepath, "r"); 427 if (f == NULL) { 428 char *errfmt = "%s: couldn't open session file `%s': %s\n"; 429 debug(errfmt, argv0, session_filepath, strerror(errno)); 430 if (args->options.require_session_file) { 431 fprintf(stderr, errfmt, argv0, session_filepath, strerror(errno)); 432 return -1; 433 } 434 return 0; 435 } 436 437 debug("loading session from `%s'\n", session_filepath); 438 439 while ((count = getline(&line, &n, f)) != -1) { 440 struct stat s = {0}; 441 long values[4]; 442 int i = 0; 443 line[count - 1] = '\0'; /* strip '\n' */ 444 for (char *s = strtok(line, ","); s != NULL && i < 4; s = strtok(NULL, ","), i++) { 445 if (estrtol(s, values + i) == -1) { 446 free(line); 447 return -1; 448 } 449 } 450 s.st_ino = (ino_t) values[0]; 451 s.st_uid = (uid_t) values[1]; 452 s.st_gid = (gid_t) values[2]; 453 s.st_mode = (mode_t) values[3]; 454 addent(&ents, &s, true); 455 } 456 457 if (ferror(f)) { 458 fprintf(stderr, "%s: error reading from `%s': %s\n", 459 argv0, session_filepath, strerror(errno)); 460 free(line); 461 return -1; 462 } 463 464 free(line); 465 return 0; 466 } 467 468 bool sfakeroot_daemon_running(void) 469 { 470 int sockfd = sfakeroot__session_open_internal(false); 471 close(sockfd); 472 return sockfd != -1; 473 } 474 475 static void sfakeroot_server(int pipewfd, const char *session_socket_path, 476 const struct cmdline_arguments *args) 477 { 478 static struct sockaddr_un sa; 479 int listen_sock, sock; 480 socklen_t namelen; 481 482 if (args->options.session_file != NULL) { 483 if (sfakeroot_load_session_from_file(args) == -1) { 484 exit(1); 485 } 486 } 487 488 listen_sock = sfakeroot_create_listener(session_socket_path); 489 490 /* close write end of pipe to indicate to parent process that we 491 * are now ready to accept incoming connections 492 */ 493 close(pipewfd); 494 495 for (;;) { 496 struct sfakeroot_msg m; 497 debug("waiting for connection...\n"); 498 debug("%d\n", getpid()); 499 debug("getenv(LD_PRELOAD): "); 500 501 sock = accept(listen_sock, (struct sockaddr *) &sa, &namelen); 502 if (sock == -1) { 503 fprintf(stderr, "%s: accept: %s\n", argv0, strerror(errno)); 504 exit(1); 505 } 506 debug("connection accepted\n"); 507 debug("waiting for message\n"); 508 if (sfakeroot_recvmsg(sock, &m) == -1) { 509 fprintf(stderr, "eof?\n"); 510 continue; 511 } 512 513 syslog(LOG_WARNING, "message type: %u\n", m.type); 514 switch (m.type) { 515 case SFAKEROOT_MSG_FSTAT: 516 case SFAKEROOT_MSG_LSTAT: 517 case SFAKEROOT_MSG_STAT: 518 case SFAKEROOT_MSG_FSTATAT: 519 case SFAKEROOT_MSG_STATX: 520 syslog(LOG_WARNING, "doing the fake stat\n"); 521 handle_stat(&m); 522 break; 523 case SFAKEROOT_MSG_CHOWN: 524 case SFAKEROOT_MSG_LCHOWN: 525 case SFAKEROOT_MSG_CHMOD: 526 // TODO: fchmod 527 // TODO: fchmodat 528 case SFAKEROOT_MSG_FCHOWN: 529 case SFAKEROOT_MSG_FCHOWNAT: 530 handle_perms_change(&m); 531 break; 532 case SFAKEROOT_MSG_FINISH: 533 if (sfakeroot_finish_session(args) == -1) { 534 exit(1); 535 } 536 exit(0); 537 } 538 if (sfakeroot_sendmsg(sock, &m, NULL, 0) == -1) { 539 exit(1); 540 } 541 close(sock); 542 } 543 } 544 545 static int sfakeroot_daemon(const char *session_socket_path, 546 const struct cmdline_arguments *args) 547 { 548 int pipefds[2]; 549 char buf[1]; 550 pid_t pid; 551 int status; 552 553 if (pipe(pipefds) == -1) { 554 fprintf(stderr, "%s: pipe: %s\n", argv0, strerror(errno)); 555 return -1; 556 } 557 558 switch ((pid = fork())) { 559 case 0: 560 /* child */ 561 close(pipefds[0]); /* close read end */ 562 debug("starting daemon...\n"); 563 if (daemon(1, 1) == -1) { 564 fprintf(stderr, "%s: failed to daemonise: %s\n", 565 argv0, strerror(errno)); 566 exit(1); 567 } 568 sfakeroot_server(pipefds[1], session_socket_path, args); 569 break; 570 case -1: 571 fprintf(stderr, "%s: fork: %s\n", argv0, strerror(errno)); 572 return -1; 573 default: 574 /* parent */ 575 close(pipefds[1]); /* close write end */ 576 /* This read will block until the child process signals it 577 * is ready, i.e. it's ready to accept connections from us. 578 */ 579 if (read(pipefds[0], buf, sizeof (buf)) == -1) { 580 fprintf(stderr, "%s: error reading from pipe: %s\n", 581 argv0, strerror(errno)); 582 return -1; 583 } 584 if (waitpid(pid, &status, 0) == -1) { 585 fprintf(stderr, "%s: waitpid: %s\n", argv0, strerror(errno)); 586 return -1; 587 } 588 if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) { 589 fprintf(stderr, "%s: sfakeroot daemon unexpected condition\n", 590 argv0); 591 return -1; 592 } 593 } 594 595 return 0; 596 } 597 598 int sfakeroot_session_open(void); 599 600 static int setpreload(void) 601 { 602 char *wd = SFAKEROOT_LIBDIR, *path = NULL; 603 size_t len; 604 605 len = snprintf(NULL, 0, "%s/%s", wd, SONAME); 606 path = malloc(len + 1); 607 if (path == NULL) { 608 fprintf(stderr, "%s: malloc: %s\n", argv0, strerror(errno)); 609 return -1; 610 } 611 snprintf(path, len + 1, "%s/%s", wd, SONAME); 612 613 if (setenv("LD_PRELOAD", path, 1) == -1) { 614 fprintf(stderr, "%s: setenv: %s\n", argv0, strerror(errno)); 615 free(path); 616 return -1; 617 } 618 free(path); 619 return 0; 620 } 621 622 static void usage(void) 623 { 624 fprintf(stderr, "usage: %s [-Ff file]\n", argv0); 625 } 626 627 static void parse_options(int argc, char *argv[], struct cmdline_arguments *args) 628 { 629 int ch; 630 extern int optind; 631 extern char *optarg; 632 633 while ((ch = getopt(argc, argv, "F:f:")) != -1) { 634 switch (ch) { 635 case 'F': 636 args->options.require_session_file = true; 637 /* fallthrough */ 638 case 'f': 639 args->options.session_file = optarg; 640 break; 641 default: 642 usage(); 643 exit(1); 644 } 645 } 646 647 args->args = argv + optind; 648 args->args_len = argc - optind; 649 } 650 651 int main(int argc, char *argv[]) 652 { 653 int sockfd, status, exit_status, len; 654 pid_t pid; 655 struct sfakeroot_msg m; 656 char *sargv[] = {FALLBACK_SHELL, NULL}, *shell, **exec_argvp; 657 char tempdir_path[8192] = "/tmp/sfakeroot.XXXXXXXXXX"; 658 char session_socket_path[8192]; 659 static struct cmdline_arguments args; 660 661 argv0 = argv[0]; 662 parse_options(argc, argv, &args); 663 664 if ((shell = getenv("SHELL")) != NULL) { 665 sargv[0] = shell; 666 } 667 668 if (mkdtemp(tempdir_path) == NULL) { 669 fprintf(stderr, "%s: mkdtemp: %s\n", argv0, strerror(errno)); 670 exit_status = 1; 671 goto cleanup; 672 } 673 674 len = snprintf(session_socket_path, sizeof (session_socket_path), "%s/%s", 675 tempdir_path, "uds.sock"); 676 if (len == -1) { 677 fprintf(stderr, "%s: snprintf\n", argv0); 678 exit_status = 1; 679 goto cleanup; 680 } 681 else if ((size_t) len > sizeof (session_socket_path)) { 682 fprintf(stderr, "%s: path too long\n", argv0); 683 exit_status = 1; 684 goto cleanup; 685 } 686 if (setenv("SFAKEROOT_SOCKET_PATH", session_socket_path, 1) == -1) { 687 fprintf(stderr, "%s: setenv: %s\n", argv0, strerror(errno)); 688 exit_status = 1; 689 goto cleanup; 690 } 691 if (!sfakeroot_daemon_running()) { 692 if (sfakeroot_daemon(session_socket_path, &args) == -1) { 693 exit_status = 1; 694 goto cleanup; 695 } 696 } 697 exec_argvp = args.args_len > 0 ? args.args : sargv; 698 switch (pid = fork()) { 699 case 0: 700 // This child process is going to be our session 701 // i.e. shell or whatever program we were asked to run 702 // we need to set LD_PRELOAD for this process so that 703 // we can intercept the 'stat' calls and redirect them 704 // to the sfakeroot daemon. 705 if (setpreload() == -1) { 706 exit_status = 1; 707 goto cleanup; 708 } 709 execvp(exec_argvp[0], exec_argvp); 710 fprintf(stderr, "%s: exec `%s': %s\n", 711 argv0, exec_argvp[0], strerror(errno)); 712 switch (errno) { 713 case EACCES: 714 exit_status = 126; 715 goto cleanup; 716 case ENOENT: 717 exit_status = 127; 718 goto cleanup; 719 default: 720 exit_status = 1; 721 goto cleanup; 722 } 723 case -1: 724 fprintf(stderr, "%s: fork: %s\n", argv0, strerror(errno)); 725 exit_status = 1; 726 goto cleanup; 727 default: 728 waitpid(pid, &status, 0); 729 debug("instructing daemon to finish...\n"); 730 m.type = SFAKEROOT_MSG_FINISH; 731 sockfd = sfakeroot_session_open(); 732 if (sockfd == -1) { 733 fprintf(stderr, "%s: failed to open session\n", argv0); 734 exit_status = 1; 735 goto cleanup; 736 } 737 if (sfakeroot_sendmsg(sockfd, &m, NULL, 0) == -1) { 738 exit_status = 1; 739 goto cleanup; 740 } 741 close(sockfd); 742 if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { 743 exit_status = WEXITSTATUS(status); 744 goto cleanup; 745 } 746 if (WIFSIGNALED(status)) { 747 exit_status = 127 + WTERMSIG(status); 748 goto cleanup; 749 } 750 else if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) { 751 exit_status = 1; 752 goto cleanup; 753 } 754 break; 755 } 756 757 exit_status = 0; 758 759 cleanup: 760 unlink(session_socket_path); 761 rmdir(tempdir_path); 762 return exit_status; 763 }