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