1/* 2 * Copyright (C) 1986-2005 The Free Software Foundation, Inc. 3 * 4 * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>, 5 * and others. 6 * 7 * Portions Copyright (C) 1992, Brian Berliner and Jeff Polk 8 * Portions Copyright (C) 1989-1992, Brian Berliner 9 * 10 * You may distribute under the terms of the GNU General Public License as 11 * specified in the README file that comes with the CVS source distribution. 12 */ 13#include <sys/cdefs.h> 14__RCSID("$NetBSD: parseinfo.c,v 1.4 2016/05/17 14:00:09 christos Exp $"); 15 16#include "cvs.h" 17#include "getline.h" 18#include "history.h" 19 20/* 21 * Parse the INFOFILE file for the specified REPOSITORY. Invoke CALLPROC for 22 * the first line in the file that matches the REPOSITORY, or if ALL != 0, any 23 * lines matching "ALL", or if no lines match, the last line matching 24 * "DEFAULT". 25 * 26 * Return 0 for success, -1 if there was not an INFOFILE, and >0 for failure. 27 */ 28int 29Parse_Info (const char *infofile, const char *repository, CALLPROC callproc, 30 int opt, void *closure) 31{ 32 int err = 0; 33 FILE *fp_info; 34 char *infopath; 35 char *line = NULL; 36 size_t line_allocated = 0; 37 char *default_value = NULL; 38 int default_line = 0; 39 char *expanded_value; 40 bool callback_done; 41 int line_number; 42 char *cp, *exp, *value; 43 const char *srepos; 44 const char *regex_err; 45 46 assert (repository); 47 48 if (!current_parsed_root) 49 { 50 /* XXX - should be error maybe? */ 51 error (0, 0, "CVSROOT variable not set"); 52 return 1; 53 } 54 55 /* find the info file and open it */ 56 infopath = Xasprintf ("%s/%s/%s", current_parsed_root->directory, 57 CVSROOTADM, infofile); 58 fp_info = CVS_FOPEN (infopath, "r"); 59 if (!fp_info) 60 { 61 /* If no file, don't do anything special. */ 62 if (!existence_error (errno)) 63 error (0, errno, "cannot open %s", infopath); 64 free (infopath); 65 return 0; 66 } 67 68 /* strip off the CVSROOT if repository was absolute */ 69 srepos = Short_Repository (repository); 70 71 TRACE (TRACE_FUNCTION, "Parse_Info (%s, %s, %s)", 72 infopath, srepos, (opt & PIOPT_ALL) ? "ALL" : "not ALL"); 73 74 /* search the info file for lines that match */ 75 callback_done = false; 76 line_number = 0; 77 while (getline (&line, &line_allocated, fp_info) >= 0) 78 { 79 line_number++; 80 81 /* skip lines starting with # */ 82 if (line[0] == '#') 83 continue; 84 85 /* skip whitespace at beginning of line */ 86 for (cp = line; *cp && isspace ((unsigned char) *cp); cp++) 87 ; 88 89 /* if *cp is null, the whole line was blank */ 90 if (*cp == '\0') 91 continue; 92 93 /* the regular expression is everything up to the first space */ 94 for (exp = cp; *cp && !isspace ((unsigned char) *cp); cp++) 95 ; 96 if (*cp != '\0') 97 *cp++ = '\0'; 98 99 /* skip whitespace up to the start of the matching value */ 100 while (*cp && isspace ((unsigned char) *cp)) 101 cp++; 102 103 /* no value to match with the regular expression is an error */ 104 if (*cp == '\0') 105 { 106 error (0, 0, "syntax error at line %d file %s; ignored", 107 line_number, infopath); 108 continue; 109 } 110 value = cp; 111 112 /* strip the newline off the end of the value */ 113 cp = strrchr (value, '\n'); 114 if (cp) *cp = '\0'; 115 116 /* 117 * At this point, exp points to the regular expression, and value 118 * points to the value to call the callback routine with. Evaluate 119 * the regular expression against srepos and callback with the value 120 * if it matches. 121 */ 122 123 /* save the default value so we have it later if we need it */ 124 if (strcmp (exp, "DEFAULT") == 0) 125 { 126 if (default_value) 127 { 128 error (0, 0, "Multiple `DEFAULT' lines (%d and %d) in %s file", 129 default_line, line_number, infofile); 130 free (default_value); 131 } 132 default_value = xstrdup (value); 133 default_line = line_number; 134 continue; 135 } 136 137 /* 138 * For a regular expression of "ALL", do the callback always We may 139 * execute lots of ALL callbacks in addition to *one* regular matching 140 * callback or default 141 */ 142 if (strcmp (exp, "ALL") == 0) 143 { 144 if (!(opt & PIOPT_ALL)) 145 error (0, 0, "Keyword `ALL' is ignored at line %d in %s file", 146 line_number, infofile); 147 else if ((expanded_value = 148 expand_path (value, current_parsed_root->directory, 149 true, infofile, line_number))) 150 { 151 err += callproc (repository, expanded_value, closure); 152 free (expanded_value); 153 } 154 else 155 err++; 156 continue; 157 } 158 159 if (callback_done) 160 /* only first matching, plus "ALL"'s */ 161 continue; 162 163 /* see if the repository matched this regular expression */ 164 regex_err = re_comp (exp); 165 if (regex_err) 166 { 167 error (0, 0, "bad regular expression at line %d file %s: %s", 168 line_number, infofile, regex_err); 169 continue; 170 } 171 if (re_exec (srepos) == 0) 172 continue; /* no match */ 173 174 /* it did, so do the callback and note that we did one */ 175 expanded_value = expand_path (value, current_parsed_root->directory, 176 true, infofile, line_number); 177 if (expanded_value) 178 { 179 err += callproc (repository, expanded_value, closure); 180 free (expanded_value); 181 } 182 else 183 err++; 184 callback_done = true; 185 } 186 if (ferror (fp_info)) 187 error (0, errno, "cannot read %s", infopath); 188 if (fclose (fp_info) < 0) 189 error (0, errno, "cannot close %s", infopath); 190 191 /* if we fell through and didn't callback at all, do the default */ 192 if (!callback_done && default_value) 193 { 194 expanded_value = expand_path (default_value, 195 current_parsed_root->directory, 196 true, infofile, line_number); 197 if (expanded_value) 198 { 199 err += callproc (repository, expanded_value, closure); 200 free (expanded_value); 201 } 202 else 203 err++; 204 } 205 206 /* free up space if necessary */ 207 if (default_value) free (default_value); 208 free (infopath); 209 if (line) free (line); 210 211 return err; 212} 213 214 215 216/* Print a warning and return false if P doesn't look like a string specifying 217 * something that can be converted into a size_t. 218 * 219 * Sets *VAL to the parsed value when it is found to be valid. *VAL will not 220 * be altered when false is returned. 221 */ 222static bool 223readSizeT (const char *infopath, const char *option, const char *p, 224 size_t *val) 225{ 226 const char *q; 227 size_t num, factor = 1; 228 229 if (!strcasecmp ("unlimited", p)) 230 { 231 *val = SIZE_MAX; 232 return true; 233 } 234 235 /* Record the factor character (kilo, mega, giga, tera). */ 236 if (!isdigit (p[strlen(p) - 1])) 237 { 238 switch (p[strlen(p) - 1]) 239 { 240 case 'T': 241 factor = xtimes (factor, 1024); 242 case 'G': 243 factor = xtimes (factor, 1024); 244 case 'M': 245 factor = xtimes (factor, 1024); 246 case 'k': 247 factor = xtimes (factor, 1024); 248 break; 249 default: 250 error (0, 0, 251 "%s: Unknown %s factor: `%c'", 252 infopath, option, p[strlen(p)]); 253 return false; 254 } 255 TRACE (TRACE_DATA, "readSizeT(): Found factor %zu for %s", 256 factor, option); 257 } 258 259 /* Verify that *q is a number. */ 260 q = p; 261 while (q < p + strlen(p) - 1 /* Checked last character above. */) 262 { 263 if (!isdigit(*q)) 264 { 265 error (0, 0, 266"%s: %s must be a postitive integer, not '%s'", 267 infopath, option, p); 268 return false; 269 } 270 q++; 271 } 272 273 /* Compute final value. */ 274 num = strtoul (p, NULL, 10); 275 if (num == ULONG_MAX || num > SIZE_MAX) 276 /* Don't return an error, just max out. */ 277 num = SIZE_MAX; 278 279 TRACE (TRACE_DATA, "readSizeT(): read number %zu for %s", num, option); 280 *val = xtimes (strtoul (p, NULL, 10), factor); 281 TRACE (TRACE_DATA, "readSizeT(): returnning %zu for %s", *val, option); 282 return true; 283} 284 285 286 287/* Allocate and initialize a new config struct. */ 288static inline struct config * 289new_config (void) 290{ 291 struct config *new = xcalloc (1, sizeof (struct config)); 292 293 TRACE (TRACE_FLOW, "new_config ()"); 294 295 new->logHistory = xstrdup (ALL_HISTORY_REC_TYPES); 296 new->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS; 297 new->UserAdminOptions = xstrdup ("k"); 298#ifdef CVS_ADMIN_GROUP 299 new->UserAdminGroup = xstrdup (CVS_ADMIN_GROUP); 300#else 301 new->UserAdminGroup = NULL; 302#endif 303 new->MaxCommentLeaderLength = 20; 304#ifdef SERVER_SUPPORT 305 new->MaxCompressionLevel = 9; 306#endif /* SERVER_SUPPORT */ 307#ifdef PROXY_SUPPORT 308 new->MaxProxyBufferSize = (size_t)(8 * 1024 * 1024); /* 8 megabytes, 309 * by default. 310 */ 311#endif /* PROXY_SUPPORT */ 312#ifdef AUTH_SERVER_SUPPORT 313 new->system_auth = true; 314#endif /* AUTH_SERVER_SUPPORT */ 315 316 return new; 317} 318 319 320 321void 322free_config (struct config *data) 323{ 324 if (data->keywords) free_keywords (data->keywords); 325 free (data); 326} 327 328 329 330/* Return true if this function has already been called for line LN of file 331 * INFOPATH. 332 */ 333bool 334parse_error (const char *infopath, unsigned int ln) 335{ 336 static List *errors = NULL; 337 char *nodename = NULL; 338 339 if (!errors) 340 errors = getlist(); 341 342 nodename = Xasprintf ("%s/%u", infopath, ln); 343 if (findnode (errors, nodename)) 344 { 345 free (nodename); 346 return true; 347 } 348 349 push_string (errors, nodename); 350 return false; 351} 352 353 354 355#ifdef ALLOW_CONFIG_OVERRIDE 356const char * const allowed_config_prefixes[] = { ALLOW_CONFIG_OVERRIDE }; 357#endif /* ALLOW_CONFIG_OVERRIDE */ 358 359 360 361/* Parse the CVS config file. The syntax right now is a bit ad hoc 362 * but tries to draw on the best or more common features of the other 363 * *info files and various unix (or non-unix) config file syntaxes. 364 * Lines starting with # are comments. Settings are lines of the form 365 * KEYWORD=VALUE. There is currently no way to have a multi-line 366 * VALUE (would be nice if there was, probably). 367 * 368 * CVSROOT is the $CVSROOT directory 369 * (current_parsed_root->directory might not be set yet, so this 370 * function takes the cvsroot as a function argument). 371 * 372 * RETURNS 373 * Always returns a fully initialized config struct, which on error may 374 * contain only the defaults. 375 * 376 * ERRORS 377 * Calls error(0, ...) on errors in addition to the return value. 378 * 379 * xmalloc() failures are fatal, per usual. 380 */ 381struct config * 382parse_config (const char *cvsroot, const char *path) 383{ 384 const char *infopath; 385 char *freeinfopath = NULL; 386 FILE *fp_info; 387 char *line = NULL; 388 unsigned int ln; /* Input file line counter. */ 389 char *buf = NULL; 390 size_t buf_allocated = 0; 391 size_t len; 392 char *p; 393 struct config *retval; 394 /* PROCESSING Whether config keys are currently being processed for 395 * this root. 396 * PROCESSED Whether any keys have been processed for this root. 397 * This is initialized to true so that any initial keys 398 * may be processed as global defaults. 399 */ 400 bool processing = true; 401 bool processed = true; 402 403 TRACE (TRACE_FUNCTION, "parse_config (%s)", cvsroot); 404 405#ifdef ALLOW_CONFIG_OVERRIDE 406 if (path) 407 { 408 const char * const *prefix; 409 char *npath = xcanonicalize_file_name (path); 410 bool approved = false; 411 for (prefix = allowed_config_prefixes; *prefix != NULL; prefix++) 412 { 413 char *nprefix; 414 415 if (!isreadable (*prefix)) continue; 416 nprefix = xcanonicalize_file_name (*prefix); 417 if (!strncmp (nprefix, npath, strlen (nprefix)) 418 && (((*prefix)[strlen (*prefix)] != '/' 419 && strlen (npath) == strlen (nprefix)) 420 || ((*prefix)[strlen (*prefix)] == '/' 421 && npath[strlen (nprefix)] == '/'))) 422 approved = true; 423 free (nprefix); 424 if (approved) break; 425 } 426 if (!approved) 427 error (1, 0, "Invalid path to config file specified: `%s'", 428 path); 429 infopath = path; 430 free (npath); 431 } 432 else 433#endif 434 infopath = freeinfopath = 435 Xasprintf ("%s/%s/%s", cvsroot, CVSROOTADM, CVSROOTADM_CONFIG); 436 437 retval = new_config (); 438 439 fp_info = CVS_FOPEN (infopath, "r"); 440 if (!fp_info) 441 { 442 /* If no file, don't do anything special. */ 443 if (!existence_error (errno)) 444 { 445 /* Just a warning message; doesn't affect return 446 value, currently at least. */ 447 error (0, errno, "cannot open %s", infopath); 448 } 449 if (freeinfopath) free (freeinfopath); 450 return retval; 451 } 452 453 ln = 0; /* Have not read any lines yet. */ 454 while (getline (&buf, &buf_allocated, fp_info) >= 0) 455 { 456 ln++; /* Keep track of input file line number for error messages. */ 457 458 line = buf; 459 460 /* Skip leading white space. */ 461 while (isspace (*line)) line++; 462 463 /* Skip comments. */ 464 if (line[0] == '#') 465 continue; 466 467 /* Is there any kind of written standard for the syntax of this 468 sort of config file? Anywhere in POSIX for example (I guess 469 makefiles are sort of close)? Red Hat Linux has a bunch of 470 these too (with some GUI tools which edit them)... 471 472 Along the same lines, we might want a table of keywords, 473 with various types (boolean, string, &c), as a mechanism 474 for making sure the syntax is consistent. Any good examples 475 to follow there (Apache?)? */ 476 477 /* Strip the trailing newline. There will be one unless we 478 read a partial line without a newline, and then got end of 479 file (or error?). */ 480 481 len = strlen (line) - 1; 482 if (line[len] == '\n') 483 line[len--] = '\0'; 484 485 /* Skip blank lines. */ 486 if (line[0] == '\0') 487 continue; 488 489 TRACE (TRACE_DATA, "parse_info() examining line: `%s'", line); 490 491 /* Check for a root specification. */ 492 if (line[0] == '[' && line[len] == ']') 493 { 494 cvsroot_t *tmproot; 495 496 line++[len] = '\0'; 497 tmproot = parse_cvsroot (line); 498 499 /* Ignoring method. */ 500 if (!tmproot 501#if defined CLIENT_SUPPORT || defined SERVER_SUPPORT 502 || (tmproot->method != local_method 503 && (!tmproot->hostname || !isThisHost (tmproot->hostname))) 504#endif /* CLIENT_SUPPORT || SERVER_SUPPORT */ 505 || !isSamePath (tmproot->directory, cvsroot)) 506 { 507 if (processed) processing = false; 508 } 509 else 510 { 511 TRACE (TRACE_FLOW, "Matched root section`%s'", line); 512 processing = true; 513 processed = false; 514 } 515 516 continue; 517 } 518 519 /* There is data on this line. */ 520 521 /* Even if the data is bad or ignored, consider data processed for 522 * this root. 523 */ 524 processed = true; 525 526 if (!processing) 527 /* ...but it is for a different root. */ 528 continue; 529 530 /* The first '=' separates keyword from value. */ 531 p = strchr (line, '='); 532 if (!p) 533 { 534 if (!parse_error (infopath, ln)) 535 error (0, 0, 536"%s [%d]: syntax error: missing `=' between keyword and value", 537 infopath, ln); 538 continue; 539 } 540 541 *p++ = '\0'; 542 543 if (strcmp (line, "RCSBIN") == 0) 544 { 545 /* This option used to specify the directory for RCS 546 executables. But since we don't run them any more, 547 this is a noop. Silently ignore it so that a 548 repository can work with either new or old CVS. */ 549 ; 550 } 551 else if (strcmp (line, "SystemAuth") == 0) 552#ifdef AUTH_SERVER_SUPPORT 553 readBool (infopath, "SystemAuth", p, &retval->system_auth); 554#else 555 { 556 /* Still parse the syntax but ignore the option. That way the same 557 * config file can be used for local and server. 558 */ 559 bool dummy; 560 readBool (infopath, "SystemAuth", p, &dummy); 561 } 562#endif 563 else if (strcmp (line, "LocalKeyword") == 0 || 564 strcmp (line, "tag") == 0) 565 RCS_setlocalid (infopath, ln, &retval->keywords, p); 566 else if (strcmp (line, "KeywordExpand") == 0) 567 RCS_setincexc (&retval->keywords, p); 568 else if (strcmp (line, "PreservePermissions") == 0) 569 { 570#ifdef PRESERVE_PERMISSIONS_SUPPORT 571 readBool (infopath, "PreservePermissions", p, 572 &retval->preserve_perms); 573#else 574 if (!parse_error (infopath, ln)) 575 error (0, 0, "\ 576%s [%u]: warning: this CVS does not support PreservePermissions", 577 infopath, ln); 578#endif 579 } 580 else if (strcmp (line, "TopLevelAdmin") == 0) 581 readBool (infopath, "TopLevelAdmin", p, &retval->top_level_admin); 582 else if (strcmp (line, "LockDir") == 0) 583 { 584 if (retval->lock_dir) 585 free (retval->lock_dir); 586 retval->lock_dir = expand_path (p, cvsroot, false, infopath, ln); 587 /* Could try some validity checking, like whether we can 588 opendir it or something, but I don't see any particular 589 reason to do that now rather than waiting until lock.c. */ 590 } 591 else if (strcmp (line, "HistoryLogPath") == 0) 592 { 593 if (retval->HistoryLogPath) free (retval->HistoryLogPath); 594 595 /* Expand ~ & $VARs. */ 596 retval->HistoryLogPath = expand_path (p, cvsroot, false, 597 infopath, ln); 598 599 if (retval->HistoryLogPath && !ISABSOLUTE (retval->HistoryLogPath)) 600 { 601 error (0, 0, "%s [%u]: HistoryLogPath must be absolute.", 602 infopath, ln); 603 free (retval->HistoryLogPath); 604 retval->HistoryLogPath = NULL; 605 } 606 } 607 else if (strcmp (line, "HistorySearchPath") == 0) 608 { 609 if (retval->HistorySearchPath) free (retval->HistorySearchPath); 610 retval->HistorySearchPath = expand_path (p, cvsroot, false, 611 infopath, ln); 612 613 if (retval->HistorySearchPath 614 && !ISABSOLUTE (retval->HistorySearchPath)) 615 { 616 error (0, 0, "%s [%u]: HistorySearchPath must be absolute.", 617 infopath, ln); 618 free (retval->HistorySearchPath); 619 retval->HistorySearchPath = NULL; 620 } 621 } 622 else if (strcmp (line, "LogHistory") == 0) 623 { 624 if (strcmp (p, "all") != 0) 625 { 626 static bool gotone = false; 627 if (gotone) 628 error (0, 0, "\ 629%s [%u]: warning: duplicate LogHistory entry found.", 630 infopath, ln); 631 else 632 gotone = true; 633 free (retval->logHistory); 634 retval->logHistory = xstrdup (p); 635 } 636 } 637 else if (strcmp (line, "RereadLogAfterVerify") == 0) 638 { 639 if (!strcasecmp (p, "never")) 640 retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER; 641 else if (!strcasecmp (p, "always")) 642 retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS; 643 else if (!strcasecmp (p, "stat")) 644 retval->RereadLogAfterVerify = LOGMSG_REREAD_STAT; 645 else 646 { 647 bool tmp; 648 if (readBool (infopath, "RereadLogAfterVerify", p, &tmp)) 649 { 650 if (tmp) 651 retval->RereadLogAfterVerify = LOGMSG_REREAD_ALWAYS; 652 else 653 retval->RereadLogAfterVerify = LOGMSG_REREAD_NEVER; 654 } 655 } 656 } 657 else if (strcmp (line, "TmpDir") == 0) 658 { 659 if (retval->TmpDir) free (retval->TmpDir); 660 retval->TmpDir = expand_path (p, cvsroot, false, infopath, ln); 661 /* Could try some validity checking, like whether we can 662 * opendir it or something, but I don't see any particular 663 * reason to do that now rather than when the first function 664 * tries to create a temp file. 665 */ 666 } 667 else if (strcmp (line, "UserAdminGroup") == 0 668 || strcmp (line, "AdminGroup") == 0) 669 retval->UserAdminGroup = xstrdup (p); 670 else if (strcmp (line, "UserAdminOptions") == 0 671 || strcmp (line, "AdminOptions") == 0) 672 retval->UserAdminOptions = xstrdup (p); 673 else if (strcmp (line, "UseNewInfoFmtStrings") == 0) 674#ifdef SUPPORT_OLD_INFO_FMT_STRINGS 675 readBool (infopath, "UseNewInfoFmtStrings", p, 676 &retval->UseNewInfoFmtStrings); 677#else /* !SUPPORT_OLD_INFO_FMT_STRINGS */ 678 { 679 bool dummy; 680 if (readBool (infopath, "UseNewInfoFmtStrings", p, &dummy) 681 && !dummy) 682 error (1, 0, 683"%s [%u]: Old style info format strings not supported by this executable.", 684 infopath, ln); 685 } 686#endif /* SUPPORT_OLD_INFO_FMT_STRINGS */ 687 else if (strcmp (line, "ImportNewFilesToVendorBranchOnly") == 0) 688 readBool (infopath, "ImportNewFilesToVendorBranchOnly", p, 689 &retval->ImportNewFilesToVendorBranchOnly); 690 else if (strcmp (line, "PrimaryServer") == 0) 691 retval->PrimaryServer = parse_cvsroot (p); 692#ifdef PROXY_SUPPORT 693 else if (!strcmp (line, "MaxProxyBufferSize")) 694 readSizeT (infopath, "MaxProxyBufferSize", p, 695 &retval->MaxProxyBufferSize); 696#endif /* PROXY_SUPPORT */ 697 else if (!strcmp (line, "MaxCommentLeaderLength")) 698 readSizeT (infopath, "MaxCommentLeaderLength", p, 699 &retval->MaxCommentLeaderLength); 700 else if (!strcmp (line, "UseArchiveCommentLeader")) 701 readBool (infopath, "UseArchiveCommentLeader", p, 702 &retval->UseArchiveCommentLeader); 703#ifdef SERVER_SUPPORT 704 else if (!strcmp (line, "MinCompressionLevel")) 705 readSizeT (infopath, "MinCompressionLevel", p, 706 &retval->MinCompressionLevel); 707 else if (!strcmp (line, "MaxCompressionLevel")) 708 readSizeT (infopath, "MaxCompressionLevel", p, 709 &retval->MaxCompressionLevel); 710#endif /* SERVER_SUPPORT */ 711 else 712 /* We may be dealing with a keyword which was added in a 713 subsequent version of CVS. In that case it is a good idea 714 to complain, as (1) the keyword might enable a behavior like 715 alternate locking behavior, in which it is dangerous and hard 716 to detect if some CVS's have it one way and others have it 717 the other way, (2) in general, having us not do what the user 718 had in mind when they put in the keyword violates the 719 principle of least surprise. Note that one corollary is 720 adding new keywords to your CVSROOT/config file is not 721 particularly recommended unless you are planning on using 722 the new features. */ 723 if (!parse_error (infopath, ln)) 724 error (0, 0, "%s [%u]: unrecognized keyword `%s'", 725 infopath, ln, line); 726 } 727 if (ferror (fp_info)) 728 error (0, errno, "cannot read %s", infopath); 729 if (fclose (fp_info) < 0) 730 error (0, errno, "cannot close %s", infopath); 731 if (freeinfopath) free (freeinfopath); 732 if (buf) free (buf); 733 734 return retval; 735} 736 737/* cvsacl patch */ 738int 739parse_aclconfig (const char *cvsroot) 740{ 741 char *infopath; 742 FILE *fp_info; 743 char *line = NULL; 744 size_t line_allocated = 0; 745 size_t len; 746 char *p; 747 /* FIXME-reentrancy: If we do a multi-threaded server, this would need 748 to go to the per-connection data structures. */ 749 static int parsed = 0; 750 751 /* Authentication code and serve_root might both want to call us. 752 Let this happen smoothly. */ 753 if (parsed) 754 return 0; 755 parsed = 1; 756 757 infopath = xmalloc (strlen (cvsroot) 758 + sizeof (CVSROOTADM_ACLCONFIG) 759 + sizeof (CVSROOTADM) 760 + 10); 761 if (infopath == NULL) 762 { 763 error (0, 0, "out of memory; cannot allocate infopath"); 764 goto error_return; 765 } 766 767 strcpy (infopath, cvsroot); 768 strcat (infopath, "/"); 769 strcat (infopath, CVSROOTADM); 770 strcat (infopath, "/"); 771 strcat (infopath, CVSROOTADM_ACLCONFIG); 772 773 fp_info = CVS_FOPEN (infopath, "r"); 774 if (fp_info == NULL) 775 { 776 /* If no file, don't do anything special. */ 777 if (!existence_error (errno)) 778 { 779 /* Just a warning message; doesn't affect return 780 value, currently at least. */ 781 error (0, errno, "cannot open %s", infopath); 782 } 783 free (infopath); 784 return 0; 785 } 786 787 while (getline (&line, &line_allocated, fp_info) >= 0) 788 { 789 /* Skip comments. */ 790 if (line[0] == '#') 791 continue; 792 793 len = strlen (line) - 1; 794 if (line[len] == '\n') 795 line[len] = '\0'; 796 797 /* Skip blank lines. */ 798 if (line[0] == '\0') 799 continue; 800 801 /* The first '=' separates keyword from value. */ 802 p = strchr (line, '='); 803 if (p == NULL) 804 { 805 /* Probably should be printing line number. */ 806 error (0, 0, "syntax error in %s: line '%s' is missing '='", 807 infopath, line); 808 goto error_return; 809 } 810 811 *p++ = '\0'; 812 813 if (strcmp (line, "UseCVSACL") == 0) 814 { 815 if (strcmp (p, "no") == 0) 816 use_cvs_acl = 0; 817 else if (strcmp (p, "yes") == 0) 818 use_cvs_acl = 1; 819 else 820 { 821 error (0, 0, "unrecognized value '%s' for UseCVSACL", p); 822 goto error_return; 823 } 824 } 825 else if (strcmp (line, "UseSeperateACLFileForEachDir") == 0) 826 { 827 if (strcmp (p, "no") == 0) 828 use_separate_acl_file_for_each_dir = 0; 829 else if (strcmp (p, "yes") == 0) 830 use_separate_acl_file_for_each_dir = 1; 831 else 832 { 833 error (0, 0, "unrecognized value '%s' for UseSeperateACLFileForEachDir", p); 834 goto error_return; 835 } 836 } 837 else if (strcmp (line, "StopAtFirstPermissionDenied") == 0) 838 { 839 if (strcmp (p, "no") == 0) 840 stop_at_first_permission_denied = 0; 841 else if (strcmp (p, "yes") == 0) 842 stop_at_first_permission_denied = 1; 843 else 844 { 845 error (0, 0, "unrecognized value '%s' for StopAtFirstPermissionDenied", p); 846 goto error_return; 847 } 848 } 849 else if (strcmp (line, "CVSACLDefaultPermissions") == 0) 850 { 851 if (cvs_acl_default_permissions != NULL) 852 free (cvs_acl_default_permissions); 853 if (!given_perms_valid (p)) 854 error (1,0,"Invalid CVS ACL Default Permissions: '%s' in CVSROOT/aclconfig", p); 855 cvs_acl_default_permissions = xstrdup (p); 856 } 857 else if (strcmp (line, "UseCVSGroups") == 0) 858 { 859 if (strcmp (p, "no") == 0) 860 use_cvs_groups = 0; 861 else if (strcmp (p, "yes") == 0) 862 use_cvs_groups = 1; 863 else 864 { 865 error (0, 0, "unrecognized value '%s' for UseCVSGroups", p); 866 goto error_return; 867 } 868 } 869 else if (strcmp (line, "UseSystemGroups") == 0) 870 { 871 if (strcmp (p, "no") == 0) 872 use_system_groups = 0; 873 else if (strcmp (p, "yes") == 0) 874 use_system_groups = 1; 875 else 876 { 877 error (0, 0, "unrecognized value '%s' for UseSystemGroups", p); 878 goto error_return; 879 } 880 } 881 else if (strcmp (line, "CVSACLFileLocation") == 0) 882 { 883 if (cvs_acl_file_location != NULL) 884 free (cvs_acl_file_location); 885 cvs_acl_file_location = xstrdup (p); 886 } 887 else if (strcmp (line, "CVSGroupsFileLocation") == 0) 888 { 889 if (cvs_groups_file_location != NULL) 890 free (cvs_groups_file_location); 891 cvs_groups_file_location = xstrdup (p); 892 } 893 else if (strcmp (line, "CVSServerRunAsUser") == 0) 894 { 895 if (cvs_server_run_as != NULL) 896 free (cvs_server_run_as); 897 cvs_server_run_as = xstrdup (p); 898 } 899 900 } 901 902 if (ferror (fp_info)) 903 { 904 error (0, errno, "cannot read %s", infopath); 905 goto error_return; 906 } 907 if (fclose (fp_info) < 0) 908 { 909 error (0, errno, "cannot close %s", infopath); 910 goto error_return; 911 } 912 free (infopath); 913 if (line != NULL) 914 free (line); 915 return 0; 916 917 error_return: 918 if (infopath != NULL) 919 free (infopath); 920 if (line != NULL) 921 free (line); 922 return -1; 923} 924