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