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