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