117721Speter/*
2177391Sobrien * Copyright (C) 1986-2008 The Free Software Foundation, Inc.
317721Speter *
4175261Sobrien * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
5175261Sobrien *                                  and others.
6175261Sobrien *
7175261Sobrien * Poritons Copyright (c) 1992, Mark D. Baushke
8175261Sobrien *
917721Speter * You may distribute under the terms of the GNU General Public License as
1032785Speter * specified in the README file that comes with the CVS source distribution.
1117721Speter *
1217721Speter * Name of Root
1317721Speter *
1417721Speter * Determine the path to the CVSROOT and set "Root" accordingly.
1517721Speter */
1617721Speter
1717721Speter#include "cvs.h"
18175261Sobrien#include <assert.h>
1925839Speter#include "getline.h"
2017721Speter
2181404Speter/* Printable names for things in the current_parsed_root->method enum variable.
2225839Speter   Watch out if the enum is changed in cvs.h! */
2325839Speter
24128266Speterconst char method_names[][16] = {
25128266Speter    "undefined", "local", "server (rsh)", "pserver",
26175261Sobrien    "kserver", "gserver", "ext", "extssh", "fork"
2725839Speter};
2825839Speter
2925839Speter#ifndef DEBUG
3025839Speter
31175261Sobriencvsroot_t *
3232785SpeterName_Root (dir, update_dir)
33175261Sobrien    const char *dir;
34175261Sobrien    const char *update_dir;
3517721Speter{
3617721Speter    FILE *fpin;
37175261Sobrien    cvsroot_t *ret;
38175261Sobrien    const char *xupdate_dir;
3925839Speter    char *root = NULL;
4025839Speter    size_t root_allocated = 0;
4125839Speter    char *tmp;
4225839Speter    char *cvsadm;
4317721Speter    char *cp;
44128266Speter    int len;
4517721Speter
4617721Speter    if (update_dir && *update_dir)
4717721Speter	xupdate_dir = update_dir;
4817721Speter    else
4917721Speter	xupdate_dir = ".";
5017721Speter
5117721Speter    if (dir != NULL)
5217721Speter    {
5325839Speter	cvsadm = xmalloc (strlen (dir) + sizeof (CVSADM) + 10);
5417721Speter	(void) sprintf (cvsadm, "%s/%s", dir, CVSADM);
5525839Speter	tmp = xmalloc (strlen (dir) + sizeof (CVSADM_ROOT) + 10);
5617721Speter	(void) sprintf (tmp, "%s/%s", dir, CVSADM_ROOT);
5717721Speter    }
5817721Speter    else
5917721Speter    {
6025839Speter	cvsadm = xstrdup (CVSADM);
6125839Speter	tmp = xstrdup (CVSADM_ROOT);
6217721Speter    }
6317721Speter
6417721Speter    /*
6517721Speter     * Do not bother looking for a readable file if there is no cvsadm
6617721Speter     * directory present.
6717721Speter     *
6817721Speter     * It is possible that not all repositories will have a CVS/Root
6917721Speter     * file. This is ok, but the user will need to specify -d
7017721Speter     * /path/name or have the environment variable CVSROOT set in
7125839Speter     * order to continue.  */
7217721Speter    if ((!isdir (cvsadm)) || (!isreadable (tmp)))
7317721Speter    {
7425839Speter	ret = NULL;
7525839Speter	goto out;
7617721Speter    }
7717721Speter
7817721Speter    /*
7917721Speter     * The assumption here is that the CVS Root is always contained in the
8017721Speter     * first line of the "Root" file.
8117721Speter     */
8217721Speter    fpin = open_file (tmp, "r");
8317721Speter
84128266Speter    if ((len = getline (&root, &root_allocated, fpin)) < 0)
8517721Speter    {
86128266Speter	int saved_errno = errno;
8725839Speter	/* FIXME: should be checking for end of file separately; errno
8825839Speter	   is not set in that case.  */
8917721Speter	error (0, 0, "in directory %s:", xupdate_dir);
90128266Speter	error (0, saved_errno, "cannot read %s", CVSADM_ROOT);
9117721Speter	error (0, 0, "please correct this problem");
9225839Speter	ret = NULL;
9325839Speter	goto out;
9417721Speter    }
95128266Speter    fclose (fpin);
96175261Sobrien    cp = root + len - 1;
97128266Speter    if (*cp == '\n')
9817721Speter	*cp = '\0';			/* strip the newline */
9917721Speter
10017721Speter    /*
10117721Speter     * root now contains a candidate for CVSroot. It must be an
10225839Speter     * absolute pathname or specify a remote server.
10317721Speter     */
10417721Speter
105175261Sobrien    ret = parse_cvsroot (root);
106175261Sobrien    if (ret == NULL)
10717721Speter    {
10817721Speter	error (0, 0, "in directory %s:", xupdate_dir);
10917721Speter	error (0, 0,
110175261Sobrien	       "ignoring %s because it does not contain a valid root.",
11117721Speter	       CVSADM_ROOT);
11225839Speter	goto out;
11317721Speter    }
11417721Speter
115175261Sobrien    if (!ret->isremote && !isdir (ret->directory))
11617721Speter    {
11717721Speter	error (0, 0, "in directory %s:", xupdate_dir);
11817721Speter	error (0, 0,
11917721Speter	       "ignoring %s because it specifies a non-existent repository %s",
12017721Speter	       CVSADM_ROOT, root);
121175261Sobrien	free_cvsroot_t (ret);
12225839Speter	ret = NULL;
12325839Speter	goto out;
12417721Speter    }
12517721Speter
126175261Sobrien
12725839Speter out:
12825839Speter    free (cvsadm);
12925839Speter    free (tmp);
13025839Speter    if (root != NULL)
13125839Speter	free (root);
132175261Sobrien    return ret;
13317721Speter}
13417721Speter
135128266Speter
136128266Speter
13717721Speter/*
13817721Speter * Write the CVS/Root file so that the environment variable CVSROOT
13917721Speter * and/or the -d option to cvs will be validated or not necessary for
14017721Speter * future work.
14117721Speter */
14217721Spetervoid
14317721SpeterCreate_Root (dir, rootdir)
144128266Speter    const char *dir;
145128266Speter    const char *rootdir;
14617721Speter{
14717721Speter    FILE *fout;
14825839Speter    char *tmp;
14917721Speter
15017721Speter    if (noexec)
15117721Speter	return;
15217721Speter
15317721Speter    /* record the current cvs root */
15417721Speter
15517721Speter    if (rootdir != NULL)
15617721Speter    {
15717721Speter        if (dir != NULL)
15825839Speter	{
15925839Speter	    tmp = xmalloc (strlen (dir) + sizeof (CVSADM_ROOT) + 10);
16017721Speter	    (void) sprintf (tmp, "%s/%s", dir, CVSADM_ROOT);
16125839Speter	}
16217721Speter        else
16325839Speter	    tmp = xstrdup (CVSADM_ROOT);
16425839Speter
16517721Speter        fout = open_file (tmp, "w+");
16617721Speter        if (fprintf (fout, "%s\n", rootdir) < 0)
16717721Speter	    error (1, errno, "write to %s failed", tmp);
16817721Speter        if (fclose (fout) == EOF)
16917721Speter	    error (1, errno, "cannot close %s", tmp);
17025839Speter	free (tmp);
17117721Speter    }
17217721Speter}
17325839Speter
17425839Speter#endif /* ! DEBUG */
17525839Speter
17625839Speter
17726801Speter/* The root_allow_* stuff maintains a list of legal CVSROOT
17826801Speter   directories.  Then we can check against them when a remote user
17926801Speter   hands us a CVSROOT directory.  */
18026801Speter
18166525Speterstatic int root_allow_count;
18226801Speterstatic char **root_allow_vector;
18366525Speterstatic int root_allow_size;
18426801Speter
185177391Sobrienint
186177391Sobrienroot_allow_used ()
187177391Sobrien{
188177391Sobrien    return root_allow_count;
189177391Sobrien}
190177391Sobrien
19126801Spetervoid
19226801Speterroot_allow_add (arg)
19326801Speter    char *arg;
19426801Speter{
19526801Speter    char *p;
19626801Speter
19726801Speter    if (root_allow_size <= root_allow_count)
19826801Speter    {
19926801Speter	if (root_allow_size == 0)
20026801Speter	{
20126801Speter	    root_allow_size = 1;
20226801Speter	    root_allow_vector =
203109655Speter		(char **) xmalloc (root_allow_size * sizeof (char *));
20426801Speter	}
20526801Speter	else
20626801Speter	{
20726801Speter	    root_allow_size *= 2;
20826801Speter	    root_allow_vector =
209109655Speter		(char **) xrealloc (root_allow_vector,
21026801Speter				   root_allow_size * sizeof (char *));
21126801Speter	}
21226801Speter
21326801Speter	if (root_allow_vector == NULL)
21426801Speter	{
21526801Speter	no_memory:
21626801Speter	    /* Strictly speaking, we're not supposed to output anything
21726801Speter	       now.  But we're about to exit(), give it a try.  */
21826801Speter	    printf ("E Fatal server error, aborting.\n\
21926801Spetererror ENOMEM Virtual memory exhausted.\n");
22026801Speter
221107484Speter	    error_exit ();
22226801Speter	}
22326801Speter    }
224109655Speter    p = xmalloc (strlen (arg) + 1);
22526801Speter    if (p == NULL)
22626801Speter	goto no_memory;
22726801Speter    strcpy (p, arg);
22826801Speter    root_allow_vector[root_allow_count++] = p;
22926801Speter}
23026801Speter
23126801Spetervoid
23226801Speterroot_allow_free ()
23326801Speter{
23426801Speter    if (root_allow_vector != NULL)
23566525Speter	free_names (&root_allow_count, root_allow_vector);
23626801Speter    root_allow_size = 0;
23726801Speter}
23826801Speter
23926801Speterint
24026801Speterroot_allow_ok (arg)
24126801Speter    char *arg;
24226801Speter{
24366525Speter    int i;
24432785Speter
24532785Speter    if (root_allow_count == 0)
24632785Speter    {
24732785Speter	/* Probably someone upgraded from CVS before 1.9.10 to 1.9.10
24832785Speter	   or later without reading the documentation about
24932785Speter	   --allow-root.  Printing an error here doesn't disclose any
25032785Speter	   particularly useful information to an attacker because a
25132785Speter	   CVS server configured in this way won't let *anyone* in.  */
25232785Speter
25332785Speter	/* Note that we are called from a context where we can spit
25432785Speter	   back "error" rather than waiting for the next request which
25532785Speter	   expects responses.  */
25632785Speter	printf ("\
25732785Spetererror 0 Server configuration missing --allow-root in inetd.conf\n");
25832785Speter	error_exit ();
25932785Speter    }
26032785Speter
26126801Speter    for (i = 0; i < root_allow_count; ++i)
26226801Speter	if (strcmp (root_allow_vector[i], arg) == 0)
26326801Speter	    return 1;
26426801Speter    return 0;
26526801Speter}
26626801Speter
26781404Speter
26881404Speter
26954427Speter/* This global variable holds the global -d option.  It is NULL if -d
27054427Speter   was not used, which means that we must get the CVSroot information
27154427Speter   from the CVSROOT environment variable or from a CVS/Root file.  */
27254427Speterchar *CVSroot_cmdline;
27354427Speter
27481404Speter
27581404Speter
276107484Speter/* FIXME - Deglobalize this. */
27781404Spetercvsroot_t *current_parsed_root = NULL;
27825839Speter
27981404Speter
28081404Speter
28181404Speter/* allocate and initialize a cvsroot_t
28281404Speter *
28381404Speter * We must initialize the strings to NULL so we know later what we should
28481404Speter * free
28581404Speter *
28681404Speter * Some of the other zeroes remain meaningful as, "never set, use default",
28781404Speter * or the like
28881404Speter */
28981404Speterstatic cvsroot_t *
29081404Speternew_cvsroot_t ()
29125839Speter{
29281404Speter    cvsroot_t *newroot;
29325839Speter
29481404Speter    /* gotta store it somewhere */
29581404Speter    newroot = xmalloc(sizeof(cvsroot_t));
29681404Speter
29781404Speter    newroot->original = NULL;
29881404Speter    newroot->method = null_method;
299175261Sobrien    newroot->isremote = 0;
300128266Speter#ifdef CLIENT_SUPPORT
30181404Speter    newroot->username = NULL;
30281404Speter    newroot->password = NULL;
30381404Speter    newroot->hostname = NULL;
30481404Speter    newroot->port = 0;
30581404Speter    newroot->directory = NULL;
306128266Speter    newroot->proxy_hostname = NULL;
307128266Speter    newroot->proxy_port = 0;
30881404Speter#endif /* CLIENT_SUPPORT */
30981404Speter
31081404Speter    return newroot;
31181404Speter}
31281404Speter
31381404Speter
31481404Speter
31581404Speter/* Dispose of a cvsroot_t and its component parts */
31681404Spetervoid
31781404Speterfree_cvsroot_t (root)
31881404Speter    cvsroot_t *root;
31981404Speter{
32081404Speter    if (root->original != NULL)
32181404Speter	free (root->original);
322128266Speter    if (root->directory != NULL)
323128266Speter	free (root->directory);
324128266Speter#ifdef CLIENT_SUPPORT
32581404Speter    if (root->username != NULL)
32681404Speter	free (root->username);
32781404Speter    if (root->password != NULL)
32825839Speter    {
32981404Speter	/* I like to be paranoid */
33081404Speter	memset (root->password, 0, strlen (root->password));
33181404Speter	free (root->password);
33225839Speter    }
33381404Speter    if (root->hostname != NULL)
33481404Speter	free (root->hostname);
335128266Speter    if (root->proxy_hostname != NULL)
336128266Speter	free (root->proxy_hostname);
337128266Speter#endif /* CLIENT_SUPPORT */
33881404Speter    free (root);
33981404Speter}
34025839Speter
34166525Speter
34225839Speter
34381404Speter/*
344107484Speter * Parse a CVSROOT string to allocate and return a new cvsroot_t structure.
345107484Speter * Valid specifications are:
346107484Speter *
347107484Speter *	:(gserver|kserver|pserver):[[user][:password]@]host[:[port]]/path
348107484Speter *	[:(ext|server):][[user]@]host[:]/path
349107484Speter *	[:local:[e:]]/path
350107484Speter *	:fork:/path
351107484Speter *
352107484Speter * INPUTS
353107484Speter *	root_in		C String containing the CVSROOT to be parsed.
354107484Speter *
355107484Speter * RETURNS
356107484Speter *	A pointer to a newly allocated cvsroot_t structure upon success and
357107484Speter *	NULL upon failure.  The caller is responsible for disposing of
358107484Speter *	new structures with a call to free_cvsroot_t().
359107484Speter *
360107484Speter * NOTES
361107484Speter * 	This would have been a lot easier to write in Perl.
362107484Speter *
363107484Speter * SEE ALSO
364107484Speter * 	free_cvsroot_t()
36581404Speter */
36681404Spetercvsroot_t *
36781404Speterparse_cvsroot (root_in)
368128266Speter    const char *root_in;
36981404Speter{
37081404Speter    cvsroot_t *newroot;			/* the new root to be returned */
37181404Speter    char *cvsroot_save;			/* what we allocated so we can dispose
37281404Speter					 * it when finished */
37381404Speter    char *firstslash;			/* save where the path spec starts
37481404Speter					 * while we parse
37581404Speter					 * [[user][:password]@]host[:[port]]
37681404Speter					 */
37781404Speter    char *cvsroot_copy, *p, *q;		/* temporary pointers for parsing */
378128266Speter#ifdef CLIENT_SUPPORT
37981404Speter    int check_hostname, no_port, no_password;
380128266Speter#endif /* CLIENT_SUPPORT */
38181404Speter
382175261Sobrien    assert (root_in);
383175261Sobrien
38481404Speter    /* allocate some space */
38581404Speter    newroot = new_cvsroot_t();
38681404Speter
38781404Speter    /* save the original string */
38881404Speter    newroot->original = xstrdup (root_in);
38981404Speter
39081404Speter    /* and another copy we can munge while parsing */
39181404Speter    cvsroot_save = cvsroot_copy = xstrdup (root_in);
39281404Speter
39366525Speter    if (*cvsroot_copy == ':')
39425839Speter    {
39525839Speter	char *method = ++cvsroot_copy;
39625839Speter
39725839Speter	/* Access method specified, as in
39881404Speter	 * "cvs -d :(gserver|kserver|pserver):[[user][:password]@]host[:[port]]/path",
39981404Speter	 * "cvs -d [:(ext|server):][[user]@]host[:]/path",
40054427Speter	 * "cvs -d :local:e:\path",
40154427Speter	 * "cvs -d :fork:/path".
40225839Speter	 * We need to get past that part of CVSroot before parsing the
40325839Speter	 * rest of it.
40425839Speter	 */
40525839Speter
40625839Speter	if (! (p = strchr (method, ':')))
40725839Speter	{
408102840Speter	    error (0, 0, "No closing `:' on method in CVSROOT.");
40981404Speter	    goto error_exit;
41025839Speter	}
41125839Speter	*p = '\0';
41225839Speter	cvsroot_copy = ++p;
41325839Speter
414128266Speter#ifdef CLIENT_SUPPORT
415128266Speter	/* Look for method options, for instance, proxy, proxyport.
416128266Speter	 * We don't handle these, but we like to try and warn the user that
417128266Speter	 * they are being ignored.
418128266Speter	 */
419175261Sobrien	if ((p = strchr (method, ';')) != NULL)
420128266Speter	{
421128266Speter	    *p++ = '\0';
422128266Speter	    if (!really_quiet)
423128266Speter	    {
424128266Speter		error (0, 0,
425128266Speter"WARNING: Ignoring method options found in CVSROOT: `%s'.",
426128266Speter		       p);
427128266Speter		error (0, 0,
428128266Speter"Use CVS version 1.12.7 or later to handle method options.");
429128266Speter	    }
430128266Speter	}
431128266Speter#endif /* CLIENT_SUPPORT */
432128266Speter
43325839Speter	/* Now we have an access method -- see if it's valid. */
43425839Speter
43525839Speter	if (strcmp (method, "local") == 0)
43681404Speter	    newroot->method = local_method;
43725839Speter	else if (strcmp (method, "pserver") == 0)
43881404Speter	    newroot->method = pserver_method;
43925839Speter	else if (strcmp (method, "kserver") == 0)
44081404Speter	    newroot->method = kserver_method;
44132785Speter	else if (strcmp (method, "gserver") == 0)
44281404Speter	    newroot->method = gserver_method;
44325839Speter	else if (strcmp (method, "server") == 0)
44481404Speter	    newroot->method = server_method;
44525839Speter	else if (strcmp (method, "ext") == 0)
44681404Speter	    newroot->method = ext_method;
447177391Sobrien	else if (strcmp (method, "extssh") == 0)
448177391Sobrien	    newroot->method = extssh_method;
44954427Speter	else if (strcmp (method, "fork") == 0)
45081404Speter	    newroot->method = fork_method;
45125839Speter	else
45225839Speter	{
453102840Speter	    error (0, 0, "Unknown method (`%s') in CVSROOT.", method);
45481404Speter	    goto error_exit;
45525839Speter	}
45625839Speter    }
45725839Speter    else
45825839Speter    {
459107484Speter	/* If the method isn't specified, assume EXT_METHOD if the string looks
460107484Speter	   like a relative path and LOCAL_METHOD otherwise.  */
46125839Speter
46281404Speter	newroot->method = ((*cvsroot_copy != '/' && strchr (cvsroot_copy, '/'))
46325839Speter			  ? ext_method
46425839Speter			  : local_method);
46525839Speter    }
46625839Speter
46781404Speter    newroot->isremote = (newroot->method != local_method);
46825839Speter
46981404Speter    if ((newroot->method != local_method)
47081404Speter	&& (newroot->method != fork_method))
47125839Speter    {
47281404Speter	/* split the string into [[user][:password]@]host[:[port]] & /path
47381404Speter	 *
47481404Speter	 * this will allow some characters such as '@' & ':' to remain unquoted
47581404Speter	 * in the path portion of the spec
47681404Speter	 */
47781404Speter	if ((p = strchr (cvsroot_copy, '/')) == NULL)
47881404Speter	{
479102840Speter	    error (0, 0, "CVSROOT requires a path spec:");
480128266Speter	    error (0, 0,
481128266Speter":(gserver|kserver|pserver):[[user][:password]@]host[:[port]]/path");
48281404Speter	    error (0, 0, "[:(ext|server):][[user]@]host[:]/path");
48381404Speter	    goto error_exit;
48481404Speter	}
48581404Speter	firstslash = p;		/* == NULL if '/' not in string */
48681404Speter	*p = '\0';
48725839Speter
488128266Speter        /* Don't parse username, password, hostname, or port without client
489128266Speter         * support.
490128266Speter         */
491128266Speter#ifdef CLIENT_SUPPORT
49281404Speter	/* Check to see if there is a username[:password] in the string. */
49366525Speter	if ((p = strchr (cvsroot_copy, '@')) != NULL)
49425839Speter	{
49525839Speter	    *p = '\0';
49681404Speter	    /* check for a password */
49781404Speter	    if ((q = strchr (cvsroot_copy, ':')) != NULL)
49881404Speter	    {
49981404Speter		*q = '\0';
50081404Speter		newroot->password = xstrdup (++q);
50181404Speter		/* Don't check for *newroot->password == '\0' since
50281404Speter		 * a user could conceivably wish to specify a blank password
503107484Speter		 *
50481404Speter		 * (newroot->password == NULL means to use the
50581404Speter		 * password from .cvspass)
50681404Speter		 */
50781404Speter	    }
50881404Speter
50981404Speter	    /* copy the username */
51081404Speter	    if (*cvsroot_copy != '\0')
51181404Speter		/* a blank username is impossible, so leave it NULL in that
51281404Speter		 * case so we know to use the default username
51381404Speter		 */
51481404Speter		newroot->username = xstrdup (cvsroot_copy);
51581404Speter
51625839Speter	    cvsroot_copy = ++p;
51725839Speter	}
51825839Speter
51981404Speter	/* now deal with host[:[port]] */
52081404Speter
52181404Speter	/* the port */
52266525Speter	if ((p = strchr (cvsroot_copy, ':')) != NULL)
52325839Speter	{
52481404Speter	    *p++ = '\0';
52581404Speter	    if (strlen(p))
52681404Speter	    {
52781404Speter		q = p;
52881404Speter		if (*q == '-') q++;
52981404Speter		while (*q)
53081404Speter		{
53181404Speter		    if (!isdigit(*q++))
53281404Speter		    {
533128266Speter			error (0, 0,
534128266Speter"CVSROOT may only specify a positive, non-zero, integer port (not `%s').",
53581404Speter				p);
536128266Speter			error (0, 0,
537128266Speter                               "Perhaps you entered a relative pathname?");
53881404Speter			goto error_exit;
53981404Speter		    }
54081404Speter		}
54181404Speter		if ((newroot->port = atoi (p)) <= 0)
54281404Speter		{
543128266Speter		    error (0, 0,
544128266Speter"CVSROOT may only specify a positive, non-zero, integer port (not `%s').",
54581404Speter			    p);
546102840Speter		    error (0, 0, "Perhaps you entered a relative pathname?");
54781404Speter		    goto error_exit;
54881404Speter		}
54981404Speter	    }
55025839Speter	}
55181404Speter
55281404Speter	/* copy host */
55381404Speter	if (*cvsroot_copy != '\0')
55481404Speter	    /* blank hostnames are invalid, but for now leave the field NULL
55581404Speter	     * and catch the error during the sanity checks later
55681404Speter	     */
55781404Speter	    newroot->hostname = xstrdup (cvsroot_copy);
55881404Speter
55981404Speter	/* restore the '/' */
56081404Speter	cvsroot_copy = firstslash;
56181404Speter	*cvsroot_copy = '/';
562128266Speter#endif /* CLIENT_SUPPORT */
56325839Speter    }
56425839Speter
565128266Speter    /*
566128266Speter     * Parse the path for all methods.
567128266Speter     */
568128266Speter    /* Here & local_cvsroot() should be the only places this needs to be
569128266Speter     * called on a CVSROOT now.  cvsroot->original is saved for error messages
570128266Speter     * and, otherwise, we want no trailing slashes.
571128266Speter     */
572128266Speter    Sanitize_Repository_Name( cvsroot_copy );
57381404Speter    newroot->directory = xstrdup(cvsroot_copy);
57425839Speter
57581404Speter    /*
57681404Speter     * Do various sanity checks.
57781404Speter     */
57881404Speter
57925839Speter#if ! defined (CLIENT_SUPPORT) && ! defined (DEBUG)
58081404Speter    if (newroot->method != local_method)
58125839Speter    {
582102840Speter	error (0, 0, "CVSROOT is set for a remote access method but your");
583102840Speter	error (0, 0, "CVS executable doesn't support it.");
58481404Speter	goto error_exit;
58525839Speter    }
58625839Speter#endif
58725839Speter
58881404Speter#if ! defined (SERVER_SUPPORT) && ! defined (DEBUG)
58981404Speter    if (newroot->method == fork_method)
59025839Speter    {
591102840Speter	error (0, 0, "CVSROOT is set to use the :fork: access method but your");
592102840Speter	error (0, 0, "CVS executable doesn't support it.");
59381404Speter	goto error_exit;
59481404Speter     }
59581404Speter#endif
59681404Speter
597128266Speter#ifdef CLIENT_SUPPORT
59881404Speter    if (newroot->username && ! newroot->hostname)
59981404Speter    {
600102840Speter	error (0, 0, "Missing hostname in CVSROOT.");
60181404Speter	goto error_exit;
60225839Speter    }
60325839Speter
60432785Speter    check_hostname = 0;
605128266Speter    no_password = 1;
60681404Speter    no_port = 0;
607128266Speter#endif /* CLIENT_SUPPORT */
60881404Speter    switch (newroot->method)
60925839Speter    {
61025839Speter    case local_method:
611128266Speter#ifdef CLIENT_SUPPORT
61281404Speter	if (newroot->username || newroot->hostname)
61325839Speter	{
614102840Speter	    error (0, 0, "Can't specify hostname and username in CVSROOT");
615102840Speter	    error (0, 0, "when using local access method.");
61681404Speter	    goto error_exit;
61725839Speter	}
618128266Speter	no_port = 1;
619128266Speter	/* no_password already set */
620128266Speter#endif /* CLIENT_SUPPORT */
62125839Speter	/* cvs.texinfo has always told people that CVSROOT must be an
62225839Speter	   absolute pathname.  Furthermore, attempts to use a relative
62325839Speter	   pathname produced various errors (I couldn't get it to work),
62425839Speter	   so there would seem to be little risk in making this a fatal
62525839Speter	   error.  */
62681404Speter	if (!isabsolute (newroot->directory))
62781404Speter	{
628102840Speter	    error (0, 0, "CVSROOT must be an absolute pathname (not `%s')",
62981404Speter		   newroot->directory);
630102840Speter	    error (0, 0, "when using local access method.");
63181404Speter	    goto error_exit;
63281404Speter	}
63325839Speter	break;
634128266Speter#ifdef CLIENT_SUPPORT
63566525Speter    case fork_method:
63666525Speter	/* We want :fork: to behave the same as other remote access
63766525Speter           methods.  Therefore, don't check to see that the repository
63866525Speter           name is absolute -- let the server do it.  */
63981404Speter	if (newroot->username || newroot->hostname)
64066525Speter	{
641102840Speter	    error (0, 0, "Can't specify hostname and username in CVSROOT");
642102840Speter	    error (0, 0, "when using fork access method.");
64381404Speter	    goto error_exit;
64466525Speter	}
645128266Speter	newroot->hostname = xstrdup("server");  /* for error messages */
64681404Speter	if (!isabsolute (newroot->directory))
64781404Speter	{
648102840Speter	    error (0, 0, "CVSROOT must be an absolute pathname (not `%s')",
64981404Speter		   newroot->directory);
650102840Speter	    error (0, 0, "when using fork access method.");
65181404Speter	    goto error_exit;
65281404Speter	}
65381404Speter	no_port = 1;
654128266Speter	/* no_password already set */
65566525Speter	break;
65625839Speter    case kserver_method:
657128266Speter# ifndef HAVE_KERBEROS
658102840Speter       	error (0, 0, "CVSROOT is set for a kerberos access method but your");
659102840Speter	error (0, 0, "CVS executable doesn't support it.");
66081404Speter	goto error_exit;
661128266Speter# else
66232785Speter	check_hostname = 1;
663128266Speter	/* no_password already set */
66432785Speter	break;
665128266Speter# endif
66632785Speter    case gserver_method:
667128266Speter# ifndef HAVE_GSSAPI
668102840Speter	error (0, 0, "CVSROOT is set for a GSSAPI access method but your");
669102840Speter	error (0, 0, "CVS executable doesn't support it.");
67081404Speter	goto error_exit;
671128266Speter# else
67232785Speter	check_hostname = 1;
673128266Speter	/* no_password already set */
67432785Speter	break;
675128266Speter# endif
67625839Speter    case server_method:
67725839Speter    case ext_method:
678177391Sobrien    case extssh_method:
67981404Speter	no_port = 1;
680128266Speter	/* no_password already set */
68181404Speter	check_hostname = 1;
68281404Speter	break;
68325839Speter    case pserver_method:
684128266Speter	no_password = 0;
68532785Speter	check_hostname = 1;
68632785Speter	break;
687128266Speter#endif /* CLIENT_SUPPORT */
688102840Speter    default:
689102840Speter	error (1, 0, "Invalid method found in parse_cvsroot");
69032785Speter    }
69132785Speter
692128266Speter#ifdef CLIENT_SUPPORT
69381404Speter    if (no_password && newroot->password)
69432785Speter    {
69581404Speter	error (0, 0, "CVSROOT password specification is only valid for");
69681404Speter	error (0, 0, "pserver connection method.");
69781404Speter	goto error_exit;
69881404Speter    }
69981404Speter
70081404Speter    if (check_hostname && !newroot->hostname)
70181404Speter    {
702102840Speter	error (0, 0, "Didn't specify hostname in CVSROOT.");
70381404Speter	goto error_exit;
70481404Speter    }
70581404Speter
70681404Speter    if (no_port && newroot->port)
70725839Speter	{
70881404Speter	    error (0, 0, "CVSROOT port specification is only valid for gserver, kserver,");
70981404Speter	    error (0, 0, "and pserver connection methods.");
71081404Speter	    goto error_exit;
71125839Speter	}
712128266Speter#endif /* CLIENT_SUPPORT */
71325839Speter
71481404Speter    if (*newroot->directory == '\0')
71525839Speter    {
716102840Speter	error (0, 0, "Missing directory in CVSROOT.");
71781404Speter	goto error_exit;
71825839Speter    }
71925839Speter
72025839Speter    /* Hooray!  We finally parsed it! */
721107484Speter    free (cvsroot_save);
72281404Speter    return newroot;
72381404Speter
72481404Spetererror_exit:
725107484Speter    free (cvsroot_save);
72681404Speter    free_cvsroot_t (newroot);
72781404Speter    return NULL;
72825839Speter}
72925839Speter
73025839Speter
73125839Speter
73281404Speter#ifdef AUTH_CLIENT_SUPPORT
73381404Speter/* Use root->username, root->hostname, root->port, and root->directory
73481404Speter * to create a normalized CVSROOT fit for the .cvspass file
73581404Speter *
73681404Speter * username defaults to the result of getcaller()
73781404Speter * port defaults to the result of get_cvs_port_number()
73881404Speter *
73981404Speter * FIXME - we could cache the canonicalized version of a root inside the
74081404Speter * cvsroot_t, but we'd have to un'const the input here and stop expecting the
74181404Speter * caller to be responsible for our return value
74281404Speter */
74381404Speterchar *
74481404Speternormalize_cvsroot (root)
74581404Speter    const cvsroot_t *root;
74681404Speter{
74781404Speter    char *cvsroot_canonical;
74881404Speter    char *p, *hostname, *username;
74981404Speter    char port_s[64];
75081404Speter
751175261Sobrien    assert (root && root->hostname && root->directory);
752175261Sobrien
75381404Speter    /* get the appropriate port string */
75481404Speter    sprintf (port_s, "%d", get_cvs_port_number (root));
75581404Speter
75681404Speter    /* use a lower case hostname since we know hostnames are case insensitive */
75781404Speter    /* Some logic says we should be tacking our domain name on too if it isn't
75881404Speter     * there already, but for now this works.  Reverse->Forward lookups are
75981404Speter     * almost certainly too much since that would make CVS immune to some of
76081404Speter     * the DNS trickery that makes life easier for sysadmins when they want to
76181404Speter     * move a repository or the like
76281404Speter     */
76381404Speter    p = hostname = xstrdup(root->hostname);
76481404Speter    while (*p)
76581404Speter    {
76681404Speter	*p = tolower(*p);
76781404Speter	p++;
76881404Speter    }
76981404Speter
77081404Speter    /* get the username string */
77181404Speter    username = root->username ? root->username : getcaller();
77281404Speter    cvsroot_canonical = xmalloc ( strlen(username)
77381404Speter				+ strlen(hostname) + strlen(port_s)
77481404Speter				+ strlen(root->directory) + 12);
77581404Speter    sprintf (cvsroot_canonical, ":pserver:%s@%s:%s%s",
77681404Speter	    username, hostname, port_s, root->directory);
77781404Speter
77881404Speter    free (hostname);
77981404Speter    return cvsroot_canonical;
78081404Speter}
78181404Speter#endif /* AUTH_CLIENT_SUPPORT */
78281404Speter
78381404Speter
78481404Speter
78581404Speter/* allocate and return a cvsroot_t structure set up as if we're using the local
78681404Speter * repository DIR.  */
78781404Spetercvsroot_t *
78881404Speterlocal_cvsroot (dir)
789128266Speter    const char *dir;
79025839Speter{
79181404Speter    cvsroot_t *newroot = new_cvsroot_t();
79281404Speter
79381404Speter    newroot->original = xstrdup(dir);
79481404Speter    newroot->method = local_method;
79581404Speter    newroot->directory = xstrdup(dir);
796128266Speter    /* Here and parse_cvsroot() should be the only places this needs to be
797128266Speter     * called on a CVSROOT now.  cvsroot->original is saved for error messages
798128266Speter     * and, otherwise, we want no trailing slashes.
799128266Speter     */
800128266Speter    Sanitize_Repository_Name( newroot->directory );
80181404Speter    return newroot;
80225839Speter}
80325839Speter
80425839Speter
80581404Speter
80625839Speter#ifdef DEBUG
80754427Speter/* This is for testing the parsing function.  Use
80825839Speter
80954427Speter     gcc -I. -I.. -I../lib -DDEBUG root.c -o root
81054427Speter
81154427Speter   to compile.  */
81254427Speter
81325839Speter#include <stdio.h>
81425839Speter
81525839Speterchar *program_name = "testing";
816128266Speterchar *cvs_cmd_name = "parse_cvsroot";		/* XXX is this used??? */
81725839Speter
81854427Speter/* Toy versions of various functions when debugging under unix.  Yes,
81954427Speter   these make various bad assumptions, but they're pretty easy to
82054427Speter   debug when something goes wrong.  */
82154427Speter
82225839Spetervoid
82354427Spetererror_exit PROTO ((void))
82454427Speter{
82554427Speter    exit (1);
82654427Speter}
82754427Speter
82854427Speterint
82954427Speterisabsolute (dir)
83054427Speter    const char *dir;
83154427Speter{
83254427Speter    return (dir && (*dir == '/'));
83354427Speter}
83454427Speter
83554427Spetervoid
83625839Spetermain (argc, argv)
83725839Speter    int argc;
83825839Speter    char *argv[];
83925839Speter{
84025839Speter    program_name = argv[0];
84125839Speter
84225839Speter    if (argc != 2)
84325839Speter    {
84425839Speter	fprintf (stderr, "Usage: %s <CVSROOT>\n", program_name);
84525839Speter	exit (2);
84625839Speter    }
84725839Speter
84881404Speter    if ((current_parsed_root = parse_cvsroot (argv[1])) == NULL)
84925839Speter    {
85054427Speter	fprintf (stderr, "%s: Parsing failed.\n", program_name);
85125839Speter	exit (1);
85225839Speter    }
85325839Speter    printf ("CVSroot: %s\n", argv[1]);
85481404Speter    printf ("current_parsed_root->method: %s\n", method_names[current_parsed_root->method]);
85581404Speter    printf ("current_parsed_root->username: %s\n",
85681404Speter	    current_parsed_root->username ? current_parsed_root->username : "NULL");
85781404Speter    printf ("current_parsed_root->hostname: %s\n",
85881404Speter	    current_parsed_root->hostname ? current_parsed_root->hostname : "NULL");
85981404Speter    printf ("current_parsed_root->directory: %s\n", current_parsed_root->directory);
86025839Speter
86125839Speter   exit (0);
86225839Speter   /* NOTREACHED */
86325839Speter}
86425839Speter#endif
865