Coverage Report

Created: 2024-02-11 07:34

/src/wget/src/netrc.c
Line
Count
Source (jump to first uncovered line)
1
/* Read and parse the .netrc file to get hosts, accounts, and passwords.
2
   Copyright (C) 1996, 2007-2011, 2015, 2018-2023 Free Software
3
   Foundation, Inc.
4
5
This file is part of GNU Wget.
6
7
GNU Wget is free software; you can redistribute it and/or modify
8
it under the terms of the GNU General Public License as published by
9
the Free Software Foundation; either version 3 of the License, or
10
(at your option) any later version.
11
12
GNU Wget is distributed in the hope that it will be useful,
13
but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
GNU General Public License for more details.
16
17
You should have received a copy of the GNU General Public License
18
along with Wget.  If not, see <http://www.gnu.org/licenses/>.
19
20
Additional permission under GNU GPL version 3 section 7
21
22
If you modify this program, or any covered work, by linking or
23
combining it with the OpenSSL project's OpenSSL library (or a
24
modified version of that library), containing parts covered by the
25
terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
26
grants you additional permission to convey the resulting work.
27
Corresponding Source for a non-source form of such a combination
28
shall include the source code for the parts of OpenSSL used as well
29
as that of the covered work.  */
30
31
/* This file used to be kept in synch with the code in Fetchmail, but
32
   the latter has diverged since.  */
33
34
#include "wget.h"
35
36
#include <stdio.h>
37
#include <stdlib.h>
38
#include <string.h>
39
#include <errno.h>
40
41
#include "utils.h"
42
#include "netrc.h"
43
#include "init.h"
44
45
#ifdef WINDOWS
46
#  define NETRC_FILE_NAME "_netrc"
47
#else
48
0
#  define NETRC_FILE_NAME ".netrc"
49
#endif
50
51
typedef struct _acc_t
52
{
53
  char *host;           /* NULL if this is the default machine
54
                           entry.  */
55
  char *acc;
56
  char *passwd;         /* NULL if there is no password.  */
57
  struct _acc_t *next;
58
} acc_t;
59
60
static acc_t *parse_netrc (const char *);
61
static acc_t *parse_netrc_fp (const char *, FILE *);
62
63
static acc_t *netrc_list;
64
static int processed_netrc;
65
66
#if defined DEBUG_MALLOC || defined TESTING
67
static void free_netrc(acc_t *);
68
69
void
70
netrc_cleanup (void)
71
1.66k
{
72
1.66k
  free_netrc (netrc_list);
73
1.66k
  processed_netrc = 0;
74
1.66k
}
75
#endif
76
77
/* Return the correct user and password, given the host, user (as
78
   given in the URL), and password (as given in the URL).  May return
79
   NULL.
80
81
   If SLACK_DEFAULT is set, allow looking for a "default" account.
82
   You will typically turn it off for HTTP.  */
83
void
84
search_netrc (const char *host, const char **acc, const char **passwd,
85
              int slack_default, FILE *fp_netrc)
