1/* Read and parse the .netrc file to get hosts, accounts, and passwords.
2   Copyright (C) 1996, 2007, 2008, 2009 Free Software Foundation, Inc.
3
4This file is part of GNU Wget.
5
6GNU Wget is free software; you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published by
8the Free Software Foundation; either version 3 of the License, or
9(at your option) any later version.
10
11GNU Wget is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with Wget.  If not, see <http://www.gnu.org/licenses/>.
18
19Additional permission under GNU GPL version 3 section 7
20
21If you modify this program, or any covered work, by linking or
22combining it with the OpenSSL project's OpenSSL library (or a
23modified version of that library), containing parts covered by the
24terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
25grants you additional permission to convey the resulting work.
26Corresponding Source for a non-source form of such a combination
27shall include the source code for the parts of OpenSSL used as well
28as that of the covered work.  */
29
30/* This file used to be kept in synch with the code in Fetchmail, but
31   the latter has diverged since.  */
32
33#include "wget.h"
34
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38#include <errno.h>
39
40#include "utils.h"
41#include "netrc.h"
42#include "init.h"
43
44#define NETRC_FILE_NAME ".netrc"
45
46acc_t *netrc_list;
47
48static acc_t *parse_netrc (const char *);
49
50/* Return the correct user and password, given the host, user (as
51   given in the URL), and password (as given in the URL).  May return
52   NULL.
53
54   If SLACK_DEFAULT is set, allow looking for a "default" account.
55   You will typically turn it off for HTTP.  */
56void
57search_netrc (const char *host, const char **acc, const char **passwd,
58              int slack_default)
59{
60  acc_t *l;
61  static int processed_netrc;
62
63  if (!opt.netrc)
64    return;
65  /* Find ~/.netrc.  */
66  if (!processed_netrc)
67    {
68#ifdef __VMS
69
70      int err;
71      struct_stat buf;
72      char *path = "SYS$LOGIN:.netrc";
73
74      netrc_list = NULL;
75      processed_netrc = 1;
76
77      err = stat (path, &buf);
78      if (err == 0)
79        netrc_list = parse_netrc (path);
80
81#else /* def __VMS */
82
83      char *home = home_dir ();
84
85      netrc_list = NULL;
86      processed_netrc = 1;
87      if (home)
88        {
89          int err;
90          struct_stat buf;
91          char *path = (char *)alloca (strlen (home) + 1
92                                       + strlen (NETRC_FILE_NAME) + 1);
93          sprintf (path, "%s/%s", home, NETRC_FILE_NAME);
94          xfree (home);
95          err = stat (path, &buf);
96          if (err == 0)
97            netrc_list = parse_netrc (path);
98        }
99
100#endif /* def __VMS [else] */
101    }
102  /* If nothing to do...  */
103  if (!netrc_list)
104    return;
105  /* Acc and password found; all OK.  */
106  if (*acc && *passwd)
107    return;
108  /* Some data not given -- try finding the host.  */
109  for (l = netrc_list; l; l = l->next)
110    {
111      if (!l->host)
112        continue;
113      else if (!strcasecmp (l->host, host))
114        break;
115    }
116  if (l)
117    {
118      if (*acc)
119        {
120          /* Looking for password in .netrc.  */
121          if (!strcmp (l->acc, *acc))
122            *passwd = l->passwd; /* usernames match; password OK */
123          else
124            *passwd = NULL;     /* usernames don't match */
125        }
126      else                      /* NOT *acc */
127        {
128          /* If password was given, use it.  The account is l->acc.  */
129          *acc = l->acc;
130          if (l->passwd)
131            *passwd = l->passwd;
132        }
133      return;
134    }
135  else
136    {
137      if (!slack_default)
138        return;
139      if (*acc)
140        return;
141      /* Try looking for the default account.  */
142      for (l = netrc_list; l; l = l->next)
143        if (!l->host)
144          break;
145      if (!l)
146        return;
147      *acc = l->acc;
148      if (!*passwd)
149        *passwd = l->passwd;
150      return;
151    }
152}
153
154
155#ifdef STANDALONE
156
157#include <assert.h>
158
159/* Normally, these functions would be defined by your package.  */
160# define xmalloc malloc
161# define xfree free
162# define xstrdup strdup
163
164# define xrealloc realloc
165
166/* Read a line from FP.  The function reallocs the storage as needed
167   to accomodate for any length of the line.  Reallocs are done
168   storage exponentially, doubling the storage after each overflow to
169   minimize the number of calls to realloc() and fgets().  The newline
170   character at the end of line is retained.
171
172   After end-of-file is encountered without anything being read, NULL
173   is returned.  NULL is also returned on error.  To distinguish
174   between these two cases, use the stdio function ferror().  */
175
176char *
177read_whole_line (FILE *fp)
178{
179  int length = 0;
180  int bufsize = 81;
181  char *line = xmalloc (bufsize);
182
183  while (fgets (line + length, bufsize - length, fp))
184    {
185      length += strlen (line + length);
186      assert (length > 0);
187      if (line[length - 1] == '\n')
188        break;
189      /* fgets() guarantees to read the whole line, or to use up the
190         space we've given it.  We can double the buffer
191         unconditionally.  */
192      bufsize <<= 1;
193      line = xrealloc (line, bufsize);
194    }
195  if (length == 0 || ferror (fp))
196    {
197      xfree (line);
198      return NULL;
199    }
200  if (length + 1 < bufsize)
201    /* Relieve the memory from our exponential greediness.  We say
202       `length + 1' because the terminating \0 is not included in
203       LENGTH.  We don't need to zero-terminate the string ourselves,
204       though, because fgets() does that.  */
205    line = xrealloc (line, length + 1);
206  return line;
207}
208#endif /* STANDALONE */
209
210/* Maybe add NEWENTRY to the account information list, LIST.  NEWENTRY is
211   set to a ready-to-use acc_t, in any event.  */
212static void
213maybe_add_to_list (acc_t **newentry, acc_t **list)
214{
215  acc_t *a, *l;
216  a = *newentry;
217  l = *list;
218
219  /* We need an account name in order to add the entry to the list.  */
220  if (a && ! a->acc)
221    {
222      /* Free any allocated space.  */
223      xfree_null (a->host);
224      xfree_null (a->acc);
225      xfree_null (a->passwd);
226    }
227  else
228    {
229      if (a)
230        {
231          /* Add the current machine into our list.  */
232          a->next = l;
233          l = a;
234        }
235
236      /* Allocate a new acc_t structure.  */
237      a = xmalloc (sizeof (acc_t));
238    }
239
240  /* Zero the structure, so that it is ready to use.  */
241  memset (a, 0, sizeof(*a));
242
243  /* Return the new pointers.  */
244  *newentry = a;
245  *list = l;
246  return;
247}
248
249/* Helper function for the parser, shifts contents of
250   null-terminated string once character to the left.
251   Used in processing \ and " constructs in the netrc file */
252static void
253shift_left(char *string)
254{
255  char *p;
256
257  for (p=string; *p; ++p)
258    *p = *(p+1);
259}
260
261/* Parse a .netrc file (as described in the ftp(1) manual page).  */
262static acc_t *
263parse_netrc (const char *path)
264{
265  FILE *fp;
266  char *line, *p, *tok;
267  const char *premature_token;
268  acc_t *current, *retval;
269  int ln, qmark;
270
271  /* The latest token we've seen in the file.  */
272  enum
273  {
274    tok_nothing, tok_account, tok_login, tok_macdef, tok_machine, tok_password
275  } last_token = tok_nothing;
276
277  current = retval = NULL;
278
279  fp = fopen (path, "r");
280  if (!fp)
281    {
282      fprintf (stderr, _("%s: Cannot read %s (%s).\n"), exec_name,
283               path, strerror (errno));
284      return retval;
285    }
286
287  /* Initialize the file data.  */
288  ln = 0;
289  premature_token = NULL;
290
291  /* While there are lines in the file...  */
292  while ((line = read_whole_line (fp)) != NULL)
293    {
294      ln ++;
295
296      /* Parse the line.  */
297      p = line;
298      qmark = 0;
299
300      /* Skip leading whitespace.  */
301      while (*p && c_isspace (*p))
302        p ++;
303
304      /* If the line is empty, then end any macro definition.  */
305      if (last_token == tok_macdef && !*p)
306        /* End of macro if the line is empty.  */
307        last_token = tok_nothing;
308
309      /* If we are defining macros, then skip parsing the line.  */
310      while (*p && last_token != tok_macdef)
311        {
312          /* Skip any whitespace.  */
313          while (*p && c_isspace (*p))
314            p ++;
315
316          /* Discard end-of-line comments; also, stop processing if
317             the above `while' merely skipped trailing whitespace.  */
318          if (*p == '#' || !*p)
319            break;
320
321          /* If the token starts with quotation mark, note this fact,
322             and squash the quotation character */
323          if (*p == '"'){
324            qmark = 1;
325            shift_left (p);
326          }
327
328          tok = p;
329
330          /* Find the end of the token, handling quotes and escapes.  */
331          while (*p && (qmark ? *p != '"' : !c_isspace (*p))){
332            if (*p == '\\')
333              shift_left (p);
334            p ++;
335          }
336
337          /* If field was quoted, squash the trailing quotation mark
338             and reset qmark flag.  */
339          if (qmark)
340            {
341              shift_left (p);
342              qmark = 0;
343            }
344
345          /* Null-terminate the token, if it isn't already.  */
346          if (*p)
347            *p ++ = '\0';
348
349          switch (last_token)
350            {
351            case tok_login:
352              if (current)
353                current->acc = xstrdup (tok);
354              else
355                premature_token = "login";
356              break;
357
358            case tok_machine:
359              /* Start a new machine entry.  */
360              maybe_add_to_list (&current, &retval);
361              current->host = xstrdup (tok);
362              break;
363
364            case tok_password:
365              if (current)
366                current->passwd = xstrdup (tok);
367              else
368                premature_token = "password";
369              break;
370
371              /* We handle most of tok_macdef above.  */
372            case tok_macdef:
373              if (!current)
374                premature_token = "macdef";
375              break;
376
377              /* We don't handle the account keyword at all.  */
378            case tok_account:
379              if (!current)
380                premature_token = "account";
381              break;
382
383              /* We handle tok_nothing below this switch.  */
384            case tok_nothing:
385              break;
386            }
387
388          if (premature_token)
389            {
390              fprintf (stderr, _("\
391%s: %s:%d: warning: %s token appears before any machine name\n"),
392                       exec_name, path, ln, quote (premature_token));
393              premature_token = NULL;
394            }
395
396          if (last_token != tok_nothing)
397            /* We got a value, so reset the token state.  */
398            last_token = tok_nothing;
399          else
400            {
401              /* Fetch the next token.  */
402              if (!strcmp (tok, "account"))
403                last_token = tok_account;
404              else if (!strcmp (tok, "default"))
405                {
406                  maybe_add_to_list (&current, &retval);
407                }
408              else if (!strcmp (tok, "login"))
409                last_token = tok_login;
410
411              else if (!strcmp (tok, "macdef"))
412                last_token = tok_macdef;
413
414              else if (!strcmp (tok, "machine"))
415                last_token = tok_machine;
416
417              else if (!strcmp (tok, "password"))
418                last_token = tok_password;
419
420              else
421                fprintf (stderr, _("%s: %s:%d: unknown token \"%s\"\n"),
422                         exec_name, path, ln, tok);
423            }
424        }
425
426      xfree (line);
427    }
428
429  fclose (fp);
430
431  /* Finalize the last machine entry we found.  */
432  maybe_add_to_list (&current, &retval);
433  xfree (current);
434
435  /* Reverse the order of the list so that it appears in file order.  */
436  current = retval;
437  retval = NULL;
438  while (current)
439    {
440      acc_t *saved_reference;
441
442      /* Change the direction of the pointers.  */
443      saved_reference = current->next;
444      current->next = retval;
445
446      /* Advance to the next node.  */
447      retval = current;
448      current = saved_reference;
449    }
450
451  return retval;
452}
453
454
455/* Free a netrc list.  */
456void
457free_netrc(acc_t *l)
458{
459  acc_t *t;
460
461  while (l)
462    {
463      t = l->next;
464      xfree_null (l->acc);
465      xfree_null (l->passwd);
466      xfree_null (l->host);
467      xfree (l);
468      l = t;
469    }
470}
471
472#ifdef STANDALONE
473#include <sys/types.h>
474#include <sys/stat.h>
475
476int
477main (int argc, char **argv)
478{
479  struct_stat sb;
480  char *program_name, *file, *target;
481  acc_t *head, *a;
482
483  if (argc < 2 || argc > 3)
484    {
485      fprintf (stderr, _("Usage: %s NETRC [HOSTNAME]\n"), argv[0]);
486      exit (1);
487    }
488
489  program_name = argv[0];
490  file = argv[1];
491  target = argv[2];
492
493  if (stat (file, &sb))
494    {
495      fprintf (stderr, _("%s: cannot stat %s: %s\n"), argv[0], file,
496               strerror (errno));
497      exit (1);
498    }
499
500  head = parse_netrc (file);
501  a = head;
502  while (a)
503    {
504      /* Skip if we have a target and this isn't it.  */
505      if (target && a->host && strcmp (target, a->host))
506        {
507          a = a->next;
508          continue;
509        }
510
511      if (!target)
512        {
513          /* Print the host name if we have no target.  */
514          if (a->host)
515            fputs (a->host, stdout);
516          else
517            fputs ("DEFAULT", stdout);
518
519          fputc (' ', stdout);
520        }
521
522      /* Print the account name.  */
523      fputs (a->acc, stdout);
524
525      if (a->passwd)
526        {
527          /* Print the password, if there is any.  */
528          fputc (' ', stdout);
529          fputs (a->passwd, stdout);
530        }
531
532      fputc ('\n', stdout);
533
534      /* Exit if we found the target.  */
535      if (target)
536        exit (0);
537      a = a->next;
538    }
539
540  /* Exit with failure if we had a target, success otherwise.  */
541  if (target)
542    exit (1);
543
544  exit (0);
545}
546#endif /* STANDALONE */
547