xref: /netbsd-src/external/gpl2/xcvs/dist/src/parseinfo.c (revision 5a6c14c844c4c665da5632061aebde7bb2cb5766)
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
Parse_Info(const char * infofile,const char * repository,CALLPROC callproc,int opt,void * closure)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
readSizeT(const char * infopath,const char * option,const char * p,size_t * val)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 *
new_config(void)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
free_config(struct config * data)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
parse_error(const char * infopath,unsigned int ln)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 *
parse_config(const char * cvsroot,const char * path)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
parse_aclconfig(const char * cvsroot)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