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