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