xref: /openbsd-src/gnu/usr.bin/cvs/src/login.c (revision f9bbbf4534171e957bb7652e706dd78782fbffa2)
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 *
construct_cvspass_filename()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 *
password_entry_parseline(cvsroot_canonical,warn,linenumber,linebuf)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 	    q = linebuf + 1;
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 *
password_entry_operation(operation,root,newpassword)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     cvsroot_canonical = normalize_cvsroot (root);
317 
318     /* Yes, the method below reads the user's password file twice when we have
319      * to delete an entry.  It's inefficient, but we're not talking about a gig of
320      * data here.
321      */
322 
323     passfile = construct_cvspass_filename ();
324     fp = CVS_FOPEN (passfile, "r");
325     if (fp == NULL)
326     {
327 	if (operation != password_entry_add )
328 	    error (0, errno, "failed to open %s for reading", passfile);
329 	goto process;
330     }
331 
332     /* Check each line to see if we have this entry already. */
333     line = 0;
334     while ((line_length = get_line (&linebuf, &linebuf_len, fp)) >= 0)
335     {
336 	line++;
337 	password = password_entry_parseline(cvsroot_canonical, 1, line, linebuf);
338 	if (password != NULL)
339 	    /* this is it!  break out and deal with linebuf */
340 	    break;
341     }
342     if (line_length < 0 && !feof (fp))
343     {
344 	error (0, errno, "cannot read %s", passfile);
345 	goto error_exit;
346     }
347     if (fclose (fp) < 0)
348 	/* not fatal, unless it cascades */
349 	error (0, errno, "cannot close %s", passfile);
350     fp = NULL;
351 
352     /* Utter, total, raving paranoia, I know. */
353     chmod (passfile, 0600);
354 
355     /* a copy to return or keep around so we can reuse linebuf */
356     if (password != NULL)
357     {
358 	/* chomp the EOL */
359 	p = strchr (password, '\n');
360 	if (p != NULL)
361 	    *p = '\0';
362 	password = xstrdup (password);
363     }
364 
365 process:
366 
367     /* might as well return now */
368     if (operation == password_entry_lookup)
369 	goto out;
370 
371     /* same here */
372     if (operation == password_entry_delete && password == NULL)
373     {
374 	error (0, 0, "Entry not found.");
375 	goto out;
376     }
377 
378     /* okay, file errors can simply be fatal from now on since we don't do
379      * anything else if we're in lookup mode
380      */
381 
382     /* copy the file with the entry deleted unless we're in add
383      * mode and the line we found contains the same password we're supposed to
384      * add
385      */
386     if (!noexec && password != NULL && (operation == password_entry_delete
387 	    || (operation == password_entry_add && strcmp (password, newpassword))))
388     {
389 	long found_at = line;
390 	char *tmp_name;
391 	FILE *tmp_fp;
392 
393 	/* open the original file again */
394 	fp = CVS_FOPEN (passfile, "r");
395 	if (fp == NULL)
396 	    error (1, errno, "failed to open %s for reading", passfile);
397 
398 	/* create and open a temp file */
399 	if ((tmp_fp = cvs_temp_file (&tmp_name)) == NULL)
400 	    error (1, errno, "unable to open temp file %s", tmp_name);
401 
402 	line = 0;
403 	while ((line_length = get_line (&linebuf, &linebuf_len, fp)) >= 0)
404 	{
405 	    line++;
406 	    if (line < found_at
407 		|| (line != found_at
408 		    && !password_entry_parseline(cvsroot_canonical, 0, line, linebuf)))
409 	    {
410 		if (fprintf (tmp_fp, "%s", linebuf) == EOF)
411 		{
412 		    /* try and clean up anyhow */
413 		    error (0, errno, "fatal error: cannot write %s", tmp_name);
414 		    if (fclose (tmp_fp) == EOF)
415 			error (0, errno, "cannot close %s", tmp_name);
416 		    /* call CVS_UNLINK instead of unlink_file since the file
417 		     * got created in noexec mode
418 		     */
419 		    if (CVS_UNLINK (tmp_name) < 0)
420 			error (0, errno, "cannot remove %s", tmp_name);
421 		    /* but quit so we don't remove all the entries from a
422 		     * user's password file accidentally
423 		     */
424 		    error (1, 0, "exiting");
425 		}
426 	    }
427 	}
428 	if (line_length < 0 && !feof (fp))
429 	{
430 	    error (0, errno, "cannot read %s", passfile);
431 	    goto error_exit;
432 	}
433 	if (fclose (fp) < 0)
434 	    /* not fatal, unless it cascades */
435 	    error (0, errno, "cannot close %s", passfile);
436 	if (fclose (tmp_fp) < 0)
437 	    /* not fatal, unless it cascades */
438 	    /* FIXME - does copy_file return correct results if the file wasn't
439 	     * closed? should this be fatal?
440 	     */
441 	    error (0, errno, "cannot close %s", tmp_name);
442 
443 	/* FIXME: rename_file would make more sense (e.g. almost
444 	 * always faster).
445 	 *
446 	 * I don't think so, unless we change the way rename_file works to
447 	 * attempt a cp/rm sequence when rename fails since rename doesn't
448 	 * work across file systems and it isn't uncommon to have /tmp
449 	 * on its own partition.
450 	 *
451 	 * For that matter, it's probably not uncommon to have a home
452 	 * directory on an NFS mount.
453 	 */
454 	copy_file (tmp_name, passfile);
455 	if (CVS_UNLINK (tmp_name) < 0)
456 	    error (0, errno, "cannot remove %s", tmp_name);
457 	free (tmp_name);
458     }
459 
460     /* in add mode, if we didn't find an entry or found an entry with a
461      * different password, append the new line
462      */
463     if (!noexec && operation == password_entry_add
464 	    && (password == NULL || strcmp (password, newpassword)))
465     {
466 	if ((fp = CVS_FOPEN (passfile, "a")) == NULL)
467 	    error (1, errno, "could not open %s for writing", passfile);
468 
469 	if (fprintf (fp, "/1 %s %s\n", cvsroot_canonical, newpassword) == EOF)
470 	    error (1, errno, "cannot write %s", passfile);
471 	if (fclose (fp) < 0)
472 	    error (0, errno, "cannot close %s", passfile);
473     }
474 
475     /* Utter, total, raving paranoia, I know. */
476     chmod (passfile, 0600);
477 
478     if (password)
479     {
480 	free (password);
481 	password = NULL;
482     }
483     if (linebuf)
484 	free (linebuf);
485 
486 out:
487     free (cvsroot_canonical);
488     free (passfile);
489     return password;
490 
491 error_exit:
492     /* just exit when we're not in lookup mode */
493     if (operation != password_entry_lookup)
494 	error (1, 0, "fatal error: exiting");
495     /* clean up and exit in lookup mode so we can try a login with a NULL
496      * password anyhow in case that's what we would have found
497      */
498     save_errno = errno;
499     if (fp != NULL)
500     {
501 	/* Utter, total, raving paranoia, I know. */
502 	chmod (passfile, 0600);
503 	if(fclose (fp) < 0)
504 	    error (0, errno, "cannot close %s", passfile);
505     }
506     if (linebuf)
507 	free (linebuf);
508     if (cvsroot_canonical)
509 	free (cvsroot_canonical);
510     free (passfile);
511     errno = save_errno;
512     return NULL;
513 }
514 
515 
516 
517 /* Prompt for a password, and store it in the file "CVS/.cvspass".
518  */
519 
520 static const char *const login_usage[] =
521 {
522     "Usage: %s %s\n",
523     "(Specify the --help global option for a list of other help options)\n",
524     NULL
525 };
526 
527 int
login(argc,argv)528 login (argc, argv)
529     int argc;
530     char **argv;
531 {
532     char *typed_password;
533     char *cvsroot_canonical;
534 
535     if (argc < 0)
536 	usage (login_usage);
537 
538     if (current_parsed_root->method != pserver_method)
539     {
540 	error (0, 0, "can only use `login' command with the 'pserver' method");
541 	error (1, 0, "CVSROOT: %s", current_parsed_root->original);
542     }
543 
544     cvsroot_canonical = normalize_cvsroot(current_parsed_root);
545     printf ("Logging in to %s\n", cvsroot_canonical);
546     fflush (stdout);
547 
548     if (current_parsed_root->password)
549     {
550 	typed_password = scramble (current_parsed_root->password);
551     }
552     else
553     {
554 	char *tmp;
555 	tmp = GETPASS ("CVS password: ");
556 	typed_password = scramble (tmp);
557 	memset (tmp, 0, strlen (tmp));
558     }
559 
560     /* Force get_cvs_password() to use this one (when the client
561      * confirms the new password with the server), instead of
562      * consulting the file.  We make a new copy because cvs_password
563      * will get zeroed by connect_to_server().  */
564     cvs_password = xstrdup (typed_password);
565 
566     connect_to_pserver (NULL, NULL, 1, 0);
567 
568     password_entry_operation (password_entry_add, current_parsed_root, typed_password);
569 
570     memset (typed_password, 0, strlen (typed_password));
571     free (typed_password);
572 
573     free (cvs_password);
574     free (cvsroot_canonical);
575     cvs_password = NULL;
576 
577     return 0;
578 }
579 
580 /* Returns the _scrambled_ password.  The server must descramble
581    before hashing and comparing.  If password file not found, or
582    password not found in the file, just return NULL. */
583 char *
get_cvs_password()584 get_cvs_password ()
585 {
586     if (current_parsed_root->password)
587 	return (scramble(current_parsed_root->password));
588 
589     /* If someone (i.e., login()) is calling connect_to_pserver() out of
590        context, then assume they have supplied the correct, scrambled
591        password. */
592     if (cvs_password)
593 	return cvs_password;
594 
595     if (getenv ("CVS_PASSWORD") != NULL)
596     {
597 	/* In previous versions of CVS one could specify a password in
598 	 * CVS_PASSWORD.  This is a bad idea, because in BSD variants
599 	 * of unix anyone can see the environment variable with 'ps'.
600 	 * But for users who were using that feature we want to at
601 	 * least let them know what is going on.  After printing this
602 	 * warning, we should fall through to the regular error where
603 	 * we tell them to run "cvs login" (unless they already ran
604 	 * it, of course).
605 	 */
606 	 error (0, 0, "CVS_PASSWORD is no longer supported; ignored");
607     }
608 
609     if (current_parsed_root->method != pserver_method)
610     {
611 	error (0, 0, "can only call get_cvs_password with pserver method");
612 	error (1, 0, "CVSROOT: %s", current_parsed_root->original);
613     }
614 
615     return password_entry_operation (password_entry_lookup, current_parsed_root, NULL);
616 }
617 
618 static const char *const logout_usage[] =
619 {
620     "Usage: %s %s\n",
621     "(Specify the --help global option for a list of other help options)\n",
622     NULL
623 };
624 
625 /* Remove any entry for the CVSRoot repository found in .cvspass. */
626 int
logout(argc,argv)627 logout (argc, argv)
628     int argc;
629     char **argv;
630 {
631     char *cvsroot_canonical;
632 
633     if (argc < 0)
634 	usage (logout_usage);
635 
636     if (current_parsed_root->method != pserver_method)
637     {
638 	error (0, 0, "can only use pserver method with `logout' command");
639 	error (1, 0, "CVSROOT: %s", current_parsed_root->original);
640     }
641 
642     /* Hmm.  Do we want a variant of this command which deletes _all_
643        the entries from the current .cvspass?  Might be easier to
644        remember than "rm ~/.cvspass" but then again if people are
645        mucking with HOME (common in Win95 as the system doesn't set
646        it), then this variant of "cvs logout" might give a false sense
647        of security, in that it wouldn't delete entries from any
648        .cvspass files but the current one.  */
649 
650     if (!quiet)
651     {
652 	cvsroot_canonical = normalize_cvsroot(current_parsed_root);
653 	printf ("Logging out of %s\n", cvsroot_canonical);
654 	fflush (stdout);
655 	free (cvsroot_canonical);
656     }
657 
658     password_entry_operation (password_entry_delete, current_parsed_root, NULL);
659 
660     return 0;
661 }
662 
663 #endif /* AUTH_CLIENT_SUPPORT from beginning of file. */
664