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