xref: /dragonfly/contrib/cvs-1.12/src/login.c (revision 86d7f5d305c6adaa56ff4582ece9859d73106103)
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 *
construct_cvspass_filename(void)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 *
password_entry_parseline(const char * cvsroot_canonical,const unsigned char warn,const int linenumber,char * linebuf)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 *
password_entry_operation(password_entry_operation_t operation,cvsroot_t * root,char * newpassword)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
login(int argc,char ** argv)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 *
get_cvs_password(void)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
logout(int argc,char ** argv)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