xref: /curl/lib/netrc.c (revision 4b07b7eb)
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  * SPDX-License-Identifier: curl
22  *
23  ***************************************************************************/
24 
25 #include "curl_setup.h"
26 #ifndef CURL_DISABLE_NETRC
27 
28 #ifdef HAVE_PWD_H
29 #include <pwd.h>
30 #endif
31 
32 #include <curl/curl.h>
33 #include "netrc.h"
34 #include "strcase.h"
35 #include "curl_get_line.h"
36 
37 /* The last 3 #include files should be in this order */
38 #include "curl_printf.h"
39 #include "curl_memory.h"
40 #include "memdebug.h"
41 
42 /* Get user and password from .netrc when given a machine name */
43 
44 enum host_lookup_state {
45   NOTHING,
46   HOSTFOUND,    /* the 'machine' keyword was found */
47   HOSTVALID,    /* this is "our" machine! */
48   MACDEF
49 };
50 
51 enum found_state {
52   NONE,
53   LOGIN,
54   PASSWORD
55 };
56 
57 #define FOUND_LOGIN    1
58 #define FOUND_PASSWORD 2
59 
60 #define NETRC_FILE_MISSING 1
61 #define NETRC_FAILED -1
62 #define NETRC_SUCCESS 0
63 
64 #define MAX_NETRC_LINE 16384
65 #define MAX_NETRC_FILE (128*1024)
66 #define MAX_NETRC_TOKEN 4096
67 
file2memory(const char * filename,struct dynbuf * filebuf)68 static CURLcode file2memory(const char *filename, struct dynbuf *filebuf)
69 {
70   CURLcode result = CURLE_OK;
71   FILE *file = fopen(filename, FOPEN_READTEXT);
72   struct dynbuf linebuf;
73   Curl_dyn_init(&linebuf, MAX_NETRC_LINE);
74 
75   if(file) {
76     while(Curl_get_line(&linebuf, file)) {
77       const char *line = Curl_dyn_ptr(&linebuf);
78       /* skip comments on load */
79       while(ISBLANK(*line))
80         line++;
81       if(*line == '#')
82         continue;
83       result = Curl_dyn_add(filebuf, line);
84       if(result)
85         goto done;
86     }
87   }
88 done:
89   Curl_dyn_free(&linebuf);
90   if(file)
91     fclose(file);
92   return result;
93 }
94 
95 /*
96  * Returns zero on success.
97  */
parsenetrc(struct store_netrc * store,const char * host,char ** loginp,char ** passwordp,const char * netrcfile)98 static int parsenetrc(struct store_netrc *store,
99                       const char *host,
100                       char **loginp, /* might point to a username */
101                       char **passwordp,
102                       const char *netrcfile)
103 {
104   int retcode = NETRC_FILE_MISSING;
105   char *login = *loginp;
106   char *password = NULL;
107   bool specific_login = !!login; /* points to something */
108   enum host_lookup_state state = NOTHING;
109   enum found_state keyword = NONE;
110   unsigned char found = 0; /* login + password found bits, as they can come in
111                               any order */
112   bool our_login = FALSE;  /* found our login name */
113   bool done = FALSE;
114   char *netrcbuffer;
115   struct dynbuf token;
116   struct dynbuf *filebuf = &store->filebuf;
117   DEBUGASSERT(!*passwordp);
118   Curl_dyn_init(&token, MAX_NETRC_TOKEN);
119 
120   if(!store->loaded) {
121     if(file2memory(netrcfile, filebuf))
122       return NETRC_FAILED;
123     store->loaded = TRUE;
124   }
125 
126   netrcbuffer = Curl_dyn_ptr(filebuf);
127 
128   while(!done) {
129     char *tok = netrcbuffer;
130     while(tok && !done) {
131       char *tok_end;
132       bool quoted;
133       Curl_dyn_reset(&token);
134       while(ISBLANK(*tok))
135         tok++;
136       /* tok is first non-space letter */
137       if(state == MACDEF) {
138         if((*tok == '\n') || (*tok == '\r'))
139           state = NOTHING; /* end of macro definition */
140       }
141 
142       if(!*tok || (*tok == '\n'))
143         /* end of line  */
144         break;
145 
146       /* leading double-quote means quoted string */
147       quoted = (*tok == '\"');
148 
149       tok_end = tok;
150       if(!quoted) {
151         size_t len = 0;
152         while(!ISSPACE(*tok_end)) {
153           tok_end++;
154           len++;
155         }
156         if(!len || Curl_dyn_addn(&token, tok, len)) {
157           retcode = NETRC_FAILED;
158           goto out;
159         }
160       }
161       else {
162         bool escape = FALSE;
163         bool endquote = FALSE;
164         tok_end++; /* pass the leading quote */
165         while(*tok_end) {
166           char s = *tok_end;
167           if(escape) {
168             escape = FALSE;
169             switch(s) {
170             case 'n':
171               s = '\n';
172               break;
173             case 'r':
174               s = '\r';
175               break;
176             case 't':
177               s = '\t';
178               break;
179             }
180           }
181           else if(s == '\\') {
182             escape = TRUE;
183             tok_end++;
184             continue;
185           }
186           else if(s == '\"') {
187             tok_end++; /* pass the ending quote */
188             endquote = TRUE;
189             break;
190           }
191           if(Curl_dyn_addn(&token, &s, 1)) {
192             retcode = NETRC_FAILED;
193             goto out;
194           }
195           tok_end++;
196         }
197         if(escape || !endquote) {
198           /* bad syntax, get out */
199           retcode = NETRC_FAILED;
200           goto out;
201         }
202       }
203 
204       tok = Curl_dyn_ptr(&token);
205 
206       switch(state) {
207       case NOTHING:
208         if(strcasecompare("macdef", tok))
209           /* Define a macro. A macro is defined with the specified name; its
210              contents begin with the next .netrc line and continue until a
211              null line (consecutive new-line characters) is encountered. */
212           state = MACDEF;
213         else if(strcasecompare("machine", tok)) {
214           /* the next tok is the machine name, this is in itself the delimiter
215              that starts the stuff entered for this machine, after this we
216              need to search for 'login' and 'password'. */
217           state = HOSTFOUND;
218           keyword = NONE;
219           found = 0;
220           our_login = FALSE;
221           Curl_safefree(password);
222           if(!specific_login)
223             Curl_safefree(login);
224         }
225         else if(strcasecompare("default", tok)) {
226           state = HOSTVALID;
227           retcode = NETRC_SUCCESS; /* we did find our host */
228         }
229         break;
230       case MACDEF:
231         if(!*tok)
232           state = NOTHING;
233         break;
234       case HOSTFOUND:
235         if(strcasecompare(host, tok)) {
236           /* and yes, this is our host! */
237           state = HOSTVALID;
238           retcode = NETRC_SUCCESS; /* we did find our host */
239         }
240         else
241           /* not our host */
242           state = NOTHING;
243         break;
244       case HOSTVALID:
245         /* we are now parsing sub-keywords concerning "our" host */
246         if(keyword == LOGIN) {
247           if(specific_login)
248             our_login = !Curl_timestrcmp(login, tok);
249           else {
250             our_login = TRUE;
251             free(login);
252             login = strdup(tok);
253             if(!login) {
254               retcode = NETRC_FAILED; /* allocation failed */
255               goto out;
256             }
257           }
258           found |= FOUND_LOGIN;
259           keyword = NONE;
260         }
261         else if(keyword == PASSWORD) {
262           free(password);
263           password = strdup(tok);
264           if(!password) {
265             retcode = NETRC_FAILED; /* allocation failed */
266             goto out;
267           }
268           found |= FOUND_PASSWORD;
269           keyword = NONE;
270         }
271         else if(strcasecompare("login", tok))
272           keyword = LOGIN;
273         else if(strcasecompare("password", tok))
274           keyword = PASSWORD;
275         else if(strcasecompare("machine", tok)) {
276           /* a new machine here */
277           state = HOSTFOUND;
278           keyword = NONE;
279           found = 0;
280           Curl_safefree(password);
281           if(!specific_login)
282             Curl_safefree(login);
283         }
284         else if(strcasecompare("default", tok)) {
285           state = HOSTVALID;
286           retcode = NETRC_SUCCESS; /* we did find our host */
287           Curl_safefree(password);
288           if(!specific_login)
289             Curl_safefree(login);
290         }
291         if((found == (FOUND_PASSWORD|FOUND_LOGIN)) && our_login) {
292           done = TRUE;
293           break;
294         }
295         break;
296       } /* switch (state) */
297       tok = ++tok_end;
298     }
299     if(!done) {
300       char *nl = NULL;
301       if(tok)
302         nl = strchr(tok, '\n');
303       if(!nl)
304         break;
305       /* point to next line */
306       netrcbuffer = &nl[1];
307     }
308   } /* while !done */
309 
310 out:
311   Curl_dyn_free(&token);
312   if(!retcode && !password && our_login) {
313     /* success without a password, set a blank one */
314     password = strdup("");
315     if(!password)
316       retcode = 1; /* out of memory */
317   }
318   if(!retcode) {
319     /* success */
320     if(!specific_login)
321       *loginp = login;
322     *passwordp = password;
323   }
324   else {
325     Curl_dyn_free(filebuf);
326     if(!specific_login)
327       free(login);
328     free(password);
329   }
330 
331   return retcode;
332 }
333 
334 /*
335  * @unittest: 1304
336  *
337  * *loginp and *passwordp MUST be allocated if they are not NULL when passed
338  * in.
339  */
Curl_parsenetrc(struct store_netrc * store,const char * host,char ** loginp,char ** passwordp,char * netrcfile)340 int Curl_parsenetrc(struct store_netrc *store, const char *host,
341                     char **loginp, char **passwordp,
342                     char *netrcfile)
343 {
344   int retcode = 1;
345   char *filealloc = NULL;
346 
347   if(!netrcfile) {
348 #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
349     char pwbuf[1024];
350 #endif
351     char *home = NULL;
352     char *homea = curl_getenv("HOME"); /* portable environment reader */
353     if(homea) {
354       home = homea;
355 #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
356     }
357     else {
358       struct passwd pw, *pw_res;
359       if(!getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res)
360          && pw_res) {
361         home = pw.pw_dir;
362       }
363 #elif defined(HAVE_GETPWUID) && defined(HAVE_GETEUID)
364     }
365     else {
366       struct passwd *pw;
367       pw = getpwuid(geteuid());
368       if(pw) {
369         home = pw->pw_dir;
370       }
371 #elif defined(_WIN32)
372     }
373     else {
374       homea = curl_getenv("USERPROFILE");
375       if(homea) {
376         home = homea;
377       }
378 #endif
379     }
380 
381     if(!home)
382       return retcode; /* no home directory found (or possibly out of
383                          memory) */
384 
385     filealloc = aprintf("%s%s.netrc", home, DIR_CHAR);
386     if(!filealloc) {
387       free(homea);
388       return -1;
389     }
390     retcode = parsenetrc(store, host, loginp, passwordp, filealloc);
391     free(filealloc);
392 #ifdef _WIN32
393     if(retcode == NETRC_FILE_MISSING) {
394       /* fallback to the old-style "_netrc" file */
395       filealloc = aprintf("%s%s_netrc", home, DIR_CHAR);
396       if(!filealloc) {
397         free(homea);
398         return -1;
399       }
400       retcode = parsenetrc(store, host, loginp, passwordp, filealloc);
401       free(filealloc);
402     }
403 #endif
404     free(homea);
405   }
406   else
407     retcode = parsenetrc(store, host, loginp, passwordp, netrcfile);
408   return retcode;
409 }
410 
Curl_netrc_init(struct store_netrc * s)411 void Curl_netrc_init(struct store_netrc *s)
412 {
413   Curl_dyn_init(&s->filebuf, MAX_NETRC_FILE);
414   s->loaded = FALSE;
415 }
Curl_netrc_cleanup(struct store_netrc * s)416 void Curl_netrc_cleanup(struct store_netrc *s)
417 {
418   Curl_dyn_free(&s->filebuf);
419   s->loaded = FALSE;
420 }
421 #endif
422