1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <[email protected]>, 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 NETRC_FILE_MISSING 1
58 #define NETRC_FAILED -1
59 #define NETRC_SUCCESS 0
60
61 #define MAX_NETRC_LINE 4096
62 #define MAX_NETRC_FILE (64*1024)
63 #define MAX_NETRC_TOKEN 128
64
file2memory(const char * filename,struct dynbuf * filebuf)65 static CURLcode file2memory(const char *filename, struct dynbuf *filebuf)
66 {
67 CURLcode result = CURLE_OK;
68 FILE *file = fopen(filename, FOPEN_READTEXT);
69 struct dynbuf linebuf;
70 Curl_dyn_init(&linebuf, MAX_NETRC_LINE);
71
72 if(file) {
73 while(Curl_get_line(&linebuf, file)) {
74 const char *line = Curl_dyn_ptr(&linebuf);
75 /* skip comments on load */
76 while(ISBLANK(*line))
77 line++;
78 if(*line == '#')
79 continue;
80 result = Curl_dyn_add(filebuf, line);
81 if(result)
82 goto done;
83 }
84 }
85 done:
86 Curl_dyn_free(&linebuf);
87 if(file)
88 fclose(file);
89 return result;
90 }
91
92 /*
93 * Returns zero on success.
94 */
parsenetrc(struct store_netrc * store,const char * host,char ** loginp,char ** passwordp,const char * netrcfile)95 static int parsenetrc(struct store_netrc *store,
96 const char *host,
97 char **loginp,
98 char **passwordp,
99 const char *netrcfile)
100 {
101 int retcode = NETRC_FILE_MISSING;
102 char *login = *loginp;
103 char *password = *passwordp;
104 bool specific_login = (login && *login != 0);
105 bool login_alloc = FALSE;
106 bool password_alloc = FALSE;
107 enum host_lookup_state state = NOTHING;
108 enum found_state found = NONE;
109 bool our_login = TRUE; /* With specific_login, found *our* login name (or
110 login-less line) */
111 bool done = FALSE;
112 char *netrcbuffer;
113 struct dynbuf token;
114 struct dynbuf *filebuf = &store->filebuf;
115 Curl_dyn_init(&token, MAX_NETRC_TOKEN);
116
117 if(!store->loaded) {
118 if(file2memory(netrcfile, filebuf))
119 return NETRC_FAILED;
120 store->loaded = TRUE;
121 }
122
123 netrcbuffer = Curl_dyn_ptr(filebuf);
124
125 while(!done) {
126 char *tok = netrcbuffer;
127 while(tok) {
128 char *tok_end;
129 bool quoted;
130 Curl_dyn_reset(&token);
131 while(ISBLANK(*tok))
132 tok++;
133 /* tok is first non-space letter */
134 if(state == MACDEF) {
135 if((*tok == '\n') || (*tok == '\r'))
136 state = NOTHING; /* end of macro definition */
137 }
138
139 if(!*tok || (*tok == '\n'))
140 /* end of line */
141 break;
142
143 /* leading double-quote means quoted string */
144 quoted = (*tok == '\"');
145
146 tok_end = tok;
147 if(!quoted) {
148 size_t len = 0;
149 while(!ISSPACE(*tok_end)) {
150 tok_end++;
151 len++;
152 }
153 if(!len || Curl_dyn_addn(&token, tok, len)) {
154 retcode = NETRC_FAILED;
155 goto out;
156 }
157 }
158 else {
159 bool escape = FALSE;
160 bool endquote = FALSE;
161 tok_end++; /* pass the leading quote */
162 while(*tok_end) {
163 char s = *tok_end;
164 if(escape) {
165 escape = FALSE;
166 switch(s) {
167 case 'n':
168 s = '\n';
169 break;
170 case 'r':
171 s = '\r';
172 break;
173 case 't':
174 s = '\t';
175 break;
176 }
177 }
178 else if(s == '\\') {
179 escape = TRUE;
180 tok_end++;
181 continue;
182 }
183 else if(s == '\"') {
184 tok_end++; /* pass the ending quote */
185 endquote = TRUE;
186 break;
187 }
188 if(Curl_dyn_addn(&token, &s, 1)) {
189 retcode = NETRC_FAILED;
190 goto out;
191 }
192 tok_end++;
193 }
194 if(escape || !endquote) {
195 /* bad syntax, get out */
196 retcode = NETRC_FAILED;
197 goto out;
198 }
199 }
200
201 if((login && *login) && (password && *password)) {
202 done = TRUE;
203 break;
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 else if(strcasecompare("default", tok)) {
221 state = HOSTVALID;
222 retcode = NETRC_SUCCESS; /* we did find our host */
223 }
224 break;
225 case MACDEF:
226 if(!*tok)
227 state = NOTHING;
228 break;
229 case HOSTFOUND:
230 if(strcasecompare(host, tok)) {
231 /* and yes, this is our host! */
232 state = HOSTVALID;
233 retcode = NETRC_SUCCESS; /* we did find our host */
234 }
235 else
236 /* not our host */
237 state = NOTHING;
238 break;
239 case HOSTVALID:
240 /* we are now parsing sub-keywords concerning "our" host */
241 if(found == LOGIN) {
242 if(specific_login) {
243 our_login = !Curl_timestrcmp(login, tok);
244 }
245 else if(!login || Curl_timestrcmp(login, tok)) {
246 if(login_alloc)
247 free(login);
248 login = strdup(tok);
249 if(!login) {
250 retcode = NETRC_FAILED; /* allocation failed */
251 goto out;
252 }
253 login_alloc = TRUE;
254 }
255 found = NONE;
256 }
257 else if(found == PASSWORD) {
258 if((our_login || !specific_login) &&
259 (!password || Curl_timestrcmp(password, tok))) {
260 if(password_alloc)
261 free(password);
262 password = strdup(tok);
263 if(!password) {
264 retcode = NETRC_FAILED; /* allocation failed */
265 goto out;
266 }
267 password_alloc = TRUE;
268 }
269 found = NONE;
270 }
271 else if(strcasecompare("login", tok))
272 found = LOGIN;
273 else if(strcasecompare("password", tok))
274 found = PASSWORD;
275 else if(strcasecompare("machine", tok)) {
276 /* ok, there is machine here go => */
277 state = HOSTFOUND;
278 found = NONE;
279 }
280 break;
281 } /* switch (state) */
282 tok = ++tok_end;
283 }
284 if(!done) {
285 char *nl = NULL;
286 if(tok)
287 nl = strchr(tok, '\n');
288 if(!nl)
289 break;
290 /* point to next line */
291 netrcbuffer = &nl[1];
292 }
293 } /* while !done */
294
295 out:
296 Curl_dyn_free(&token);
297 if(!retcode) {
298 /* success */
299 if(login_alloc) {
300 free(*loginp);
301 *loginp = login;
302 }
303 if(password_alloc) {
304 free(*passwordp);
305 *passwordp = password;
306 }
307 }
308 else {
309 Curl_dyn_free(filebuf);
310 if(login_alloc)
311 free(login);
312 if(password_alloc)
313 free(password);
314 }
315
316 return retcode;
317 }
318
319 /*
320 * @unittest: 1304
321 *
322 * *loginp and *passwordp MUST be allocated if they are not NULL when passed
323 * in.
324 */
Curl_parsenetrc(struct store_netrc * store,const char * host,char ** loginp,char ** passwordp,char * netrcfile)325 int Curl_parsenetrc(struct store_netrc *store, const char *host,
326 char **loginp, char **passwordp,
327 char *netrcfile)
328 {
329 int retcode = 1;
330 char *filealloc = NULL;
331
332 if(!netrcfile) {
333 #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
334 char pwbuf[1024];
335 #endif
336 char *home = NULL;
337 char *homea = curl_getenv("HOME"); /* portable environment reader */
338 if(homea) {
339 home = homea;
340 #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
341 }
342 else {
343 struct passwd pw, *pw_res;
344 if(!getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res)
345 && pw_res) {
346 home = pw.pw_dir;
347 }
348 #elif defined(HAVE_GETPWUID) && defined(HAVE_GETEUID)
349 }
350 else {
351 struct passwd *pw;
352 pw = getpwuid(geteuid());
353 if(pw) {
354 home = pw->pw_dir;
355 }
356 #elif defined(_WIN32)
357 }
358 else {
359 homea = curl_getenv("USERPROFILE");
360 if(homea) {
361 home = homea;
362 }
363 #endif
364 }
365
366 if(!home)
367 return retcode; /* no home directory found (or possibly out of
368 memory) */
369
370 filealloc = aprintf("%s%s.netrc", home, DIR_CHAR);
371 if(!filealloc) {
372 free(homea);
373 return -1;
374 }
375 retcode = parsenetrc(store, host, loginp, passwordp, filealloc);
376 free(filealloc);
377 #ifdef _WIN32
378 if(retcode == NETRC_FILE_MISSING) {
379 /* fallback to the old-style "_netrc" file */
380 filealloc = aprintf("%s%s_netrc", home, DIR_CHAR);
381 if(!filealloc) {
382 free(homea);
383 return -1;
384 }
385 retcode = parsenetrc(store, host, loginp, passwordp, filealloc);
386 free(filealloc);
387 }
388 #endif
389 free(homea);
390 }
391 else
392 retcode = parsenetrc(store, host, loginp, passwordp, netrcfile);
393 return retcode;
394 }
395
Curl_netrc_init(struct store_netrc * s)396 void Curl_netrc_init(struct store_netrc *s)
397 {
398 Curl_dyn_init(&s->filebuf, MAX_NETRC_FILE);
399 s->loaded = FALSE;
400 }
Curl_netrc_cleanup(struct store_netrc * s)401 void Curl_netrc_cleanup(struct store_netrc *s)
402 {
403 Curl_dyn_free(&s->filebuf);
404 s->loaded = FALSE;
405 }
406 #endif
407