]> sjero.net Git - wget/blob - src/netrc.c
[svn] Handle trailing whitespace in .netrc. Published in
[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 <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   if (!*acc && !slack_default)
94     return;
95   /* Some data not given -- try finding the host.  */
96   for (l = netrc_list; l; l = l->next)
97     {
98       if (!l->host)
99         continue;
100       else if (!strcasecmp (l->host, host))
101         break;
102     }
103   if (l)
104     {
105       if (*acc)
106         {
107           /* Looking for password in .netrc.  */
108           if (!strcmp (l->acc, *acc))
109             *passwd = l->passwd; /* usernames match; password OK */
110           else
111             *passwd = NULL;     /* usernames don't match */
112         }
113       else                      /* NOT *acc */
114         {
115           /* If password was given, use it.  The account is l->acc.  */
116           *acc = l->acc;
117           if (l->passwd)
118             *passwd = l->passwd;
119         }
120       return;
121     }
122   else
123     {
124       if (!slack_default)
125         return;
126       if (*acc)
127         return;
128       /* Try looking for the default account.  */
129       for (l = netrc_list; l; l = l->next)
130         if (!l->host)
131           break;
132       if (!l)
133         return;
134       *acc = l->acc;
135       if (!*passwd)
136         *passwd = l->passwd;
137       return;
138     }
139 }
140
141
142 #ifdef STANDALONE
143 /* Normally, these functions would be defined by your package.  */
144 # define xmalloc malloc
145 # define xfree free
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; also, stop processing if
291              the above `while' merely skipped trailing whitespace.  */
292           if (*p == '#' || !*p)
293             break;
294
295           /* If the token starts with quotation mark, note this fact,
296              and squash the quotation character */
297           if (*p == '"'){
298             quote = 1;
299             shift_left (p);
300           }
301
302           tok = p;
303
304           /* Find the end of the token, handling quotes and escapes.  */
305           while (*p && (quote ? *p != '"' : !ISSPACE (*p))){
306             if (*p == '\\')
307               shift_left (p);
308             p ++;
309           }
310
311           /* if field was quoted, squash the trailing quotation mark */
312           if (quote)
313             shift_left(p);
314
315           /* Null-terminate the token, if it isn't already.  */
316           if (*p)
317             *p ++ = '\0';
318
319           switch (last_token)
320             {
321             case tok_login:
322               if (current)
323                 current->acc = xstrdup (tok);
324               else
325                 premature_token = "login";
326               break;
327
328             case tok_machine:
329               /* Start a new machine entry.  */
330               maybe_add_to_list (&current, &retval);
331               current->host = xstrdup (tok);
332               break;
333
334             case tok_password:
335               if (current)
336                 current->passwd = xstrdup (tok);
337               else
338                 premature_token = "password";
339               break;
340
341               /* We handle most of tok_macdef above.  */
342             case tok_macdef:
343               if (!current)
344                 premature_token = "macdef";
345               break;
346
347               /* We don't handle the account keyword at all.  */
348             case tok_account:
349               if (!current)
350                 premature_token = "account";
351               break;
352
353               /* We handle tok_nothing below this switch.  */
354             case tok_nothing:
355               break;
356             }
357
358           if (premature_token)
359             {
360               fprintf (stderr, _("\
361 %s: %s:%d: warning: \"%s\" token appears before any machine name\n"),
362                        exec_name, path, ln, premature_token);
363               premature_token = NULL;
364             }
365
366           if (last_token != tok_nothing)
367             /* We got a value, so reset the token state.  */
368             last_token = tok_nothing;
369           else
370             {
371               /* Fetch the next token.  */
372               if (!strcmp (tok, "account"))
373                 last_token = tok_account;
374               else if (!strcmp (tok, "default"))
375                 {
376                   maybe_add_to_list (&current, &retval);
377                 }
378               else if (!strcmp (tok, "login"))
379                 last_token = tok_login;
380
381               else if (!strcmp (tok, "macdef"))
382                 last_token = tok_macdef;
383
384               else if (!strcmp (tok, "machine"))
385                 last_token = tok_machine;
386
387               else if (!strcmp (tok, "password"))
388                 last_token = tok_password;
389
390               else
391                 fprintf (stderr, _("%s: %s:%d: unknown token \"%s\"\n"),
392                          exec_name, path, ln, tok);
393             }
394         }
395
396       xfree (line);
397     }
398
399   fclose (fp);
400
401   /* Finalize the last machine entry we found.  */
402   maybe_add_to_list (&current, &retval);
403   xfree (current);
404
405   /* Reverse the order of the list so that it appears in file order.  */
406   current = retval;
407   retval = NULL;
408   while (current)
409     {
410       acc_t *saved_reference;
411
412       /* Change the direction of the pointers.  */
413       saved_reference = current->next;
414       current->next = retval;
415
416       /* Advance to the next node.  */
417       retval = current;
418       current = saved_reference;
419     }
420
421   return retval;
422 }
423
424
425 /* Free a netrc list.  */
426 void
427 free_netrc(acc_t *l)
428 {
429   acc_t *t;
430
431   while (l)
432     {
433       t = l->next;
434       FREE_MAYBE (l->acc);
435       FREE_MAYBE (l->passwd);
436       FREE_MAYBE (l->host);
437       xfree (l);
438       l = t;
439     }
440 }
441
442 #ifdef STANDALONE
443 #include <sys/types.h>
444 #include <sys/stat.h>
445
446 int
447 main (int argc, char **argv)
448 {
449   struct stat sb;
450   char *program_name, *file, *target;
451   acc_t *head, *a;
452
453   if (argc < 2 || argc > 3)
454     {
455       fprintf (stderr, _("Usage: %s NETRC [HOSTNAME]\n"), argv[0]);
456       exit (1);
457     }
458
459   program_name = argv[0];
460   file = argv[1];
461   target = argv[2];
462
463   if (stat (file, &sb))
464     {
465       fprintf (stderr, _("%s: cannot stat %s: %s\n"), argv[0], file,
466                strerror (errno));
467       exit (1);
468     }
469
470   head = parse_netrc (file);
471   a = head;
472   while (a)
473     {
474       /* Skip if we have a target and this isn't it.  */
475       if (target && a->host && strcmp (target, a->host))
476         {
477           a = a->next;
478           continue;
479         }
480
481       if (!target)
482         {
483           /* Print the host name if we have no target.  */
484           if (a->host)
485             fputs (a->host, stdout);
486           else
487             fputs ("DEFAULT", stdout);
488
489           fputc (' ', stdout);
490         }
491
492       /* Print the account name.  */
493       fputs (a->acc, stdout);
494
495       if (a->passwd)
496         {
497           /* Print the password, if there is any.  */
498           fputc (' ', stdout);
499           fputs (a->passwd, stdout);
500         }
501
502       fputc ('\n', stdout);
503
504       /* Exit if we found the target.  */
505       if (target)
506         exit (0);
507       a = a->next;
508     }
509
510   /* Exit with failure if we had a target, success otherwise.  */
511   if (target)
512     exit (1);
513
514   exit (0);
515 }
516 #endif /* STANDALONE */