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