Home | History | Annotate | Line # | Download | only in src
      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  */
     28 int
     29 Parse_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  */
    222 static bool
    223 readSizeT (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.  */
    288 static inline struct config *
    289 new_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 
    321 void
    322 free_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  */
    333 bool
    334 parse_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
    356 const 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  */
    381 struct config *
    382 parse_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 */
    738 int
    739 parse_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