xref: /openbsd/gnu/usr.bin/cvs/src/login.c (revision d485f761)
1 /*
2  * Copyright (c) 1995, Cyclic Software, Bloomington, IN, USA
3  *
4  * You may distribute under the terms of the GNU General Public License as
5  * specified in the README file that comes with CVS.
6  *
7  * Allow user to log in for an authenticating server.
8  */
9 
10 #include "cvs.h"
11 #include "getline.h"
12 
13 #ifdef AUTH_CLIENT_SUPPORT   /* This covers the rest of the file. */
14 
15 #ifdef HAVE_GETPASSPHRASE
16 #define GETPASS getpassphrase
17 #else
18 #define GETPASS getpass
19 #endif
20 
21 /* There seems to be very little agreement on which system header
22    getpass is declared in.  With a lot of fancy autoconfiscation,
23    we could perhaps detect this, but for now we'll just rely on
24    _CRAY, since Cray is perhaps the only system on which our own
25    declaration won't work (some Crays declare the 2#$@% thing as
26    varadic, believe it or not).  On Cray, getpass will be declared
27    in either stdlib.h or unistd.h.  */
28 #ifndef _CRAY
29 extern char *GETPASS ();
30 #endif
31 
32 #ifndef CVS_PASSWORD_FILE
33 #define CVS_PASSWORD_FILE ".cvspass"
34 #endif
35 
36 /* If non-NULL, get_cvs_password() will just return this. */
37 static char *cvs_password = NULL;
38 
39 static char *construct_cvspass_filename PROTO ((void));
40 
41 /* The return value will need to be freed. */
42 static char *
43 construct_cvspass_filename ()
44 {
45     char *homedir;
46     char *passfile;
47 
48     /* Environment should override file. */
49     if ((passfile = getenv ("CVS_PASSFILE")) != NULL)
50 	return xstrdup (passfile);
51 
52     /* Construct absolute pathname to user's password file. */
53     /* todo: does this work under OS/2 ? */
54     homedir = get_homedir ();
55     if (! homedir)
56     {
57 	/* FIXME?  This message confuses a lot of users, at least
58 	   on Win95 (which doesn't set HOMEDRIVE and HOMEPATH like
59 	   NT does).  I suppose the answer for Win95 is to store the
60 	   passwords in the registry or something (??).  And .cvsrc
61 	   and such too?  Wonder what WinCVS does (about .cvsrc, the
62 	   right thing for a GUI is to just store the password in
63 	   memory only)...  */
64 	error (1, 0, "could not find out home directory");
65 	return (char *) NULL;
66     }
67 
68     passfile =
69 	(char *) xmalloc (strlen (homedir) + strlen (CVS_PASSWORD_FILE) + 3);
70     strcpy (passfile, homedir);
71 #ifndef NO_SLASH_AFTER_HOME
72     /* NO_SLASH_AFTER_HOME is defined for VMS, where foo:[bar]/.cvspass is not
73        a legal filename but foo:[bar].cvspass is.  A more clean solution would
74        be something more along the lines of a "join a directory to a filename"
75        kind of thing....  */
76     strcat (passfile, "/");
77 #endif
78     strcat (passfile, CVS_PASSWORD_FILE);
79 
80     /* Safety first and last, Scouts. */
81     if (isfile (passfile))
82 	/* xchmod() is too polite. */
83 	chmod (passfile, 0600);
84 
85     return passfile;
86 }
87 
88 
89 
90 /*
91  * static char *
92  * password_entry_parseline (
93  *			      const char *cvsroot_canonical,
94  *			      const unsigned char warn,
95  *			      const int linenumber,
96  *			      char *linebuf
97  *			     );
98  *
99  * Internal function used by password_entry_operation.  Parse a single line
100  * from a ~/.cvsroot password file and return a pointer to the password if the
101  * line refers to the same cvsroot as cvsroot_canonical
102  *
103  * INPUTS
104  *	cvsroot_canonical	the root we are looking for
105  *	warn			Boolean: print warnings for invalid lines?
106  *	linenumber		the line number for error messages
107  *	linebuf			the current line
108  *
109  * RETURNS
110  * 	NULL			if the line doesn't match
111  * 	char *password		as a pointer into linebuf
112  *
113  * NOTES
114  *	This function temporarily alters linebuf, so it isn't thread safe when
115  *	called on the same linebuf
116  */
117 static char *
118 password_entry_parseline (cvsroot_canonical, warn, linenumber, linebuf)
119     const char *cvsroot_canonical;
120     const unsigned char warn;
121     const int linenumber;
122     char *linebuf;
123 {
124     char *password = NULL;
125     char *p;
126 
127     /* look for '^/' */
128     if (*linebuf == '/')
129     {
130 	/* Yes: slurp '^/\d+\D' and parse the rest of the line according to version number */
131 	char *q;
132 	unsigned long int entry_version;
133 
134 	if (isspace(*(linebuf + 1)))
135 	    /* special case since strtoul ignores leading white space */
136 	    entry_version = 0;
137 	else
138 	    entry_version = strtoul (linebuf + 1, &q, 10);
139 
140 	if (q == linebuf + 1)
141 	    /* no valid digits found by strtoul */
142 	    entry_version = 0;
143 	else
144 	    /* assume a delimiting seperator */
145 	    q++;
146 
147 	switch (entry_version)
148 	{
149 	    case 1:
150 		/* this means the same normalize_cvsroot we are using was
151 		 * used to create this entry.  strcmp is good enough for
152 		 * us.
153 		 */
154 		p = strchr (q, ' ');
155 		if (p == NULL)
156 		{
157 		    if (warn && !really_quiet)
158 			error (0, 0, "warning: skipping invalid entry in password file at line %d",
159 				linenumber);
160 		}
161 		else
162 		{
163 		    *p = '\0';
164 		    if (strcmp (cvsroot_canonical, q) == 0)
165 			password = p + 1;
166 		    *p = ' ';
167 		}
168 		break;
169 	    case ULONG_MAX:
170 		if (warn && !really_quiet)
171 		{
172 		    error (0, errno, "warning: unable to convert version number in password file at line %d",
173 			    linenumber);
174 		    error (0, 0, "skipping entry");
175 		}
176 		break;
177 	    case 0:
178 		if (warn && !really_quiet)
179 		    error (0, 0, "warning: skipping entry with invalid version string in password file at line %d",
180 			    linenumber);
181 		break;
182 	    default:
183 		if (warn && !really_quiet)
184 		    error (0, 0, "warning: skipping entry with unknown version (%lu) in password file at line %d",
185 			    entry_version, linenumber);
186 		break;
187 	}
188     }
189     else
190     {
191 	/* No: assume:
192 	 *
193 	 *	^cvsroot Aencoded_password$
194 	 *
195 	 * as header comment specifies and parse accordingly
196 	 */
197 	cvsroot_t *tmp_root;
198 	char *tmp_root_canonical;
199 
200 	p = strchr (linebuf, ' ');
201 	if (p == NULL)
202 	{
203 	    if (warn && !really_quiet)
204 		error (0, 0, "warning: skipping invalid entry in password file at line %d", linenumber);
205 	    return NULL;;
206 	}
207 
208 	*p = '\0';
209 	if ((tmp_root = parse_cvsroot (linebuf)) == NULL)
210 	{
211 	    if (warn && !really_quiet)
212 		error (0, 0, "warning: skipping invalid entry in password file at line %d", linenumber);
213 	    *p = ' ';
214 	    return NULL;
215 	}
216 	*p = ' ';
217 	tmp_root_canonical = normalize_cvsroot (tmp_root);
218 	if (strcmp (cvsroot_canonical, tmp_root_canonical) == 0)
219 	    password = p + 1;
220 
221 	free (tmp_root_canonical);
222 	free_cvsroot_t (tmp_root);
223     }
224 
225     return password;
226 }
227 
228 
229 
230 /*
231  * static char *
232  * password_entry_operation (
233  * 			     password_entry_operation_t operation,
234  * 			     cvsroot_t *root,
235  * 			     char *newpassword
236  * 			    );
237  *
238  * Search the password file and depending on the value of operation:
239  *
240  *	Mode				Action
241  *	password_entry_lookup		Return the password
242  *	password_entry_delete		Delete the entry from the file, if it exists
243  *	password_entry_add		Replace the line with the new one, else append it
244  *
245  * Because the user might be accessing multiple repositories, with
246  * different passwords for each one, the format of ~/.cvspass is:
247  *
248  * [user@]host:[port]/path Aencoded_password
249  * [user@]host:[port]/path Aencoded_password
250  * ...
251  *
252  * New entries are always of the form:
253  *
254  * /1 user@host:port/path Aencoded_password
255  *
256  * but the old format is supported for backwards compatibility.
257  * The entry version string wasn't strictly necessary, but it avoids the
258  * overhead of parsing some entries since we know it is already in canonical
259  * form and allows room for expansion later, say, if we want to allow spaces
260  * and/or other characters to be escaped in the string.  Also, the new entries
261  * would have been ignored by old versions of CVS anyhow since those versions
262  * didn't know how to parse a port number.
263  *
264  * The "A" before "encoded_password" is a literal capital A.  It's a
265  * version number indicating which form of scrambling we're doing on
266  * the password -- someday we might provide something more secure than
267  * the trivial encoding we do now, and when that day comes, it would
268  * be nice to remain backward-compatible.
269  *
270  * Like .netrc, the file's permissions are the only thing preventing
271  * it from being read by others.  Unlike .netrc, we will not be
272  * fascist about it, at most issuing a warning, and never refusing to
273  * work.
274  *
275  * INPUTS
276  * 	operation	operation to perform
277  * 	root		cvsroot_t to look up
278  * 	newpassword	prescrambled new password, for password_entry_add_mode
279  *
280  * RETURNS
281  * 	-1	if password_entry_lookup_mode not specified
282  * 	NULL	on failed lookup
283  * 	pointer to a copy of the password string otherwise, which the caller is
284  * 		responsible for disposing of
285  */
286 
287 typedef enum password_entry_operation_e {
288     password_entry_lookup,
289     password_entry_delete,
290     password_entry_add
291 } password_entry_operation_t;
292 
293 static char *
294 password_entry_operation (operation, root, newpassword)
295     password_entry_operation_t operation;
296     cvsroot_t *root;
297     char *newpassword;
298 {
299     char *passfile;
300     FILE *fp;
301     char *cvsroot_canonical = NULL;
302     char *password = NULL;
303     int line_length;
304     long line;
305     char *linebuf = NULL;
306     size_t linebuf_len;
307     char *p;
308     int save_errno = 0;
309 
310     if (root->method != pserver_method)
311     {
312 	error (0, 0, "internal error: can only call password_entry_operation with pserver method");
313 	error (1, 0, "CVSROOT: %s", root->original);
314     }
315 
316     /* Yes, the method below reads the user's password file twice when we have
317      * to delete an entry.  It's inefficient, but we're not talking about a gig of
318      * data here.
319      */
320 
321     passfile = construct_cvspass_filename ();
322     fp = CVS_FOPEN (passfile, "r");
323     if (fp == NULL)
324     {
325 	error (0, errno, "failed to open %s for reading", passfile);
326 	goto error_exit;
327     }
328 
329     cvsroot_canonical = normalize_cvsroot (root);
330 
331     /* Check each line to see if we have this entry already. */
332     line = 0;
333     while ((line_length = getline (&linebuf, &linebuf_len, fp)) >= 0)
334     {
335 	line++;
336 	password = password_entry_parseline(cvsroot_canonical, 1, line, linebuf);
337 	if (password != NULL)
338 	    /* this is it!  break out and deal with linebuf */
339 	    break;
340     }
341     if (line_length < 0 && !feof (fp))
342     {
343 	error (0, errno, "cannot read %s", passfile);
344 	goto error_exit;
345     }
346     if (fclose (fp) < 0)
347 	/* not fatal, unless it cascades */
348 	error (0, errno, "cannot close %s", passfile);
349     fp = NULL;
350 
351     /* Utter, total, raving paranoia, I know. */
352     chmod (passfile, 0600);
353 
354     /* a copy to return or keep around so we can reuse linebuf */
355     if (password != NULL)
356     {
357 	/* chomp the EOL */
358 	p = strchr (password, '\n');
359 	if (p != NULL)
360 	    *p = '\0';
361 	password = xstrdup (password);
362     }
363 
364     /* might as well return now */
365     if (operation == password_entry_lookup)
366 	goto out;
367 
368     /* same here */
369     if (operation == password_entry_delete && password == NULL)
370     {
371 	error (0, 0, "Entry not found.");
372 	goto out;
373     }
374 
375     /* okay, file errors can simply be fatal from now on since we don't do
376      * anything else if we're in lookup mode
377      */
378 
379     /* copy the file with the entry deleted unless we're in add
380      * mode and the line we found contains the same password we're supposed to
381      * add
382      */
383     if (!noexec && password != NULL && (operation == password_entry_delete
384 	    || (operation == password_entry_add && strcmp (password, newpassword))))
385     {
386 	long found_at = line;
387 	char *tmp_name;
388 	FILE *tmp_fp;
389 
390 	/* open the original file again */
391 	fp = CVS_FOPEN (passfile, "r");
392 	if (fp == NULL)
393 	    error (1, errno, "failed to open %s for reading", passfile);
394 
395 	/* create and open a temp file */
396 	if ((tmp_fp = cvs_temp_file (&tmp_name)) == NULL)
397 	    error (1, errno, "unable to open temp file %s", tmp_name);
398 
399 	line = 0;
400 	while ((line_length = getline (&linebuf, &linebuf_len, fp)) >= 0)
401 	{
402 	    line++;
403 	    if (line < found_at
404 		|| (line != found_at
405 		    && !password_entry_parseline(cvsroot_canonical, 0, line, linebuf)))
406 	    {
407 		if (fprintf (tmp_fp, "%s", linebuf) == EOF)
408 		{
409 		    /* try and clean up anyhow */
410 		    error (0, errno, "fatal error: cannot write %s", tmp_name);
411 		    if (fclose (tmp_fp) == EOF)
412 			error (0, errno, "cannot close %s", tmp_name);
413 		    /* call CVS_UNLINK instead of unlink_file since the file
414 		     * got created in noexec mode
415 		     */
416 		    if (CVS_UNLINK (tmp_name) < 0)
417 			error (0, errno, "cannot remove %s", tmp_name);
418 		    /* but quit so we don't remove all the entries from a
419 		     * user's password file accidentally
420 		     */
421 		    error (1, 0, "exiting");
422 		}
423 	    }
424 	}
425 	if (line_length < 0 && !feof (fp))
426 	{
427 	    error (0, errno, "cannot read %s", passfile);
428 	    goto error_exit;
429 	}
430 	if (fclose (fp) < 0)
431 	    /* not fatal, unless it cascades */
432 	    error (0, errno, "cannot close %s", passfile);
433 	if (fclose (tmp_fp) < 0)
434 	    /* not fatal, unless it cascades */
435 	    /* FIXME - does copy_file return correct results if the file wasn't
436 	     * closed? should this be fatal?
437 	     */
438 	    error (0, errno, "cannot close %s", tmp_name);
439 
440 	/* FIXME: rename_file would make more sense (e.g. almost
441 	 * always faster).
442 	 *
443 	 * I don't think so, unless we change the way rename_file works to
444 	 * attempt a cp/rm sequence when rename fails since rename doesn't
445 	 * work across file systems and it isn't uncommon to have /tmp
446 	 * on its own partition.
447 	 *
448 	 * For that matter, it's probably not uncommon to have a home
449 	 * directory on an NFS mount.
450 	 */
451 	copy_file (tmp_name, passfile);
452 	if (CVS_UNLINK (tmp_name) < 0)
453 	    error (0, errno, "cannot remove %s", tmp_name);
454 	free (tmp_name);
455     }
456 
457     /* in add mode, if we didn't find an entry or found an entry with a
458      * different password, append the new line
459      */
460     if (!noexec && operation == password_entry_add
461 	    && (password == NULL || strcmp (password, newpassword)))
462     {
463 	if ((fp = CVS_FOPEN (passfile, "a")) == NULL)
464 	    error (1, errno, "could not open %s for writing", passfile);
465 
466 	if (fprintf (fp, "/1 %s %s\n", cvsroot_canonical, newpassword) == EOF)
467 	    error (1, errno, "cannot write %s", passfile);
468 	if (fclose (fp) < 0)
469 	    error (0, errno, "cannot close %s", passfile);
470     }
471 
472     /* Utter, total, raving paranoia, I know. */
473     chmod (passfile, 0600);
474 
475     if (password)
476     {
477 	free (password);
478 	password = NULL;
479     }
480     if (linebuf)
481 	free (linebuf);
482 
483 out:
484     free (cvsroot_canonical);
485     free (passfile);
486     return password;
487 
488 error_exit:
489     /* just exit when we're not in lookup mode */
490     if (operation != password_entry_lookup)
491 	error (1, 0, "fatal error: exiting");
492     /* clean up and exit in lookup mode so we can try a login with a NULL
493      * password anyhow in case that's what we would have found
494      */
495     save_errno = errno;
496     if (fp != NULL)
497     {
498 	/* Utter, total, raving paranoia, I know. */
499 	chmod (passfile, 0600);
500 	if(fclose (fp) < 0)
501 	    error (0, errno, "cannot close %s", passfile);
502     }
503     if (linebuf)
504 	free (linebuf);
505     if (cvsroot_canonical)
506 	free (cvsroot_canonical);
507     free (passfile);
508     errno = save_errno;
509     return NULL;
510 }
511 
512 
513 
514 /* Prompt for a password, and store it in the file "CVS/.cvspass".
515  */
516 
517 static const char *const login_usage[] =
518 {
519     "Usage: %s %s\n",
520     "(Specify the --help global option for a list of other help options)\n",
521     NULL
522 };
523 
524 int
525 login (argc, argv)
526     int argc;
527     char **argv;
528 {
529     char *typed_password;
530     char *cvsroot_canonical;
531 
532     if (argc < 0)
533 	usage (login_usage);
534 
535     if (current_parsed_root->method != pserver_method)
536     {
537 	error (0, 0, "can only use `login' command with the 'pserver' method");
538 	error (1, 0, "CVSROOT: %s", current_parsed_root->original);
539     }
540 
541     cvsroot_canonical = normalize_cvsroot(current_parsed_root);
542     printf ("Logging in to %s\n", cvsroot_canonical);
543     fflush (stdout);
544 
545     if (current_parsed_root->password)
546     {
547 	typed_password = scramble (current_parsed_root->password);
548     }
549     else
550     {
551 	char *tmp;
552 	tmp = GETPASS ("CVS password: ");
553 	typed_password = scramble (tmp);
554 	memset (tmp, 0, strlen (tmp));
555     }
556 
557     /* Force get_cvs_password() to use this one (when the client
558      * confirms the new password with the server), instead of
559      * consulting the file.  We make a new copy because cvs_password
560      * will get zeroed by connect_to_server().  */
561     cvs_password = xstrdup (typed_password);
562 
563     connect_to_pserver (NULL, NULL, 1, 0);
564 
565     password_entry_operation (password_entry_add, current_parsed_root, typed_password);
566 
567     memset (typed_password, 0, strlen (typed_password));
568     free (typed_password);
569 
570     free (cvs_password);
571     free (cvsroot_canonical);
572     cvs_password = NULL;
573 
574     return 0;
575 }
576 
577 /* Returns the _scrambled_ password.  The server must descramble
578    before hashing and comparing.  If password file not found, or
579    password not found in the file, just return NULL. */
580 char *
581 get_cvs_password ()
582 {
583     if (current_parsed_root->password)
584 	return (scramble(current_parsed_root->password));
585 
586     /* If someone (i.e., login()) is calling connect_to_pserver() out of
587        context, then assume they have supplied the correct, scrambled
588        password. */
589     if (cvs_password)
590 	return cvs_password;
591 
592     if (getenv ("CVS_PASSWORD") != NULL)
593     {
594 	/* In previous versions of CVS one could specify a password in
595 	 * CVS_PASSWORD.  This is a bad idea, because in BSD variants
596 	 * of unix anyone can see the environment variable with 'ps'.
597 	 * But for users who were using that feature we want to at
598 	 * least let them know what is going on.  After printing this
599 	 * warning, we should fall through to the regular error where
600 	 * we tell them to run "cvs login" (unless they already ran
601 	 * it, of course).
602 	 */
603 	 error (0, 0, "CVS_PASSWORD is no longer supported; ignored");
604     }
605 
606     if (current_parsed_root->method != pserver_method)
607     {
608 	error (0, 0, "can only call get_cvs_password with pserver method");
609 	error (1, 0, "CVSROOT: %s", current_parsed_root->original);
610     }
611 
612     return password_entry_operation (password_entry_lookup, current_parsed_root, NULL);
613 }
614 
615 static const char *const logout_usage[] =
616 {
617     "Usage: %s %s\n",
618     "(Specify the --help global option for a list of other help options)\n",
619     NULL
620 };
621 
622 /* Remove any entry for the CVSRoot repository found in .cvspass. */
623 int
624 logout (argc, argv)
625     int argc;
626     char **argv;
627 {
628     char *cvsroot_canonical;
629 
630     if (argc < 0)
631 	usage (logout_usage);
632 
633     if (current_parsed_root->method != pserver_method)
634     {
635 	error (0, 0, "can only use pserver method with `logout' command");
636 	error (1, 0, "CVSROOT: %s", current_parsed_root->original);
637     }
638 
639     /* Hmm.  Do we want a variant of this command which deletes _all_
640        the entries from the current .cvspass?  Might be easier to
641        remember than "rm ~/.cvspass" but then again if people are
642        mucking with HOME (common in Win95 as the system doesn't set
643        it), then this variant of "cvs logout" might give a false sense
644        of security, in that it wouldn't delete entries from any
645        .cvspass files but the current one.  */
646 
647     if (!quiet)
648     {
649 	cvsroot_canonical = normalize_cvsroot(current_parsed_root);
650 	printf ("Logging out of %s\n", cvsroot_canonical);
651 	fflush (stdout);
652 	free (cvsroot_canonical);
653     }
654 
655     password_entry_operation (password_entry_delete, current_parsed_root, NULL);
656 
657     return 0;
658 }
659 
660 #endif /* AUTH_CLIENT_SUPPORT from beginning of file. */
661