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