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