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