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