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