PageRenderTime 45ms CodeModel.GetById 14ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 0ms

/contrib/cvs/src/login.c

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