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