xref: /openbsd-src/gnu/usr.bin/cvs/src/root.c (revision 62a742911104f98b9185b2c6b6007d9b1c36396c)
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 source distribution.
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 
20 char *method_names[] = {
21     "local", "server (rsh)", "pserver", "kserver", "gserver", "ext", "fork"
22 };
23 
24 #ifndef DEBUG
25 
26 char *
27 Name_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  * Write the CVS/Root file so that the environment variable CVSROOT
137  * and/or the -d option to cvs will be validated or not necessary for
138  * future work.
139  */
140 void
141 Create_Root (dir, rootdir)
142     char *dir;
143     char *rootdir;
144 {
145     FILE *fout;
146     char *tmp;
147 
148     if (noexec)
149 	return;
150 
151     /* record the current cvs root */
152 
153     if (rootdir != NULL)
154     {
155         if (dir != NULL)
156 	{
157 	    tmp = xmalloc (strlen (dir) + sizeof (CVSADM_ROOT) + 10);
158 	    (void) sprintf (tmp, "%s/%s", dir, CVSADM_ROOT);
159 	}
160         else
161 	    tmp = xstrdup (CVSADM_ROOT);
162 
163         fout = open_file (tmp, "w+");
164         if (fprintf (fout, "%s\n", rootdir) < 0)
165 	    error (1, errno, "write to %s failed", tmp);
166         if (fclose (fout) == EOF)
167 	    error (1, errno, "cannot close %s", tmp);
168 	free (tmp);
169     }
170 }
171 
172 #endif /* ! DEBUG */
173 
174 
175 /* The root_allow_* stuff maintains a list of legal CVSROOT
176    directories.  Then we can check against them when a remote user
177    hands us a CVSROOT directory.  */
178 
179 static unsigned int root_allow_count;
180 static char **root_allow_vector;
181 static unsigned int root_allow_size;
182 
183 void
184 root_allow_add (arg)
185     char *arg;
186 {
187     char *p;
188 
189     if (root_allow_size <= root_allow_count)
190     {
191 	if (root_allow_size == 0)
192 	{
193 	    root_allow_size = 1;
194 	    root_allow_vector =
195 		(char **) malloc (root_allow_size * sizeof (char *));
196 	}
197 	else
198 	{
199 	    root_allow_size *= 2;
200 	    root_allow_vector =
201 		(char **) realloc (root_allow_vector,
202 				   root_allow_size * sizeof (char *));
203 	}
204 
205 	if (root_allow_vector == NULL)
206 	{
207 	no_memory:
208 	    /* Strictly speaking, we're not supposed to output anything
209 	       now.  But we're about to exit(), give it a try.  */
210 	    printf ("E Fatal server error, aborting.\n\
211 error ENOMEM Virtual memory exhausted.\n");
212 
213 	    /* I'm doing this manually rather than via error_exit ()
214 	       because I'm not sure whether we want to call server_cleanup.
215 	       Needs more investigation....  */
216 
217 #ifdef SYSTEM_CLEANUP
218 	    /* Hook for OS-specific behavior, for example socket
219 	       subsystems on NT and OS2 or dealing with windows
220 	       and arguments on Mac.  */
221 	    SYSTEM_CLEANUP ();
222 #endif
223 
224 	    exit (EXIT_FAILURE);
225 	}
226     }
227     p = malloc (strlen (arg) + 1);
228     if (p == NULL)
229 	goto no_memory;
230     strcpy (p, arg);
231     root_allow_vector[root_allow_count++] = p;
232 }
233 
234 void
235 root_allow_free ()
236 {
237     if (root_allow_vector != NULL)
238 	free (root_allow_vector);
239     root_allow_count = 0;
240     root_allow_size = 0;
241 }
242 
243 int
244 root_allow_ok (arg)
245     char *arg;
246 {
247     unsigned int i;
248 
249     if (root_allow_count == 0)
250     {
251 	/* Probably someone upgraded from CVS before 1.9.10 to 1.9.10
252 	   or later without reading the documentation about
253 	   --allow-root.  Printing an error here doesn't disclose any
254 	   particularly useful information to an attacker because a
255 	   CVS server configured in this way won't let *anyone* in.  */
256 
257 	/* Note that we are called from a context where we can spit
258 	   back "error" rather than waiting for the next request which
259 	   expects responses.  */
260 	printf ("\
261 error 0 Server configuration missing --allow-root in inetd.conf\n");
262 	error_exit ();
263     }
264 
265     for (i = 0; i < root_allow_count; ++i)
266 	if (strcmp (root_allow_vector[i], arg) == 0)
267 	    return 1;
268     return 0;
269 }
270 
271 /* This global variable holds the global -d option.  It is NULL if -d
272    was not used, which means that we must get the CVSroot information
273    from the CVSROOT environment variable or from a CVS/Root file.  */
274 
275 char *CVSroot_cmdline;
276 
277 /* Parse a CVSROOT variable into its constituent parts -- method,
278  * username, hostname, directory.  The prototypical CVSROOT variable
279  * looks like:
280  *
281  * :method:user@host:path
282  *
283  * Some methods may omit fields; local, for example, doesn't need user
284  * and host.
285  *
286  * Returns zero on success, non-zero on failure. */
287 
288 char *CVSroot_original = NULL;	/* the CVSroot that was passed in */
289 int client_active;		/* nonzero if we are doing remote access */
290 CVSmethod CVSroot_method;	/* one of the enum values defined in cvs.h */
291 char *CVSroot_username;		/* the username or NULL if method == local */
292 char *CVSroot_hostname;		/* the hostname or NULL if method == local */
293 char *CVSroot_directory;	/* the directory name */
294 
295 int
296 parse_cvsroot (CVSroot)
297     char *CVSroot;
298 {
299     static int cvsroot_parsed = 0;
300     char *cvsroot_copy, *p;
301     int check_hostname;
302 
303     /* Don't go through the trouble twice. */
304     if (cvsroot_parsed)
305     {
306 	error (0, 0, "WARNING (parse_cvsroot): someone called me twice!\n");
307 	return 0;
308     }
309 
310     CVSroot_original = xstrdup (CVSroot);
311     cvsroot_copy = xstrdup (CVSroot);
312 
313     if ((*cvsroot_copy == ':'))
314     {
315 	char *method = ++cvsroot_copy;
316 
317 	/* Access method specified, as in
318 	 * "cvs -d :pserver:user@host:/path",
319 	 * "cvs -d :local:e:\path",
320 	 * "cvs -d :kserver:user@host:/path", or
321 	 * "cvs -d :fork:/path".
322 	 * We need to get past that part of CVSroot before parsing the
323 	 * rest of it.
324 	 */
325 
326 	if (! (p = strchr (method, ':')))
327 	{
328 	    error (0, 0, "bad CVSroot: %s", CVSroot);
329 	    return 1;
330 	}
331 	*p = '\0';
332 	cvsroot_copy = ++p;
333 
334 	/* Now we have an access method -- see if it's valid. */
335 
336 	if (strcmp (method, "local") == 0)
337 	    CVSroot_method = local_method;
338 	else if (strcmp (method, "pserver") == 0)
339 	    CVSroot_method = pserver_method;
340 	else if (strcmp (method, "kserver") == 0)
341 	    CVSroot_method = kserver_method;
342 	else if (strcmp (method, "gserver") == 0)
343 	    CVSroot_method = gserver_method;
344 	else if (strcmp (method, "server") == 0)
345 	    CVSroot_method = server_method;
346 	else if (strcmp (method, "ext") == 0)
347 	    CVSroot_method = ext_method;
348 	else if (strcmp (method, "fork") == 0)
349 	    CVSroot_method = fork_method;
350 	else
351 	{
352 	    error (0, 0, "unknown method in CVSroot: %s", CVSroot);
353 	    return 1;
354 	}
355     }
356     else
357     {
358 	/* If the method isn't specified, assume
359 	   SERVER_METHOD/EXT_METHOD if the string contains a colon or
360 	   LOCAL_METHOD otherwise.  */
361 
362 	CVSroot_method = ((strchr (cvsroot_copy, ':'))
363 #ifdef RSH_NOT_TRANSPARENT
364 			  ? server_method
365 #else
366 			  ? ext_method
367 #endif
368 			  : local_method);
369     }
370 
371     client_active = (CVSroot_method != local_method);
372 
373     /* Check for username/hostname if we're not LOCAL_METHOD. */
374 
375     CVSroot_username = NULL;
376     CVSroot_hostname = NULL;
377 
378     if ((CVSroot_method != local_method)
379 	&& (CVSroot_method != fork_method))
380     {
381 	/* Check to see if there is a username in the string. */
382 
383 	if ((p = strchr (cvsroot_copy, '@')))
384 	{
385 	    CVSroot_username = cvsroot_copy;
386 	    *p = '\0';
387 	    cvsroot_copy = ++p;
388 	    if (*CVSroot_username == '\0')
389 		CVSroot_username = NULL;
390 	}
391 
392 	if ((p = strchr (cvsroot_copy, ':')))
393 	{
394 	    CVSroot_hostname = cvsroot_copy;
395 	    *p = '\0';
396 	    cvsroot_copy = ++p;
397 
398 	    if (*CVSroot_hostname == '\0')
399 		CVSroot_hostname = NULL;
400 	}
401     }
402 
403     CVSroot_directory = cvsroot_copy;
404 
405 #if ! defined (CLIENT_SUPPORT) && ! defined (DEBUG)
406     if (CVSroot_method != local_method)
407     {
408 	error (0, 0, "Your CVSROOT is set for a remote access method");
409 	error (0, 0, "but your CVS executable doesn't support it");
410 	error (0, 0, "(%s)", CVSroot);
411 	return 1;
412     }
413 #endif
414 
415     /* Do various sanity checks. */
416 
417     if (CVSroot_username && ! CVSroot_hostname)
418     {
419 	error (0, 0, "missing hostname in CVSROOT: %s", CVSroot);
420 	return 1;
421     }
422 
423     check_hostname = 0;
424     switch (CVSroot_method)
425     {
426     case local_method:
427     case fork_method:
428 	if (CVSroot_username || CVSroot_hostname)
429 	{
430 	    error (0, 0, "can't specify hostname and username in CVSROOT");
431 	    error (0, 0, "when using %s access method",
432 		   CVSroot_method == local_method ? "local" : "fork");
433 	    error (0, 0, "(%s)", CVSroot);
434 	    return 1;
435 	}
436 	/* cvs.texinfo has always told people that CVSROOT must be an
437 	   absolute pathname.  Furthermore, attempts to use a relative
438 	   pathname produced various errors (I couldn't get it to work),
439 	   so there would seem to be little risk in making this a fatal
440 	   error.  */
441 	if (!isabsolute (CVSroot_directory))
442 	    error (1, 0, "CVSROOT %s must be an absolute pathname",
443 		   CVSroot_directory);
444 	break;
445     case kserver_method:
446 #ifndef HAVE_KERBEROS
447 	error (0, 0, "Your CVSROOT is set for a kerberos access method");
448 	error (0, 0, "but your CVS executable doesn't support it");
449 	error (0, 0, "(%s)", CVSroot);
450 	return 1;
451 #else
452 	check_hostname = 1;
453 	break;
454 #endif
455     case gserver_method:
456 #ifndef HAVE_GSSAPI
457 	error (0, 0, "Your CVSROOT is set for a GSSAPI access method");
458 	error (0, 0, "but your CVS executable doesn't support it");
459 	error (0, 0, "(%s)", CVSroot);
460 	return 1;
461 #else
462 	check_hostname = 1;
463 	break;
464 #endif
465     case server_method:
466     case ext_method:
467     case pserver_method:
468 	check_hostname = 1;
469 	break;
470     }
471 
472     if (check_hostname)
473     {
474 	if (! CVSroot_hostname)
475 	{
476 	    error (0, 0, "didn't specify hostname in CVSROOT: %s", CVSroot);
477 	    return 1;
478 	}
479     }
480 
481     if (*CVSroot_directory == '\0')
482     {
483 	error (0, 0, "missing directory in CVSROOT: %s", CVSroot);
484 	return 1;
485     }
486 
487     /* Hooray!  We finally parsed it! */
488     return 0;
489 }
490 
491 
492 /* Set up the global CVSroot* variables as if we're using the local
493    repository DIR.  DIR must point to storage which will last for the
494    rest of the CVS invocation (for example, the caller might malloc it
495    and never free it, or free it just before exiting CVS).  */
496 
497 void
498 set_local_cvsroot (dir)
499     char *dir;
500 {
501     CVSroot_original = dir;
502     CVSroot_method = local_method;
503     CVSroot_directory = CVSroot_original;
504     CVSroot_username = NULL;
505     CVSroot_hostname = NULL;
506     client_active = 0;
507 }
508 
509 
510 #ifdef DEBUG
511 /* This is for testing the parsing function.  Use
512 
513      gcc -I. -I.. -I../lib -DDEBUG root.c -o root
514 
515    to compile.  */
516 
517 #include <stdio.h>
518 
519 char *CVSroot;
520 char *program_name = "testing";
521 char *command_name = "parse_cvsroot";		/* XXX is this used??? */
522 
523 /* Toy versions of various functions when debugging under unix.  Yes,
524    these make various bad assumptions, but they're pretty easy to
525    debug when something goes wrong.  */
526 
527 void
528 error_exit PROTO ((void))
529 {
530     exit (1);
531 }
532 
533 char *
534 xstrdup (str)
535      const char *str;
536 {
537     return strdup (str);
538 }
539 
540 int
541 isabsolute (dir)
542     const char *dir;
543 {
544     return (dir && (*dir == '/'));
545 }
546 
547 void
548 main (argc, argv)
549     int argc;
550     char *argv[];
551 {
552     program_name = argv[0];
553 
554     if (argc != 2)
555     {
556 	fprintf (stderr, "Usage: %s <CVSROOT>\n", program_name);
557 	exit (2);
558     }
559 
560     if (parse_cvsroot (argv[1]))
561     {
562 	fprintf (stderr, "%s: Parsing failed.\n", program_name);
563 	exit (1);
564     }
565     printf ("CVSroot: %s\n", argv[1]);
566     printf ("CVSroot_method: %s\n", method_names[CVSroot_method]);
567     printf ("CVSroot_username: %s\n",
568 	    CVSroot_username ? CVSroot_username : "NULL");
569     printf ("CVSroot_hostname: %s\n",
570 	    CVSroot_hostname ? CVSroot_hostname : "NULL");
571     printf ("CVSroot_directory: %s\n", CVSroot_directory);
572 
573    exit (0);
574    /* NOTREACHED */
575 }
576 #endif
577