root.c revision 26801
1/*
2 * Copyright (c) 1992, Mark D. Baushke
3 *
4 * You may distribute under the terms of the GNU General Public License as
5 * specified in the README file that comes with the CVS 1.4 kit.
6 *
7 * Name of Root
8 *
9 * Determine the path to the CVSROOT and set "Root" accordingly.
10 * If this looks like of modified clone of Name_Repository() in
11 * repos.c, it is...
12 */
13
14#include "cvs.h"
15#include "getline.h"
16
17/* Printable names for things in the CVSroot_method enum variable.
18   Watch out if the enum is changed in cvs.h! */
19
20char *method_names[] = {
21  "local", "server (rsh)", "pserver", "kserver", "ext"
22};
23
24#ifndef DEBUG
25
26char *
27Name_Root(dir, update_dir)
28     char *dir;
29     char *update_dir;
30{
31    FILE *fpin;
32    char *ret, *xupdate_dir;
33    char *root = NULL;
34    size_t root_allocated = 0;
35    char *tmp;
36    char *cvsadm;
37    char *cp;
38
39    if (update_dir && *update_dir)
40	xupdate_dir = update_dir;
41    else
42	xupdate_dir = ".";
43
44    if (dir != NULL)
45    {
46	cvsadm = xmalloc (strlen (dir) + sizeof (CVSADM) + 10);
47	(void) sprintf (cvsadm, "%s/%s", dir, CVSADM);
48	tmp = xmalloc (strlen (dir) + sizeof (CVSADM_ROOT) + 10);
49	(void) sprintf (tmp, "%s/%s", dir, CVSADM_ROOT);
50    }
51    else
52    {
53	cvsadm = xstrdup (CVSADM);
54	tmp = xstrdup (CVSADM_ROOT);
55    }
56
57    /*
58     * Do not bother looking for a readable file if there is no cvsadm
59     * directory present.
60     *
61     * It is possible that not all repositories will have a CVS/Root
62     * file. This is ok, but the user will need to specify -d
63     * /path/name or have the environment variable CVSROOT set in
64     * order to continue.  */
65    if ((!isdir (cvsadm)) || (!isreadable (tmp)))
66    {
67	ret = NULL;
68	goto out;
69    }
70
71    /*
72     * The assumption here is that the CVS Root is always contained in the
73     * first line of the "Root" file.
74     */
75    fpin = open_file (tmp, "r");
76
77    if (getline (&root, &root_allocated, fpin) < 0)
78    {
79	/* FIXME: should be checking for end of file separately; errno
80	   is not set in that case.  */
81	error (0, 0, "in directory %s:", xupdate_dir);
82	error (0, errno, "cannot read %s", CVSADM_ROOT);
83	error (0, 0, "please correct this problem");
84	ret = NULL;
85	goto out;
86    }
87    (void) fclose (fpin);
88    if ((cp = strrchr (root, '\n')) != NULL)
89	*cp = '\0';			/* strip the newline */
90
91    /*
92     * root now contains a candidate for CVSroot. It must be an
93     * absolute pathname or specify a remote server.
94     */
95
96    if (
97#ifdef CLIENT_SUPPORT
98	(strchr (root, ':') == NULL) &&
99#endif
100    	! isabsolute (root))
101    {
102	error (0, 0, "in directory %s:", xupdate_dir);
103	error (0, 0,
104	       "ignoring %s because it does not contain an absolute pathname.",
105	       CVSADM_ROOT);
106	ret = NULL;
107	goto out;
108    }
109
110#ifdef CLIENT_SUPPORT
111    if ((strchr (root, ':') == NULL) && !isdir (root))
112#else /* ! CLIENT_SUPPORT */
113    if (!isdir (root))
114#endif /* CLIENT_SUPPORT */
115    {
116	error (0, 0, "in directory %s:", xupdate_dir);
117	error (0, 0,
118	       "ignoring %s because it specifies a non-existent repository %s",
119	       CVSADM_ROOT, root);
120	ret = NULL;
121	goto out;
122    }
123
124    /* allocate space to return and fill it in */
125    strip_trailing_slashes (root);
126    ret = xstrdup (root);
127 out:
128    free (cvsadm);
129    free (tmp);
130    if (root != NULL)
131	free (root);
132    return (ret);
133}
134
135/*
136 * Returns non-zero if the two directories have the same stat values
137 * which indicates that they are really the same directories.
138 */
139int
140same_directories (dir1, dir2)
141     char *dir1;
142     char *dir2;
143{
144    struct stat sb1;
145    struct stat sb2;
146    int ret;
147
148    if ( CVS_STAT (dir1, &sb1) < 0)
149        return (0);
150    if ( CVS_STAT (dir2, &sb2) < 0)
151        return (0);
152
153    ret = 0;
154    if ( (memcmp( &sb1.st_dev, &sb2.st_dev, sizeof(dev_t) ) == 0) &&
155	 (memcmp( &sb1.st_ino, &sb2.st_ino, sizeof(ino_t) ) == 0))
156        ret = 1;
157
158    return (ret);
159}
160
161
162/*
163 * Write the CVS/Root file so that the environment variable CVSROOT
164 * and/or the -d option to cvs will be validated or not necessary for
165 * future work.
166 */
167void
168Create_Root (dir, rootdir)
169     char *dir;
170     char *rootdir;
171{
172    FILE *fout;
173    char *tmp;
174
175    if (noexec)
176	return;
177
178    /* record the current cvs root */
179
180    if (rootdir != NULL)
181    {
182        if (dir != NULL)
183	{
184	    tmp = xmalloc (strlen (dir) + sizeof (CVSADM_ROOT) + 10);
185	    (void) sprintf (tmp, "%s/%s", dir, CVSADM_ROOT);
186	}
187        else
188	    tmp = xstrdup (CVSADM_ROOT);
189
190        fout = open_file (tmp, "w+");
191        if (fprintf (fout, "%s\n", rootdir) < 0)
192	    error (1, errno, "write to %s failed", tmp);
193        if (fclose (fout) == EOF)
194	    error (1, errno, "cannot close %s", tmp);
195	free (tmp);
196    }
197}
198
199#endif /* ! DEBUG */
200
201
202/* The root_allow_* stuff maintains a list of legal CVSROOT
203   directories.  Then we can check against them when a remote user
204   hands us a CVSROOT directory.  */
205
206static unsigned int root_allow_count;
207static char **root_allow_vector;
208static unsigned int root_allow_size;
209
210void
211root_allow_add (arg)
212    char *arg;
213{
214    char *p;
215
216    if (root_allow_size <= root_allow_count)
217    {
218	if (root_allow_size == 0)
219	{
220	    root_allow_size = 1;
221	    root_allow_vector =
222		(char **) malloc (root_allow_size * sizeof (char *));
223	}
224	else
225	{
226	    root_allow_size *= 2;
227	    root_allow_vector =
228		(char **) realloc (root_allow_vector,
229				   root_allow_size * sizeof (char *));
230	}
231
232	if (root_allow_vector == NULL)
233	{
234	no_memory:
235	    /* Strictly speaking, we're not supposed to output anything
236	       now.  But we're about to exit(), give it a try.  */
237	    printf ("E Fatal server error, aborting.\n\
238error ENOMEM Virtual memory exhausted.\n");
239
240	    /* I'm doing this manually rather than via error_exit ()
241	       because I'm not sure whether we want to call server_cleanup.
242	       Needs more investigation....  */
243
244#ifdef SYSTEM_CLEANUP
245	    /* Hook for OS-specific behavior, for example socket
246	       subsystems on NT and OS2 or dealing with windows
247	       and arguments on Mac.  */
248	    SYSTEM_CLEANUP ();
249#endif
250
251	    exit (EXIT_FAILURE);
252	}
253    }
254    p = malloc (strlen (arg) + 1);
255    if (p == NULL)
256	goto no_memory;
257    strcpy (p, arg);
258    root_allow_vector[root_allow_count++] = p;
259}
260
261void
262root_allow_free ()
263{
264    if (root_allow_vector != NULL)
265	free (root_allow_vector);
266    root_allow_count = 0;
267    root_allow_size = 0;
268}
269
270int
271root_allow_ok (arg)
272    char *arg;
273{
274    unsigned int i;
275    for (i = 0; i < root_allow_count; ++i)
276	if (strcmp (root_allow_vector[i], arg) == 0)
277	    return 1;
278    return 0;
279}
280
281
282/* Parse a CVSROOT variable into its constituent parts -- method,
283 * username, hostname, directory.  The prototypical CVSROOT variable
284 * looks like:
285 *
286 * :method:user@host:path
287 *
288 * Some methods may omit fields; local, for example, doesn't need user
289 * and host.
290 *
291 * Returns zero on success, non-zero on failure. */
292
293char *CVSroot_original = NULL;	/* the CVSroot that was passed in */
294int client_active;		/* nonzero if we are doing remote access */
295CVSmethod CVSroot_method;	/* one of the enum values defined in cvs.h */
296char *CVSroot_username;		/* the username or NULL if method == local */
297char *CVSroot_hostname;		/* the hostname or NULL if method == local */
298char *CVSroot_directory;	/* the directory name */
299
300#ifdef AUTH_SERVER_SUPPORT
301/* Die if CVSroot_directory and Pserver_Repos don't match. */
302static void
303check_root_consistent ()
304{
305    /* FIXME: Should be using a deferred error, as the rest of
306       serve_root does.  As it is now the call to error could conceivably
307       cause deadlock, as noted in server_cleanup.  Best solution would
308       presumably be to write some code so that error() automatically
309       defers the error in those cases where that is needed.  */
310    /* FIXME?  Possible that the wording should be more clear (e.g.
311          Root says "%s" but pserver protocol says "%s"
312       or something which would aid people who are writing implementations
313       of the client side of the CVS protocol.  I don't see any security
314       problem with revealing that information.  */
315    if ((Pserver_Repos != NULL) && (CVSroot_directory != NULL))
316	if (strcmp (Pserver_Repos, CVSroot_directory) != 0)
317	    error (1, 0, "repository mismatch: \"%s\" vs \"%s\"",
318		   Pserver_Repos, CVSroot_directory);
319}
320
321#endif /* AUTH_SERVER_SUPPORT */
322
323
324int
325parse_cvsroot (CVSroot)
326    char *CVSroot;
327{
328    static int cvsroot_parsed = 0;
329    char *cvsroot_copy, *p;
330
331    /* Don't go through the trouble twice. */
332    if (cvsroot_parsed)
333    {
334	error (0, 0, "WARNING (parse_cvsroot): someone called me twice!\n");
335	return 0;
336    }
337
338    CVSroot_original = xstrdup (CVSroot);
339    cvsroot_copy = xstrdup (CVSroot);
340
341    if ((*cvsroot_copy == ':'))
342    {
343	char *method = ++cvsroot_copy;
344
345	/* Access method specified, as in
346	 * "cvs -d :pserver:user@host:/path",
347	 * "cvs -d :local:e:\path", or
348	 * "cvs -d :kserver:user@host:/path".
349	 * We need to get past that part of CVSroot before parsing the
350	 * rest of it.
351	 */
352
353	if (! (p = strchr (method, ':')))
354	{
355	    error (0, 0, "bad CVSroot: %s", CVSroot);
356	    return 1;
357	}
358	*p = '\0';
359	cvsroot_copy = ++p;
360
361	/* Now we have an access method -- see if it's valid. */
362
363	if (strcmp (method, "local") == 0)
364	    CVSroot_method = local_method;
365	else if (strcmp (method, "pserver") == 0)
366	    CVSroot_method = pserver_method;
367	else if (strcmp (method, "kserver") == 0)
368	    CVSroot_method = kserver_method;
369	else if (strcmp (method, "server") == 0)
370	    CVSroot_method = server_method;
371	else if (strcmp (method, "ext") == 0)
372	    CVSroot_method = ext_method;
373	else
374	{
375	    error (0, 0, "unknown method in CVSroot: %s", CVSroot);
376	    return 1;
377	}
378    }
379    else
380    {
381	/* If the method isn't specified, assume
382	   SERVER_METHOD/EXT_METHOD if the string contains a colon or
383	   LOCAL_METHOD otherwise.  */
384
385	CVSroot_method = ((strchr (cvsroot_copy, ':'))
386#ifdef RSH_NOT_TRANSPARENT
387			  ? server_method
388#else
389			  ? ext_method
390#endif
391			  : local_method);
392    }
393
394    client_active = (CVSroot_method != local_method);
395
396    /* Check for username/hostname if we're not LOCAL_METHOD. */
397
398    CVSroot_username = NULL;
399    CVSroot_hostname = NULL;
400
401    if (CVSroot_method != local_method)
402    {
403	/* Check to see if there is a username in the string. */
404
405	if ((p = strchr (cvsroot_copy, '@')))
406	{
407	    CVSroot_username = cvsroot_copy;
408	    *p = '\0';
409	    cvsroot_copy = ++p;
410	    if (*CVSroot_username == '\0')
411		CVSroot_username = NULL;
412	}
413
414	if ((p = strchr (cvsroot_copy, ':')))
415	{
416	    CVSroot_hostname = cvsroot_copy;
417	    *p = '\0';
418	    cvsroot_copy = ++p;
419
420	    if (*CVSroot_hostname == '\0')
421		CVSroot_hostname = NULL;
422	}
423    }
424
425    CVSroot_directory = cvsroot_copy;
426#ifdef AUTH_SERVER_SUPPORT
427    check_root_consistent ();
428#endif /* AUTH_SERVER_SUPPORT */
429
430#if ! defined (CLIENT_SUPPORT) && ! defined (DEBUG)
431    if (CVSroot_method != local_method)
432    {
433	error (0, 0, "Your CVSROOT is set for a remote access method");
434	error (0, 0, "but your CVS executable doesn't support it");
435	error (0, 0, "(%s)", CVSroot);
436	return 1;
437    }
438#endif
439
440    /* Do various sanity checks. */
441
442    if (CVSroot_username && ! CVSroot_hostname)
443    {
444	error (0, 0, "missing hostname in CVSROOT: %s", CVSroot);
445	return 1;
446    }
447
448    switch (CVSroot_method)
449    {
450    case local_method:
451	if (CVSroot_username || CVSroot_hostname)
452	{
453	    error (0, 0, "can't specify hostname and username in CVSROOT");
454	    error (0, 0, "when using local access method");
455	    error (0, 0, "(%s)", CVSroot);
456	    return 1;
457	}
458	/* cvs.texinfo has always told people that CVSROOT must be an
459	   absolute pathname.  Furthermore, attempts to use a relative
460	   pathname produced various errors (I couldn't get it to work),
461	   so there would seem to be little risk in making this a fatal
462	   error.  */
463	if (!isabsolute (CVSroot_directory))
464	    error (1, 0, "CVSROOT %s must be an absolute pathname",
465		   CVSroot_directory);
466	break;
467    case kserver_method:
468#ifndef HAVE_KERBEROS
469	error (0, 0, "Your CVSROOT is set for a kerberos access method");
470	error (0, 0, "but your CVS executable doesn't support it");
471	error (0, 0, "(%s)", CVSroot);
472	return 1;
473#endif
474    case server_method:
475    case ext_method:
476    case pserver_method:
477	if (! CVSroot_hostname)
478	{
479	    error (0, 0, "didn't specify hostname in CVSROOT: %s", CVSroot);
480	    return 1;
481	}
482	break;
483    }
484
485    if (*CVSroot_directory == '\0')
486    {
487	error (0, 0, "missing directory in CVSROOT: %s", CVSroot);
488	return 1;
489    }
490
491    /* Hooray!  We finally parsed it! */
492    return 0;
493}
494
495
496/* Set up the global CVSroot* variables as if we're using the local
497   repository DIR. */
498
499void
500set_local_cvsroot (dir)
501    char *dir;
502{
503    CVSroot_original = xstrdup (dir);
504    CVSroot_method = local_method;
505    CVSroot_directory = CVSroot_original;
506#ifdef AUTH_SERVER_SUPPORT
507    check_root_consistent ();
508#endif /* AUTH_SERVER_SUPPORT */
509    CVSroot_username = NULL;
510    CVSroot_hostname = NULL;
511    client_active = 0;
512}
513
514
515#ifdef DEBUG
516/* This is for testing the parsing function. */
517
518#include <stdio.h>
519
520char *CVSroot;
521char *program_name = "testing";
522char *command_name = "parse_cvsroot";		/* XXX is this used??? */
523
524void
525main (argc, argv)
526    int argc;
527    char *argv[];
528{
529    program_name = argv[0];
530
531    if (argc != 2)
532    {
533	fprintf (stderr, "Usage: %s <CVSROOT>\n", program_name);
534	exit (2);
535    }
536
537    if (parse_cvsroot (argv[1]))
538    {
539	fprintf (stderr, "%s: Parsing failed.", program_name);
540	exit (1);
541    }
542    printf ("CVSroot: %s\n", argv[1]);
543    printf ("CVSroot_method: %s\n", method_names[CVSroot_method]);
544    printf ("CVSroot_username: %s\n",
545	    CVSroot_username ? CVSroot_username : "NULL");
546    printf ("CVSroot_hostname: %s\n",
547	    CVSroot_hostname ? CVSroot_hostname : "NULL");
548    printf ("CVSroot_directory: %s\n", CVSroot_directory);
549
550   exit (0);
551   /* NOTREACHED */
552}
553#endif
554