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