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