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