86
1.66k
{
87
1.66k
  acc_t *l;
88
89
1.66k
  if (!opt.netrc)
90
0
    return;
91
  /* Find ~/.netrc.  */
92
1.66k
  if (!processed_netrc)
93
1.66k
    {
94
#ifdef __VMS
95
96
      int err;
97
      struct stat buf;
98
      char *path = "SYS$LOGIN:.netrc";
99
100
      netrc_list = NULL;
101
      processed_netrc = 1;
102
103
      err = stat (path, &buf);
104
      if (err == 0)
105
        netrc_list = parse_netrc (path);
106
107
#else /* def __VMS */
108
109
1.66k
      netrc_list = NULL;
110
1.66k
      processed_netrc = 1;
111
112
1.66k
      if (fp_netrc)
113
1.66k
        netrc_list = parse_netrc_fp (".netrc", fp_netrc);
114
0
      else if (opt.homedir)
115
0
        {
116
0
          struct stat buf;
117
0
          char *path = aprintf ("%s/%s", opt.homedir, NETRC_FILE_NAME);
118
0
          if (stat (path, &buf) == 0)
119
0
            netrc_list = parse_netrc (path);
120
0
          xfree (path);
121
0
        }
122
123
1.66k
#endif /* def __VMS [else] */
124
1.66k
    }
125
  /* If nothing to do...  */
126
1.66k
  if (!netrc_list)
127
1.57k
    return;
128
  /* Acc and password found; all OK.  */
129
89
  if (*acc && *passwd)
130
0
    return;
131
  /* Some data not given -- try finding the host.  */
132
2.33k
  for (l = netrc_list; l; l = l->next)
133
2.24k
    {
134
2.24k
      if (!l->host)
135
1.75k
        continue;
136
495
      else if (!strcasecmp (l->host, host))
137
2
        break;
138
2.24k
    }
139
89
  if (l)
140
2
    {
141
2
      if (*acc)
142
0
        {
143
          /* Looking for password in .netrc.  */
144
0
          if (!strcmp (l->acc, *acc))
145
0
            *passwd = l->passwd; /* usernames match; password OK */
146
0
          else
147
0
            *passwd = NULL;     /* usernames don't match */
148
0
        }
149
2
      else                      /* NOT *acc */
150
2
        {
151
          /* If password was given, use it.  The account is l->acc.  */
152
2
          *acc = l->acc;
153
2
          if (l->passwd)
154
1
            *passwd = l->passwd;
155
2
        }
156
2
      return;
157
2
    }
158
87
  else
159
87
    {
160
87
      if (!slack_default)
161
0
        return;
162
87
      if (*acc)
163
0
        return;
164
      /* Try looking for the default account.  */
165
543
      for (l = netrc_list; l; l = l->next)
166
508
        if (!l->host)
167
52
          break;
168
87
      if (!l)
169
35
        return;
170
52
      *acc = l->acc;
171
52
      if (!*passwd)
172
52
        *passwd = l->passwd;
173
52
      return;
174
87
    }
175
89
}
176
177
178
#ifdef STANDALONE
179
180
/* Normally, these functions would be defined by your package.  */
181
# define xmalloc malloc
182
# define xfree(p) do { free ((void *) (p)); p = NULL; } while (0)
183
# define xstrdup strdup
184
185
# define xrealloc realloc
186
187
#endif /* STANDALONE */
188
189
/* Maybe add NEWENTRY to the account information list, LIST.  NEWENTRY is
190
   set to a ready-to-use acc_t, in any event.  */
191
static void
192
maybe_add_to_list (acc_t **newentry, acc_t **list)
193
5.14k
{
194
5.14k
  acc_t *a, *l;
195
5.14k
  a = *newentry;
196
5.14k
  l = *list;
197
198
  /* We need an account name in order to add the entry to the list.  */
199
5.14k
  if (a && ! a->acc)
200
1.23k
    {
201
      /* Free any allocated space.  */
202
1.23k
      xfree (a->host);
203
1.23k
      xfree (a->acc);
204
1.23k
      xfree (a->passwd);
205
1.23k
    }
206
3.91k
  else
207
3.91k
    {
208
3.91k
      if (a)
209
2.24k
        {
210
          /* Add the current machine into our list.  */
211
2.24k
          a->next = l;
212
2.24k
          l = a;
213
2.24k
        }
214
215
      /* Allocate a new acc_t structure.  */
216
3.91k
      a = xmalloc (sizeof (acc_t));
217
3.91k
    }
218
219
  /* Zero the structure, so that it is ready to use.  */
220
5.14k
  memset (a, 0, sizeof(*a));
221
222
  /* Return the new pointers.  */
223
5.14k
  *newentry = a;
224
5.14k
  *list = l;
225
5.14k
  return;
226
5.14k
}
227
228
/* Helper function for the parser, shifts contents of
229
   null-terminated string once character to the left.
230
   Used in processing \ and " constructs in the netrc file */
