Annotation of src/usr.bin/cvs/cvsd.c, Revision 1.1
1.1 ! jfb 1: /* $OpenBSD$ */
! 2: /*
! 3: * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
! 4: * All rights reserved.
! 5: *
! 6: * Redistribution and use in source and binary forms, with or without
! 7: * modification, are permitted provided that the following conditions
! 8: * are met:
! 9: *
! 10: * 1. Redistributions of source code must retain the above copyright
! 11: * notice, this list of conditions and the following disclaimer.
! 12: * 2. The name of the author may not be used to endorse or promote products
! 13: * derived from this software without specific prior written permission.
! 14: *
! 15: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
! 16: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
! 17: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
! 18: * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
! 19: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
! 20: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
! 21: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
! 22: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
! 23: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
! 24: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
! 25: */
! 26:
! 27: #include <sys/param.h>
! 28: #include <sys/stat.h>
! 29: #include <sys/wait.h>
! 30: #include <sys/uio.h>
! 31:
! 32: #include <err.h>
! 33: #include <pwd.h>
! 34: #include <grp.h>
! 35: #include <poll.h>
! 36: #include <fcntl.h>
! 37: #include <dirent.h>
! 38: #include <stdlib.h>
! 39: #include <stdio.h>
! 40: #include <unistd.h>
! 41: #include <signal.h>
! 42: #include <errno.h>
! 43: #include <string.h>
! 44: #include <sysexits.h>
! 45:
! 46: #include "log.h"
! 47: #include "sock.h"
! 48: #include "cvs.h"
! 49: #include "cvsd.h"
! 50:
! 51:
! 52: static void cvsd_parent_loop (void);
! 53: static void cvsd_child_loop (void);
! 54: static int cvsd_privdrop (void);
! 55:
! 56:
! 57: extern char *__progname;
! 58:
! 59:
! 60:
! 61: int foreground = 0;
! 62:
! 63: volatile sig_atomic_t restart = 0;
! 64:
! 65:
! 66: uid_t cvsd_uid = -1;
! 67: gid_t cvsd_gid = -1;
! 68:
! 69:
! 70: char *cvsd_root = NULL;
! 71: char *cvsd_aclfile = NULL;
! 72:
! 73:
! 74: static int cvsd_privfd = -1;
! 75:
! 76:
! 77:
! 78: static TAILQ_HEAD(,cvsd_child) cvsd_children;
! 79: static volatile sig_atomic_t cvsd_chnum = 0;
! 80: static volatile sig_atomic_t cvsd_chmin = CVSD_CHILD_DEFMIN;
! 81: static volatile sig_atomic_t cvsd_chmax = CVSD_CHILD_DEFMAX;
! 82:
! 83:
! 84: /*
! 85: * sighup_handler()
! 86: *
! 87: * Handler for the SIGHUP signal.
! 88: */
! 89:
! 90: void
! 91: sighup_handler(int signo)
! 92: {
! 93: restart = 1;
! 94: }
! 95:
! 96:
! 97: /*
! 98: * sigint_handler()
! 99: *
! 100: * Handler for the SIGINT signal.
! 101: */
! 102:
! 103: void
! 104: sigint_handler(int signo)
! 105: {
! 106: cvs_sock_doloop = 0;
! 107: }
! 108:
! 109:
! 110: /*
! 111: * sigchld_handler()
! 112: *
! 113: */
! 114:
! 115: void
! 116: sigchld_handler(int signo)
! 117: {
! 118: int status;
! 119:
! 120: wait(&status);
! 121:
! 122: }
! 123:
! 124:
! 125: /*
! 126: * usage()
! 127: *
! 128: * Display program usage.
! 129: */
! 130:
! 131: void
! 132: usage(void)
! 133: {
! 134: fprintf(stderr,
! 135: "Usage: %s [-dfpv] [-a aclfile] [-c config] [-r root] [-s path]\n"
! 136: "\t-a aclfile\tUse the file <aclfile> for ACL ruleset\n"
! 137: "\t-d\t\tStart the server in debugging mode (very verbose)\n"
! 138: "\t-f\t\tStay in foreground instead of becoming a daemon\n"
! 139: "\t-p\t\tPerform permission and ownership check on the repository\n"
! 140: "\t-r root\tUse <root> as the root directory of the repository\n"
! 141: "\t-s path\tUse <path> as the path for the CVS server socket\n"
! 142: "\t-v\t\tBe verbose\n",
! 143: __progname);
! 144: }
! 145:
! 146:
! 147: int
! 148: main(int argc, char **argv)
! 149: {
! 150: u_int i;
! 151: int ret, checkrepo;
! 152: uid_t uid;
! 153: struct passwd *pwd;
! 154: struct group *grp;
! 155:
! 156: checkrepo = 0;
! 157:
! 158: if (cvs_log_init(LD_STD|LD_SYSLOG, LF_PID) < 0)
! 159: err(1, "failed to initialize logging mechanism");
! 160:
! 161: while ((ret = getopt(argc, argv, "a:dfpr:s:v")) != -1) {
! 162: switch (ret) {
! 163: case 'a':
! 164: cvsd_aclfile = optarg;
! 165: break;
! 166: case 'd':
! 167: cvs_log_filter(LP_FILTER_UNSET, LP_DEBUG);
! 168: cvs_log_filter(LP_FILTER_UNSET, LP_INFO);
! 169: break;
! 170: case 'f':
! 171: foreground = 1;
! 172: break;
! 173: case 'p':
! 174: checkrepo = 1;
! 175: break;
! 176: case 'r':
! 177: cvsd_root = optarg;
! 178: break;
! 179: case 's':
! 180: cvsd_sock_path = optarg;
! 181: break;
! 182: case 'v':
! 183: cvs_log_filter(LP_FILTER_UNSET, LP_INFO);
! 184: break;
! 185: default:
! 186: usage();
! 187: exit(EX_USAGE);
! 188: }
! 189: }
! 190:
! 191: argc -= optind;
! 192: argv += optind;
! 193:
! 194: if ((cvsd_aclfile != NULL) && (cvs_acl_parse(cvsd_aclfile) < 0))
! 195: errx(1, "error while parsing the ACL file `%s'", cvsd_aclfile);
! 196:
! 197: if (cvsd_root == NULL)
! 198: errx(1, "no CVS root directory specified");
! 199:
! 200: if (argc > 0)
! 201: errx(EX_USAGE, "unrecognized trailing arguments");
! 202:
! 203: TAILQ_INIT(&cvsd_children);
! 204:
! 205: pwd = getpwnam(CVSD_USER);
! 206: if (pwd == NULL)
! 207: err(EX_NOUSER, "failed to get user `%s'", CVSD_USER);
! 208:
! 209: grp = getgrnam(CVSD_GROUP);
! 210: if (grp == NULL)
! 211: err(EX_NOUSER, "failed to get group `%s'", CVSD_GROUP);
! 212:
! 213: cvsd_uid = pwd->pw_uid;
! 214: cvsd_gid = grp->gr_gid;
! 215:
! 216: signal(SIGHUP, sighup_handler);
! 217: signal(SIGINT, sigint_handler);
! 218: signal(SIGQUIT, sigint_handler);
! 219: signal(SIGTERM, sigint_handler);
! 220:
! 221: if (!foreground && daemon(0, 0) == -1) {
! 222: cvs_log(LP_ERRNO, "failed to become a daemon");
! 223: exit(EX_OSERR);
! 224: }
! 225:
! 226: if (cvsd_sock_open() < 0) {
! 227: exit(1);
! 228: }
! 229:
! 230: if (setegid(cvsd_gid) == -1) {
! 231: cvs_log(LP_ERRNO, "failed to drop group privileges");
! 232: exit(EX_OSERR);
! 233: }
! 234: if (seteuid(cvsd_uid) == -1) {
! 235: cvs_log(LP_ERRNO, "failed to drop user privileges");
! 236: exit(EX_OSERR);
! 237: }
! 238:
! 239: if (checkrepo && cvsd_checkperms("/") != 0) {
! 240: cvs_log(LP_ERR,
! 241: "exiting due to permission errors on repository");
! 242: exit(1);
! 243: }
! 244:
! 245: /* spawn the initial pool of children */
! 246: for (i = 0; i < cvsd_chmin; i++) {
! 247: ret = cvsd_forkchild();
! 248: if (ret == -1) {
! 249: cvs_log(LP_ERR, "failed to spawn child");
! 250: exit(EX_OSERR);
! 251: }
! 252: }
! 253:
! 254: cvsd_sock_loop();
! 255:
! 256: cvs_log(LP_NOTICE, "shutting down");
! 257: cvs_log_cleanup();
! 258:
! 259: cvsd_sock_close();
! 260:
! 261: return (0);
! 262: }
! 263:
! 264:
! 265: /*
! 266: * cvsd_privdrop()
! 267: *
! 268: * Drop privileges.
! 269: */
! 270:
! 271: int
! 272: cvsd_privdrop(void)
! 273: {
! 274: cvs_log(LP_INFO, "dropping privileges to %s:%s", CVSD_USER,
! 275: CVSD_GROUP);
! 276: if (setegid(cvsd_gid) == -1) {
! 277: cvs_log(LP_ERRNO, "failed to drop group privileges to %s",
! 278: CVSD_GROUP);
! 279: return (-1);
! 280: }
! 281:
! 282: if (seteuid(cvsd_uid) == -1) {
! 283: cvs_log(LP_ERRNO, "failed to drop user privileges to %s",
! 284: CVSD_USER);
! 285: return (-1);
! 286: }
! 287:
! 288: return (0);
! 289: }
! 290:
! 291:
! 292: /*
! 293: * cvsd_checkperms()
! 294: *
! 295: * Check permissions on the CVS repository and log warnings for any
! 296: * weird of loose permissions.
! 297: * Returns the number of warnings on success, or -1 on failure.
! 298: */
! 299:
! 300: int
! 301: cvsd_checkperms(const char *path)
! 302: {
! 303: int fd, nbwarn, ret;
! 304: mode_t fmode;
! 305: long base;
! 306: void *dp, *endp;
! 307: char buf[1024], spath[MAXPATHLEN];
! 308: struct stat st;
! 309: struct dirent *dep;
! 310:
! 311: nbwarn = 0;
! 312:
! 313: cvs_log(LP_DEBUG, "checking permissions on `%s'", path);
! 314:
! 315: if (stat(path, &st) == -1) {
! 316: cvs_log(LP_ERRNO, "failed to stat `%s'", path);
! 317: return (-1);
! 318: }
! 319:
! 320: if (S_ISDIR(st.st_mode))
! 321: fmode = CVSD_DPERM;
! 322: else
! 323: fmode = CVSD_FPERM;
! 324:
! 325: if (st.st_uid != cvsd_uid) {
! 326: cvs_log(LP_WARN, "owner of `%s' is not %s", path, CVSD_USER);
! 327: nbwarn++;
! 328: }
! 329:
! 330: if (st.st_gid != cvsd_gid) {
! 331: cvs_log(LP_WARN, "group of `%s' is not %s", path, CVSD_GROUP);
! 332: nbwarn++;
! 333: }
! 334:
! 335: if (st.st_mode & S_IWGRP) {
! 336: cvs_log(LP_WARN, "file `%s' is group-writable", path,
! 337: fmode);
! 338: nbwarn++;
! 339: }
! 340:
! 341: if (st.st_mode & S_IWOTH) {
! 342: cvs_log(LP_WARN, "file `%s' is world-writable", path,
! 343: fmode);
! 344: nbwarn++;
! 345: }
! 346:
! 347: if (S_ISDIR(st.st_mode)) {
! 348: fd = open(path, O_RDONLY, 0);
! 349: if (fd == -1) {
! 350: cvs_log(LP_ERRNO, "failed to open directory `%s'",
! 351: path);
! 352: return (nbwarn);
! 353: }
! 354: /* recurse */
! 355: ret = getdirentries(fd, buf, sizeof(buf), &base);
! 356: if (ret == -1) {
! 357: cvs_log(LP_ERRNO,
! 358: "failed to get directory entries for `%s'", path);
! 359: (void)close(fd);
! 360: return (nbwarn);
! 361: }
! 362:
! 363: dp = buf;
! 364: endp = buf + ret;
! 365:
! 366: while (dp < endp) {
! 367: dep = (struct dirent *)dp;
! 368: dp += dep->d_reclen;
! 369:
! 370: if ((dep->d_namlen == 1) && (dep->d_name[0] == '.'))
! 371: continue;
! 372: if ((dep->d_namlen == 2) && (dep->d_name[0] == '.') &&
! 373: (dep->d_name[1] == '.'))
! 374: continue;
! 375:
! 376: /* skip the CVSROOT directory */
! 377: if (strcmp(dep->d_name, CVS_PATH_ROOT) == 0)
! 378: continue;
! 379:
! 380: snprintf(spath, sizeof(spath), "%s/%s", path,
! 381: dep->d_name);
! 382: ret = cvsd_checkperms(spath);
! 383: if (ret == -1)
! 384: nbwarn++;
! 385: else
! 386: nbwarn += ret;
! 387: }
! 388: (void)close(fd);
! 389: }
! 390:
! 391:
! 392: return (nbwarn);
! 393: }
! 394:
! 395:
! 396: /*
! 397: * cvsd_forkchild()
! 398: *
! 399: * Fork a child process which chroots to the CVS repository's root directory.
! 400: * We need to temporarily regain privileges in order to chroot.
! 401: * On success, returns 0 if this is the child process, 1 if this is the
! 402: * parent, or -1 on failure.
! 403: */
! 404:
! 405: int
! 406: cvsd_forkchild(void)
! 407: {
! 408: int svec[2];
! 409: pid_t pid;
! 410: struct cvsd_child *chp;
! 411:
! 412: if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, svec) == -1) {
! 413: cvs_log(LP_ERRNO, "failed to create socket pair");
! 414: return (-1);
! 415: }
! 416:
! 417: /*
! 418: * We need to temporarily regain original privileges in order for the
! 419: * child to chroot().
! 420: */
! 421: if (seteuid(0) == -1) {
! 422: cvs_log(LP_ERRNO, "failed to regain privileges");
! 423: return (-1);
! 424: }
! 425:
! 426: pid = fork();
! 427: if (pid == -1) {
! 428: cvs_log(LP_ERRNO, "failed to fork child");
! 429: (void)close(svec[0]);
! 430: (void)close(svec[1]);
! 431: return (-1);
! 432: }
! 433:
! 434: if (pid == 0) {
! 435: cvsd_privfd = svec[1];
! 436: (void)close(svec[0]);
! 437:
! 438: cvs_log(LP_INFO, "changing root to %s", cvsd_root);
! 439: if (chroot(cvsd_root) == -1) {
! 440: cvs_log(LP_ERRNO, "failed to chroot to `%s'",
! 441: cvsd_root);
! 442: exit(EX_OSERR);
! 443: }
! 444: (void)chdir("/");
! 445:
! 446: /* redrop privileges */
! 447: if (setgid(cvsd_gid) == -1) {
! 448: cvs_log(LP_ERRNO, "failed to drop privileges");
! 449: exit(EX_OSERR);
! 450: }
! 451: if (setuid(cvsd_uid) == -1) {
! 452: cvs_log(LP_ERRNO, "failed to drop privileges");
! 453: exit(EX_OSERR);
! 454: }
! 455:
! 456: setproctitle("%s [child %d]", __progname, getpid());
! 457:
! 458: cvsd_child_loop();
! 459:
! 460: return (0);
! 461: }
! 462:
! 463: cvs_log(LP_INFO, "spawning child %d", pid);
! 464:
! 465: if (seteuid(cvsd_uid) == -1) {
! 466: cvs_log(LP_ERRNO, "failed to redrop privs");
! 467: return (-1);
! 468: }
! 469:
! 470: chp = (struct cvsd_child *)malloc(sizeof(*chp));
! 471: if (chp == NULL) {
! 472: cvs_log(LP_ERRNO, "failed to allocate child data");
! 473: return (-1);
! 474: }
! 475:
! 476: chp->ch_pid = pid;
! 477: chp->ch_sock = svec[0];
! 478: (void)close(svec[1]);
! 479:
! 480: return (1);
! 481: }
! 482:
! 483:
! 484: /*
! 485: * cvsd_parent_loop()
! 486: *
! 487: * Main loop of the parent cvsd process, which listens on its end of the
! 488: * socket pair for requests from the chrooted child.
! 489: */
! 490:
! 491: static void
! 492: cvsd_parent_loop(void)
! 493: {
! 494: uid_t uid;
! 495: int timeout, ret;
! 496: nfds_t nfds, i;
! 497: struct pollfd *pfd;
! 498: struct cvsd_child *chp;
! 499:
! 500: nfds = 0;
! 501: timeout = INFTIM;
! 502:
! 503: for (;;) {
! 504: nfds = cvsd_chnum;
! 505: pfd = (struct pollfd *)realloc(pfd,
! 506: nfds * sizeof(struct pollfd));
! 507: if (pfd == NULL) {
! 508: cvs_log(LP_ERRNO, "failed to reallocate polling data");
! 509: return;
! 510: }
! 511:
! 512: i = 0;
! 513: TAILQ_FOREACH(chp, &cvsd_children, ch_list) {
! 514: pfd[i].fd = chp->ch_sock;
! 515: pfd[i].events = POLLIN;
! 516: pfd[i].revents = 0;
! 517: i++;
! 518:
! 519: if (i == nfds)
! 520: break;
! 521: }
! 522:
! 523: ret = poll(pfd, nfds, timeout);
! 524: if (ret == -1) {
! 525: cvs_log(LP_ERRNO, "poll error");
! 526: break;
! 527: }
! 528:
! 529: chp = TAILQ_FIRST(&cvsd_children);
! 530: for (i = 0; i < nfds; i++) {
! 531: if (pfd[i].revents & (POLLERR|POLLNVAL)) {
! 532: cvs_log(LP_ERR,
! 533: "poll error on child socket (PID %d)",
! 534: chp->ch_pid);
! 535: }
! 536: else
! 537: cvsd_msghdlr(chp, pfd[i].fd);
! 538:
! 539: chp = TAILQ_NEXT(chp, ch_list);
! 540: }
! 541:
! 542: }
! 543: }
! 544:
! 545:
! 546: /*
! 547: * cvsd_child_loop()
! 548: *
! 549: */
! 550:
! 551: static void
! 552: cvsd_child_loop(void)
! 553: {
! 554: int ret, timeout;
! 555: struct pollfd pfd[1];
! 556:
! 557: pfd[0].fd = cvsd_privfd;
! 558: pfd[0].events = POLLIN;
! 559: timeout = INFTIM;
! 560:
! 561: for (;;) {
! 562: ret = poll(pfd, 1, timeout);
! 563: if (ret == -1) {
! 564: }
! 565: else if (ret == 0) {
! 566: cvs_log(LP_INFO, "parent process closed descriptor");
! 567: break;
! 568: }
! 569: cvs_log(LP_INFO, "polling");
! 570:
! 571: }
! 572:
! 573: exit(0);
! 574: }
! 575:
! 576:
! 577: /*
! 578: * cvsd_msghdlr()
! 579: */
! 580:
! 581: int
! 582: cvsd_msghdlr(struct cvsd_child *child, int fd)
! 583: {
! 584: uid_t uid;
! 585: ssize_t ret;
! 586: char rbuf[CVSD_MSG_MAXLEN];
! 587: struct group *gr;
! 588: struct passwd *pw;
! 589: struct iovec iov[2];
! 590: struct cvsd_msg msg;
! 591:
! 592: ret = read(fd, &msg, sizeof(msg));
! 593: if (ret == -1) {
! 594: cvs_log(LP_ERRNO, "failed to read CVS message");
! 595: return (-1);
! 596: }
! 597: else if (ret == 0) {
! 598: cvs_log(LP_WARN, "child closed socket pair");
! 599: return (0);
! 600: }
! 601:
! 602: if (msg.cm_len > 0) {
! 603: ret = read(fd, rbuf, msg.cm_len);
! 604: if (ret != (ssize_t)msg.cm_len) {
! 605: cvs_log(LP_ERR, "failed to read entire msg");
! 606: return (-1);
! 607: }
! 608: }
! 609:
! 610: /* setup the I/O vector for the reply */
! 611: iov[0].iov_base = &msg;
! 612: iov[0].iov_len = sizeof(msg);
! 613:
! 614: msg.cm_type = CVSD_MSG_ERROR;
! 615: msg.cm_len = 0;
! 616:
! 617: switch (msg.cm_type) {
! 618: case CVSD_MSG_GETUID:
! 619: rbuf[ret] = '\0';
! 620: cvs_log(LP_INFO, "getting UID for `%s'", rbuf);
! 621:
! 622: pw = getpwnam(rbuf);
! 623: if (pw != NULL) {
! 624: msg.cm_type = CVSD_MSG_UID;
! 625: msg.cm_len = sizeof(uid_t);
! 626: iov[1].iov_len = msg.cm_len;
! 627: iov[1].iov_base = &(pw->pw_uid);
! 628: }
! 629: break;
! 630: case CVSD_MSG_GETUNAME:
! 631: memcpy(&uid, rbuf, sizeof(uid));
! 632: cvs_log(LP_INFO, "getting username for UID %u", uid);
! 633: pw = getpwuid(uid);
! 634: if (pw != NULL) {
! 635: msg.cm_type = CVSD_MSG_UNAME;
! 636: msg.cm_len = strlen(pw->pw_name);
! 637: iov[1].iov_len = msg.cm_len;
! 638: iov[1].iov_base = pw->pw_name;
! 639: }
! 640: break;
! 641: default:
! 642: cvs_log(LP_ERR, "unknown command type %u", msg.cm_type);
! 643: return (-1);
! 644: }
! 645:
! 646: ret = writev(fd, iov, 2);
! 647:
! 648: return (ret);
! 649: }