summaryrefslogtreecommitdiffstats
path: root/contrib/cvs/src/login.c
blob: fc3a1783665b9fc92e431b50640fcbd0dd306f52 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
/*
 * Copyright (c) 1995, Cyclic Software, Bloomington, IN, USA
 * 
 * You may distribute under the terms of the GNU General Public License as
 * specified in the README file that comes with CVS.
 * 
 * Allow user to log in for an authenticating server.
 */

#include "cvs.h"
#include "getline.h"

#ifdef AUTH_CLIENT_SUPPORT   /* This covers the rest of the file. */

extern char *getpass ();

#ifndef CVS_PASSWORD_FILE 
#define CVS_PASSWORD_FILE ".cvspass"
#endif

/* If non-NULL, get_cvs_password() will just return this. */
static char *cvs_password = NULL;

/* The return value will need to be freed. */
char *
construct_cvspass_filename ()
{
  char *homedir;
  char *passfile;

  /* Environment should override file. */
  if ((passfile = getenv ("CVS_PASSFILE")) != NULL)
    return xstrdup (passfile);

  /* Construct absolute pathname to user's password file. */
  /* todo: does this work under OS/2 ? */
  homedir = get_homedir ();
  if (! homedir)
    {
      error (1, errno, "could not find out home directory");
      return (char *) NULL;
    }
  
  passfile =
    (char *) xmalloc (strlen (homedir) + strlen (CVS_PASSWORD_FILE) + 3);
  strcpy (passfile, homedir);
  strcat (passfile, "/");
  strcat (passfile, CVS_PASSWORD_FILE);
  
  /* Safety first and last, Scouts. */
  if (isfile (passfile))
    /* xchmod() is too polite. */
    chmod (passfile, 0600);

  return passfile;
}


/* Prompt for a password, and store it in the file "CVS/.cvspass".
 *
 * Because the user might be accessing multiple repositories, with
 * different passwords for each one, the format of ~/.cvspass is:
 *
 * user@host:/path Acleartext_password
 * user@host:/path Acleartext_password
 * ...
 *
 * Of course, the "user@" might be left off -- it's just based on the
 * value of CVSroot.
 *
 * The "A" before "cleartext_password" is a literal capital A.  It's a
 * version number indicating which form of scrambling we're doing on
 * the password -- someday we might provide something more secure than
 * the trivial encoding we do now, and when that day comes, it would
 * be nice to remain backward-compatible.
 *
 * Like .netrc, the file's permissions are the only thing preventing
 * it from being read by others.  Unlike .netrc, we will not be
 * fascist about it, at most issuing a warning, and never refusing to
 * work.
 */
int
login (argc, argv)
    int argc;
    char **argv;
{
  char *passfile;
  FILE *fp;
  char *typed_password, *found_password;
  char *linebuf = (char *) NULL;
  size_t linebuf_len;
  int root_len, already_entered = 0;

  /* Make this a "fully-qualified" CVSroot if necessary. */
  if (! strchr (CVSroot, '@'))
    {
      /* We need to prepend "user@host:". */
      char *tmp;

      printf ("Repository \"%s\" not fully-qualified.\n", CVSroot);
      printf ("Please enter \"user@host:/path\": ");
      fflush (stdout);
      getline (&linebuf, &linebuf_len, stdin);

      tmp = xmalloc (strlen (linebuf) + 1);

      /* Give it some permanent storage. */
      strcpy (tmp, linebuf);
      tmp[strlen (linebuf) - 1] = '\0';
      CVSroot = tmp;

      /* Reset. */
      free (linebuf);
      linebuf = (char *) NULL;
    }

  if (CVSroot[0] != ':')
    {
      /* Then we need to prepend ":pserver:". */
      char *tmp;

      tmp = xmalloc (strlen (":pserver:") + strlen (CVSroot) + 1);
      strcpy (tmp, ":pserver:");
      strcat (tmp, CVSroot);
      CVSroot = tmp;
    }

  /* Check to make sure it's fully-qualified before going on. 
   * Fully qualified in this context means it has both a user and a
   * host:repos portion.
   */
  {
    char *r;

    /* After confirming that CVSroot is non-NULL, we skip past the
       initial ":pserver:" to test the rest of it. */

    if (! CVSroot)
      error (1, 0, "CVSroot is NULL");
    else if (! strchr ((r = (CVSroot + strlen (":pserver:"))), '@'))
      goto not_fqrn;
    else if (! strchr (r, ':'))
      goto not_fqrn;
    
    if (0)        /* Lovely. */
      {
      not_fqrn:
        error (0, 0, "CVSroot not fully-qualified: %s", CVSroot);
        error (1, 0, "should be format user@host:/path/to/repository");
      }
  }
    
  /* CVSroot is now fully qualified and has ":pserver:" prepended.
     We'll print out most of it so user knows exactly what is being
     dealt with here. */
  {
    char *s;
    s = strchr (CVSroot, ':');
    s++;
    s = strchr (s, ':');
    s++;

    if (s == NULL)
      error (1, 0, "NULL CVSroot");

    printf ("(Logging in to %s)\n", s);
    fflush (stdout);
  }

  passfile = construct_cvspass_filename ();
  typed_password = getpass ("CVS password: ");
  typed_password = scramble (typed_password);

  /* Force get_cvs_password() to use this one (when the client
   * confirms the new password with the server), instead of consulting
   * the file.  We make a new copy because cvs_password will get
   * zeroed by connect_to_server().
   */
  cvs_password = xstrdup (typed_password);

  if (connect_to_pserver (NULL, NULL, 1) == 0)
    {
      /* The password is wrong, according to the server. */
      error (1, 0, "incorrect password");
    }

  /* IF we have a password for this "[user@]host:/path" already
   *  THEN
   *    IF it's the same as the password we read from the prompt
   *     THEN 
   *       do nothing
   *     ELSE
   *       replace the old password with the new one
   *  ELSE
   *    append new entry to the end of the file.
   */

  root_len = strlen (CVSroot);

  /* Yes, the method below reads the user's password file twice.  It's
     inefficient, but we're not talking about a gig of data here. */

  fp = fopen (passfile, "r");
  /* FIXME: should be printing a message if fp == NULL and not
     existence_error (errno).  */
  if (fp != NULL)
    {
      /* Check each line to see if we have this entry already. */
      while (getline (&linebuf, &linebuf_len, fp) >= 0)
        {
          if (strncmp (CVSroot, linebuf, root_len) == 0)
            {
              already_entered = 1;
              break;
            }
          else
            {
              free (linebuf);
              linebuf = (char *) NULL;
            }
        }
      fclose (fp);
    }
      
  if (already_entered)
    {
      /* This user/host has a password in the file already. */

      strtok (linebuf, " ");
      found_password = strtok (NULL, "\n");
      if (strcmp (found_password, typed_password))
        {
          /* typed_password and found_password don't match, so we'll
           * have to update passfile.  We replace the old password
           * with the new one by writing a tmp file whose contents are
           * exactly the same as passfile except that this one entry
           * gets typed_password instead of found_password.  Then we
           * rename the tmp file on top of passfile.
           */
          char *tmp_name;
          FILE *tmp_fp;

          tmp_name = tmpnam (NULL);
          if ((tmp_fp = fopen (tmp_name, "w")) == NULL)
            {
              error (1, errno, "unable to open temp file %s", tmp_name);
              return 1;
            }
          chmod (tmp_name, 0600);

          fp = fopen (passfile, "r");
          if (fp == NULL)
            {
              error (1, errno, "unable to open %s", passfile);
              return 1;
            }
          /* I'm not paranoid, they really ARE out to get me: */
          chmod (passfile, 0600);

          free (linebuf);
          linebuf = (char *) NULL;
          while (getline (&linebuf, &linebuf_len, fp) >= 0)
            {
              if (strncmp (CVSroot, linebuf, root_len))
                fprintf (tmp_fp, "%s", linebuf);
              else
                fprintf (tmp_fp, "%s %s\n", CVSroot, typed_password);

              free (linebuf);
              linebuf = (char *) NULL;
            }
          fclose (tmp_fp);
          fclose (fp);
          rename_file (tmp_name, passfile);
          chmod (passfile, 0600);
        }
    }
  else
    {
      if ((fp = fopen (passfile, "a")) == NULL)
        {
          error (1, errno, "could not open %s", passfile);
          free (passfile);
          return 1;
        }

      fprintf (fp, "%s %s\n", CVSroot, typed_password);
      fclose (fp);
    }

  /* Utter, total, raving paranoia, I know. */
  chmod (passfile, 0600);
  memset (typed_password, 0, strlen (typed_password));
  free (typed_password);

  free (passfile);
  free (cvs_password);
  cvs_password = NULL;
  return 0;
}

/* todo: "cvs logout" could erase an entry from the file.
 * But to what purpose?
 */

/* Returns the _scrambled_ password.  The server must descramble
   before hashing and comparing. */
char *
get_cvs_password ()
{
  int found_it = 0;
  int root_len;
  char *password;
  char *linebuf = (char *) NULL;
  size_t linebuf_len;
  FILE *fp;
  char *passfile;

  /* If someone (i.e., login()) is calling connect_to_pserver() out of
     context, then assume they have supplied the correct, scrambled
     password. */
  if (cvs_password)
    return cvs_password;

  /* Environment should override file. */
  if ((password = getenv ("CVS_PASSWORD")) != NULL)
    {
      char *p;
      p = xstrdup (password);
      /* If we got it from the environment, then it wasn't properly
         scrambled.  Since unscrambling is done on the server side, we
         need to transmit it scrambled. */
      p = scramble (p);
      return p;
    }

  /* Else get it from the file. */
  passfile = construct_cvspass_filename ();
  fp = fopen (passfile, "r");
  if (fp == NULL)
    {
      error (0, errno, "could not open %s", passfile);
      free (passfile);
      error (1, 0, "use \"cvs login\" to log in first");
    }

  root_len = strlen (CVSroot);

  /* Check each line to see if we have this entry already. */
  while (getline (&linebuf, &linebuf_len, fp) >= 0)
    {
      if (strncmp (CVSroot, linebuf, root_len) == 0)
        {
          /* This is it!  So break out and deal with linebuf. */
          found_it = 1;
          break;
        }
      else
        {
          free (linebuf);
          linebuf = (char *) NULL;
        }
    }

  if (found_it)
    {
      /* linebuf now contains the line with the password. */
      char *tmp;
      
      strtok (linebuf, " ");
      password = strtok (NULL, "\n");
      
      /* Give it permanent storage. */
      tmp = xmalloc (strlen (password) + 1);
      strcpy (tmp, password);
      tmp[strlen (password)] = '\0';
      memset (password, 0, strlen (password));
      free (linebuf);
      return tmp;
    }
  else
    {
      error (0, 0, "cannot find password");
      error (0, 0, "use \"cvs login\" to log in first");
      error (1, 0, "or set the CVS_PASSWORD environment variable");
    }
  /* NOTREACHED */
  return NULL;
}

#endif /* AUTH_CLIENT_SUPPORT from beginning of file. */

OpenPOWER on IntegriCloud