mirror of
https://github.com/curl/curl.git
synced 2026-04-14 22:51:53 +03:00
netrc: refactor into smaller sub functions
Fixes #20950 - test 685 is extended for this Closes #20932
This commit is contained in:
parent
a46816b4cb
commit
a327a5bded
4 changed files with 334 additions and 221 deletions
|
|
@ -35,10 +35,18 @@ On Windows two filenames in the home directory are checked: *.netrc* and
|
|||
*_netrc*, preferring the former. Older versions on Windows checked for
|
||||
*_netrc* only.
|
||||
|
||||
A quick and simple example of how to setup a *.netrc* to allow curl to FTP to
|
||||
the machine host.example.com with username 'myself' and password 'secret'
|
||||
A quick and simple example of how to setup a *.netrc* to allow curl to access
|
||||
the machine host.example.com with username `myself` and password `secret`
|
||||
could look similar to:
|
||||
|
||||
machine host.example.com
|
||||
login myself
|
||||
password secret
|
||||
|
||||
curl also supports the `default` keyword. This is the same as machine name
|
||||
except that default matches any name. There can be only one `default` token,
|
||||
and it must be after all machine tokens.
|
||||
|
||||
When providing a username in the URL and a *.netrc* file, curl looks for the
|
||||
password for that specific user for the given host if such an entry appears in
|
||||
the file before a "generic" `machine` entry without `login` specified.
|
||||
|
|
|
|||
|
|
@ -50,6 +50,11 @@ and similar things are not supported).
|
|||
The netrc file provides credentials for a hostname independent of which
|
||||
protocol and port number that are used.
|
||||
|
||||
When providing a username in the URL and a *.netrc* file, libcurl looks for
|
||||
and uses the password for that specific user for the given host if such an
|
||||
entry appears in the file before a "generic" `machine` entry without `login`
|
||||
specified.
|
||||
|
||||
libcurl does not verify that the file has the correct properties set (as the
|
||||
standard Unix ftp client does). It should only be readable by user.
|
||||
|
||||
|
|
|
|||
476
lib/netrc.c
476
lib/netrc.c
|
|
@ -83,6 +83,7 @@ static NETRCcode file2memory(const char *filename, struct dynbuf *filebuf)
|
|||
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);
|
||||
|
|
@ -105,81 +106,31 @@ static NETRCcode file2memory(const char *filename, struct dynbuf *filebuf)
|
|||
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;
|
||||
};
|
||||
|
||||
/*
|
||||
* Returns zero on success.
|
||||
* 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 parsenetrc(struct store_netrc *store,
|
||||
const char *host,
|
||||
char **loginp, /* might point to a username */
|
||||
char **passwordp,
|
||||
const char *netrcfile)
|
||||
static NETRCcode netrc_quoted_token(const char **tok_endp,
|
||||
struct dynbuf *token)
|
||||
{
|
||||
NETRCcode retcode = NETRC_NO_MATCH;
|
||||
char *login = *loginp;
|
||||
char *password = NULL;
|
||||
bool specific_login = !!login; /* points to something */
|
||||
enum host_lookup_state state = NOTHING;
|
||||
enum found_state keyword = NONE;
|
||||
unsigned char found = 0; /* login + password found bits, as they can come in
|
||||
any order */
|
||||
bool our_login = FALSE; /* found our login name */
|
||||
bool done = FALSE;
|
||||
const char *netrcbuffer;
|
||||
struct dynbuf token;
|
||||
struct dynbuf *filebuf = &store->filebuf;
|
||||
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(!done) {
|
||||
const char *tok = netrcbuffer;
|
||||
while(tok && !done) {
|
||||
const char *tok_end;
|
||||
bool quoted;
|
||||
curlx_dyn_reset(&token);
|
||||
curlx_str_passblanks(&tok);
|
||||
/* tok is first non-space letter */
|
||||
if(state == MACDEF) {
|
||||
if((*tok == '\n') || (*tok == '\r'))
|
||||
state = NOTHING; /* end of macro definition */
|
||||
}
|
||||
|
||||
if(!*tok || (*tok == '\n'))
|
||||
/* end of line */
|
||||
break;
|
||||
|
||||
/* leading double-quote means quoted string */
|
||||
quoted = (*tok == '\"');
|
||||
|
||||
tok_end = tok;
|
||||
if(!quoted) {
|
||||
size_t len = 0;
|
||||
CURLcode result;
|
||||
while(*tok_end > ' ') {
|
||||
tok_end++;
|
||||
len++;
|
||||
}
|
||||
if(!len) {
|
||||
retcode = NETRC_SYNTAX_ERROR;
|
||||
goto out;
|
||||
}
|
||||
result = curlx_dyn_addn(&token, tok, len);
|
||||
if(result) {
|
||||
retcode = curl2netrc(result);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
else {
|
||||
bool escape = FALSE;
|
||||
bool endquote = FALSE;
|
||||
NETRCcode rc = NETRC_SYNTAX_ERROR;
|
||||
const char *tok_end = *tok_endp;
|
||||
tok_end++; /* pass the leading quote */
|
||||
while(*tok_end) {
|
||||
CURLcode result;
|
||||
|
|
@ -205,129 +156,301 @@ static NETRCcode parsenetrc(struct store_netrc *store,
|
|||
}
|
||||
else if(s == '\"') {
|
||||
tok_end++; /* pass the ending quote */
|
||||
endquote = TRUE;
|
||||
rc = NETRC_OK;
|
||||
break;
|
||||
}
|
||||
result = curlx_dyn_addn(&token, &s, 1);
|
||||
result = curlx_dyn_addn(token, &s, 1);
|
||||
if(result) {
|
||||
retcode = curl2netrc(result);
|
||||
goto out;
|
||||
*tok_endp = tok_end;
|
||||
return curl2netrc(result);
|
||||
}
|
||||
tok_end++;
|
||||
}
|
||||
if(escape || !endquote) {
|
||||
/* bad syntax, get out */
|
||||
retcode = NETRC_SYNTAX_ERROR;
|
||||
goto out;
|
||||
}
|
||||
*tok_endp = tok_end;
|
||||
return rc;
|
||||
}
|
||||
|
||||
if(curlx_dyn_len(&token))
|
||||
tok = curlx_dyn_ptr(&token);
|
||||
/*
|
||||
* 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
|
||||
/* since tok might actually be NULL for no content, set it to blank
|
||||
to avoid having to deal with it being NULL */
|
||||
tok = "";
|
||||
/* set it to blank to avoid NULL */
|
||||
*tokp = "";
|
||||
|
||||
switch(state) {
|
||||
case NOTHING:
|
||||
if(curl_strequal("macdef", tok))
|
||||
/* Define a macro. A macro is defined with the specified name; its
|
||||
contents begin with the next .netrc line and continue until a
|
||||
null line (consecutive new-line characters) is encountered. */
|
||||
state = MACDEF;
|
||||
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)) {
|
||||
/* the next tok is the machine name, this is in itself the delimiter
|
||||
that starts the stuff entered for this machine, after this we
|
||||
need to search for 'login' and 'password'. */
|
||||
state = HOSTFOUND;
|
||||
keyword = NONE;
|
||||
found = 0;
|
||||
our_login = FALSE;
|
||||
Curl_safefree(password);
|
||||
if(!specific_login)
|
||||
Curl_safefree(login);
|
||||
/* 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)) {
|
||||
state = HOSTVALID;
|
||||
retcode = NETRC_OK; /* we did find our host */
|
||||
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)
|
||||
state = NOTHING;
|
||||
ns->state = NOTHING;
|
||||
break;
|
||||
case HOSTFOUND:
|
||||
if(curl_strequal(host, tok)) {
|
||||
/* and yes, this is our host! */
|
||||
state = HOSTVALID;
|
||||
retcode = NETRC_OK; /* we did find our host */
|
||||
ns->state = HOSTVALID;
|
||||
ns->retcode = NETRC_OK;
|
||||
}
|
||||
else
|
||||
/* not our host */
|
||||
state = NOTHING;
|
||||
ns->state = NOTHING;
|
||||
break;
|
||||
case HOSTVALID:
|
||||
/* we are now parsing sub-keywords concerning "our" host */
|
||||
if(keyword == LOGIN) {
|
||||
if(specific_login)
|
||||
our_login = !Curl_timestrcmp(login, tok);
|
||||
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 {
|
||||
our_login = TRUE;
|
||||
curlx_free(login);
|
||||
login = curlx_strdup(tok);
|
||||
if(!login) {
|
||||
retcode = NETRC_OUT_OF_MEMORY; /* allocation failed */
|
||||
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;
|
||||
}
|
||||
}
|
||||
found |= FOUND_LOGIN;
|
||||
keyword = NONE;
|
||||
}
|
||||
else if(keyword == PASSWORD) {
|
||||
curlx_free(password);
|
||||
password = curlx_strdup(tok);
|
||||
if(!password) {
|
||||
retcode = NETRC_OUT_OF_MEMORY; /* allocation failed */
|
||||
if(lineend)
|
||||
break;
|
||||
|
||||
ret = netrc_handle_token(&ns, tok, host);
|
||||
if(ret) {
|
||||
ns.retcode = ret;
|
||||
goto out;
|
||||
}
|
||||
if(!specific_login || our_login)
|
||||
found |= FOUND_PASSWORD;
|
||||
keyword = NONE;
|
||||
}
|
||||
else if(curl_strequal("login", tok))
|
||||
keyword = LOGIN;
|
||||
else if(curl_strequal("password", tok))
|
||||
keyword = PASSWORD;
|
||||
else if(curl_strequal("machine", tok)) {
|
||||
/* a new machine here */
|
||||
if(found & FOUND_PASSWORD) {
|
||||
done = TRUE;
|
||||
break;
|
||||
}
|
||||
state = HOSTFOUND;
|
||||
keyword = NONE;
|
||||
found = 0;
|
||||
Curl_safefree(password);
|
||||
if(!specific_login)
|
||||
Curl_safefree(login);
|
||||
}
|
||||
else if(curl_strequal("default", tok)) {
|
||||
state = HOSTVALID;
|
||||
retcode = NETRC_OK; /* we did find our host */
|
||||
Curl_safefree(password);
|
||||
if(!specific_login)
|
||||
Curl_safefree(login);
|
||||
}
|
||||
if((found == (FOUND_PASSWORD | FOUND_LOGIN)) && our_login) {
|
||||
done = TRUE;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
} /* switch (state) */
|
||||
/* tok_end cannot point to a null byte here since lines are always
|
||||
newline terminated */
|
||||
DEBUGASSERT(*tok_end);
|
||||
tok = ++tok_end;
|
||||
}
|
||||
if(!done) {
|
||||
if(!ns.done) {
|
||||
const char *nl = NULL;
|
||||
if(tok)
|
||||
nl = strchr(tok, '\n');
|
||||
|
|
@ -340,32 +463,7 @@ static NETRCcode parsenetrc(struct store_netrc *store,
|
|||
|
||||
out:
|
||||
curlx_dyn_free(&token);
|
||||
if(!retcode) {
|
||||
if(!password && our_login) {
|
||||
/* success without a password, set a blank one */
|
||||
password = curlx_strdup("");
|
||||
if(!password)
|
||||
retcode = NETRC_OUT_OF_MEMORY; /* out of memory */
|
||||
}
|
||||
else if(!login && !password)
|
||||
/* a default with no credentials */
|
||||
retcode = NETRC_NO_MATCH;
|
||||
}
|
||||
if(!retcode) {
|
||||
/* success */
|
||||
if(!specific_login)
|
||||
*loginp = login;
|
||||
*passwordp = password;
|
||||
}
|
||||
else {
|
||||
curlx_dyn_free(filebuf);
|
||||
store->loaded = FALSE;
|
||||
if(!specific_login)
|
||||
curlx_free(login);
|
||||
curlx_free(password);
|
||||
}
|
||||
|
||||
return retcode;
|
||||
return netrc_finalize(&ns, loginp, passwordp, store);
|
||||
}
|
||||
|
||||
const char *Curl_netrc_strerror(NETRCcode ret)
|
||||
|
|
|
|||
|
|
@ -26,13 +26,15 @@ Connection: close
|
|||
http
|
||||
</server>
|
||||
<name>
|
||||
netrc with no login - provided user
|
||||
netrc with no login - but provided user in URL
|
||||
</name>
|
||||
<command>
|
||||
--netrc-optional --netrc-file %LOGDIR/netrc%TESTNUMBER http://user@%HOSTIP:%HTTPPORT/
|
||||
</command>
|
||||
<file name="%LOGDIR/netrc%TESTNUMBER" >
|
||||
machine %HOSTIP password firstthis login unknown
|
||||
machine %HOSTIP password 5up3r53cr37
|
||||
machine %HOSTIP password d1ff3r3nt login anotherone
|
||||
</file>
|
||||
</client>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue