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