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