1/* $OpenBSD: cvs.c,v 1.160 2021/01/27 07:18:16 deraadt Exp $ */ 2/* 3 * Copyright (c) 2006, 2007 Joris Vink <joris@openbsd.org> 4 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 18 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 19 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28#include <sys/stat.h> 29 30#include <ctype.h> 31#include <errno.h> 32#include <pwd.h> 33#include <stdlib.h> 34#include <string.h> 35#include <time.h> 36#include <unistd.h> 37#include <err.h> 38 39#include "cvs.h" 40#include "remote.h" 41#include "hash.h" 42 43extern char *__progname; 44 45/* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */ 46int verbosity = 2; 47 48/* compression level used with zlib, 0 meaning no compression taking place */ 49int cvs_compress = 0; 50int cvs_readrc = 1; /* read .cvsrc on startup */ 51int cvs_trace = 0; 52int cvs_nolog = 0; 53int cvs_readonly = 0; 54int cvs_readonlyfs = 0; 55int cvs_nocase = 0; /* set to 1 to disable filename case sensitivity */ 56int cvs_noexec = 0; /* set to 1 to disable disk operations (-n option) */ 57int cvs_cmdop; 58int cvs_umask = CVS_UMASK_DEFAULT; 59int cvs_server_active = 0; 60 61char *cvs_tagname = NULL; 62char *cvs_defargs; /* default global arguments from .cvsrc */ 63char *cvs_rootstr; 64char *cvs_rsh = CVS_RSH_DEFAULT; 65char *cvs_editor = CVS_EDITOR_DEFAULT; 66char *cvs_homedir = NULL; 67char *cvs_tmpdir = CVS_TMPDIR_DEFAULT; 68 69struct cvsroot *current_cvsroot = NULL; 70struct cvs_cmd *cmdp; /* struct of command we are running */ 71 72int cvs_getopt(int, char **); 73__dead void usage(void); 74static void cvs_read_rcfile(void); 75 76struct cvs_varhead cvs_variables; 77 78struct wklhead temp_files; 79 80void sighandler(int); 81volatile sig_atomic_t cvs_quit = 0; 82volatile sig_atomic_t sig_received = 0; 83 84extern CVSENTRIES *current_list; 85 86struct hash_table created_directories; 87struct hash_table created_cvs_directories; 88 89void 90sighandler(int sig) 91{ 92 sig_received = sig; 93 94 switch (sig) { 95 case SIGINT: 96 case SIGTERM: 97 case SIGPIPE: 98 cvs_quit = 1; 99 break; 100 default: 101 break; 102 } 103} 104 105void 106cvs_cleanup(void) 107{ 108 cvs_log(LP_TRACE, "cvs_cleanup: removing locks"); 109 worklist_run(&repo_locks, worklist_unlink); 110 111 cvs_log(LP_TRACE, "cvs_cleanup: removing temp files"); 112 worklist_run(&temp_files, worklist_unlink); 113 114 if (cvs_server_path != NULL) { 115 if (cvs_rmdir(cvs_server_path) == -1) 116 cvs_log(LP_ERR, 117 "warning: failed to remove server directory: %s", 118 cvs_server_path); 119 free(cvs_server_path); 120 cvs_server_path = NULL; 121 } 122 123 if (current_list != NULL) 124 cvs_ent_close(current_list, ENT_SYNC); 125} 126 127__dead void 128usage(void) 129{ 130 (void)fprintf(stderr, 131 "usage: %s [-flnQqRrtvw] [-d root] [-e editor] [-s var=val]\n" 132 " [-T tmpdir] [-z level] command ...\n", __progname); 133 exit(1); 134} 135 136int 137cvs_build_cmd(char ***cmd_argv, char **argv, int argc) 138{ 139 int cmd_argc, i, cur; 140 char *cp, *linebuf, *lp; 141 142 if (cmdp->cmd_defargs == NULL) { 143 *cmd_argv = argv; 144 return argc; 145 } 146 147 cur = argc + 2; 148 cmd_argc = 0; 149 *cmd_argv = xcalloc(cur, sizeof(char *)); 150 (*cmd_argv)[cmd_argc++] = argv[0]; 151 152 linebuf = xstrdup(cmdp->cmd_defargs); 153 for (lp = linebuf; lp != NULL;) { 154 cp = strsep(&lp, " \t\b\f\n\r\t\v"); 155 if (cp == NULL) 156 break; 157 if (*cp == '\0') 158 continue; 159 160 if (cmd_argc == cur) { 161 cur += 8; 162 *cmd_argv = xreallocarray(*cmd_argv, cur, 163 sizeof(char *)); 164 } 165 166 (*cmd_argv)[cmd_argc++] = cp; 167 } 168 169 if (cmd_argc + argc > cur) { 170 cur = cmd_argc + argc + 1; 171 *cmd_argv = xreallocarray(*cmd_argv, cur, 172 sizeof(char *)); 173 } 174 175 for (i = 1; i < argc; i++) 176 (*cmd_argv)[cmd_argc++] = argv[i]; 177 178 (*cmd_argv)[cmd_argc] = NULL; 179 180 return cmd_argc; 181} 182 183int 184main(int argc, char **argv) 185{ 186 char *envstr, **cmd_argv, **targv; 187 int i, ret, cmd_argc; 188 struct passwd *pw; 189 struct stat st; 190 char fpath[PATH_MAX]; 191 192 if (pledge("stdio rpath wpath cpath fattr getpw proc exec", NULL) == -1) 193 err(1, "pledge"); 194 195 tzset(); 196 197 TAILQ_INIT(&cvs_variables); 198 SLIST_INIT(&repo_locks); 199 SLIST_INIT(&temp_files); 200 201 hash_table_init(&created_directories, 100); 202 hash_table_init(&created_cvs_directories, 100); 203 204 /* check environment so command-line options override it */ 205 if ((envstr = getenv("CVS_RSH")) != NULL) 206 cvs_rsh = envstr; 207 208 if (((envstr = getenv("CVSEDITOR")) != NULL) || 209 ((envstr = getenv("VISUAL")) != NULL) || 210 ((envstr = getenv("EDITOR")) != NULL)) 211 cvs_editor = envstr; 212 213 if ((envstr = getenv("CVSREAD")) != NULL) 214 cvs_readonly = 1; 215 216 if ((envstr = getenv("CVSREADONLYFS")) != NULL) { 217 cvs_readonlyfs = 1; 218 cvs_nolog = 1; 219 } 220 221 if ((cvs_homedir = getenv("HOME")) == NULL) { 222 if ((pw = getpwuid(getuid())) != NULL) 223 cvs_homedir = pw->pw_dir; 224 } 225 226 if ((envstr = getenv("TMPDIR")) != NULL) 227 cvs_tmpdir = envstr; 228 229 ret = cvs_getopt(argc, argv); 230 231 argc -= ret; 232 argv += ret; 233 if (argc == 0) 234 usage(); 235 236 cmdp = cvs_findcmd(argv[0]); 237 if (cmdp == NULL) { 238 fprintf(stderr, "Unknown command: `%s'\n\n", argv[0]); 239 fprintf(stderr, "CVS commands are:\n"); 240 for (i = 0; cvs_cdt[i] != NULL; i++) 241 fprintf(stderr, "\t%-16s%s\n", 242 cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr); 243 exit(1); 244 } 245 246 /* 247 * check the tmp dir, either specified through 248 * the environment variable TMPDIR, or via 249 * the global option -T <dir> 250 */ 251 if (stat(cvs_tmpdir, &st) == -1) 252 fatal("stat failed on `%s': %s", cvs_tmpdir, strerror(errno)); 253 else if (!S_ISDIR(st.st_mode)) 254 fatal("`%s' is not valid temporary directory", cvs_tmpdir); 255 256 if (cvs_readrc == 1 && cvs_homedir != NULL) { 257 cvs_read_rcfile(); 258 259 if (cvs_defargs != NULL) { 260 if ((targv = cvs_makeargv(cvs_defargs, &i)) == NULL) 261 fatal("failed to load default arguments to %s", 262 __progname); 263 264 cvs_getopt(i, targv); 265 cvs_freeargv(targv, i); 266 free(targv); 267 } 268 } 269 270 /* setup signal handlers */ 271 signal(SIGTERM, sighandler); 272 signal(SIGINT, sighandler); 273 signal(SIGHUP, sighandler); 274 signal(SIGABRT, sighandler); 275 signal(SIGALRM, sighandler); 276 signal(SIGPIPE, sighandler); 277 278 cvs_cmdop = cmdp->cmd_op; 279 280 cmd_argc = cvs_build_cmd(&cmd_argv, argv, argc); 281 282 cvs_file_init(); 283 284 if (cvs_cmdop == CVS_OP_SERVER) { 285 cmdp->cmd(cmd_argc, cmd_argv); 286 cvs_cleanup(); 287 return (0); 288 } 289 290 cvs_umask = umask(0); 291 umask(cvs_umask); 292 293 if ((current_cvsroot = cvsroot_get(".")) == NULL) { 294 cvs_log(LP_ERR, 295 "No CVSROOT specified! Please use the '-d' option"); 296 fatal("or set the CVSROOT environment variable."); 297 } 298 299 if (cvsroot_is_remote()) { 300 cmdp->cmd(cmd_argc, cmd_argv); 301 cvs_cleanup(); 302 return (0); 303 } 304 305 (void)xsnprintf(fpath, sizeof(fpath), "%s/%s", 306 current_cvsroot->cr_dir, CVS_PATH_ROOT); 307 308 if (stat(fpath, &st) == -1 && cvs_cmdop != CVS_OP_INIT) { 309 if (errno == ENOENT) 310 fatal("repository '%s' does not exist", 311 current_cvsroot->cr_dir); 312 else 313 fatal("%s: %s", current_cvsroot->cr_dir, 314 strerror(errno)); 315 } else { 316 if (!S_ISDIR(st.st_mode)) 317 fatal("'%s' is not a directory", 318 current_cvsroot->cr_dir); 319 } 320 321 if (cvs_cmdop != CVS_OP_INIT) { 322 cvs_parse_configfile(); 323 cvs_parse_modules(); 324 } 325 326 cmdp->cmd(cmd_argc, cmd_argv); 327 cvs_cleanup(); 328 329 return (0); 330} 331 332int 333cvs_getopt(int argc, char **argv) 334{ 335 int ret; 336 char *ep; 337 const char *errstr; 338 339 while ((ret = getopt(argc, argv, "b:d:e:flnQqRrs:T:tvwxz:")) != -1) { 340 switch (ret) { 341 case 'b': 342 /* 343 * We do not care about the bin directory for RCS files 344 * as this program has no dependencies on RCS programs, 345 * so it is only here for backwards compatibility. 346 */ 347 cvs_log(LP_NOTICE, "the -b argument is obsolete"); 348 break; 349 case 'd': 350 cvs_rootstr = optarg; 351 break; 352 case 'e': 353 cvs_editor = optarg; 354 break; 355 case 'f': 356 cvs_readrc = 0; 357 break; 358 case 'l': 359 cvs_nolog = 1; 360 break; 361 case 'n': 362 cvs_noexec = 1; 363 cvs_nolog = 1; 364 break; 365 case 'Q': 366 verbosity = 0; 367 break; 368 case 'q': 369 if (verbosity > 1) 370 verbosity = 1; 371 break; 372 case 'R': 373 cvs_readonlyfs = 1; 374 cvs_nolog = 1; 375 break; 376 case 'r': 377 cvs_readonly = 1; 378 break; 379 case 's': 380 ep = strchr(optarg, '='); 381 if (ep == NULL) { 382 cvs_log(LP_ERR, "no = in variable assignment"); 383 exit(1); 384 } 385 *(ep++) = '\0'; 386 if (cvs_var_set(optarg, ep) < 0) 387 exit(1); 388 break; 389 case 'T': 390 cvs_tmpdir = optarg; 391 break; 392 case 't': 393 cvs_trace = 1; 394 break; 395 case 'v': 396 printf("%s\n", CVS_VERSION); 397 exit(0); 398 /* NOTREACHED */ 399 case 'w': 400 cvs_readonly = 0; 401 break; 402 case 'x': 403 /* 404 * Kerberos encryption support, kept for compatibility 405 */ 406 break; 407 case 'z': 408 cvs_compress = strtonum(optarg, 0, 9, &errstr); 409 if (errstr != NULL) 410 fatal("cvs_compress: %s", errstr); 411 break; 412 default: 413 usage(); 414 /* NOTREACHED */ 415 } 416 } 417 418 ret = optind; 419 optind = 1; 420 optreset = 1; /* for next call */ 421 422 return (ret); 423} 424 425/* 426 * cvs_read_rcfile() 427 * 428 * Read the CVS `.cvsrc' file in the user's home directory. If the file 429 * exists, it should contain a list of arguments that should always be given 430 * implicitly to the specified commands. 431 */ 432static void 433cvs_read_rcfile(void) 434{ 435 char rcpath[PATH_MAX], *buf, *lbuf, *lp, *p; 436 int cmd_parsed, cvs_parsed, i, linenum; 437 size_t len, pos; 438 struct cvs_cmd *tcmdp; 439 FILE *fp; 440 441 linenum = 0; 442 443 i = snprintf(rcpath, PATH_MAX, "%s/%s", cvs_homedir, CVS_PATH_RC); 444 if (i < 0 || i >= PATH_MAX) { 445 cvs_log(LP_ERRNO, "%s", rcpath); 446 return; 447 } 448 449 fp = fopen(rcpath, "r"); 450 if (fp == NULL) { 451 if (errno != ENOENT) 452 cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath, 453 strerror(errno)); 454 return; 455 } 456 457 cmd_parsed = cvs_parsed = 0; 458 lbuf = NULL; 459 while ((buf = fgetln(fp, &len)) != NULL) { 460 if (buf[len - 1] == '\n') { 461 buf[len - 1] = '\0'; 462 } else { 463 lbuf = xmalloc(len + 1); 464 memcpy(lbuf, buf, len); 465 lbuf[len] = '\0'; 466 buf = lbuf; 467 } 468 469 linenum++; 470 471 /* skip any whitespaces */ 472 p = buf; 473 while (*p == ' ') 474 p++; 475 476 /* 477 * Allow comments. 478 * GNU cvs stops parsing a line if it encounters a \t 479 * in front of a command, stick at this behaviour for 480 * compatibility. 481 */ 482 if (*p == '#' || *p == '\t') 483 continue; 484 485 pos = strcspn(p, " \t"); 486 if (pos == strlen(p)) { 487 lp = NULL; 488 } else { 489 lp = p + pos; 490 *lp = '\0'; 491 } 492 493 if (strcmp(p, "cvs") == 0 && !cvs_parsed) { 494 /* 495 * Global default options. In the case of cvs only, 496 * we keep the 'cvs' string as first argument because 497 * getopt() does not like starting at index 0 for 498 * argument processing. 499 */ 500 if (lp != NULL) { 501 *lp = ' '; 502 cvs_defargs = xstrdup(p); 503 } 504 cvs_parsed = 1; 505 } else { 506 tcmdp = cvs_findcmd(p); 507 if (tcmdp == NULL && verbosity == 2) 508 cvs_log(LP_NOTICE, 509 "unknown command `%s' in `%s:%d'", 510 p, rcpath, linenum); 511 512 if (tcmdp != cmdp || cmd_parsed) 513 continue; 514 515 if (lp != NULL) { 516 lp++; 517 cmdp->cmd_defargs = xstrdup(lp); 518 } 519 cmd_parsed = 1; 520 } 521 } 522 free(lbuf); 523 524 if (ferror(fp)) { 525 cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath); 526 } 527 528 (void)fclose(fp); 529} 530 531/* 532 * cvs_var_set() 533 * 534 * Set the value of the variable <var> to <val>. If there is no such variable, 535 * a new entry is created, otherwise the old value is overwritten. 536 * Returns 0 on success, or -1 on failure. 537 */ 538int 539cvs_var_set(const char *var, const char *val) 540{ 541 const char *cp; 542 struct cvs_var *vp; 543 544 if (var == NULL || *var == '\0') { 545 cvs_log(LP_ERR, "no variable name"); 546 return (-1); 547 } 548 549 /* sanity check on the name */ 550 for (cp = var; *cp != '\0'; cp++) 551 if (!isalnum((unsigned char)*cp) && (*cp != '_')) { 552 cvs_log(LP_ERR, 553 "variable name `%s' contains invalid characters", 554 var); 555 return (-1); 556 } 557 558 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 559 if (strcmp(vp->cv_name, var) == 0) 560 break; 561 562 if (vp == NULL) { 563 vp = xcalloc(1, sizeof(*vp)); 564 565 vp->cv_name = xstrdup(var); 566 TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link); 567 568 } else /* free the previous value */ 569 free(vp->cv_val); 570 571 vp->cv_val = xstrdup(val); 572 573 return (0); 574} 575 576/* 577 * cvs_var_unset() 578 * 579 * Remove any entry for the variable <var>. 580 * Returns 0 on success, or -1 on failure. 581 */ 582int 583cvs_var_unset(const char *var) 584{ 585 struct cvs_var *vp; 586 587 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 588 if (strcmp(vp->cv_name, var) == 0) { 589 TAILQ_REMOVE(&cvs_variables, vp, cv_link); 590 free(vp->cv_name); 591 free(vp->cv_val); 592 free(vp); 593 return (0); 594 } 595 596 return (-1); 597} 598 599/* 600 * cvs_var_get() 601 * 602 * Get the value associated with the variable <var>. Returns a pointer to the 603 * value string on success, or NULL if the variable does not exist. 604 */ 605 606const char * 607cvs_var_get(const char *var) 608{ 609 struct cvs_var *vp; 610 611 TAILQ_FOREACH(vp, &cvs_variables, cv_link) 612 if (strcmp(vp->cv_name, var) == 0) 613 return (vp->cv_val); 614 615 return (NULL); 616} 617