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