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 "strtok.h"
35 #include "strcase.h"
36 #include "curl_get_line.h"
37
38 /* The last 3 #include files should be in this order */
39 #include "curl_printf.h"
40 #include "curl_memory.h"
41 #include "memdebug.h"
42
43 /* Get user and password from .netrc when given a machine name */
44
45 enum host_lookup_state {
46 NOTHING,
47 HOSTFOUND, /* the 'machine' keyword was found */
48 HOSTVALID, /* this is "our" machine! */
49 MACDEF
50 };
51
52 #define NETRC_FILE_MISSING 1
53 #define NETRC_FAILED -1
54 #define NETRC_SUCCESS 0
55
56 #define MAX_NETRC_LINE 4096
57
58 /*
59 * Returns zero on success.
60 */
parsenetrc(const char * host,char ** loginp,char ** passwordp,char * netrcfile)61 static int parsenetrc(const char *host,
62 char **loginp,
63 char **passwordp,
64 char *netrcfile)
65 {
66 FILE *file;
67 int retcode = NETRC_FILE_MISSING;
68 char *login = *loginp;
69 char *password = *passwordp;
70 bool specific_login = (login && *login != 0);
71 bool login_alloc = FALSE;
72 bool password_alloc = FALSE;
73 enum host_lookup_state state = NOTHING;
74
75 char state_login = 0; /* Found a login keyword */
76 char state_password = 0; /* Found a password keyword */
77 int state_our_login = TRUE; /* With specific_login, found *our* login
78 name (or login-less line) */
79
80 DEBUGASSERT(netrcfile);
81
82 file = fopen(netrcfile, FOPEN_READTEXT);
83 if(file) {
84 bool done = FALSE;
85 struct dynbuf buf;
86 Curl_dyn_init(&buf, MAX_NETRC_LINE);
87
88 while(!done && Curl_get_line(&buf, file)) {
89 char *tok;
90 char *tok_end;
91 bool quoted;
92 char *netrcbuffer = Curl_dyn_ptr(&buf);
93 if(state == MACDEF) {
94 if((netrcbuffer[0] == '\n') || (netrcbuffer[0] == '\r'))
95 state = NOTHING;
96 else
97 continue;
98 }
99 tok = netrcbuffer;
100 while(tok) {
101 while(ISBLANK(*tok))
102 tok++;
103 /* tok is first non-space letter */
104 if(!*tok || (*tok == '#'))
105 /* end of line or the rest is a comment */
106 break;
107
108 /* leading double-quote means quoted string */
109 quoted = (*tok == '\"');
110
111 tok_end = tok;
112 if(!quoted) {
113 while(!ISSPACE(*tok_end))
114 tok_end++;
115 *tok_end = 0;
116 }
117 else {
118 bool escape = FALSE;
119 bool endquote = FALSE;
120 char *store = tok;
121 tok_end++; /* pass the leading quote */
122 while(*tok_end) {
123 char s = *tok_end;
124 if(escape) {
125 escape = FALSE;
126 switch(s) {
127 case 'n':
128 s = '\n';
129 break;
130 case 'r':
131 s = '\r';
132 break;
133 case 't':
134 s = '\t';
135 break;
136 }
137 }
138 else if(s == '\\') {
139 escape = TRUE;
140 tok_end++;
141 continue;
142 }
143 else if(s == '\"') {
144 tok_end++; /* pass the ending quote */
145 endquote = TRUE;
146 break;
147 }
148 *store++ = s;
149 tok_end++;
150 }
151 *store = 0;
152 if(escape || !endquote) {
153 /* bad syntax, get out */
154 retcode = NETRC_FAILED;
155 goto out;
156 }
157 }
158
159 if((login && *login) && (password && *password)) {
160 done = TRUE;
161 break;
162 }
163
164 switch(state) {
165 case NOTHING:
166 if(strcasecompare("macdef", tok)) {
167 /* Define a macro. A macro is defined with the specified name; its
168 contents begin with the next .netrc line and continue until a
169 null line (consecutive new-line characters) is encountered. */
170 state = MACDEF;
171 }
172 else if(strcasecompare("machine", tok)) {
173 /* the next tok is the machine name, this is in itself the
174 delimiter that starts the stuff entered for this machine,
175 after this we need to search for 'login' and
176 'password'. */
177 state = HOSTFOUND;
178 }
179 else if(strcasecompare("default", tok)) {
180 state = HOSTVALID;
181 retcode = NETRC_SUCCESS; /* we did find our host */
182 }
183 break;
184 case MACDEF:
185 if(!strlen(tok)) {
186 state = NOTHING;
187 }
188 break;
189 case HOSTFOUND:
190 if(strcasecompare(host, tok)) {
191 /* and yes, this is our host! */
192 state = HOSTVALID;
193 retcode = NETRC_SUCCESS; /* we did find our host */
194 }
195 else
196 /* not our host */
197 state = NOTHING;
198 break;
199 case HOSTVALID:
200 /* we are now parsing sub-keywords concerning "our" host */
201 if(state_login) {
202 if(specific_login) {
203 state_our_login = !Curl_timestrcmp(login, tok);
204 }
205 else if(!login || Curl_timestrcmp(login, tok)) {
206 if(login_alloc) {
207 free(login);
208 login_alloc = FALSE;
209 }
210 login = strdup(tok);
211 if(!login) {
212 retcode = NETRC_FAILED; /* allocation failed */
213 goto out;
214 }
215 login_alloc = TRUE;
216 }
217 state_login = 0;
218 }
219 else if(state_password) {
220 if((state_our_login || !specific_login)
221 && (!password || Curl_timestrcmp(password, tok))) {
222 if(password_alloc) {
223 free(password);
224 password_alloc = FALSE;
225 }
226 password = strdup(tok);
227 if(!password) {
228 retcode = NETRC_FAILED; /* allocation failed */
229 goto out;
230 }
231 password_alloc = TRUE;
232 }
233 state_password = 0;
234 }
235 else if(strcasecompare("login", tok))
236 state_login = 1;
237 else if(strcasecompare("password", tok))
238 state_password = 1;
239 else if(strcasecompare("machine", tok)) {
240 /* ok, there's machine here go => */
241 state = HOSTFOUND;
242 state_our_login = FALSE;
243 }
244 else if(strcasecompare("default", tok)) {
245 state = HOSTVALID;
246 retcode = NETRC_SUCCESS; /* we did find our host */
247 Curl_safefree(password);
248 if(!specific_login)
249 if(login_alloc) {
250 free(login);
251 login_alloc = FALSE;
252 }
253 }
254 break;
255 } /* switch (state) */
256 tok = ++tok_end;
257 }
258 } /* while Curl_get_line() */
259
260 out:
261 Curl_dyn_free(&buf);
262 if(!retcode) {
263 if(!password && state_our_login) {
264 /* success without a password, set a blank one */
265 password = strdup("");
266 if(!password)
267 retcode = 1; /* out of memory */
268 }
269 else if(!login && !password)
270 /* a default with no credentials */
271 retcode = NETRC_FILE_MISSING;
272 }
273 if(!retcode) {
274 /* success */
275 if(login_alloc) {
276 if(*loginp)
277 free(*loginp);
278 *loginp = login;
279 }
280 if(password_alloc) {
281 if(*passwordp)
282 free(*passwordp);
283 *passwordp = password;
284 }
285 }
286 else {
287 if(login_alloc)
288 free(login);
289 if(password_alloc)
290 free(password);
291 }
292 fclose(file);
293 }
294
295 return retcode;
296 }
297
298 /*
299 * @unittest: 1304
300 *
301 * *loginp and *passwordp MUST be allocated if they aren't NULL when passed
302 * in.
303 */
Curl_parsenetrc(const char * host,char ** loginp,char ** passwordp,char * netrcfile)304 int Curl_parsenetrc(const char *host, char **loginp, char **passwordp,
305 char *netrcfile)
306 {
307 int retcode = 1;
308 char *filealloc = NULL;
309
310 if(!netrcfile) {
311 #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
312 char pwbuf[1024];
313 #endif
314 char *home = NULL;
315 char *homea = curl_getenv("HOME"); /* portable environment reader */
316 if(homea) {
317 home = homea;
318 #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
319 }
320 else {
321 struct passwd pw, *pw_res;
322 if(!getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res)
323 && pw_res) {
324 home = pw.pw_dir;
325 }
326 #elif defined(HAVE_GETPWUID) && defined(HAVE_GETEUID)
327 }
328 else {
329 struct passwd *pw;
330 pw = getpwuid(geteuid());
331 if(pw) {
332 home = pw->pw_dir;
333 }
334 #elif defined(_WIN32)
335 }
336 else {
337 homea = curl_getenv("USERPROFILE");
338 if(homea) {
339 home = homea;
340 }
341 #endif
342 }
343
344 if(!home)
345 return retcode; /* no home directory found (or possibly out of
346 memory) */
347
348 filealloc = curl_maprintf("%s%s.netrc", home, DIR_CHAR);
349 if(!filealloc) {
350 free(homea);
351 return -1;
352 }
353 retcode = parsenetrc(host, loginp, passwordp, filealloc);
354 free(filealloc);
355 #ifdef _WIN32
356 if(retcode == NETRC_FILE_MISSING) {
357 /* fallback to the old-style "_netrc" file */
358 filealloc = curl_maprintf("%s%s_netrc", home, DIR_CHAR);
359 if(!filealloc) {
360 free(homea);
361 return -1;
362 }
363 retcode = parsenetrc(host, loginp, passwordp, filealloc);
364 free(filealloc);
365 }
366 #endif
367 free(homea);
368 }
369 else
370 retcode = parsenetrc(host, loginp, passwordp, netrcfile);
371 return retcode;
372 }
373
374 #endif
375