update lintian overrides
[alioth/cvs.git] / src / root.c
1 /*
2  * Copyright © 2017
3  *      mirabilos <m@mirbsd.org>
4  *
5  * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
6  *
7  * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
8  *                                  and others.
9  *
10  * Poritons Copyright (c) 1992, Mark D. Baushke
11  *
12  * You may distribute under the terms of the GNU General Public License as
13  * specified in the README file that comes with the CVS source distribution.
14  * 
15  * Name of Root
16  * 
17  * Determine the path to the CVSROOT and set "Root" accordingly.
18  */
19
20 #include "cvs.h"
21 #include <assert.h>
22 #include "getline.h"
23
24 /* Printable names for things in the current_parsed_root->method enum variable.
25    Watch out if the enum is changed in cvs.h! */
26
27 const char method_names[][16] = {
28     "undefined", "local", "server (rsh)", "pserver",
29     "kserver", "gserver", "ext", "fork"
30 };
31
32 #ifndef DEBUG
33
34 cvsroot_t *
35 Name_Root (const char *dir, const char *update_dir)
36 {
37     FILE *fpin;
38     cvsroot_t *ret;
39     const char *xupdate_dir;
40     char *root = NULL;
41     size_t root_allocated = 0;
42     char *tmp;
43     char *cvsadm;
44     char *cp;
45     int len;
46
47     TRACE (TRACE_FLOW, "Name_Root (%s, %s)",
48            dir ? dir : "(null)",
49            update_dir ? update_dir : "(null)");
50
51     if (update_dir && *update_dir)
52         xupdate_dir = update_dir;
53     else
54         xupdate_dir = ".";
55
56     if (dir != NULL)
57     {
58         cvsadm = Xasprintf ("%s/%s", dir, CVSADM);
59         tmp = Xasprintf ("%s/%s", dir, CVSADM_ROOT);
60     }
61     else
62     {
63         cvsadm = xstrdup (CVSADM);
64         tmp = xstrdup (CVSADM_ROOT);
65     }
66
67     /*
68      * Do not bother looking for a readable file if there is no cvsadm
69      * directory present.
70      *
71      * It is possible that not all repositories will have a CVS/Root
72      * file. This is ok, but the user will need to specify -d
73      * /path/name or have the environment variable CVSROOT set in
74      * order to continue.  */
75     if ((!isdir (cvsadm)) || (!isreadable (tmp)))
76     {
77         ret = NULL;
78         goto out;
79     }
80
81     /*
82      * The assumption here is that the CVS Root is always contained in the
83      * first line of the "Root" file.
84      */
85     fpin = xfopen (tmp, "r");
86
87     if ((len = getline (&root, &root_allocated, fpin)) < 0)
88     {
89         int saved_errno = errno;
90         /* FIXME: should be checking for end of file separately; errno
91            is not set in that case.  */
92         error (0, 0, "in directory %s:", xupdate_dir);
93         error (0, saved_errno, "cannot read %s", CVSADM_ROOT);
94         error (0, 0, "please correct this problem");
95         ret = NULL;
96         goto out;
97     }
98     fclose (fpin);
99     cp = root + len - 1;
100     if (*cp == '\n')
101         *cp = '\0';                     /* strip the newline */
102
103     /*
104      * root now contains a candidate for CVSroot. It must be an
105      * absolute pathname or specify a remote server.
106      */
107
108     ret = parse_cvsroot (root);
109     if (ret == NULL)
110     {
111         error (0, 0, "in directory %s:", xupdate_dir);
112         error (0, 0,
113                "ignoring %s because it does not contain a valid root.",
114                CVSADM_ROOT);
115         goto out;
116     }
117
118     if (!ret->isremote && !isdir (ret->directory))
119     {
120         error (0, 0, "in directory %s:", xupdate_dir);
121         error (0, 0,
122                "ignoring %s because it specifies a non-existent repository %s",
123                CVSADM_ROOT, root);
124         ret = NULL;
125         goto out;
126     }
127
128
129  out:
130     free (cvsadm);
131     free (tmp);
132     if (root != NULL)
133         free (root);
134     return ret;
135 }
136
137
138
139 /*
140  * Write the CVS/Root file so that the environment variable CVSROOT
141  * and/or the -d option to cvs will be validated or not necessary for
142  * future work.
143  */
144 void
145 Create_Root (const char *dir, const char *rootdir)
146 {
147     FILE *fout;
148     char *tmp;
149
150     if (noexec)
151         return;
152
153     /* record the current cvs root */
154
155     if (rootdir != NULL)
156     {
157         if (dir != NULL)
158             tmp = Xasprintf ("%s/%s", dir, CVSADM_ROOT);
159         else
160             tmp = xstrdup (CVSADM_ROOT);
161
162         fout = xfopen (tmp, "w+");
163         if (fprintf (fout, "%s\n", rootdir) < 0)
164             error (1, errno, "write to %s failed", tmp);
165         if (fclose (fout) == EOF)
166             error (1, errno, "cannot close %s", tmp);
167         free (tmp);
168     }
169 }
170
171 #endif /* ! DEBUG */
172
173
174
175 /* Translate an absolute repository string for a primary server and return it.
176  *
177  * INPUTS
178  *   root_in    The root to be translated.
179  *
180  * RETURNS
181  *   A translated string this function owns, or a pointer to the original
182  *   string passed in if no translation was necessary.
183  *
184  *   If the returned string is the translated one, it may be overwritten
185  *   by the next call to this function.
186  */
187 const char *
188 primary_root_translate (const char *root_in)
189 {
190 #ifdef PROXY_SUPPORT
191     char *translated;
192     static char *previous = NULL;
193     static size_t len;
194
195     /* This can happen, for instance, during `cvs init'.  */
196     if (!config) return root_in;
197
198     if (config->PrimaryServer
199         && !strncmp (root_in, config->PrimaryServer->directory,
200                      strlen (config->PrimaryServer->directory))
201         && (ISSLASH (root_in[strlen (config->PrimaryServer->directory)])
202             || root_in[strlen (config->PrimaryServer->directory)] == '\0')
203        )
204     {
205         translated =
206             Xasnprintf (previous, &len,
207                         "%s%s", current_parsed_root->directory,
208                         root_in + strlen (config->PrimaryServer->directory));
209         if (previous && previous != translated)
210             free (previous);
211         return previous = translated;
212     }
213 #endif
214
215     /* There is no primary root configured or it didn't match.  */
216     return root_in;
217 }
218
219
220
221 /* Translate a primary root in reverse for PATHNAMEs in responses.
222  *
223  * INPUTS
224  *   root_in    The root to be translated.
225  *
226  * RETURNS
227  *   A translated string this function owns, or a pointer to the original
228  *   string passed in if no translation was necessary.
229  *
230  *   If the returned string is the translated one, it may be overwritten
231  *   by the next call to this function.
232  */
233 const char *
234 primary_root_inverse_translate (const char *root_in)
235 {
236 #ifdef PROXY_SUPPORT
237     char *translated;
238     static char *previous = NULL;
239     static size_t len;
240
241     /* This can happen, for instance, during `cvs init'.  */
242     if (!config) return root_in;
243
244     if (config->PrimaryServer
245         && !strncmp (root_in, current_parsed_root->directory,
246                      strlen (current_parsed_root->directory))
247         && (ISSLASH (root_in[strlen (current_parsed_root->directory)])
248             || root_in[strlen (current_parsed_root->directory)] == '\0')
249        )
250     {
251         translated =
252             Xasnprintf (previous, &len,
253                         "%s%s", config->PrimaryServer->directory,
254                         root_in + strlen (current_parsed_root->directory));
255         if (previous && previous != translated)
256             free (previous);
257         return previous = translated;
258     }
259 #endif
260
261     /* There is no primary root configured or it didn't match.  */
262     return root_in;
263 }
264
265
266
267 /* The root_allow_* stuff maintains a list of valid CVSROOT
268    directories.  Then we can check against them when a remote user
269    hands us a CVSROOT directory.  */
270 static List *root_allow;
271 static List *root_allow_regexp;
272
273 static void
274 delconfig (Node *n)
275 {
276     if (n->data) free_config (n->data);
277 }
278
279
280
281 void
282 root_allow_add (const char *arg, const char *configPath)
283 {
284     Node *n;
285
286     if (!root_allow) root_allow = getlist();
287     n = getnode();
288     n->key = xstrdup (arg);
289     n->data = parse_config (arg, configPath);
290     n->delproc = delconfig;
291     addnode (root_allow, n);
292 }
293
294 void
295 root_allow_regexp_add (const char *arg, const char *configPath)
296 {
297     Node *n;
298
299     if (!root_allow_regexp) root_allow_regexp = getlist();
300     n = getnode();
301     n->key = xstrdup (arg);
302
303     /* This is a regexp, not the final cvsroot path - we cannot attach
304        it a config. So we attach configPath and we'll root_allow_add()
305        the actual, matching root in root_allow_compare_regexp() */
306     n->data = (void*)configPath;
307
308     addnode (root_allow_regexp, n);
309 }
310
311 void
312 root_allow_free (void)
313 {
314     dellist (&root_allow);
315     dellist (&root_allow_regexp);
316 }
317
318 int
319 root_allow_used (void)
320 {
321     return root_allow || root_allow_regexp;
322 }
323
324 /* walklist() callback for determining if 'root_to_check' matches
325    n->key (a regexp). If yes, 'root_to_check' will be added as if
326    directly specified through --allow-root.
327  */
328 static int
329 root_allow_compare_regexp (Node *n, void *root_to_check)
330 {
331   int status;
332   regex_t re;
333
334   if (regcomp(&re, n->key,
335               REG_EXTENDED|REG_NOSUB) != 0)
336   {
337       return 0;      /* report error? */
338   }
339   status = regexec(&re, root_to_check, (size_t) 0, NULL, 0);
340   regfree(&re);
341   if (status == 0)
342   {
343       /* n->data contains gConfigPath */
344       root_allow_add (root_to_check, n->data);
345       return 1;
346   }
347   return 0;
348 }
349
350 bool
351 root_allow_ok (const char *arg)
352 {
353     if (!root_allow_used())
354     {
355         /* Probably someone upgraded from CVS before 1.9.10 to 1.9.10
356            or later without reading the documentation about
357            --allow-root.  Printing an error here doesn't disclose any
358            particularly useful information to an attacker because a
359            CVS server configured in this way won't let *anyone* in.  */
360
361         /* Note that we are called from a context where we can spit
362            back "error" rather than waiting for the next request which
363            expects responses.  */
364         printf ("\
365 error 0 Server configuration missing --allow-root or --allow-root-regexp in inetd.conf\n");
366         exit (EXIT_FAILURE);
367     }
368
369     /* Look for 'arg' in the list of full-path allowed roots */
370     if (findnode (root_allow, arg))
371         return true;
372
373     /* Match 'arg' against the list of allowed roots regexps */
374     if (walklist (root_allow_regexp, root_allow_compare_regexp, (void*)arg))
375       return true;
376
377     return false;
378 }
379
380
381
382 /* Get a config we stored in response to root_allow.
383  *
384  * RETURNS
385  *   The config associated with ARG.
386  */
387 struct config *
388 get_root_allow_config (const char *arg, const char *configPath)
389 {
390     Node *n;
391
392     TRACE (TRACE_FUNCTION, "get_root_allow_config (%s)", arg);
393
394     if (root_allow)
395         n = findnode (root_allow, arg);
396     else
397         n = NULL;
398
399     if (n) return n->data;
400     return parse_config (arg, configPath);
401 }
402
403
404
405 /* This global variable holds the global -d option.  It is NULL if -d
406    was not used, which means that we must get the CVSroot information
407    from the CVSROOT environment variable or from a CVS/Root file.  */
408 char *CVSroot_cmdline;
409
410
411
412 /* FIXME - Deglobalize this. */
413 cvsroot_t *current_parsed_root = NULL;
414 /* Used to save the original root being processed so that we can still find it
415  * in lists and the like after a `Redirect' response.  Also set to mirror
416  * current_parsed_root in server mode so that code which runs on both the
417  * client and server but which wants to use original data on the client can
418  * just always reference the original_parsed_root.
419  */
420 const cvsroot_t *original_parsed_root;
421
422
423 /* allocate and initialize a cvsroot_t
424  *
425  * We must initialize the strings to NULL so we know later what we should
426  * free
427  *
428  * Some of the other zeroes remain meaningful as, "never set, use default",
429  * or the like
430  */
431 /* Functions which allocate memory are not pure.  */
432 static cvsroot_t *new_cvsroot_t(void)
433     __attribute__( (__malloc__) );
434 static cvsroot_t *
435 new_cvsroot_t (void)
436 {
437     cvsroot_t *newroot;
438
439     /* gotta store it somewhere */
440     newroot = xmalloc(sizeof(cvsroot_t));
441
442     newroot->original = NULL;
443     newroot->directory = NULL;
444     newroot->method = null_method;
445     newroot->isremote = false;
446 #ifdef CLIENT_SUPPORT
447     newroot->username = NULL;
448     newroot->password = NULL;
449     newroot->hostname = NULL;
450     newroot->cvs_rsh = NULL;
451     newroot->cvs_server = NULL;
452     newroot->port = 0;
453     newroot->proxy_hostname = NULL;
454     newroot->proxy_port = 0;
455     newroot->redirect = true;   /* Advertise Redirect support */
456 #endif /* CLIENT_SUPPORT */
457
458     return newroot;
459 }
460
461
462
463 /* Dispose of a cvsroot_t and its component parts.
464  *
465  * NOTE
466  *  It is dangerous for most code to call this function since parse_cvsroot
467  *  maintains a cache of parsed roots.
468  */
469 static void
470 free_cvsroot_t (cvsroot_t *root)
471 {
472     assert (root);
473     if (root->original != NULL)
474         free (root->original);
475     if (root->directory != NULL)
476         free (root->directory);
477 #ifdef CLIENT_SUPPORT
478     if (root->username != NULL)
479         free (root->username);
480     if (root->password != NULL)
481     {
482         /* I like to be paranoid */
483         memset (root->password, 0, strlen (root->password));
484         free (root->password);
485     }
486     if (root->hostname != NULL)
487         free (root->hostname);
488     if (root->cvs_rsh != NULL)
489         free (root->cvs_rsh);
490     if (root->cvs_server != NULL)
491         free (root->cvs_server);
492     if (root->proxy_hostname != NULL)
493         free (root->proxy_hostname);
494 #endif /* CLIENT_SUPPORT */
495     free (root);
496 }
497
498
499 #if defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
500 static char *validate_hostname(const char *) __attribute__((__malloc__));
501 #endif /* defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT) */
502
503 /*
504  * Parse a CVSROOT string to allocate and return a new cvsroot_t structure.
505  * Valid specifications are:
506  *
507  *      :(gserver|kserver|pserver):[[user][:password]@]host[:[port]]/path
508  *      [:(ext|server):][[user]@]host[:]/path
509  *      [:local:[e:]]/path
510  *      :fork:/path
511  *
512  * INPUTS
513  *      root_in         C String containing the CVSROOT to be parsed.
514  *
515  * RETURNS
516  *      A pointer to a newly allocated cvsroot_t structure upon success and
517  *      NULL upon failure.  The caller should never dispose of this structure,
518  *      as it is stored in a cache, but the caller may rely on it not to
519  *      change.
520  *
521  * NOTES
522  *      This would have been a lot easier to write in Perl.
523  *
524  *      Would it make sense to reimplement the root and config file parsing
525  *      gunk in Lex/Yacc?
526  *
527  * SEE ALSO
528  *      free_cvsroot_t()
529  */
530 cvsroot_t *
531 parse_cvsroot (const char *root_in)
532 {
533     cvsroot_t *newroot;                 /* the new root to be returned */
534     char *cvsroot_save;                 /* what we allocated so we can dispose
535                                          * it when finished */
536     char *cvsroot_copy, *p;             /* temporary pointers for parsing */
537 #if defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
538     char *q;                            /* temporary pointer for parsing */
539     char *firstslash;                   /* save where the path spec starts
540                                          * while we parse
541                                          * [[user][:password]@]host[:[port]]
542                                          */
543     int check_hostname, no_port, no_password, no_proxy;
544 #endif /* defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT) */
545     static List *cache = NULL;
546     Node *node;
547
548     assert (root_in != NULL);
549
550     /* This message is TRACE_FLOW since this function is called repeatedly by
551      * the recursion routines.
552      */
553     TRACE (TRACE_FLOW, "parse_cvsroot (%s)", root_in);
554
555     if ((node = findnode (cache, root_in)))
556         return node->data;
557
558     assert (root_in);
559
560     /* allocate some space */
561     newroot = new_cvsroot_t();
562
563     /* save the original string */
564     newroot->original = xstrdup (root_in);
565
566     /* and another copy we can munge while parsing */
567     cvsroot_save = cvsroot_copy = xstrdup (root_in);
568
569     if (*cvsroot_copy == ':')
570     {
571         char *method = ++cvsroot_copy;
572
573         /* Access method specified, as in
574          * "cvs -d :(gserver|kserver|pserver):[[user][:password]@]host[:[port]]/path",
575          * "cvs -d [:(ext|server):][[user]@]host[:]/path",
576          * "cvs -d :local:e:\path",
577          * "cvs -d :fork:/path".
578          * We need to get past that part of CVSroot before parsing the
579          * rest of it.
580          */
581
582         if (! (p = strchr (method, ':')))
583         {
584             error (0, 0, "No closing `:' on method in CVSROOT.");
585             goto error_exit;
586         }
587         *p = '\0';
588         cvsroot_copy = ++p;
589
590 #if defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
591         /* Look for method options, for instance, proxy, proxyport.
592          * Calling strtok again is saved until after parsing the method.
593          */
594         method = strtok (method, ";");
595         if (!method)
596             /* Could just exit now, but this keeps the error message in sync.
597              */
598             method = "";
599 #endif /* defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT) */
600
601     if (NULL == method)
602         {
603             error (0, 0, "Missing method in CVSROOT.");
604             goto error_exit;
605         }
606
607         /* Now we have an access method -- see if it's valid. */
608
609         if (!strcasecmp (method, "local"))
610             newroot->method = local_method;
611         else if (!strcasecmp (method, "pserver"))
612             newroot->method = pserver_method;
613         else if (!strcasecmp (method, "kserver"))
614             newroot->method = kserver_method;
615         else if (!strcasecmp (method, "gserver"))
616             newroot->method = gserver_method;
617         else if (!strcasecmp (method, "server"))
618             newroot->method = server_method;
619         else if (strncmp (method, "ext=", 4) == 0)
620         {
621             newroot->cvs_rsh = xstrdup(method + 4);
622             newroot->method = ext_method;
623         }
624         else if (!strcasecmp (method, "extssh"))
625         {
626             newroot->cvs_rsh = xstrdup("ssh");
627             newroot->method = extssh_method;
628         }
629         else if (!strcasecmp (method, "ext"))
630             newroot->method = ext_method;
631         else if (!strcasecmp (method, "fork"))
632             newroot->method = fork_method;
633         else
634         {
635             error (0, 0, "Unknown method (`%s') in CVSROOT.", method);
636             goto error_exit;
637         }
638
639 #if defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
640         /* Parse the method options, for instance, proxy, proxyport */
641         while ((p = strtok (NULL, ";")))
642         {
643             char *q = strchr (p, '=');
644             if (q == NULL)
645             {
646                 error (0, 0, "Option (`%s') has no argument in CVSROOT.",
647                        p);
648                 goto error_exit;
649             }
650
651             *q++ = '\0';
652             TRACE (TRACE_DATA, "CVSROOT option=`%s' value=`%s'", p, q);
653             if (!strcasecmp (p, "proxy"))
654             {
655                 if (!(newroot->proxy_hostname = validate_hostname(q))) {
656                         error(0, 0, "Invalid proxy hostname: %s", q);
657                         goto error_exit;
658                 }
659             }
660             else if (!strcasecmp (p, "proxyport"))
661             {
662                 char *r = q;
663
664                 do {
665                     if (!isdigit(*r)) {
666  proxy_port_error:
667                         error (0, 0,
668 "CVSROOT may only specify a positive, non-zero, integer proxy port (not `%s').",
669                                q);
670                         goto error_exit;
671                     }
672                 } while (*++r);
673                 if ((newroot->proxy_port = atoi(q)) <= 0 ||
674                     newroot->proxy_port > 65535)
675                         goto proxy_port_error;
676             }
677             else if (!strcasecmp (p, "CVS_RSH"))
678             {
679                 /* override CVS_RSH environment variable */
680                 if (newroot->method == ext_method
681                     || newroot->method == extssh_method)
682                 newroot->cvs_rsh = xstrdup (q);
683             }
684             else if (!strcasecmp (p, "CVS_SERVER"))
685             {
686                 /* override CVS_SERVER environment variable */
687                 if (newroot->method == ext_method
688                     || newroot->method == extssh_method
689                     || newroot->method == fork_method)
690                     newroot->cvs_server = xstrdup (q);
691             }
692             else if (!strcasecmp (p, "Redirect"))
693                 readBool ("CVSROOT", "Redirect", q, &newroot->redirect);
694             else
695             {
696                 error (0, 0, "Unknown option (`%s') in CVSROOT.", p);
697                 goto error_exit;
698             }
699         }
700 #endif /* defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT) */
701     }
702     else
703     {
704         /* If the method isn't specified, assume EXT_METHOD if the string looks
705            like a relative path and LOCAL_METHOD otherwise.  */
706
707         newroot->method = ((*cvsroot_copy != '/' && strchr (cvsroot_copy, '/'))
708                           ? ext_method
709                           : local_method);
710     }
711
712     /*
713      * There are a few sanity checks we can do now, only knowing the
714      * method of this root.
715      */
716
717     newroot->isremote = (newroot->method != local_method);
718
719 #if defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
720     if (readonlyfs && newroot->isremote && (newroot->method != ext_method)
721       && (newroot->method != extssh_method) && (newroot->method != fork_method))
722         error (1, 0,
723 "Read-only repository feature unavailable with remote roots (cvsroot = %s)",
724                cvsroot_copy);
725
726     if ((newroot->method != local_method)
727         && (newroot->method != fork_method)
728        )
729     {
730         /* split the string into [[user][:password]@]host[:[port]] & /path
731          *
732          * this will allow some characters such as '@' & ':' to remain unquoted
733          * in the path portion of the spec
734          */
735         if ((p = strchr (cvsroot_copy, '/')) == NULL)
736         {
737             error (0, 0, "CVSROOT requires a path spec:");
738             error (0, 0,
739 ":(gserver|kserver|pserver):[[user][:password]@]host[:[port]]/path");
740             error (0, 0, "[:(ext|server):][[user]@]host[:]/path");
741             goto error_exit;
742         }
743         firstslash = p;         /* == NULL if '/' not in string */
744         *p = '\0';
745
746         /* Check to see if there is a username[:password] in the string. */
747         if ((p = strchr (cvsroot_copy, '@')) != NULL)
748         {
749             *p = '\0';
750             /* check for a password */
751             if ((q = strchr (cvsroot_copy, ':')) != NULL)
752             {
753                 *q = '\0';
754                 newroot->password = xstrdup (++q);
755                 /* Don't check for *newroot->password == '\0' since
756                  * a user could conceivably wish to specify a blank password
757                  *
758                  * (newroot->password == NULL means to use the
759                  * password from .cvspass)
760                  */
761             }
762
763             /* copy the username */
764             if (*cvsroot_copy != '\0')
765                 /* a blank username is impossible, so leave it NULL in that
766                  * case so we know to use the default username
767                  */
768             {
769                 /* for want of strcspn */
770                 if (/* no at, obviously */ strchr(cvsroot_copy, '@') ||
771                     /* no colon, interference with CVSROOT/passwd file */
772                     strchr(cvsroot_copy, ':') ||
773                     /* no linefeeds, interference with pserver protocol */
774                     strchr(cvsroot_copy, '\012')) {
775                         error(0, 0, "Bad username \"%s\"", cvsroot_copy);
776                         goto error_exit;
777                 }
778                 /* other limitations include not beginning with a
779                  * hyphen-minus but that’s not even a requirement
780                  * in POSIX, let alone other operating environments…
781                  */
782                 newroot->username = xstrdup (cvsroot_copy);
783             }
784
785             cvsroot_copy = ++p;
786         }
787
788         /* now deal with host[:[port]] */
789
790         /* the port */
791         if ((p = strchr (cvsroot_copy, ':')) != NULL)
792         {
793             *p++ = '\0';
794             if (*p)
795             {
796                 char qch;
797
798                 q = p;
799                 while ((qch = *q++))
800                 {
801                     if (!isdigit(qch))
802                         goto parse_port_error;
803                 }
804                 if ((newroot->port = atoi(p)) <= 0 || newroot->port > 65535) {
805  parse_port_error:
806                     error (0, 0,
807 "CVSROOT may only specify a positive, non-zero, integer port (not `%s').",
808                             p);
809                     error (0, 0, "Perhaps you entered a relative pathname?");
810                     goto error_exit;
811                 }
812             }
813         }
814
815         /* check and copy host */
816         newroot->hostname = validate_hostname(cvsroot_copy);
817
818         /* restore the '/' */
819         cvsroot_copy = firstslash;
820         *cvsroot_copy = '/';
821     }
822 #endif /* defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT) */
823
824     /*
825      * Parse the path for all methods.
826      */
827     /* Here & local_cvsroot() should be the only places this needs to be
828      * called on a CVSROOT now.  cvsroot->original is saved for error messages
829      * and, otherwise, we want no trailing slashes.
830      */
831     Sanitize_Repository_Name (cvsroot_copy);
832     newroot->directory = xstrdup (cvsroot_copy);
833
834     /*
835      * Do various sanity checks.
836      */
837
838 #if defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
839     if (newroot->username && ! newroot->hostname)
840     {
841         /* this defangs sanity.sh tests for remote reject, though */
842  bad_hostname:
843         error (0, 0, "Missing or bad hostname in CVSROOT.");
844         goto error_exit;
845     }
846
847     /* We won't have attempted to parse these without CLIENT_SUPPORT or
848      * SERVER_SUPPORT.
849      */
850     check_hostname = 0;
851     no_password = 1;
852     no_proxy = 1;
853     no_port = 0;
854 #endif /* defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT) */
855     switch (newroot->method)
856     {
857     case local_method:
858 #if defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
859         if (newroot->username || newroot->hostname)
860         {
861             error (0, 0, "Can't specify hostname and username in CVSROOT");
862             error (0, 0, "when using local access method.");
863             goto error_exit;
864         }
865 #endif /* defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT) */
866         /* cvs.texinfo has always told people that CVSROOT must be an
867            absolute pathname.  Furthermore, attempts to use a relative
868            pathname produced various errors (I couldn't get it to work),
869            so there would seem to be little risk in making this a fatal
870            error.  */
871         if (!ISABSOLUTE (newroot->directory))
872         {
873             error (0, 0, "CVSROOT must be an absolute pathname (not `%s')",
874                    newroot->directory);
875             error (0, 0, "when using local access method.");
876             goto error_exit;
877         }
878 #if defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
879         /* We don't need to check for these in :local: mode, really, since
880          * we shouldn't be able to hit the code above which parses them, but
881          * I'm leaving them here in lieu of assertions.
882          */
883         no_port = 1;
884         /* no_password already set */
885 #endif /* defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT) */
886         break;
887 #if defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
888     case fork_method:
889         /* We want :fork: to behave the same as other remote access
890            methods.  Therefore, don't check to see that the repository
891            name is absolute -- let the server do it.  */
892         if (newroot->username || newroot->hostname)
893         {
894             error (0, 0, "Can't specify hostname and username in CVSROOT");
895             error (0, 0, "when using fork access method.");
896             goto error_exit;
897         }
898         newroot->hostname = xstrdup("server");  /* for error messages */
899         if (!ISABSOLUTE (newroot->directory))
900         {
901             error (0, 0, "CVSROOT must be an absolute pathname (not `%s')",
902                    newroot->directory);
903             error (0, 0, "when using fork access method.");
904             goto error_exit;
905         }
906         no_port = 1;
907         /* no_password already set */
908         break;
909     case kserver_method:
910         check_hostname = 1;
911         /* no_password already set */
912         break;
913     case gserver_method:
914         check_hostname = 1;
915         no_proxy = 0;
916         /* no_password already set */
917         break;
918     case server_method:
919     case ext_method:
920         no_port = 1;
921     case extssh_method:
922         /* no_password already set */
923         check_hostname = 1;
924         break;
925     case pserver_method:
926         no_password = 0;
927         no_proxy = 0;
928         check_hostname = 1;
929         break;
930 #endif /* defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT) */
931     default:
932         error (1, 0, "Invalid method found in parse_cvsroot");
933     }
934
935 #if defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
936     if (no_password && newroot->password)
937     {
938         error (0, 0, "CVSROOT password specification is only valid for");
939         error (0, 0, "pserver connection method.");
940         goto error_exit;
941     }
942     if (no_proxy && (newroot->proxy_hostname || newroot->proxy_port))
943     {
944         error (0, 0,
945 "CVSROOT proxy specification is only valid for gserver and");
946         error (0, 0, "pserver connection methods.");
947         goto error_exit;
948     }
949
950     if (!newroot->proxy_hostname && newroot->proxy_port)
951     {
952         error (0, 0, "Proxy port specified in CVSROOT without proxy host.");
953         goto error_exit;
954     }
955
956     if (check_hostname && !newroot->hostname)
957         goto bad_hostname;
958
959     if (no_port && newroot->port)
960     {
961         error (0, 0,
962 "CVSROOT port specification is only valid for extssh,");
963         error (0, 0, "gserver, kserver and pserver connection methods.");
964         goto error_exit;
965     }
966 #endif /* defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT) */
967
968     if (*newroot->directory == '\0')
969     {
970         error (0, 0, "Missing directory in CVSROOT.");
971         goto error_exit;
972     }
973     
974     /* Hooray!  We finally parsed it! */
975     free (cvsroot_save);
976
977     if (!cache) cache = getlist();
978     node = getnode();
979     node->key = xstrdup (newroot->original);
980     node->data = newroot;
981     addnode (cache, node);
982     return newroot;
983
984 error_exit:
985     free (cvsroot_save);
986     free_cvsroot_t (newroot);
987     return NULL;
988 }
989
990
991
992 #ifdef AUTH_CLIENT_SUPPORT
993 /* Use root->username, root->hostname, root->port, and root->directory
994  * to create a normalized CVSROOT fit for the .cvspass file
995  *
996  * username defaults to the result of getcaller()
997  * port defaults to the result of get_cvs_port_number()
998  *
999  * FIXME - we could cache the canonicalized version of a root inside the
1000  * cvsroot_t, but we'd have to un'const the input here and stop expecting the
1001  * caller to be responsible for our return value
1002  *
1003  * ASSUMPTIONS
1004  *   ROOT->method == pserver_method
1005  */
1006 char *
1007 normalize_cvsroot (const cvsroot_t *root)
1008 {
1009     char *cvsroot_canonical;
1010     char *p, *hostname;
1011
1012     assert (root && root->hostname && root->directory);
1013
1014     /* use a lower case hostname since we know hostnames are case insensitive */
1015     /* Some logic says we should be tacking our domain name on too if it isn't
1016      * there already, but for now this works.  Reverse->Forward lookups are
1017      * almost certainly too much since that would make CVS immune to some of
1018      * the DNS trickery that makes life easier for sysadmins when they want to
1019      * move a repository or the like
1020      */
1021     p = hostname = xstrdup (root->hostname);
1022     while (*p)
1023     {
1024         *p = tolower (*p);
1025         p++;
1026     }
1027
1028     cvsroot_canonical = Xasprintf (":pserver:%s@%s:%d%s",
1029                                    root->username ? root->username
1030                                                   : getcaller(),
1031                                    hostname, get_cvs_port_number (root),
1032                                    root->directory);
1033
1034     free (hostname);
1035     return cvsroot_canonical;
1036 }
1037 #endif /* AUTH_CLIENT_SUPPORT */
1038
1039
1040
1041 #ifdef PROXY_SUPPORT
1042 /* A walklist() function to walk the root_allow list looking for a PrimaryServer
1043  * configuration with a directory matching the requested directory.
1044  *
1045  * If found, replace it.
1046  */
1047 static bool get_local_root_dir_done;
1048 static int
1049 get_local_root_dir (Node *p, void *root_in)
1050 {
1051     struct config *c = p->data;
1052     char **r = root_in;
1053
1054     if (get_local_root_dir_done)
1055         return 0;
1056
1057     if (c->PrimaryServer && !strcmp (*r, c->PrimaryServer->directory))
1058     {
1059         free (*r);
1060         *r = xstrdup (p->key);
1061         get_local_root_dir_done = true;
1062     }
1063     return 0;
1064 }
1065 #endif /* PROXY_SUPPORT */
1066
1067
1068
1069 /* allocate and return a cvsroot_t structure set up as if we're using the local
1070  * repository DIR.  */
1071 cvsroot_t *
1072 local_cvsroot (const char *dir)
1073 {
1074     cvsroot_t *newroot = new_cvsroot_t();
1075
1076     newroot->original = xstrdup(dir);
1077     newroot->method = local_method;
1078     newroot->directory = xstrdup(dir);
1079     /* Here and parse_cvsroot() should be the only places this needs to be
1080      * called on a CVSROOT now.  cvsroot->original is saved for error messages
1081      * and, otherwise, we want no trailing slashes.
1082      */
1083     Sanitize_Repository_Name (newroot->directory);
1084
1085 #ifdef PROXY_SUPPORT
1086     /* Translate the directory to a local one in the case that we are
1087      * configured as a secondary.  If root_allow has not been initialized,
1088      * nothing happens.
1089      */
1090     get_local_root_dir_done = false;
1091     walklist (root_allow, get_local_root_dir, &newroot->directory);
1092 #endif /* PROXY_SUPPORT */
1093
1094     return newroot;
1095 }
1096
1097
1098
1099 #ifdef DEBUG
1100 /* This is for testing the parsing function.  Use
1101
1102      gcc -I. -I.. -I../lib -DDEBUG root.c -o root
1103
1104    to compile.  */
1105
1106 #include <stdio.h>
1107
1108 char *program_name = "testing";
1109 char *cvs_cmd_name = "parse_cvsroot";           /* XXX is this used??? */
1110
1111 void
1112 main (int argc, char *argv[])
1113 {
1114     program_name = argv[0];
1115
1116     if (argc != 2)
1117     {
1118         fprintf (stderr, "Usage: %s <CVSROOT>\n", program_name);
1119         exit (2);
1120     }
1121   
1122     if ((current_parsed_root = parse_cvsroot (argv[1])) == NULL)
1123     {
1124         fprintf (stderr, "%s: Parsing failed.\n", program_name);
1125         exit (1);
1126     }
1127     printf ("CVSroot: %s\n", argv[1]);
1128     printf ("current_parsed_root->method: %s\n",
1129             method_names[current_parsed_root->method]);
1130     printf ("current_parsed_root->username: %s\n",
1131             current_parsed_root->username
1132               ? current_parsed_root->username : "NULL");
1133     printf ("current_parsed_root->hostname: %s\n",
1134             current_parsed_root->hostname
1135               ? current_parsed_root->hostname : "NULL");
1136     printf ("current_parsed_root->directory: %s\n",
1137             current_parsed_root->directory);
1138
1139    exit (0);
1140    /* NOTREACHED */
1141 }
1142 #endif
1143
1144 #if defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
1145 #define CLS_INVALID     0
1146 #define CLS_INSIDE      1
1147 #define CLS_OUTSIDE     2
1148 #define CLS_SEPARATOR   4
1149 /* EBCDIC safe */
1150 #define CLASSIFY(x)     classify[(unsigned char)(x)]
1151 static char *
1152 validate_hostname(const char *s)
1153 {
1154         char *buf, *cp;
1155         size_t sz;
1156         static char classify_initialised = 0, *classify;
1157
1158         /* initialise classification table */
1159         if (!classify_initialised) {
1160                 const char *ccp;
1161
1162                 classify = xmalloc(256);
1163                 for (sz = 0; sz < 256; ++sz)
1164                         CLASSIFY(sz) = CLS_INVALID;
1165                 for (ccp = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
1166                     *ccp; ++ccp)
1167                         CLASSIFY(*ccp) = CLS_INSIDE | CLS_OUTSIDE;
1168                 CLASSIFY('-') = CLS_INSIDE;
1169                 CLASSIFY('.') = CLS_SEPARATOR;
1170                 classify_initialised = 1;
1171         }
1172
1173         /* total size limit tolerating a trailing dot */
1174         if ((sz = strlen(s)) > 256)
1175                 return (NULL);
1176         buf = xstrdup(s);
1177
1178         /* drop trailing dot */
1179         if ((unsigned char)buf[sz - 1] == (unsigned char)'.')
1180                 buf[--sz] = '\0';
1181         /* recheck */
1182         if (sz > 255) {
1183  err:
1184                 free(buf);
1185                 return (NULL);
1186         }
1187
1188         /* check each label */
1189         cp = buf;
1190  loop:
1191         /* must begin with [0-9A-Za-z] */
1192         if (!(CLASSIFY(*cp++) & CLS_OUTSIDE))
1193                 goto err;
1194         sz = 1;
1195         /* arbitrary many [0-9A-Za-z-] */
1196         while (CLASSIFY(*cp) & CLS_INSIDE) {
1197                 ++cp;
1198                 ++sz;
1199         }
1200         /* except the last must have been [0-9A-Za-z] again */
1201         if (!(CLASSIFY(cp[-1]) & CLS_OUTSIDE))
1202                 goto err;
1203         /* maximum label size */
1204         if (sz > 63)
1205                 goto err;
1206         /* next label? */
1207         if (CLASSIFY(*cp) & CLS_SEPARATOR) {
1208                 ++cp;
1209                 goto loop;
1210         }
1211         /* must be end of string now */
1212         if (*cp)
1213                 goto err;
1214         /* it is, everything okay */
1215         return (buf);
1216 }
1217 #endif /* defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT) */