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