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