]> sjero.net Git - wget/blob - src/netrc.c
[svn] Allow Wget to be compiled with a K&R compiler.
[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 /* 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   /* 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 = (char *)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 (a->host);
209       xfree (a->acc);
210       xfree (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 = (acc_t *)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, *premature_token;
252   acc_t *current, *retval;
253   int ln, quote;
254
255   /* The latest token we've seen in the file.  */
256   enum
257   {
258     tok_nothing, tok_account, tok_login, tok_macdef, tok_machine, tok_password
259   } last_token = tok_nothing;
260
261   current = retval = NULL;
262
263   fp = fopen (path, "r");
264   if (!fp)
265     {
266       fprintf (stderr, _("%s: Cannot read %s (%s).\n"), exec_name,
267                path, strerror (errno));
268       return retval;
269     }
270
271   /* Initialize the file data.  */
272   ln = 0;
273   premature_token = NULL;
274
275   /* While there are lines in the file...  */
276   while ((line = read_whole_line (fp)))
277     {
278       ln ++;
279
280       /* Parse the line.  */
281       p = line;
282       quote = 0;
283
284       /* Skip leading whitespace.  */
285       while (*p && ISSPACE (*p))
286         p ++;
287
288       /* If the line is empty, then end any macro definition.  */
289       if (last_token == tok_macdef && !*p)
290         /* End of macro if the line is empty.  */
291         last_token = tok_nothing;
292
293       /* If we are defining macros, then skip parsing the line.  */
294       while (*p && last_token != tok_macdef)
295         {
296           /* Skip any whitespace.  */
297           while (*p && ISSPACE (*p))
298             p ++;
299
300           /* Discard end-of-line comments; also, stop processing if
301              the above `while' merely skipped trailing whitespace.  */
302           if (*p == '#' || !*p)
303             break;
304
305           /* If the token starts with quotation mark, note this fact,
306              and squash the quotation character */
307           if (*p == '"'){
308             quote = 1;
309             shift_left (p);
310           }
311
312           tok = p;
313
314           /* Find the end of the token, handling quotes and escapes.  */
315           while (*p && (quote ? *p != '"' : !ISSPACE (*p))){
316             if (*p == '\\')
317               shift_left (p);
318             p ++;
319           }
320
321           /* if field was quoted, squash the trailing quotation mark */
322           if (quote)
323             shift_left(p);
324
325           /* Null-terminate the token, if it isn't already.  */
326           if (*p)
327             *p ++ = '\0';
328
329           switch (last_token)
330             {
331             case tok_login:
332               if (current)
333                 current->acc = xstrdup (tok);
334               else
335                 premature_token = "login";
336               break;
337
338             case tok_machine:
339               /* Start a new machine entry.  */
340               maybe_add_to_list (&current, &retval);
341               current->host = xstrdup (tok);
342               break;
343
344             case tok_password:
345               if (current)
346                 current->passwd = xstrdup (tok);
347               else
348                 premature_token = "password";
349               break;
350
351               /* We handle most of tok_macdef above.  */
352             case tok_macdef:
353               if (!current)
354                 premature_token = "macdef";
355               break;
356
357               /* We don't handle the account keyword at all.  */
358             case tok_account:
359               if (!current)
360                 premature_token = "account";
361               break;
362
363               /* We handle tok_nothing below this switch.  */
364             case tok_nothing:
365               break;
366             }
367
368           if (premature_token)
369             {
370               fprintf (stderr, _("\
371 %s: %s:%d: warning: \"%s\" token appears before any machine name\n"),
372                        exec_name, path, ln, premature_token);
373               premature_token = NULL;
374             }
375
376           if (last_token != tok_nothing)
377             /* We got a value, so reset the token state.  */
378             last_token = tok_nothing;
379           else
380             {
381               /* Fetch the next token.  */
382               if (!strcmp (tok, "account"))
383                 last_token = tok_account;
384               else if (!strcmp (tok, "default"))
385                 {
386                   maybe_add_to_list (&current, &retval);
387                 }
388               else if (!strcmp (tok, "login"))
389                 last_token = tok_login;
390
391               else if (!strcmp (tok, "macdef"))
392                 last_token = tok_macdef;
393
394               else if (!strcmp (tok, "machine"))
395                 last_token = tok_machine;
396
397               else if (!strcmp (tok, "password"))
398                 last_token = tok_password;
399
400               else
401                 fprintf (stderr, _("%s: %s:%d: unknown token \"%s\"\n"),
402                          exec_name, path, ln, tok);
403             }
404         }
405
406       xfree (line);
407     }
408
409   fclose (fp);
410
411   /* Finalize the last machine entry we found.  */
412   maybe_add_to_list (&current, &retval);
413   xfree (current);
414
415   /* Reverse the order of the list so that it appears in file order.  */
416   current = retval;
417   retval = NULL;
418   while (current)
419     {
420       acc_t *saved_reference;
421
422       /* Change the direction of the pointers.  */
423       saved_reference = current->next;
424       current->next = retval;
425
426       /* Advance to the next node.  */
427       retval = current;
428       current = saved_reference;
429     }
430
431   return retval;
432 }
433
434
435 /* Free a netrc list.  */
436 void
437 free_netrc(acc_t *l)
438 {
439   acc_t *t;
440
441   while (l)
442     {
443       t = l->next;
444       FREE_MAYBE (l->acc);
445       FREE_MAYBE (l->passwd);
446       FREE_MAYBE (l->host);
447       xfree (l);
448       l = t;
449     }
450 }
451
452 #ifdef STANDALONE
453 #include <sys/types.h>
454 #include <sys/stat.h>
455
456 int
457 main (int argc, char **argv)
458 {
459   struct stat sb;
460   char *program_name, *file, *target;
461   acc_t *head, *a;
462
463   if (argc < 2 || argc > 3)
464     {
465       fprintf (stderr, _("Usage: %s NETRC [HOSTNAME]\n"), argv[0]);
466       exit (1);
467     }
468
469   program_name = argv[0];
470   file = argv[1];
471   target = argv[2];
472
473   if (stat (file, &sb))
474     {
475       fprintf (stderr, _("%s: cannot stat %s: %s\n"), argv[0], file,
476                strerror (errno));
477       exit (1);
478     }
479
480   head = parse_netrc (file);
481   a = head;
482   while (a)
483     {
484       /* Skip if we have a target and this isn't it.  */
485       if (target && a->host && strcmp (target, a->host))
486         {
487           a = a->next;
488           continue;
489         }
490
491       if (!target)
492         {
493           /* Print the host name if we have no target.  */
494           if (a->host)
495             fputs (a->host, stdout);
496           else
497             fputs ("DEFAULT", stdout);
498
499           fputc (' ', stdout);
500         }
501
502       /* Print the account name.  */
503       fputs (a->acc, stdout);
504
505       if (a->passwd)
506         {
507           /* Print the password, if there is any.  */
508           fputc (' ', stdout);
509           fputs (a->passwd, stdout);
510         }
511
512       fputc ('\n', stdout);
513
514       /* Exit if we found the target.  */
515       if (target)
516         exit (0);
517       a = a->next;
518     }
519
520   /* Exit with failure if we had a target, success otherwise.  */
521   if (target)
522     exit (1);
523
524   exit (0);
525 }
526 #endif /* STANDALONE */