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