root.c revision 81404
1/* 2 * Copyright (c) 1992, Mark D. Baushke 3 * 4 * You may distribute under the terms of the GNU General Public License as 5 * specified in the README file that comes with the CVS source distribution. 6 * 7 * Name of Root 8 * 9 * Determine the path to the CVSROOT and set "Root" accordingly. 10 */ 11 12#include "cvs.h" 13#include "getline.h" 14 15/* Printable names for things in the current_parsed_root->method enum variable. 16 Watch out if the enum is changed in cvs.h! */ 17 18char *method_names[] = { 19 "undefined", "local", "server (rsh)", "pserver", "kserver", "gserver", "ext", "fork" 20}; 21 22#ifndef DEBUG 23 24char * 25Name_Root (dir, update_dir) 26 char *dir; 27 char *update_dir; 28{ 29 FILE *fpin; 30 char *ret, *xupdate_dir; 31 char *root = NULL; 32 size_t root_allocated = 0; 33 char *tmp; 34 char *cvsadm; 35 char *cp; 36 37 if (update_dir && *update_dir) 38 xupdate_dir = update_dir; 39 else 40 xupdate_dir = "."; 41 42 if (dir != NULL) 43 { 44 cvsadm = xmalloc (strlen (dir) + sizeof (CVSADM) + 10); 45 (void) sprintf (cvsadm, "%s/%s", dir, CVSADM); 46 tmp = xmalloc (strlen (dir) + sizeof (CVSADM_ROOT) + 10); 47 (void) sprintf (tmp, "%s/%s", dir, CVSADM_ROOT); 48 } 49 else 50 { 51 cvsadm = xstrdup (CVSADM); 52 tmp = xstrdup (CVSADM_ROOT); 53 } 54 55 /* 56 * Do not bother looking for a readable file if there is no cvsadm 57 * directory present. 58 * 59 * It is possible that not all repositories will have a CVS/Root 60 * file. This is ok, but the user will need to specify -d 61 * /path/name or have the environment variable CVSROOT set in 62 * order to continue. */ 63 if ((!isdir (cvsadm)) || (!isreadable (tmp))) 64 { 65 ret = NULL; 66 goto out; 67 } 68 69 /* 70 * The assumption here is that the CVS Root is always contained in the 71 * first line of the "Root" file. 72 */ 73 fpin = open_file (tmp, "r"); 74 75 if (getline (&root, &root_allocated, fpin) < 0) 76 { 77 /* FIXME: should be checking for end of file separately; errno 78 is not set in that case. */ 79 error (0, 0, "in directory %s:", xupdate_dir); 80 error (0, errno, "cannot read %s", CVSADM_ROOT); 81 error (0, 0, "please correct this problem"); 82 ret = NULL; 83 goto out; 84 } 85 (void) fclose (fpin); 86 if ((cp = strrchr (root, '\n')) != NULL) 87 *cp = '\0'; /* strip the newline */ 88 89 /* 90 * root now contains a candidate for CVSroot. It must be an 91 * absolute pathname or specify a remote server. 92 */ 93 94 if ( 95#ifdef CLIENT_SUPPORT 96 (strchr (root, ':') == NULL) && 97#endif 98 ! isabsolute (root)) 99 { 100 error (0, 0, "in directory %s:", xupdate_dir); 101 error (0, 0, 102 "ignoring %s because it does not contain an absolute pathname.", 103 CVSADM_ROOT); 104 ret = NULL; 105 goto out; 106 } 107 108#ifdef CLIENT_SUPPORT 109 if ((strchr (root, ':') == NULL) && !isdir (root)) 110#else /* ! CLIENT_SUPPORT */ 111 if (!isdir (root)) 112#endif /* CLIENT_SUPPORT */ 113 { 114 error (0, 0, "in directory %s:", xupdate_dir); 115 error (0, 0, 116 "ignoring %s because it specifies a non-existent repository %s", 117 CVSADM_ROOT, root); 118 ret = NULL; 119 goto out; 120 } 121 122 /* allocate space to return and fill it in */ 123 strip_trailing_slashes (root); 124 ret = xstrdup (root); 125 out: 126 free (cvsadm); 127 free (tmp); 128 if (root != NULL) 129 free (root); 130 return (ret); 131} 132 133/* 134 * Write the CVS/Root file so that the environment variable CVSROOT 135 * and/or the -d option to cvs will be validated or not necessary for 136 * future work. 137 */ 138void 139Create_Root (dir, rootdir) 140 char *dir; 141 char *rootdir; 142{ 143 FILE *fout; 144 char *tmp; 145 146 if (noexec) 147 return; 148 149 /* record the current cvs root */ 150 151 if (rootdir != NULL) 152 { 153 if (dir != NULL) 154 { 155 tmp = xmalloc (strlen (dir) + sizeof (CVSADM_ROOT) + 10); 156 (void) sprintf (tmp, "%s/%s", dir, CVSADM_ROOT); 157 } 158 else 159 tmp = xstrdup (CVSADM_ROOT); 160 161 fout = open_file (tmp, "w+"); 162 if (fprintf (fout, "%s\n", rootdir) < 0) 163 error (1, errno, "write to %s failed", tmp); 164 if (fclose (fout) == EOF) 165 error (1, errno, "cannot close %s", tmp); 166 free (tmp); 167 } 168} 169 170#endif /* ! DEBUG */ 171 172 173/* The root_allow_* stuff maintains a list of legal CVSROOT 174 directories. Then we can check against them when a remote user 175 hands us a CVSROOT directory. */ 176 177static int root_allow_count; 178static char **root_allow_vector; 179static int root_allow_size; 180 181void 182root_allow_add (arg) 183 char *arg; 184{ 185 char *p; 186 187 if (root_allow_size <= root_allow_count) 188 { 189 if (root_allow_size == 0) 190 { 191 root_allow_size = 1; 192 root_allow_vector = 193 (char **) malloc (root_allow_size * sizeof (char *)); 194 } 195 else 196 { 197 root_allow_size *= 2; 198 root_allow_vector = 199 (char **) realloc (root_allow_vector, 200 root_allow_size * sizeof (char *)); 201 } 202 203 if (root_allow_vector == NULL) 204 { 205 no_memory: 206 /* Strictly speaking, we're not supposed to output anything 207 now. But we're about to exit(), give it a try. */ 208 printf ("E Fatal server error, aborting.\n\ 209error ENOMEM Virtual memory exhausted.\n"); 210 211 /* I'm doing this manually rather than via error_exit () 212 because I'm not sure whether we want to call server_cleanup. 213 Needs more investigation.... */ 214 215#ifdef SYSTEM_CLEANUP 216 /* Hook for OS-specific behavior, for example socket 217 subsystems on NT and OS2 or dealing with windows 218 and arguments on Mac. */ 219 SYSTEM_CLEANUP (); 220#endif 221 222 exit (EXIT_FAILURE); 223 } 224 } 225 p = malloc (strlen (arg) + 1); 226 if (p == NULL) 227 goto no_memory; 228 strcpy (p, arg); 229 root_allow_vector[root_allow_count++] = p; 230} 231 232void 233root_allow_free () 234{ 235 if (root_allow_vector != NULL) 236 free_names (&root_allow_count, root_allow_vector); 237 root_allow_size = 0; 238} 239 240int 241root_allow_ok (arg) 242 char *arg; 243{ 244 int i; 245 246 if (root_allow_count == 0) 247 { 248 /* Probably someone upgraded from CVS before 1.9.10 to 1.9.10 249 or later without reading the documentation about 250 --allow-root. Printing an error here doesn't disclose any 251 particularly useful information to an attacker because a 252 CVS server configured in this way won't let *anyone* in. */ 253 254 /* Note that we are called from a context where we can spit 255 back "error" rather than waiting for the next request which 256 expects responses. */ 257 printf ("\ 258error 0 Server configuration missing --allow-root in inetd.conf\n"); 259 error_exit (); 260 } 261 262 for (i = 0; i < root_allow_count; ++i) 263 if (strcmp (root_allow_vector[i], arg) == 0) 264 return 1; 265 return 0; 266} 267 268 269 270/* This global variable holds the global -d option. It is NULL if -d 271 was not used, which means that we must get the CVSroot information 272 from the CVSROOT environment variable or from a CVS/Root file. */ 273 274char *CVSroot_cmdline; 275 276 277 278/* Parse a CVSROOT variable into its constituent parts -- method, 279 * username, hostname, directory. The prototypical CVSROOT variable 280 * looks like: 281 * 282 * :method:user@host:path 283 * 284 * Some methods may omit fields; local, for example, doesn't need user 285 * and host. 286 * 287 * Returns pointer to new cvsroot_t on success, NULL on failure. */ 288 289cvsroot_t *current_parsed_root = NULL; 290 291 292 293/* allocate and initialize a cvsroot_t 294 * 295 * We must initialize the strings to NULL so we know later what we should 296 * free 297 * 298 * Some of the other zeroes remain meaningful as, "never set, use default", 299 * or the like 300 */ 301static cvsroot_t * 302new_cvsroot_t () 303{ 304 cvsroot_t *newroot; 305 306 /* gotta store it somewhere */ 307 newroot = xmalloc(sizeof(cvsroot_t)); 308 309 newroot->original = NULL; 310 newroot->method = null_method; 311 newroot->username = NULL; 312 newroot->password = NULL; 313 newroot->hostname = NULL; 314 newroot->port = 0; 315 newroot->directory = NULL; 316#ifdef CLIENT_SUPPORT 317 newroot->isremote = 0; 318#endif /* CLIENT_SUPPORT */ 319 320 return newroot; 321} 322 323 324 325/* Dispose of a cvsroot_t and its component parts */ 326void 327free_cvsroot_t (root) 328 cvsroot_t *root; 329{ 330 if (root->original != NULL) 331 free (root->original); 332 if (root->username != NULL) 333 free (root->username); 334 if (root->password != NULL) 335 { 336 /* I like to be paranoid */ 337 memset (root->password, 0, strlen (root->password)); 338 free (root->password); 339 } 340 if (root->hostname != NULL) 341 free (root->hostname); 342 if (root->directory != NULL) 343 free (root->directory); 344 free (root); 345} 346 347 348 349/* 350 * parse a CVSROOT string to allocate and return a new cvsroot_t structure 351 */ 352cvsroot_t * 353parse_cvsroot (root_in) 354 char *root_in; 355{ 356 cvsroot_t *newroot; /* the new root to be returned */ 357 char *cvsroot_save; /* what we allocated so we can dispose 358 * it when finished */ 359 char *firstslash; /* save where the path spec starts 360 * while we parse 361 * [[user][:password]@]host[:[port]] 362 */ 363 char *cvsroot_copy, *p, *q; /* temporary pointers for parsing */ 364 int check_hostname, no_port, no_password; 365 366 /* allocate some space */ 367 newroot = new_cvsroot_t(); 368 369 /* save the original string */ 370 newroot->original = xstrdup (root_in); 371 372 /* and another copy we can munge while parsing */ 373 cvsroot_save = cvsroot_copy = xstrdup (root_in); 374 375 if (*cvsroot_copy == ':') 376 { 377 char *method = ++cvsroot_copy; 378 379 /* Access method specified, as in 380 * "cvs -d :(gserver|kserver|pserver):[[user][:password]@]host[:[port]]/path", 381 * "cvs -d [:(ext|server):][[user]@]host[:]/path", 382 * "cvs -d :local:e:\path", 383 * "cvs -d :fork:/path". 384 * We need to get past that part of CVSroot before parsing the 385 * rest of it. 386 */ 387 388 if (! (p = strchr (method, ':'))) 389 { 390 error (0, 0, "bad CVSroot: %s", root_in); 391 free (cvsroot_save); 392 goto error_exit; 393 } 394 *p = '\0'; 395 cvsroot_copy = ++p; 396 397 /* Now we have an access method -- see if it's valid. */ 398 399 if (strcmp (method, "local") == 0) 400 newroot->method = local_method; 401 else if (strcmp (method, "pserver") == 0) 402 newroot->method = pserver_method; 403 else if (strcmp (method, "kserver") == 0) 404 newroot->method = kserver_method; 405 else if (strcmp (method, "gserver") == 0) 406 newroot->method = gserver_method; 407 else if (strcmp (method, "server") == 0) 408 newroot->method = server_method; 409 else if (strcmp (method, "ext") == 0) 410 newroot->method = ext_method; 411 else if (strcmp (method, "fork") == 0) 412 newroot->method = fork_method; 413 else 414 { 415 error (0, 0, "unknown method in CVSroot: %s", root_in); 416 free (cvsroot_save); 417 goto error_exit; 418 } 419 } 420 else 421 { 422 /* If the method isn't specified, assume 423 SERVER_METHOD/EXT_METHOD if the string contains a colon or 424 LOCAL_METHOD otherwise. */ 425 426 newroot->method = ((*cvsroot_copy != '/' && strchr (cvsroot_copy, '/')) 427/*#ifdef RSH_NOT_TRANSPARENT 428 ? server_method 429#else*/ 430 ? ext_method 431/*#endif*/ 432 : local_method); 433 } 434 435#ifdef CLIENT_SUPPORT 436 newroot->isremote = (newroot->method != local_method); 437#endif /* CLIENT_SUPPORT */ 438 439 440 if ((newroot->method != local_method) 441 && (newroot->method != fork_method)) 442 { 443 /* split the string into [[user][:password]@]host[:[port]] & /path 444 * 445 * this will allow some characters such as '@' & ':' to remain unquoted 446 * in the path portion of the spec 447 */ 448 if ((p = strchr (cvsroot_copy, '/')) == NULL) 449 { 450 error (0, 0, "CVSROOT (\"%s\")", root_in); 451 error (0, 0, "requires a path spec"); 452 error (0, 0, ":(gserver|kserver|pserver):[[user][:password]@]host[:[port]]/path"); 453 error (0, 0, "[:(ext|server):][[user]@]host[:]/path"); 454 free (cvsroot_save); 455 goto error_exit; 456 } 457 firstslash = p; /* == NULL if '/' not in string */ 458 *p = '\0'; 459 460 /* Check to see if there is a username[:password] in the string. */ 461 if ((p = strchr (cvsroot_copy, '@')) != NULL) 462 { 463 *p = '\0'; 464 /* check for a password */ 465 if ((q = strchr (cvsroot_copy, ':')) != NULL) 466 { 467 *q = '\0'; 468 newroot->password = xstrdup (++q); 469 /* Don't check for *newroot->password == '\0' since 470 * a user could conceivably wish to specify a blank password 471 * (newroot->password == NULL means to use the 472 * password from .cvspass) 473 */ 474 } 475 476 /* copy the username */ 477 if (*cvsroot_copy != '\0') 478 /* a blank username is impossible, so leave it NULL in that 479 * case so we know to use the default username 480 */ 481 newroot->username = xstrdup (cvsroot_copy); 482 483 cvsroot_copy = ++p; 484 } 485 486 /* now deal with host[:[port]] */ 487 488 /* the port */ 489 if ((p = strchr (cvsroot_copy, ':')) != NULL) 490 { 491 *p++ = '\0'; 492 if (strlen(p)) 493 { 494 q = p; 495 if (*q == '-') q++; 496 while (*q) 497 { 498 if (!isdigit(*q++)) 499 { 500 error(0, 0, "CVSROOT (\"%s\")", root_in); 501 error(0, 0, "may only specify a positive, non-zero, integer port (not \"%s\").", 502 p); 503 error(0, 0, "perhaps you entered a relative pathname?"); 504 free (cvsroot_save); 505 goto error_exit; 506 } 507 } 508 if ((newroot->port = atoi (p)) <= 0) 509 { 510 error (0, 0, "CVSROOT (\"%s\")", root_in); 511 error(0, 0, "may only specify a positive, non-zero, integer port (not \"%s\").", 512 p); 513 error(0, 0, "perhaps you entered a relative pathname?"); 514 free (cvsroot_save); 515 goto error_exit; 516 } 517 } 518 } 519 520 /* copy host */ 521 if (*cvsroot_copy != '\0') 522 /* blank hostnames are invalid, but for now leave the field NULL 523 * and catch the error during the sanity checks later 524 */ 525 newroot->hostname = xstrdup (cvsroot_copy); 526 527 /* restore the '/' */ 528 cvsroot_copy = firstslash; 529 *cvsroot_copy = '/'; 530 } 531 532 /* parse the path for all methods */ 533 newroot->directory = xstrdup(cvsroot_copy); 534 free (cvsroot_save); 535 536 /* 537 * Do various sanity checks. 538 */ 539 540#if ! defined (CLIENT_SUPPORT) && ! defined (DEBUG) 541 if (newroot->method != local_method) 542 { 543 error (0, 0, "CVSROOT \"%s\"", root_in); 544 error (0, 0, "is set for a remote access method but your"); 545 error (0, 0, "CVS executable doesn't support it"); 546 goto error_exit; 547 } 548#endif 549 550#if ! defined (SERVER_SUPPORT) && ! defined (DEBUG) 551 if (newroot->method == fork_method) 552 { 553 error (0, 0, "CVSROOT \"%s\"", root_in); 554 error (0, 0, "is set to use the :fork: access method but your"); 555 error (0, 0, "CVS executable doesn't support it"); 556 goto error_exit; 557 } 558#endif 559 560 if (newroot->username && ! newroot->hostname) 561 { 562 error (0, 0, "missing hostname in CVSROOT: \"%s\"", root_in); 563 goto error_exit; 564 } 565 566 check_hostname = 0; 567 no_password = 0; 568 no_port = 0; 569 switch (newroot->method) 570 { 571 case local_method: 572 if (newroot->username || newroot->hostname) 573 { 574 error (0, 0, "can't specify hostname and username in CVSROOT"); 575 error (0, 0, "(\"%s\")", root_in); 576 error (0, 0, "when using local access method"); 577 goto error_exit; 578 } 579 /* cvs.texinfo has always told people that CVSROOT must be an 580 absolute pathname. Furthermore, attempts to use a relative 581 pathname produced various errors (I couldn't get it to work), 582 so there would seem to be little risk in making this a fatal 583 error. */ 584 if (!isabsolute (newroot->directory)) 585 { 586 error (0, 0, "CVSROOT \"%s\" must be an absolute pathname", 587 newroot->directory); 588 goto error_exit; 589 } 590 no_port = 1; 591 no_password = 1; 592 break; 593 case fork_method: 594 /* We want :fork: to behave the same as other remote access 595 methods. Therefore, don't check to see that the repository 596 name is absolute -- let the server do it. */ 597 if (newroot->username || newroot->hostname) 598 { 599 error (0, 0, "can't specify hostname and username in CVSROOT"); 600 error (0, 0, "(\"%s\")", root_in); 601 error (0, 0, "when using fork access method"); 602 goto error_exit; 603 } 604 if (!isabsolute (newroot->directory)) 605 { 606 error (0, 0, "CVSROOT \"%s\" must be an absolute pathname", 607 newroot->directory); 608 goto error_exit; 609 } 610 no_port = 1; 611 no_password = 1; 612 break; 613 case kserver_method: 614#ifndef HAVE_KERBEROS 615 error (0, 0, "CVSROOT \"%s\"", root_in); 616 error (0, 0, "is set for a kerberos access method but your"); 617 error (0, 0, "CVS executable doesn't support it"); 618 goto error_exit; 619#else 620 check_hostname = 1; 621 break; 622#endif 623 case gserver_method: 624#ifndef HAVE_GSSAPI 625 error (0, 0, "CVSROOT \"%s\"", root_in); 626 error (0, 0, "is set for a GSSAPI access method but your"); 627 error (0, 0, "CVS executable doesn't support it"); 628 goto error_exit; 629#else 630 check_hostname = 1; 631 break; 632#endif 633 case server_method: 634 case ext_method: 635 no_port = 1; 636 no_password = 1; 637 check_hostname = 1; 638 break; 639 case pserver_method: 640 check_hostname = 1; 641 break; 642 } 643 644 if (no_password && newroot->password) 645 { 646 error (0, 0, "CVSROOT password specification is only valid for"); 647 error (0, 0, "pserver connection method."); 648 goto error_exit; 649 } 650 651 if (check_hostname && !newroot->hostname) 652 { 653 error (0, 0, "didn't specify hostname in CVSROOT: %s", root_in); 654 goto error_exit; 655 } 656 657 if (no_port && newroot->port) 658 { 659 error (0, 0, "CVSROOT port specification is only valid for gserver, kserver,"); 660 error (0, 0, "and pserver connection methods."); 661 goto error_exit; 662 } 663 664 if (*newroot->directory == '\0') 665 { 666 error (0, 0, "missing directory in CVSROOT: %s", root_in); 667 goto error_exit; 668 } 669 670 /* Hooray! We finally parsed it! */ 671 return newroot; 672 673error_exit: 674 free_cvsroot_t (newroot); 675 return NULL; 676} 677 678 679 680#ifdef AUTH_CLIENT_SUPPORT 681/* Use root->username, root->hostname, root->port, and root->directory 682 * to create a normalized CVSROOT fit for the .cvspass file 683 * 684 * username defaults to the result of getcaller() 685 * port defaults to the result of get_cvs_port_number() 686 * 687 * FIXME - we could cache the canonicalized version of a root inside the 688 * cvsroot_t, but we'd have to un'const the input here and stop expecting the 689 * caller to be responsible for our return value 690 */ 691char * 692normalize_cvsroot (root) 693 const cvsroot_t *root; 694{ 695 char *cvsroot_canonical; 696 char *p, *hostname, *username; 697 char port_s[64]; 698 699 /* get the appropriate port string */ 700 sprintf (port_s, "%d", get_cvs_port_number (root)); 701 702 /* use a lower case hostname since we know hostnames are case insensitive */ 703 /* Some logic says we should be tacking our domain name on too if it isn't 704 * there already, but for now this works. Reverse->Forward lookups are 705 * almost certainly too much since that would make CVS immune to some of 706 * the DNS trickery that makes life easier for sysadmins when they want to 707 * move a repository or the like 708 */ 709 p = hostname = xstrdup(root->hostname); 710 while (*p) 711 { 712 *p = tolower(*p); 713 p++; 714 } 715 716 /* get the username string */ 717 username = root->username ? root->username : getcaller(); 718 cvsroot_canonical = xmalloc ( strlen(username) 719 + strlen(hostname) + strlen(port_s) 720 + strlen(root->directory) + 12); 721 sprintf (cvsroot_canonical, ":pserver:%s@%s:%s%s", 722 username, hostname, port_s, root->directory); 723 724 free (hostname); 725 return cvsroot_canonical; 726} 727#endif /* AUTH_CLIENT_SUPPORT */ 728 729 730 731/* allocate and return a cvsroot_t structure set up as if we're using the local 732 * repository DIR. */ 733cvsroot_t * 734local_cvsroot (dir) 735 char *dir; 736{ 737 cvsroot_t *newroot = new_cvsroot_t(); 738 739 newroot->original = xstrdup(dir); 740 newroot->method = local_method; 741 newroot->directory = xstrdup(dir); 742 743 return newroot; 744} 745 746 747 748#ifdef DEBUG 749/* This is for testing the parsing function. Use 750 751 gcc -I. -I.. -I../lib -DDEBUG root.c -o root 752 753 to compile. */ 754 755#include <stdio.h> 756 757char *program_name = "testing"; 758char *command_name = "parse_cvsroot"; /* XXX is this used??? */ 759 760/* Toy versions of various functions when debugging under unix. Yes, 761 these make various bad assumptions, but they're pretty easy to 762 debug when something goes wrong. */ 763 764void 765error_exit PROTO ((void)) 766{ 767 exit (1); 768} 769 770int 771isabsolute (dir) 772 const char *dir; 773{ 774 return (dir && (*dir == '/')); 775} 776 777void 778main (argc, argv) 779 int argc; 780 char *argv[]; 781{ 782 program_name = argv[0]; 783 784 if (argc != 2) 785 { 786 fprintf (stderr, "Usage: %s <CVSROOT>\n", program_name); 787 exit (2); 788 } 789 790 if ((current_parsed_root = parse_cvsroot (argv[1])) == NULL) 791 { 792 fprintf (stderr, "%s: Parsing failed.\n", program_name); 793 exit (1); 794 } 795 printf ("CVSroot: %s\n", argv[1]); 796 printf ("current_parsed_root->method: %s\n", method_names[current_parsed_root->method]); 797 printf ("current_parsed_root->username: %s\n", 798 current_parsed_root->username ? current_parsed_root->username : "NULL"); 799 printf ("current_parsed_root->hostname: %s\n", 800 current_parsed_root->hostname ? current_parsed_root->hostname : "NULL"); 801 printf ("current_parsed_root->directory: %s\n", current_parsed_root->directory); 802 803 exit (0); 804 /* NOTREACHED */ 805} 806#endif 807