/*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at https://curl.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is * furnished to do so, under the terms of the COPYING file. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * * SPDX-License-Identifier: curl * ***************************************************************************/ #include "curl_setup.h" #ifndef CURL_DISABLE_NETRC #ifdef HAVE_PWD_H #ifdef __AMIGA__ #undef __NO_NET_API /* required for AmigaOS to declare getpwuid() */ #endif #include #ifdef __AMIGA__ #define __NO_NET_API #endif #endif #include "netrc.h" #include "strcase.h" #include "curl_get_line.h" #include "curlx/fopen.h" #include "curlx/strparse.h" /* Get user and password from .netrc when given a machine name */ enum host_lookup_state { NOTHING, HOSTFOUND, /* the 'machine' keyword was found */ HOSTVALID, /* this is "our" machine! */ MACDEF }; enum found_state { NONE, LOGIN, PASSWORD }; #define FOUND_LOGIN 1 #define FOUND_PASSWORD 2 #define MAX_NETRC_LINE 16384 #define MAX_NETRC_FILE (128 * 1024) #define MAX_NETRC_TOKEN 4096 /* convert a dynbuf call CURLcode error to a NETRCcode error */ #define curl2netrc(result) \ (((result) == CURLE_OUT_OF_MEMORY) ? \ NETRC_OUT_OF_MEMORY : NETRC_SYNTAX_ERROR) static NETRCcode file2memory(const char *filename, struct dynbuf *filebuf) { NETRCcode ret = NETRC_FILE_MISSING; /* if it cannot open the file */ FILE *file = curlx_fopen(filename, FOPEN_READTEXT); if(file) { curlx_struct_stat stat; if((curlx_fstat(fileno(file), &stat) == -1) || !S_ISDIR(stat.st_mode)) { CURLcode result = CURLE_OK; bool eof; struct dynbuf linebuf; curlx_dyn_init(&linebuf, MAX_NETRC_LINE); ret = NETRC_OK; do { const char *line; /* Curl_get_line always returns lines ending with a newline */ result = Curl_get_line(&linebuf, file, &eof); if(!result) { line = curlx_dyn_ptr(&linebuf); /* skip comments on load */ curlx_str_passblanks(&line); if(*line == '#') continue; result = curlx_dyn_add(filebuf, line); } if(result) { curlx_dyn_free(filebuf); ret = curl2netrc(result); break; } } while(!eof); curlx_dyn_free(&linebuf); } curlx_fclose(file); } return ret; } /* bundled parser state to keep function signatures compact */ struct netrc_state { char *login; char *password; enum host_lookup_state state; enum found_state keyword; NETRCcode retcode; unsigned char found; /* FOUND_LOGIN | FOUND_PASSWORD bits */ bool our_login; bool done; bool specific_login; }; /* * Parse a quoted token starting after the opening '"'. Handles \n, \r, \t * escape sequences. Advances *tok_endp past the closing '"'. * * Returns NETRC_OK or error. */ static NETRCcode netrc_quoted_token(const char **tok_endp, struct dynbuf *token) { bool escape = FALSE; NETRCcode rc = NETRC_SYNTAX_ERROR; const char *tok_end = *tok_endp; tok_end++; /* pass the leading quote */ while(*tok_end) { CURLcode result; char s = *tok_end; if(escape) { escape = FALSE; switch(s) { case 'n': s = '\n'; break; case 'r': s = '\r'; break; case 't': s = '\t'; break; } } else if(s == '\\') { escape = TRUE; tok_end++; continue; } else if(s == '\"') { tok_end++; /* pass the ending quote */ rc = NETRC_OK; break; } result = curlx_dyn_addn(token, &s, 1); if(result) { *tok_endp = tok_end; return curl2netrc(result); } tok_end++; } *tok_endp = tok_end; return rc; } /* * Gets the next token from the netrc buffer at *tokp. Writes the token into * the 'token' dynbuf. Advances *tok_endp past the consumed token in the input * buffer. Updates *statep for MACDEF newline handling. Sets *lineend = TRUE * when the line is exhausted. * * Returns NETRC_OK or an error code. */ static NETRCcode netrc_get_token(const char **tokp, const char **tok_endp, struct dynbuf *token, enum host_lookup_state *statep, bool *lineend) { const char *tok = *tokp; const char *tok_end; *lineend = FALSE; curlx_dyn_reset(token); curlx_str_passblanks(&tok); /* tok is first non-space letter */ if(*statep == MACDEF) { if((*tok == '\n') || (*tok == '\r')) *statep = NOTHING; /* end of macro definition */ } if(!*tok || (*tok == '\n')) { /* end of line */ *lineend = TRUE; *tokp = tok; return NETRC_OK; } tok_end = tok; if(*tok == '\"') { /* quoted string */ NETRCcode ret = netrc_quoted_token(&tok_end, token); if(ret) return ret; } else { /* unquoted token */ size_t len = 0; CURLcode result; while(*tok_end > ' ') { tok_end++; len++; } if(!len) return NETRC_SYNTAX_ERROR; result = curlx_dyn_addn(token, tok, len); if(result) return curl2netrc(result); } *tok_endp = tok_end; if(curlx_dyn_len(token)) *tokp = curlx_dyn_ptr(token); else /* set it to blank to avoid NULL */ *tokp = ""; return NETRC_OK; } /* * Reset parser for a new machine entry. Frees password and optionally login * if it was not user-specified. */ static void netrc_new_machine(struct netrc_state *ns) { ns->keyword = NONE; ns->found = 0; ns->our_login = FALSE; Curl_safefree(ns->password); if(!ns->specific_login) Curl_safefree(ns->login); } /* * Process a parsed token through the HOSTVALID state machine branch. This * handles login/password values and keyword transitions for the matched host. * * Returns NETRC_OK or an error code. */ static NETRCcode netrc_hostvalid(struct netrc_state *ns, const char *tok) { if(ns->keyword == LOGIN) { if(ns->specific_login) ns->our_login = !Curl_timestrcmp(ns->login, tok); else { ns->our_login = TRUE; curlx_free(ns->login); ns->login = curlx_strdup(tok); if(!ns->login) return NETRC_OUT_OF_MEMORY; } ns->found |= FOUND_LOGIN; ns->keyword = NONE; } else if(ns->keyword == PASSWORD) { curlx_free(ns->password); ns->password = curlx_strdup(tok); if(!ns->password) return NETRC_OUT_OF_MEMORY; ns->found |= FOUND_PASSWORD; ns->keyword = NONE; } else if(curl_strequal("login", tok)) ns->keyword = LOGIN; else if(curl_strequal("password", tok)) ns->keyword = PASSWORD; else if(curl_strequal("machine", tok)) { /* a new machine here */ if(ns->found & FOUND_PASSWORD && /* a password was provided for this host */ ((!ns->specific_login || ns->our_login) || /* either there was no specific login to search for, or this is the specific one we wanted */ (ns->specific_login && !(ns->found & FOUND_LOGIN)))) { /* or we look for a specific login, but that was not specified */ ns->done = TRUE; return NETRC_OK; } ns->state = HOSTFOUND; netrc_new_machine(ns); } else if(curl_strequal("default", tok)) { ns->state = HOSTVALID; ns->retcode = NETRC_OK; netrc_new_machine(ns); } if((ns->found == (FOUND_PASSWORD | FOUND_LOGIN)) && ns->our_login) ns->done = TRUE; return NETRC_OK; } /* * Process one parsed token through the netrc state * machine. Updates the parser state in *ns. * Returns NETRC_OK or an error code. */ static NETRCcode netrc_handle_token(struct netrc_state *ns, const char *tok, const char *host) { switch(ns->state) { case NOTHING: if(curl_strequal("macdef", tok)) ns->state = MACDEF; else if(curl_strequal("machine", tok)) { ns->state = HOSTFOUND; netrc_new_machine(ns); } else if(curl_strequal("default", tok)) { ns->state = HOSTVALID; ns->retcode = NETRC_OK; } break; case MACDEF: if(!*tok) ns->state = NOTHING; break; case HOSTFOUND: if(curl_strequal(host, tok)) { ns->state = HOSTVALID; ns->retcode = NETRC_OK; } else ns->state = NOTHING; break; case HOSTVALID: return netrc_hostvalid(ns, tok); } return NETRC_OK; } /* * Finalize the parse result: fill in defaults and free * resources on error. */ static NETRCcode netrc_finalize(struct netrc_state *ns, char **loginp, char **passwordp, struct store_netrc *store) { NETRCcode retcode = ns->retcode; if(!retcode) { if(!ns->password && ns->our_login) { /* success without a password, set a blank one */ ns->password = curlx_strdup(""); if(!ns->password) retcode = NETRC_OUT_OF_MEMORY; } else if(!ns->login && !ns->password) /* a default with no credentials */ retcode = NETRC_NO_MATCH; } if(!retcode) { /* success */ if(!ns->specific_login) *loginp = ns->login; /* netrc_finalize() can return a password even when specific_login is set but our_login is false (e.g., host matched but the requested login never matched). See test 685. */ *passwordp = ns->password; } else { curlx_dyn_free(&store->filebuf); store->loaded = FALSE; if(!ns->specific_login) curlx_free(ns->login); curlx_free(ns->password); } return retcode; } /* * Returns zero on success. */ static NETRCcode parsenetrc(struct store_netrc *store, const char *host, char **loginp, char **passwordp, const char *netrcfile) { const char *netrcbuffer; struct dynbuf token; struct dynbuf *filebuf = &store->filebuf; struct netrc_state ns; memset(&ns, 0, sizeof(ns)); ns.retcode = NETRC_NO_MATCH; ns.login = *loginp; ns.specific_login = !!ns.login; DEBUGASSERT(!*passwordp); curlx_dyn_init(&token, MAX_NETRC_TOKEN); if(!store->loaded) { NETRCcode ret = file2memory(netrcfile, filebuf); if(ret) return ret; store->loaded = TRUE; } netrcbuffer = curlx_dyn_ptr(filebuf); while(!ns.done) { const char *tok = netrcbuffer; while(tok && !ns.done) { const char *tok_end; bool lineend; NETRCcode ret; ret = netrc_get_token(&tok, &tok_end, &token, &ns.state, &lineend); if(ret) { ns.retcode = ret; goto out; } if(lineend) break; ret = netrc_handle_token(&ns, tok, host); if(ret) { ns.retcode = ret; goto out; } /* tok_end cannot point to a null byte here since lines are always newline terminated */ DEBUGASSERT(*tok_end); tok = ++tok_end; } if(!ns.done) { const char *nl = NULL; if(tok) nl = strchr(tok, '\n'); if(!nl) break; /* point to next line */ netrcbuffer = &nl[1]; } } /* while !done */ out: curlx_dyn_free(&token); return netrc_finalize(&ns, loginp, passwordp, store); } const char *Curl_netrc_strerror(NETRCcode ret) { switch(ret) { default: return ""; /* not a legit error */ case NETRC_FILE_MISSING: return "no such file"; case NETRC_NO_MATCH: return "no matching entry"; case NETRC_OUT_OF_MEMORY: return "out of memory"; case NETRC_SYNTAX_ERROR: return "syntax error"; } /* never reached */ } /* * @unittest: 1304 * * *loginp and *passwordp MUST be allocated if they are not NULL when passed * in. */ NETRCcode Curl_parsenetrc(struct store_netrc *store, const char *host, char **loginp, char **passwordp, const char *netrcfile) { NETRCcode retcode = NETRC_OK; char *filealloc = NULL; if(!netrcfile) { char *home = NULL; char *homea = NULL; #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID) char pwbuf[1024]; #endif filealloc = curl_getenv("NETRC"); if(!filealloc) { homea = curl_getenv("HOME"); /* portable environment reader */ if(homea) { home = homea; #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID) } else { struct passwd pw, *pw_res; if(!getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res) && pw_res) { home = pw.pw_dir; } #elif defined(HAVE_GETPWUID) && defined(HAVE_GETEUID) } else { struct passwd *pw; pw = getpwuid(geteuid()); if(pw) { home = pw->pw_dir; } #elif defined(_WIN32) } else { homea = curl_getenv("USERPROFILE"); if(homea) { home = homea; } #endif } if(!home) return NETRC_FILE_MISSING; /* no home directory found (or possibly out of memory) */ filealloc = curl_maprintf("%s%s.netrc", home, DIR_CHAR); if(!filealloc) { curlx_free(homea); return NETRC_OUT_OF_MEMORY; } } retcode = parsenetrc(store, host, loginp, passwordp, filealloc); curlx_free(filealloc); #ifdef _WIN32 if(retcode == NETRC_FILE_MISSING) { /* fallback to the old-style "_netrc" file */ filealloc = curl_maprintf("%s%s_netrc", home, DIR_CHAR); if(!filealloc) { curlx_free(homea); return NETRC_OUT_OF_MEMORY; } retcode = parsenetrc(store, host, loginp, passwordp, filealloc); curlx_free(filealloc); } #endif curlx_free(homea); } else retcode = parsenetrc(store, host, loginp, passwordp, netrcfile); return retcode; } void Curl_netrc_init(struct store_netrc *store) { curlx_dyn_init(&store->filebuf, MAX_NETRC_FILE); store->loaded = FALSE; } void Curl_netrc_cleanup(struct store_netrc *store) { curlx_dyn_free(&store->filebuf); store->loaded = FALSE; } #endif