]> sjero.net Git - wget/blob - src/netrc.c
[svn] Do away with line separator.
[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 xfree free
147 # define xstrdup strdup
148
149 /* The function reads a whole line.  It reads the line realloc-ing the
150    storage exponentially, doubling the storage after each overflow to
151    minimize the number of calls to realloc().
152
153    It is not an exemplary of correctness, since it kills off the
154    newline (and no, there is no way to know if there was a newline at
155    EOF).  */
156 # define xrealloc realloc
157 # define DYNAMIC_LINE_BUFFER 40
158
159 char *
160 read_whole_line (FILE *fp)
161 {
162   char *line;
163   int i, bufsize, c;
164
165   i = 0;
166   bufsize = DYNAMIC_LINE_BUFFER;
167   line = xmalloc(bufsize);
168   /* Construct the line.  */
169   while ((c = getc(fp)) != EOF && c != '\n')
170     {
171       if (i > bufsize - 1)
172         line = (char *)xrealloc(line, (bufsize <<= 1));
173       line[i++] = c;
174     }
175   if (c == EOF && !i)
176     {
177       xfree(line);
178       return NULL;
179     }
180
181   /* Check for overflow at zero-termination (no need to double the
182      buffer in this case.  */
183   if (i == bufsize)
184     line = (char *)xrealloc(line, i + 1);
185   line[i] = '\0';
186   return line;
187 }
188
189 #endif /* STANDALONE */
190
191 /* Maybe add NEWENTRY to the account information list, LIST.  NEWENTRY is
192    set to a ready-to-use acc_t, in any event.  */
193 static void
194 maybe_add_to_list (acc_t **newentry, acc_t **list)
195 {
196   acc_t *a, *l;
197   a = *newentry;
198   l = *list;
199
200   /* We need an account name in order to add the entry to the list.  */
201   if (a && ! a->acc)
202     {
203       /* Free any allocated space.  */
204       xfree (a->host);
205       xfree (a->acc);
206       xfree (a->passwd);
207     }
208   else
209     {
210       if (a)
211         {
212           /* Add the current machine into our list.  */
213           a->next = l;
214           l = a;
215         }
216
217       /* Allocate a new acc_t structure.  */
218       a = (acc_t *)xmalloc (sizeof (acc_t));
219     }
220
221   /* Zero the structure, so that it is ready to use.  */
222   memset (a, 0, sizeof(*a));
223
224   /* Return the new pointers.  */
225   *newentry = a;
226   *list = l;
227   return;
228 }
229
230 /* Helper function for the parser, shifts contents of
231    null-terminated string once character to the left.
232    Used in processing \ and " constructs in the netrc file */
233 static void
234 shift_left(char *string){
235   char *p;
236   
237   for (p=string; *p; ++p)
238     *p = *(p+1);
239 }
240
241 /* Parse a .netrc file (as described in the ftp(1) manual page).  */
242 static acc_t *
243 parse_netrc (const char *path)
244 {
245   FILE *fp;
246   char *line, *p, *tok, *premature_token;
247   acc_t *current, *retval;
248   int ln, quote;
249
250   /* The latest token we've seen in the file.  */
251   enum
252   {
253     tok_nothing, tok_account, tok_login, tok_macdef, tok_machine, tok_password
254   } last_token = tok_nothing;
255
256   current = retval = NULL;
257
258   fp = fopen (path, "r");
259   if (!fp)
260     {
261       fprintf (stderr, _("%s: Cannot read %s (%s).\n"), exec_name,
262                path, strerror (errno));
263       return retval;
264     }
265
266   /* Initialize the file data.  */
267   ln = 0;
268   premature_token = NULL;
269
270   /* While there are lines in the file...  */
271   while ((line = read_whole_line (fp)))
272     {
273       /* Do away with line separators. */
274       int len = strlen (line);
275       if (len && line[len - 1] == '\n')
276         line[--len] = '\0';
277       if (len && line[len - 1] == '\r')
278         line[--len] = '\0';
279
280       ln ++;
281
282       /* Parse the line.  */
283       p = line;
284       quote = 0;
285
286       /* If the line is empty, then end any macro definition.  */
287       if (last_token == tok_macdef && !*p)
288         /* End of macro if the line is empty.  */
289         last_token = tok_nothing;
290
291       /* If we are defining macros, then skip parsing the line.  */
292       while (*p && last_token != tok_macdef)
293         {
294           /* Skip any whitespace.  */
295           while (*p && ISSPACE (*p))
296             p ++;
297
298           /* Discard end-of-line comments.  */
299           if (*p == '#')
300             break;
301
302           /* If the token starts with quotation mark, note this fact,
303              and squash the quotation character */
304           if (*p == '"'){
305             quote = 1;
306             shift_left (p);
307           }
308
309           tok = p;
310
311           /* Find the end of the token, handling quotes and escapes.  */
312           while (*p && (quote ? *p != '"' : !ISSPACE (*p))){
313             if (*p == '\\')
314               shift_left (p);
315             p ++;
316           }
317
318           /* if field was quoted, squash the trailing quotation mark */
319           if (quote)
320             shift_left(p);
321
322           /* Null-terminate the token, if it isn't already.  */
323           if (*p)
324             *p ++ = '\0';
325
326           switch (last_token)
327             {
328             case tok_login:
329               if (current)
330                 current->acc = xstrdup (tok);
331               else
332                 premature_token = "login";
333               break;
334
335             case tok_machine:
336               /* Start a new machine entry.  */
337               maybe_add_to_list (&current, &retval);
338               current->host = xstrdup (tok);
339               break;
340
341             case tok_password:
342               if (current)
343                 current->passwd = xstrdup (tok);
344               else
345                 premature_token = "password";
346               break;
347
348               /* We handle most of tok_macdef above.  */
349             case tok_macdef:
350               if (!current)
351                 premature_token = "macdef";
352               break;
353
354               /* We don't handle the account keyword at all.  */
355             case tok_account:
356               if (!current)
357                 premature_token = "account";
358               break;
359
360               /* We handle tok_nothing below this switch.  */
361             case tok_nothing:
362               break;
363             }
364
365           if (premature_token)
366             {
367               fprintf (stderr, _("\
368 %s: %s:%d: warning: \"%s\" token appears before any machine name\n"),
369                        exec_name, path, ln, premature_token);
370               premature_token = NULL;
371             }
372
373           if (last_token != tok_nothing)
374             /* We got a value, so reset the token state.  */
375             last_token = tok_nothing;
376           else
377             {
378               /* Fetch the next token.  */
379               if (!strcmp (tok, "account"))
380                 last_token = tok_account;
381               else if (!strcmp (tok, "default"))
382                 {
383                   maybe_add_to_list (&current, &retval);
384                 }
385               else if (!strcmp (tok, "login"))
386                 last_token = tok_login;
387
388               else if (!strcmp (tok, "macdef"))
389                 last_token = tok_macdef;
390
391               else if (!strcmp (tok, "machine"))
392                 last_token = tok_machine;
393
394               else if (!strcmp (tok, "password"))
395                 last_token = tok_password;
396
397               else
398                 fprintf (stderr, _("%s: %s:%d: unknown token \"%s\"\n"),
399                          exec_name, path, ln, tok);
400             }
401         }
402
403       xfree (line);
404     }
405
406   fclose (fp);
407
408   /* Finalize the last machine entry we found.  */
409   maybe_add_to_list (&current, &retval);
410   xfree (current);
411
412   /* Reverse the order of the list so that it appears in file order.  */
413   current = retval;
414   retval = NULL;
415   while (current)
416     {
417       acc_t *saved_reference;
418
419       /* Change the direction of the pointers.  */
420       saved_reference = current->next;
421       current->next = retval;
422
423       /* Advance to the next node.  */
424       retval = current;
425       current = saved_reference;
426     }
427
428   return retval;
429 }
430
431
432 /* Free a netrc list.  */
433 void
434 free_netrc(acc_t *l)
435 {
436   acc_t *t;
437
438   while (l)
439     {
440       t = l->next;
441       FREE_MAYBE (l->acc);
442       FREE_MAYBE (l->passwd);
443       FREE_MAYBE (l->host);
444       xfree (l);
445       l = t;
446     }
447 }
448
449 #ifdef STANDALONE
450 #include <sys/types.h>
451 #include <sys/stat.h>
452
453 int
454 main (int argc, char **argv)
455 {
456   struct stat sb;
457   char *program_name, *file, *target;
458   acc_t *head, *a;
459
460   if (argc < 2 || argc > 3)
461     {
462       fprintf (stderr, _("Usage: %s NETRC [HOSTNAME]\n"), argv[0]);
463       exit (1);
464     }
465
466   program_name = argv[0];
467   file = argv[1];
468   target = argv[2];
469
470   if (stat (file, &sb))
471     {
472       fprintf (stderr, _("%s: cannot stat %s: %s\n"), argv[0], file,
473                strerror (errno));
474       exit (1);
475     }
476
477   head = parse_netrc (file);
478   a = head;
479   while (a)
480     {
481       /* Skip if we have a target and this isn't it.  */
482       if (target && a->host && strcmp (target, a->host))
483         {
484           a = a->next;
485           continue;
486         }
487
488       if (!target)
489         {
490           /* Print the host name if we have no target.  */
491           if (a->host)
492             fputs (a->host, stdout);
493           else
494             fputs ("DEFAULT", stdout);
495
496           fputc (' ', stdout);
497         }
498
499       /* Print the account name.  */
500       fputs (a->acc, stdout);
501
502       if (a->passwd)
503         {
504           /* Print the password, if there is any.  */
505           fputc (' ', stdout);
506           fputs (a->passwd, stdout);
507         }
508
509       fputc ('\n', stdout);
510
511       /* Exit if we found the target.  */
512       if (target)
513         exit (0);
514       a = a->next;
515     }
516
517   /* Exit with failure if we had a target, success otherwise.  */
518   if (target)
519     exit (1);
520
521   exit (0);
522 }
523 #endif /* STANDALONE */