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 * If this looks like of modified clone of Name_Repository() in 11 * repos.c, it is... 12 */ 13 14 #include "cvs.h" 15 #include "getline.h" 16 17 /* Printable names for things in the CVSroot_method enum variable. 18 Watch out if the enum is changed in cvs.h! */ 19 20 char *method_names[] = { 21 "local", "server (rsh)", "pserver", "kserver", "gserver", "ext", "fork" 22 }; 23 24 #ifndef DEBUG 25 26 char * 27 Name_Root (dir, update_dir) 28 char *dir; 29 char *update_dir; 30 { 31 FILE *fpin; 32 char *ret, *xupdate_dir; 33 char *root = NULL; 34 size_t root_allocated = 0; 35 char *tmp; 36 char *cvsadm; 37 char *cp; 38 39 if (update_dir && *update_dir) 40 xupdate_dir = update_dir; 41 else 42 xupdate_dir = "."; 43 44 if (dir != NULL) 45 { 46 cvsadm = xmalloc (strlen (dir) + sizeof (CVSADM) + 10); 47 (void) sprintf (cvsadm, "%s/%s", dir, CVSADM); 48 tmp = xmalloc (strlen (dir) + sizeof (CVSADM_ROOT) + 10); 49 (void) sprintf (tmp, "%s/%s", dir, CVSADM_ROOT); 50 } 51 else 52 { 53 cvsadm = xstrdup (CVSADM); 54 tmp = xstrdup (CVSADM_ROOT); 55 } 56 57 /* 58 * Do not bother looking for a readable file if there is no cvsadm 59 * directory present. 60 * 61 * It is possible that not all repositories will have a CVS/Root 62 * file. This is ok, but the user will need to specify -d 63 * /path/name or have the environment variable CVSROOT set in 64 * order to continue. */ 65 if ((!isdir (cvsadm)) || (!isreadable (tmp))) 66 { 67 ret = NULL; 68 goto out; 69 } 70 71 /* 72 * The assumption here is that the CVS Root is always contained in the 73 * first line of the "Root" file. 74 */ 75 fpin = open_file (tmp, "r"); 76 77 if (getline (&root, &root_allocated, fpin) < 0) 78 { 79 /* FIXME: should be checking for end of file separately; errno 80 is not set in that case. */ 81 error (0, 0, "in directory %s:", xupdate_dir); 82 error (0, errno, "cannot read %s", CVSADM_ROOT); 83 error (0, 0, "please correct this problem"); 84 ret = NULL; 85 goto out; 86 } 87 (void) fclose (fpin); 88 if ((cp = strrchr (root, '\n')) != NULL) 89 *cp = '\0'; /* strip the newline */ 90 91 /* 92 * root now contains a candidate for CVSroot. It must be an 93 * absolute pathname or specify a remote server. 94 */ 95 96 if ( 97 #ifdef CLIENT_SUPPORT 98 (strchr (root, ':') == NULL) && 99 #endif 100 ! isabsolute (root)) 101 { 102 error (0, 0, "in directory %s:", xupdate_dir); 103 error (0, 0, 104 "ignoring %s because it does not contain an absolute pathname.", 105 CVSADM_ROOT); 106 ret = NULL; 107 goto out; 108 } 109 110 #ifdef CLIENT_SUPPORT 111 if ((strchr (root, ':') == NULL) && !isdir (root)) 112 #else /* ! CLIENT_SUPPORT */ 113 if (!isdir (root)) 114 #endif /* CLIENT_SUPPORT */ 115 { 116 error (0, 0, "in directory %s:", xupdate_dir); 117 error (0, 0, 118 "ignoring %s because it specifies a non-existent repository %s", 119 CVSADM_ROOT, root); 120 ret = NULL; 121 goto out; 122 } 123 124 /* allocate space to return and fill it in */ 125 strip_trailing_slashes (root); 126 ret = xstrdup (root); 127 out: 128 free (cvsadm); 129 free (tmp); 130 if (root != NULL) 131 free (root); 132 return (ret); 133 } 134 135 /* 136 * Write the CVS/Root file so that the environment variable CVSROOT 137 * and/or the -d option to cvs will be validated or not necessary for 138 * future work. 139 */ 140 void 141 Create_Root (dir, rootdir) 142 char *dir; 143 char *rootdir; 144 { 145 FILE *fout; 146 char *tmp; 147 148 if (noexec) 149 return; 150 151 /* record the current cvs root */ 152 153 if (rootdir != NULL) 154 { 155 if (dir != NULL) 156 { 157 tmp = xmalloc (strlen (dir) + sizeof (CVSADM_ROOT) + 10); 158 (void) sprintf (tmp, "%s/%s", dir, CVSADM_ROOT); 159 } 160 else 161 tmp = xstrdup (CVSADM_ROOT); 162 163 fout = open_file (tmp, "w+"); 164 if (fprintf (fout, "%s\n", rootdir) < 0) 165 error (1, errno, "write to %s failed", tmp); 166 if (fclose (fout) == EOF) 167 error (1, errno, "cannot close %s", tmp); 168 free (tmp); 169 } 170 } 171 172 #endif /* ! DEBUG */ 173 174 175 /* The root_allow_* stuff maintains a list of legal CVSROOT 176 directories. Then we can check against them when a remote user 177 hands us a CVSROOT directory. */ 178 179 static unsigned int root_allow_count; 180 static char **root_allow_vector; 181 static unsigned int root_allow_size; 182 183 void 184 root_allow_add (arg) 185 char *arg; 186 { 187 char *p; 188 189 if (root_allow_size <= root_allow_count) 190 { 191 if (root_allow_size == 0) 192 { 193 root_allow_size = 1; 194 root_allow_vector = 195 (char **) malloc (root_allow_size * sizeof (char *)); 196 } 197 else 198 { 199 root_allow_size *= 2; 200 root_allow_vector = 201 (char **) realloc (root_allow_vector, 202 root_allow_size * sizeof (char *)); 203 } 204 205 if (root_allow_vector == NULL) 206 { 207 no_memory: 208 /* Strictly speaking, we're not supposed to output anything 209 now. But we're about to exit(), give it a try. */ 210 printf ("E Fatal server error, aborting.\n\ 211 error ENOMEM Virtual memory exhausted.\n"); 212 213 /* I'm doing this manually rather than via error_exit () 214 because I'm not sure whether we want to call server_cleanup. 215 Needs more investigation.... */ 216 217 #ifdef SYSTEM_CLEANUP 218 /* Hook for OS-specific behavior, for example socket 219 subsystems on NT and OS2 or dealing with windows 220 and arguments on Mac. */ 221 SYSTEM_CLEANUP (); 222 #endif 223 224 exit (EXIT_FAILURE); 225 } 226 } 227 p = malloc (strlen (arg) + 1); 228 if (p == NULL) 229 goto no_memory; 230 strcpy (p, arg); 231 root_allow_vector[root_allow_count++] = p; 232 } 233 234 void 235 root_allow_free () 236 { 237 if (root_allow_vector != NULL) 238 free (root_allow_vector); 239 root_allow_count = 0; 240 root_allow_size = 0; 241 } 242 243 int 244 root_allow_ok (arg) 245 char *arg; 246 { 247 unsigned int i; 248 249 if (root_allow_count == 0) 250 { 251 /* Probably someone upgraded from CVS before 1.9.10 to 1.9.10 252 or later without reading the documentation about 253 --allow-root. Printing an error here doesn't disclose any 254 particularly useful information to an attacker because a 255 CVS server configured in this way won't let *anyone* in. */ 256 257 /* Note that we are called from a context where we can spit 258 back "error" rather than waiting for the next request which 259 expects responses. */ 260 printf ("\ 261 error 0 Server configuration missing --allow-root in inetd.conf\n"); 262 error_exit (); 263 } 264 265 for (i = 0; i < root_allow_count; ++i) 266 if (strcmp (root_allow_vector[i], arg) == 0) 267 return 1; 268 return 0; 269 } 270 271 /* This global variable holds the global -d option. It is NULL if -d 272 was not used, which means that we must get the CVSroot information 273 from the CVSROOT environment variable or from a CVS/Root file. */ 274 275 char *CVSroot_cmdline; 276 277 /* Parse a CVSROOT variable into its constituent parts -- method, 278 * username, hostname, directory. The prototypical CVSROOT variable 279 * looks like: 280 * 281 * :method:user@host:path 282 * 283 * Some methods may omit fields; local, for example, doesn't need user 284 * and host. 285 * 286 * Returns zero on success, non-zero on failure. */ 287 288 char *CVSroot_original = NULL; /* the CVSroot that was passed in */ 289 int client_active; /* nonzero if we are doing remote access */ 290 CVSmethod CVSroot_method; /* one of the enum values defined in cvs.h */ 291 char *CVSroot_username; /* the username or NULL if method == local */ 292 char *CVSroot_hostname; /* the hostname or NULL if method == local */ 293 char *CVSroot_directory; /* the directory name */ 294 295 int 296 parse_cvsroot (CVSroot) 297 char *CVSroot; 298 { 299 static int cvsroot_parsed = 0; 300 char *cvsroot_copy, *p; 301 int check_hostname; 302 303 /* Don't go through the trouble twice. */ 304 if (cvsroot_parsed) 305 { 306 error (0, 0, "WARNING (parse_cvsroot): someone called me twice!\n"); 307 return 0; 308 } 309 310 CVSroot_original = xstrdup (CVSroot); 311 cvsroot_copy = xstrdup (CVSroot); 312 313 if ((*cvsroot_copy == ':')) 314 { 315 char *method = ++cvsroot_copy; 316 317 /* Access method specified, as in 318 * "cvs -d :pserver:user@host:/path", 319 * "cvs -d :local:e:\path", 320 * "cvs -d :kserver:user@host:/path", or 321 * "cvs -d :fork:/path". 322 * We need to get past that part of CVSroot before parsing the 323 * rest of it. 324 */ 325 326 if (! (p = strchr (method, ':'))) 327 { 328 error (0, 0, "bad CVSroot: %s", CVSroot); 329 return 1; 330 } 331 *p = '\0'; 332 cvsroot_copy = ++p; 333 334 /* Now we have an access method -- see if it's valid. */ 335 336 if (strcmp (method, "local") == 0) 337 CVSroot_method = local_method; 338 else if (strcmp (method, "pserver") == 0) 339 CVSroot_method = pserver_method; 340 else if (strcmp (method, "kserver") == 0) 341 CVSroot_method = kserver_method; 342 else if (strcmp (method, "gserver") == 0) 343 CVSroot_method = gserver_method; 344 else if (strcmp (method, "server") == 0) 345 CVSroot_method = server_method; 346 else if (strcmp (method, "ext") == 0) 347 CVSroot_method = ext_method; 348 else if (strcmp (method, "fork") == 0) 349 CVSroot_method = fork_method; 350 else 351 { 352 error (0, 0, "unknown method in CVSroot: %s", CVSroot); 353 return 1; 354 } 355 } 356 else 357 { 358 /* If the method isn't specified, assume 359 SERVER_METHOD/EXT_METHOD if the string contains a colon or 360 LOCAL_METHOD otherwise. */ 361 362 CVSroot_method = ((strchr (cvsroot_copy, ':')) 363 #ifdef RSH_NOT_TRANSPARENT 364 ? server_method 365 #else 366 ? ext_method 367 #endif 368 : local_method); 369 } 370 371 client_active = (CVSroot_method != local_method); 372 373 /* Check for username/hostname if we're not LOCAL_METHOD. */ 374 375 CVSroot_username = NULL; 376 CVSroot_hostname = NULL; 377 378 if ((CVSroot_method != local_method) 379 && (CVSroot_method != fork_method)) 380 { 381 /* Check to see if there is a username in the string. */ 382 383 if ((p = strchr (cvsroot_copy, '@'))) 384 { 385 CVSroot_username = cvsroot_copy; 386 *p = '\0'; 387 cvsroot_copy = ++p; 388 if (*CVSroot_username == '\0') 389 CVSroot_username = NULL; 390 } 391 392 if ((p = strchr (cvsroot_copy, ':'))) 393 { 394 CVSroot_hostname = cvsroot_copy; 395 *p = '\0'; 396 cvsroot_copy = ++p; 397 398 if (*CVSroot_hostname == '\0') 399 CVSroot_hostname = NULL; 400 } 401 } 402 403 CVSroot_directory = cvsroot_copy; 404 405 #if ! defined (CLIENT_SUPPORT) && ! defined (DEBUG) 406 if (CVSroot_method != local_method) 407 { 408 error (0, 0, "Your CVSROOT is set for a remote access method"); 409 error (0, 0, "but your CVS executable doesn't support it"); 410 error (0, 0, "(%s)", CVSroot); 411 return 1; 412 } 413 #endif 414 415 /* Do various sanity checks. */ 416 417 if (CVSroot_username && ! CVSroot_hostname) 418 { 419 error (0, 0, "missing hostname in CVSROOT: %s", CVSroot); 420 return 1; 421 } 422 423 check_hostname = 0; 424 switch (CVSroot_method) 425 { 426 case local_method: 427 case fork_method: 428 if (CVSroot_username || CVSroot_hostname) 429 { 430 error (0, 0, "can't specify hostname and username in CVSROOT"); 431 error (0, 0, "when using %s access method", 432 CVSroot_method == local_method ? "local" : "fork"); 433 error (0, 0, "(%s)", CVSroot); 434 return 1; 435 } 436 /* cvs.texinfo has always told people that CVSROOT must be an 437 absolute pathname. Furthermore, attempts to use a relative 438 pathname produced various errors (I couldn't get it to work), 439 so there would seem to be little risk in making this a fatal 440 error. */ 441 if (!isabsolute (CVSroot_directory)) 442 error (1, 0, "CVSROOT %s must be an absolute pathname", 443 CVSroot_directory); 444 break; 445 case kserver_method: 446 #ifndef HAVE_KERBEROS 447 error (0, 0, "Your CVSROOT is set for a kerberos access method"); 448 error (0, 0, "but your CVS executable doesn't support it"); 449 error (0, 0, "(%s)", CVSroot); 450 return 1; 451 #else 452 check_hostname = 1; 453 break; 454 #endif 455 case gserver_method: 456 #ifndef HAVE_GSSAPI 457 error (0, 0, "Your CVSROOT is set for a GSSAPI access method"); 458 error (0, 0, "but your CVS executable doesn't support it"); 459 error (0, 0, "(%s)", CVSroot); 460 return 1; 461 #else 462 check_hostname = 1; 463 break; 464 #endif 465 case server_method: 466 case ext_method: 467 case pserver_method: 468 check_hostname = 1; 469 break; 470 } 471 472 if (check_hostname) 473 { 474 if (! CVSroot_hostname) 475 { 476 error (0, 0, "didn't specify hostname in CVSROOT: %s", CVSroot); 477 return 1; 478 } 479 } 480 481 if (*CVSroot_directory == '\0') 482 { 483 error (0, 0, "missing directory in CVSROOT: %s", CVSroot); 484 return 1; 485 } 486 487 /* Hooray! We finally parsed it! */ 488 return 0; 489 } 490 491 492 /* Set up the global CVSroot* variables as if we're using the local 493 repository DIR. DIR must point to storage which will last for the 494 rest of the CVS invocation (for example, the caller might malloc it 495 and never free it, or free it just before exiting CVS). */ 496 497 void 498 set_local_cvsroot (dir) 499 char *dir; 500 { 501 CVSroot_original = dir; 502 CVSroot_method = local_method; 503 CVSroot_directory = CVSroot_original; 504 CVSroot_username = NULL; 505 CVSroot_hostname = NULL; 506 client_active = 0; 507 } 508 509 510 #ifdef DEBUG 511 /* This is for testing the parsing function. Use 512 513 gcc -I. -I.. -I../lib -DDEBUG root.c -o root 514 515 to compile. */ 516 517 #include <stdio.h> 518 519 char *CVSroot; 520 char *program_name = "testing"; 521 char *command_name = "parse_cvsroot"; /* XXX is this used??? */ 522 523 /* Toy versions of various functions when debugging under unix. Yes, 524 these make various bad assumptions, but they're pretty easy to 525 debug when something goes wrong. */ 526 527 void 528 error_exit PROTO ((void)) 529 { 530 exit (1); 531 } 532 533 char * 534 xstrdup (str) 535 const char *str; 536 { 537 return strdup (str); 538 } 539 540 int 541 isabsolute (dir) 542 const char *dir; 543 { 544 return (dir && (*dir == '/')); 545 } 546 547 void 548 main (argc, argv) 549 int argc; 550 char *argv[]; 551 { 552 program_name = argv[0]; 553 554 if (argc != 2) 555 { 556 fprintf (stderr, "Usage: %s <CVSROOT>\n", program_name); 557 exit (2); 558 } 559 560 if (parse_cvsroot (argv[1])) 561 { 562 fprintf (stderr, "%s: Parsing failed.\n", program_name); 563 exit (1); 564 } 565 printf ("CVSroot: %s\n", argv[1]); 566 printf ("CVSroot_method: %s\n", method_names[CVSroot_method]); 567 printf ("CVSroot_username: %s\n", 568 CVSroot_username ? CVSroot_username : "NULL"); 569 printf ("CVSroot_hostname: %s\n", 570 CVSroot_hostname ? CVSroot_hostname : "NULL"); 571 printf ("CVSroot_directory: %s\n", CVSroot_directory); 572 573 exit (0); 574 /* NOTREACHED */ 575 } 576 #endif 577