231
static void
232
shift_left(char *string)
233
1.28k
{
234
1.28k
  char *p;
235
236
62.6k
  for (p=string; *p; ++p)
237
61.3k
    *p = *(p+1);
238
1.28k
}
239
240
/* Parse a .netrc file (as described in the ftp(1) manual page).  */
241
static acc_t *
242
parse_netrc_fp (const char *path, FILE *fp)
243
1.66k
{
244
1.66k
  char *line = NULL, *p, *tok;
245
1.66k
  const char *premature_token = NULL;
246
1.66k
  acc_t *current = NULL, *retval = NULL;
247
1.66k
  int ln = 0, qmark;
248
1.66k
  size_t bufsize = 0;
249
250
  /* The latest token we've seen in the file.  */
251
1.66k
  enum
252
1.66k
  {
253
1.66k
    tok_nothing, tok_account, tok_login, tok_macdef, tok_machine, tok_password, tok_port, tok_force
254
1.66k
  } last_token = tok_nothing;
255
256
  /* While there are lines in the file...  */
257
15.9k
  while (getline (&line, &bufsize, fp) > 0)
258
14.3k
    {
259
14.3k
      ln ++;
260
261
      /* Parse the line.  */
262
14.3k
      p = line;
263
14.3k
      qmark = 0;
264
265
      /* Skip leading whitespace.  */
266
15.5k
      while (*p && c_isspace (*p))
267
1.24k
        p ++;
268
269
      /* If the line is empty, then end any macro definition.  */
270
14.3k
      if (last_token == tok_macdef && !*p)
271
        /* End of macro if the line is empty.  */
272
383
        last_token = tok_nothing;
273
274
      /* If we are defining macros, then skip parsing the line.  */
275
35.1k
      while (*p && last_token != tok_macdef)
276
21.3k
        {
277
          /* Skip any whitespace.  */
278
23.1k
          while (*p && c_isspace (*p))
279
1.79k
            p ++;
280
281
          /* Discard end-of-line comments; also, stop processing if
282
             the above `while' merely skipped trailing whitespace.  */
283
21.3k
          if (*p == '#' || !*p)
284
491
            break;
285
286
          /* If the token starts with quotation mark, note this fact,
287
             and squash the quotation character */
288
20.8k
          if (*p == '"'){
289
500
            qmark = 1;
290
500
            shift_left (p);
291
500
          }
292
293
20.8k
          tok = p;
294
295
          /* Find the end of the token, handling quotes and escapes.  */
296
100k
          while (*p && (qmark ? *p != '"' : !c_isspace (*p))){
297
79.5k
            if (*p == '\\')
298
288
              shift_left (p);
299
79.5k
            p ++;
300
79.5k
          }
301
302
          /* If field was quoted, squash the trailing quotation mark
303
             and reset qmark flag.  */
304
20.8k
          if (qmark)
305
500
            {
306
500
              shift_left (p);
307
500
              qmark = 0;
308
500
            }
309
310
          /* Null-terminate the token, if it isn't already.  */
311
20.8k
          if (*p)
312
19.6k
            *p ++ = '\0';
313
314
20.8k
          switch (last_token)
315
20.8k
            {
316
3.22k
            case tok_login:
317
3.22k
              if (current)
318
2.63k
                {
319
2.63k
                  xfree (current->acc);
320
2.63k
                  current->acc = xstrdup (tok);
321
2.63k
                }
322
583
              else
323
583
                premature_token = "login";
324
3.22k
              break;
325
326
1.15k
            case tok_machine:
327
              /* Start a new machine entry.  */
328
1.15k
              maybe_add_to_list (&current, &retval);
329
1.15k
              current->host = xstrdup (tok);
330
1.15k
              break;
331
332
1.10k
            case tok_password:
333
1.10k
              if (current)
334
442
                {
335
442
                  xfree (current->passwd);
336
442
                  current->passwd = xstrdup (tok);
337
442
                }
338
664
              else
339
664
                premature_token = "password";
340
1.10k
              break;
341
342
              /* We handle most of tok_macdef above.  */
343
0
            case tok_macdef:
344
0
              if (!current)
345
0
                premature_token = "macdef";
346
0
              break;
347
348
              /* We don't handle the account keyword at all.  */
349
687
            case tok_account:
350
687
              if (!current)
351
453
                premature_token = "account";
352
687
              break;
353
354
              /* We don't handle the port keyword at all.  */
355
699
            case tok_port:
356
699
              if (!current)
357
448
                premature_token = "port";
358
699
              break;
359
360
              /* We don't handle the force keyword at all.  */
361
636
            case tok_force:
362
636
              if (!current)
363
381
                premature_token = "force";
364
636
              break;
365
366
              /* We handle tok_nothing below this switch.  */
367
13.3k
            case tok_nothing:
368
13.3k
              break;
369
20.8k
            }
370
371
20.8k
          if (premature_token)
372
2.52k
            {
373
2.52k
              fprintf (stderr, _("\
374
2.52k
%s: %s:%d: warning: %s token appears before any machine name\n"),
375
2.52k
                       exec_name, path, ln, quote (premature_token));
376
2.52k
              premature_token = NULL;
377
2.52k
            }
378
379
20.8k
          if (last_token != tok_nothing)
380
            /* We got a value, so reset the token state.  */
381
7.50k
            last_token = tok_nothing;
382
13.3k
          else
383
13.3k
            {
384
              /* Fetch the next token.  */
385
13.3k
              if (!strcmp (tok, "account"))
386
695
                last_token = tok_account;
387
388
12.6k
              else if (!strcmp (tok, "default"))
389
2.32k
                  maybe_add_to_list (&current, &retval);
390
391
              /* fetchmail compatibility, "user" is an alias for "login" */
392
10.3k
              else if (!strcmp (tok, "login") || !strcmp (tok, "user"))
393
3.23k
                last_token = tok_login;
394
395
7.08k
              else if (!strcmp (tok, "macdef"))
396
399
                last_token = tok_macdef;
397
398
6.68k
              else if (!strcmp (tok, "machine"))
399
1.16k
                last_token = tok_machine;
400
401
              /* fetchmail compatibility, "passwd" is an alias for "password" */
402
5.52k
              else if (!strcmp (tok, "password") || !strcmp (tok, "passwd"))
403
1.12k
                last_token = tok_password;
404
405
          /* GNU extensions 'port' and 'force', not operational
406
          * see https://www.gnu.org/software/emacs/manual/html_node/gnus/NNTP.html#index-nntp_002dauthinfo_002dfunction-2003
407
          * see https://savannah.gnu.org/bugs/index.php?52066
408
          */
409
4.40k
              else if (!strcmp (tok, "port"))
410
707
                last_token = tok_port;
411
412
3.69k
              else if (!strcmp (tok, "force"))
413
644
                last_token = tok_force;
414
415
3.05k
              else
416
3.05k
                fprintf (stderr, _("%s: %s:%d: unknown token \"%s\"\n"),
417
3.05k
                         exec_name, path, ln, tok);
418
13.3k
            }
419
20.8k
        }
420
14.3k
    }
421
422
1.66k
  xfree (line);
423
424
  /* Finalize the last machine entry we found.  */
425
1.66k
  maybe_add_to_list (&current, &retval);
426
1.66k
  xfree (current);
427
428
  /* Reverse the order of the list so that it appears in file order.  */
429
1.66k
  current = retval;
430
1.66k
  retval = NULL;
431
3.91k
  while (current)
432
2.24k
    {
433
2.24k
      acc_t *saved_reference;
434
435
      /* Change the direction of the pointers.  */
436
2.24k
      saved_reference = current->next;
437
2.24k
      current->next = retval;
438
439
      /* Advance to the next node.  */
440
2.24k
      retval = current;
441
2.24k
      current = saved_reference;
442
2.24k
    }
443
444
1.66k
  return retval;
445
1.66k
}
446
447
static acc_t *
448
parse_netrc (const char *path)
449
0
{
450
0
  FILE *fp;
451
0
  acc_t *acc;
452
453
0
  fp = fopen (path, "r");
454
0
  if (!fp)
455
0
    {
456
0
      fprintf (stderr, _("%s: Cannot read %s (%s).\n"), exec_name,
457
0
               path, strerror (errno));
458
0
      return NULL;
459
0
    }
460
461
0
  acc = parse_netrc_fp (path, fp);
462
0
  fclose(fp);
463
464
0
  return acc;
465
0
}
466
467
#if defined DEBUG_MALLOC || defined TESTING
468
/* Free a netrc list.  */
469
static void
470
free_netrc(acc_t *l)
471
1.66k
{
472
1.66k
  acc_t *t;
473
474
3.91k
  while (l)
475
2.24k
    {
476
2.24k
      t = l->next;
477
2.24k
      xfree (l->acc);
478
2.24k
      xfree (l->passwd);
479
2.24k
      xfree (l->host);
480
2.24k
      xfree (l);
481
2.24k
      l = t;
482
2.24k
    }
483
1.66k
}
484
#endif
485
486
#ifdef TESTING
487
#include "../tests/unit-tests.h"
488
const char *
489
test_parse_netrc(void)
490
0
{
491
0
#ifdef HAVE_FMEMOPEN
492
0
  static const struct test {
493
0
    const char *pw_in;
494
0
    const char *pw_expected;
495
0
  } tests[] = {
496
0
    { "a\\b", "ab" },
497
0
    { "a\\\\b", "a\\b" },
498
0
    { "\"a\\\\b\"", "a\\b" },
499
0
    { "\"a\\\"b\"", "a\"b" },
500
0
    { "a\"b", "a\"b" },
501
0
    { "a\\\\\\\\b", "a\\\\b" },
502
0
    { "a\\\\", "a\\" },
503
0
    { "\"a\\\\\"", "a\\" },
504
0
    { "a\\", "a" },
505
0
    { "\"a b\"", "a b" },
506
0
    { "a b", "a" },
507
0
  };
508
0
  unsigned i;
509
0
  static char errmsg[128];
510
511
0
  for (i = 0; i < countof(tests); ++i)
512
0
    {
513
0
      const struct test *t = &tests[i];
514
0
      char netrc[128];
515
0
      FILE *fp;
516
0
      acc_t *acc;
517
0
      int n;
518
519
0
      n = snprintf (netrc, sizeof(netrc), "machine localhost\n\tlogin me\n\tpassword %s", t->pw_in);
520
0
      mu_assert ("test_parse_netrc: failed to fmemopen() netrc", (fp = fmemopen(netrc, n, "r")) != NULL);
521
522
0
      acc = parse_netrc_fp ("memory", fp);
523
0
      fclose(fp);
524
525
0
      if (strcmp(acc->passwd, t->pw_expected))
526
0
        {
527
0
          snprintf(errmsg, sizeof(errmsg), "test_parse_netrc: wrong result [%u]. Expected '%s', got '%s'",
528
0
                   i, t->pw_expected, acc->passwd);
529
0
          free_netrc(acc);
530
0
          return errmsg;
531
0
        }
532
533
0
      free_netrc(acc);
534
0
    }
535
536
0
#endif // HAVE_FMEMOPEN
537
0
  return NULL;
538
0
}
539
#endif
540
541
#ifdef STANDALONE
542
#include <sys/types.h>
543
#include <sys/stat.h>
544
#include "exits.h"
545
546
const char *program_argstring = NULL; /* Needed by warc.c */
547
548
int
549
main (int argc, char **argv)
550
{
551
  struct stat sb;
552
  char *program_name, *file, *target;
553
  acc_t *head, *a;
554
555
  if (argc < 2 || argc > 3)
556
    {
557
      fprintf (stderr, _("Usage: %s NETRC [HOSTNAME]\n"), argv[0]);
558
      exit (WGET_EXIT_GENERIC_ERROR);
559
    }
560
561
  program_name = argv[0];
562
  file = argv[1];
563
  target = argv[2];
564
565
#ifdef ENABLE_NLS
566
  /* Set the current locale.  */
567
  setlocale (LC_ALL, "");
568
  /* Set the text message domain.  */
569
  bindtextdomain ("wget", LOCALEDIR);
570
  textdomain ("wget");
571
#endif /* ENABLE_NLS */
572
573
  if (stat (file, &sb))
574
    {
575
      fprintf (stderr, _("%s: cannot stat %s: %s\n"), argv[0], file,
576
               strerror (errno));
577
      exit (WGET_EXIT_GENERIC_ERROR);
578
    }
579
580
  head = parse_netrc (file);
581
  a = head;
582
  while (a)
583
    {
584
      /* Skip if we have a target and this isn't it.  */
585
      if (target && a->host && strcmp (target, a->host))
586
        {
587
          a = a->next;
588
          continue;
589
        }
590
591
      if (!target)
592
        {
593
          /* Print the host name if we have no target.  */
594
          if (a->host)
595
            fputs (a->host, stdout);
596
          else
597
            fputs ("DEFAULT", stdout);
598
599
          fputc (' ', stdout);
600
        }
601
602
      /* Print the account name.  */
603
      fputs (a->acc, stdout);
604
605
      if (a->passwd)
606
        {
607
          /* Print the password, if there is any.  */
608
          fputc (' ', stdout);
609
          fputs (a->passwd, stdout);
610
        }
611
612
      fputc ('\n', stdout);
613
614
      /* Exit if we found the target.  */
615
      if (target)
616
        exit (WGET_EXIT_SUCCESS);
617
      a = a->next;
618
    }
619
620
  /* Exit with failure if we had a target, success otherwise.  */
621
  if (target)
622
    exit (WGET_EXIT_GENERIC_ERROR);
623
624
  exit (WGET_EXIT_SUCCESS);
625
}
626
#endif /* STANDALONE */