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