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