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