Fix compiler warnings
[wget] / src / netrc.c
1 /* Read and parse the .netrc file to get hosts, accounts, and passwords.
2    Copyright (C) 1996, 2007, 2008, 2009, 2010, 2011 Free Software
3    Foundation, Inc.
4
5 This file is part of GNU Wget.
6
7 GNU Wget is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
11
12 GNU Wget is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Wget.  If not, see <http://www.gnu.org/licenses/>.
19
20 Additional permission under GNU GPL version 3 section 7
21
22 If you modify this program, or any covered work, by linking or
23 combining it with the OpenSSL project's OpenSSL library (or a
24 modified version of that library), containing parts covered by the
25 terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
26 grants you additional permission to convey the resulting work.
27 Corresponding Source for a non-source form of such a combination
28 shall include the source code for the parts of OpenSSL used as well
29 as that of the covered work.  */
30
31 /* This file used to be kept in synch with the code in Fetchmail, but
32    the latter has diverged since.  */
33
34 #include "wget.h"
35
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <errno.h>
40
41 #include "utils.h"
42 #include "netrc.h"
43 #include "init.h"
44
45 #define NETRC_FILE_NAME ".netrc"
46
47 static acc_t *netrc_list;
48
49 static acc_t *parse_netrc (const char *);
50
51 void
52 netrc_cleanup(void)
53 {
54   free_netrc (netrc_list);
55 }
56
57 /* Return the correct user and password, given the host, user (as
58    given in the URL), and password (as given in the URL).  May return
59    NULL.
60
61    If SLACK_DEFAULT is set, allow looking for a "default" account.
62    You will typically turn it off for HTTP.  */
63 void
64 search_netrc (const char *host, const char **acc, const char **passwd,
65               int slack_default)
66 {
67   acc_t *l;
68   static int processed_netrc;
69
70   if (!opt.netrc)
71     return;
72   /* Find ~/.netrc.  */
73   if (!processed_netrc)
74     {
75 #ifdef __VMS
76
77       int err;
78       struct_stat buf;
79       char *path = "SYS$LOGIN:.netrc";
80
81       netrc_list = NULL;
82       processed_netrc = 1;
83
84       err = stat (path, &buf);
85       if (err == 0)
86         netrc_list = parse_netrc (path);
87
88 #else /* def __VMS */
89
90       char *home = home_dir ();
91
92       netrc_list = NULL;
93       processed_netrc = 1;
94       if (home)
95         {
96           int err;
97           struct_stat buf;
98           char *path = (char *)alloca (strlen (home) + 1
99                                        + strlen (NETRC_FILE_NAME) + 1);
100           sprintf (path, "%s/%s", home, NETRC_FILE_NAME);
101           xfree (home);
102           err = stat (path, &buf);
103           if (err == 0)
104             netrc_list = parse_netrc (path);
105         }
106
107 #endif /* def __VMS [else] */
108     }
109   /* If nothing to do...  */
110   if (!netrc_list)
111     return;
112   /* Acc and password found; all OK.  */
113   if (*acc && *passwd)
114     return;
115   /* Some data not given -- try finding the host.  */
116   for (l = netrc_list; l; l = l->next)
117     {
118       if (!l->host)
119         continue;
120       else if (!strcasecmp (l->host, host))
121         break;
122     }
123   if (l)
124     {
125       if (*acc)
126         {
127           /* Looking for password in .netrc.  */
128           if (!strcmp (l->acc, *acc))
129             *passwd = l->passwd; /* usernames match; password OK */
130           else
131             *passwd = NULL;     /* usernames don't match */
132         }
133       else                      /* NOT *acc */
134         {
135           /* If password was given, use it.  The account is l->acc.  */
136           *acc = l->acc;
137           if (l->passwd)
138             *passwd = l->passwd;
139         }
140       return;
141     }
142   else
143     {
144       if (!slack_default)
145         return;
146       if (*acc)
147         return;
148       /* Try looking for the default account.  */
149       for (l = netrc_list; l; l = l->next)
150         if (!l->host)
151           break;
152       if (!l)
153         return;
154       *acc = l->acc;
155       if (!*passwd)
156         *passwd = l->passwd;
157       return;
158     }
159 }
160
161
162 #ifdef STANDALONE
163
164 #include <assert.h>
165
166 /* Normally, these functions would be defined by your package.  */
167 # define xmalloc malloc
168 # define xfree free
169 # define xstrdup strdup
170
171 # define xrealloc realloc
172
173 #endif /* STANDALONE */
174
175 /* Maybe add NEWENTRY to the account information list, LIST.  NEWENTRY is
176    set to a ready-to-use acc_t, in any event.  */
177 static void
178 maybe_add_to_list (acc_t **newentry, acc_t **list)
179 {
180   acc_t *a, *l;
181   a = *newentry;
182   l = *list;
183
184   /* We need an account name in order to add the entry to the list.  */
185   if (a && ! a->acc)
186     {
187       /* Free any allocated space.  */
188       xfree_null (a->host);
189       xfree_null (a->acc);
190       xfree_null (a->passwd);
191     }
192   else
193     {
194       if (a)
195         {
196           /* Add the current machine into our list.  */
197           a->next = l;
198           l = a;
199         }
200
201       /* Allocate a new acc_t structure.  */
202       a = xmalloc (sizeof (acc_t));
203     }
204
205   /* Zero the structure, so that it is ready to use.  */
206   memset (a, 0, sizeof(*a));
207
208   /* Return the new pointers.  */
209   *newentry = a;
210   *list = l;
211   return;
212 }
213
214 /* Helper function for the parser, shifts contents of
215    null-terminated string once character to the left.
216    Used in processing \ and " constructs in the netrc file */
217 static void
218 shift_left(char *string)
219 {
220   char *p;
221
222   for (p=string; *p; ++p)
223     *p = *(p+1);
224 }
225
226 /* Parse a .netrc file (as described in the ftp(1) manual page).  */
227 static acc_t *
228 parse_netrc (const char *path)
229 {
230   FILE *fp;
231   char *line = NULL, *p, *tok;
232   const char *premature_token;
233   acc_t *current, *retval;
234   int ln, qmark;
235   size_t bufsize = 0;
236
237   /* The latest token we've seen in the file.  */
238   enum
239   {
240     tok_nothing, tok_account, tok_login, tok_macdef, tok_machine, tok_password
241   } last_token = tok_nothing;
242
243   current = retval = NULL;
244
245   fp = fopen (path, "r");
246   if (!fp)
247     {
248       fprintf (stderr, _("%s: Cannot read %s (%s).\n"), exec_name,
249                path, strerror (errno));
250       return retval;
251     }
252
253   /* Initialize the file data.  */
254   ln = 0;
255   premature_token = NULL;
256
257   /* While there are lines in the file...  */
258   while (getline (&line, &bufsize, fp) > 0)
259     {
260       ln ++;
261
262       /* Parse the line.  */
263       p = line;
264       qmark = 0;
265
266       /* Skip leading whitespace.  */
267       while (*p && c_isspace (*p))
268         p ++;
269
270       /* If the line is empty, then end any macro definition.  */
271       if (last_token == tok_macdef && !*p)
272         /* End of macro if the line is empty.  */
273         last_token = tok_nothing;
274
275       /* If we are defining macros, then skip parsing the line.  */
276       while (*p && last_token != tok_macdef)
277         {
278           /* Skip any whitespace.  */
279           while (*p && c_isspace (*p))
280             p ++;
281
282           /* Discard end-of-line comments; also, stop processing if
283              the above `while' merely skipped trailing whitespace.  */
284           if (*p == '#' || !*p)
285             break;
286
287           /* If the token starts with quotation mark, note this fact,
288              and squash the quotation character */
289           if (*p == '"'){
290             qmark = 1;
291             shift_left (p);
292           }
293
294           tok = p;
295
296           /* Find the end of the token, handling quotes and escapes.  */
297           while (*p && (qmark ? *p != '"' : !c_isspace (*p))){
298             if (*p == '\\')
299               shift_left (p);
300             p ++;
301           }
302
303           /* If field was quoted, squash the trailing quotation mark
304              and reset qmark flag.  */
305           if (qmark)
306             {
307               shift_left (p);
308               qmark = 0;
309             }
310
311           /* Null-terminate the token, if it isn't already.  */
312           if (*p)
313             *p ++ = '\0';
314
315           switch (last_token)
316             {
317             case tok_login:
318               if (current)
319                 current->acc = xstrdup (tok);
320               else
321                 premature_token = "login";
322               break;
323
324             case tok_machine:
325               /* Start a new machine entry.  */
326               maybe_add_to_list (&current, &retval);
327               current->host = xstrdup (tok);
328               break;
329
330             case tok_password:
331               if (current)
332                 current->passwd = xstrdup (tok);
333               else
334                 premature_token = "password";
335               break;
336
337               /* We handle most of tok_macdef above.  */
338             case tok_macdef:
339               if (!current)
340                 premature_token = "macdef";
341               break;
342
343               /* We don't handle the account keyword at all.  */
344             case tok_account:
345               if (!current)
346                 premature_token = "account";
347               break;
348
349               /* We handle tok_nothing below this switch.  */
350             case tok_nothing:
351               break;
352             }
353
354           if (premature_token)
355             {
356               fprintf (stderr, _("\
357 %s: %s:%d: warning: %s token appears before any machine name\n"),
358                        exec_name, path, ln, quote (premature_token));
359               premature_token = NULL;
360             }
361
362           if (last_token != tok_nothing)
363             /* We got a value, so reset the token state.  */
364             last_token = tok_nothing;
365           else
366             {
367               /* Fetch the next token.  */
368               if (!strcmp (tok, "account"))
369                 last_token = tok_account;
370               else if (!strcmp (tok, "default"))
371                 {
372                   maybe_add_to_list (&current, &retval);
373                 }
374               else if (!strcmp (tok, "login"))
375                 last_token = tok_login;
376
377               else if (!strcmp (tok, "macdef"))
378                 last_token = tok_macdef;
379
380               else if (!strcmp (tok, "machine"))
381                 last_token = tok_machine;
382
383               else if (!strcmp (tok, "password"))
384                 last_token = tok_password;
385
386               else
387                 fprintf (stderr, _("%s: %s:%d: unknown token \"%s\"\n"),
388                          exec_name, path, ln, tok);
389             }
390         }
391     }
392
393   xfree (line);
394   fclose (fp);
395
396   /* Finalize the last machine entry we found.  */
397   maybe_add_to_list (&current, &retval);
398   xfree (current);
399
400   /* Reverse the order of the list so that it appears in file order.  */
401   current = retval;
402   retval = NULL;
403   while (current)
404     {
405       acc_t *saved_reference;
406
407       /* Change the direction of the pointers.  */
408       saved_reference = current->next;
409       current->next = retval;
410
411       /* Advance to the next node.  */
412       retval = current;
413       current = saved_reference;
414     }
415
416   return retval;
417 }
418
419
420 /* Free a netrc list.  */
421 void
422 free_netrc(acc_t *l)
423 {
424   acc_t *t;
425
426   while (l)
427     {
428       t = l->next;
429       xfree_null (l->acc);
430       xfree_null (l->passwd);
431       xfree_null (l->host);
432       xfree (l);
433       l = t;
434     }
435 }
436
437 #ifdef STANDALONE
438 #include <sys/types.h>
439 #include <sys/stat.h>
440
441 int
442 main (int argc, char **argv)
443 {
444   struct_stat sb;
445   char *program_name, *file, *target;
446   acc_t *head, *a;
447
448   if (argc < 2 || argc > 3)
449     {
450       fprintf (stderr, _("Usage: %s NETRC [HOSTNAME]\n"), argv[0]);
451       exit (1);
452     }
453
454   program_name = argv[0];
455   file = argv[1];
456   target = argv[2];
457
458   if (stat (file, &sb))
459     {
460       fprintf (stderr, _("%s: cannot stat %s: %s\n"), argv[0], file,
461                strerror (errno));
462       exit (1);
463     }
464
465   head = parse_netrc (file);
466   a = head;
467   while (a)
468     {
469       /* Skip if we have a target and this isn't it.  */
470       if (target && a->host && strcmp (target, a->host))
471         {
472           a = a->next;
473           continue;
474         }
475
476       if (!target)
477         {
478           /* Print the host name if we have no target.  */
479           if (a->host)
480             fputs (a->host, stdout);
481           else
482             fputs ("DEFAULT", stdout);
483
484           fputc (' ', stdout);
485         }
486
487       /* Print the account name.  */
488       fputs (a->acc, stdout);
489
490       if (a->passwd)
491         {
492           /* Print the password, if there is any.  */
493           fputc (' ', stdout);
494           fputs (a->passwd, stdout);
495         }
496
497       fputc ('\n', stdout);
498
499       /* Exit if we found the target.  */
500       if (target)
501         exit (0);
502       a = a->next;
503     }
504
505   /* Exit with failure if we had a target, success otherwise.  */
506   if (target)
507     exit (1);
508
509   exit (0);
510 }
511 #endif /* STANDALONE */