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