branch: master
netrc.c
14847 bytesRaw
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, 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 <pwd.h>
#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