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