#endif
#include "netrc.h"
+#include "urldata.h"
#include "creds.h"
+#include "curl_trc.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
+/* .netrc is not really a standard. The GNU definition can be found here:
+ * https://www.gnu.org/software/inetutils/manual/\
+ * html_node/The-_002enetrc-file.html
+ * This gives grammar like:
+ *
+ * LITERAL := \S+ | QUOTED
+ * QUOTED := "(\\[rnt\]|[^"])*"
+ * ANYTHING := .
+ * EMPTY_LINE := \r*\n\r*\n
+ * MACHINE := machine # case-insensitive
+ * LOGIN := login # case-insensitive
+ * PASSWD := password # case-insensitive
+ * ACCOUNT := account # case-insensitive
+ * MACDEF := macdef # case-insensitive
+ * DEFAULT := default # case-insensitive
+ *
+ * MACRO := MACDEF ANYTHING* EMPTY_LINE
+ * JUNK := LITERAL
+ * LKEY := ( LOGIN | PASSWD | ACCOUNT ) LITERAL
+ * MENTRY := MACHINE LITERAL LKEY*
+ * DENTRY := DEFAULT LKEY*
+ * NETRC := (MENTRY | DENTRY | MACRO | JUNK )* EOF
+ *
+ * Tokens are separated by whitespace or newlines. which have otherwise
+ * no special meaning, apart from the empty line ending a MACRO.
+ *
+ * Parsing is not strict, unmatched LITERALs are ignored
+ */
#define MAX_NETRC_LINE 16384
#define MAX_NETRC_FILE (128 * 1024)
#define MAX_NETRC_TOKEN 4096
+#define NETRC_DEBUG 0
+
/* 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)
+#define curl2netrc(r) \
+ ((!(r)) ? NETRC_OK : (((r) == CURLE_OUT_OF_MEMORY) ? \
+ NETRC_OUT_OF_MEMORY : NETRC_SYNTAX_ERROR))
+
+typedef enum {
+ NETRC_TOK_EOF,
+ NETRC_TOK_LITERAL,
+ NETRC_TOK_MACHINE,
+ NETRC_TOK_DEFAULT,
+ NETRC_TOK_ACCOUNT,
+ NETRC_TOK_LOGIN,
+ NETRC_TOK_PASSWD,
+ NETRC_TOK_MACDEF,
+ NETRC_TOK_JUNK
+} curl_netrc_token;
+
+struct netrc_lexer {
+ struct Curl_easy *data;
+ const char *content;
+ const char *pos;
+ struct dynbuf literal;
+ curl_netrc_token token;
+ bool pushed;
+};
-static NETRCcode file2memory(const char *filename, struct dynbuf *filebuf)
+#if NETRC_DEBUG
+static const char *netrc_tokenstr(curl_netrc_token token)
{
- NETRCcode ret = NETRC_FILE_MISSING; /* if it cannot open the file */
- FILE *file = curlx_fopen(filename, FOPEN_READTEXT);
+ switch(token) {
+ case NETRC_TOK_EOF:
+ return "[EOF]";
+ case NETRC_TOK_LITERAL:
+ return "[LITERAL]";
+ case NETRC_TOK_MACHINE:
+ return "[MACHINE]";
+ case NETRC_TOK_DEFAULT:
+ return "[DEFAULT]";
+ case NETRC_TOK_ACCOUNT:
+ return "[ACCOUNT]";
+ case NETRC_TOK_LOGIN:
+ return "[LOGIN]";
+ case NETRC_TOK_PASSWD:
+ return "[PASSWORD]";
+ case NETRC_TOK_MACDEF:
+ return "[MACDEF]";
+ case NETRC_TOK_JUNK:
+ return "[JUNK]";
+ default:
+ return "[???]";
+ }
+}
- 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);
+#endif
+
+static void netrc_lexer_init(struct netrc_lexer *lexer,
+ struct Curl_easy *data,
+ const char *content)
+{
+ curlx_dyn_init(&lexer->literal, MAX_NETRC_TOKEN);
+ lexer->data = data;
+ lexer->content = lexer->pos = content;
+}
+
+static void netrc_lexer_cleanup(struct netrc_lexer *lexer)
+{
+ lexer->content = lexer->pos = NULL;
+ lexer->data = NULL;
+ curlx_dyn_free(&lexer->literal);
+}
+
+static void netrc_skip_blanks(struct netrc_lexer *lexer)
+{
+ const char *s = lexer->pos;
+ while(*s) {
+ curlx_str_passblanks(&s);
+ while(*s == '\r')
+ ++s;
+ if(*s == '\n') {
+ ++s;
}
- curlx_fclose(file);
+ else
+ break;
}
- return ret;
+ lexer->pos = s;
}
-/* bundled parser state to keep function signatures compact */
-struct netrc_state {
- struct Curl_creds *existing;
- 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;
-};
+static void netrc_skip_to_empty_line(struct netrc_lexer *lexer)
+{
+ const char *s = lexer->pos;
+ while(*s) {
+ if(*s == '\r')
+ ++s;
+ else if(*s == '\n') {
+ ++s;
+ while(*s == '\r')
+ ++s;
+ if(*s == '\n')
+ goto out;
+ }
+ else
+ ++s;
+ }
+out:
+ lexer->pos = s;
+}
/*
* Parse a quoted token starting after the opening '"'. Handles \n, \r, \t
*
* Returns NETRC_OK or error.
*/
-static NETRCcode netrc_quoted_token(const char **tok_endp,
- struct dynbuf *token)
+static NETRCcode netrc_lexer_quoted(struct netrc_lexer *lexer)
{
- 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;
+ const char *s = lexer->pos;
+ bool escape = FALSE;
+ CURLcode result;
+
+ DEBUGASSERT(*s == '\"');
+ ++s; /* pass the leading quote */
+ while(*s) {
+ char c = *s;
if(escape) {
escape = FALSE;
- switch(s) {
+ switch(c) {
case 'n':
- s = '\n';
+ c = '\n';
break;
case 'r':
- s = '\r';
+ c = '\r';
break;
case 't':
- s = '\t';
+ c = '\t';
break;
}
}
- else if(s == '\\') {
+ else if(c == '\\') {
escape = TRUE;
- tok_end++;
+ ++s;
continue;
}
- else if(s == '\"') {
- tok_end++; /* pass the ending quote */
+ else if(c == '\"') {
+ ++s; /* pass the ending quote */
rc = NETRC_OK;
- break;
+ goto out;
}
- result = curlx_dyn_addn(token, &s, 1);
+ result = curlx_dyn_addn(&lexer->literal, &c, 1);
if(result) {
- *tok_endp = tok_end;
- return curl2netrc(result);
+ rc = curl2netrc(result);
+ goto out;
}
- tok_end++;
+ ++s;
}
- *tok_endp = tok_end;
+out:
+ lexer->pos = s;
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)
+static void netrc_lexer_push(struct netrc_lexer *lexer)
{
- 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 */
- *lineend = TRUE;
- *tokp = tok;
- return NETRC_OK;
- }
+ lexer->pushed = TRUE;
+}
- if(!*tok || (*tok == '\n')) {
- /* end of line */
- *lineend = TRUE;
- *tokp = tok;
- return NETRC_OK;
+static NETRCcode netrc_lexer_next(struct netrc_lexer *lexer,
+ bool want_literal)
+{
+ const char *s = lexer->pos, *start;
+ NETRCcode rc = NETRC_OK;
+ size_t slen;
+ CURLcode result;
+
+ if(lexer->pushed) {
+ lexer->pushed = FALSE;
+ goto out;
}
- tok_end = tok;
- if(*tok == '\"') {
- /* quoted string */
- NETRCcode ret = netrc_quoted_token(&tok_end, token);
- if(ret)
- return ret;
- }
- else {
+ curlx_dyn_reset(&lexer->literal);
+ netrc_skip_blanks(lexer);
+ s = lexer->pos;
+
+ switch(*s) {
+ case 0:
+ lexer->token = NETRC_TOK_EOF;
+ break;
+ case '\"':
+ rc = netrc_lexer_quoted(lexer);
+ lexer->token = NETRC_TOK_LITERAL;
+ s = lexer->pos;
+ break;
+ default:
/* unquoted token */
- size_t len = 0;
- CURLcode result;
- while(*tok_end > ' ') {
- tok_end++;
- len++;
+ start = s;
+ while(*s && !ISBLANK(*s) && !ISNEWLINE(*s))
+ ++s;
+ slen = s - start;
+ if(!slen) {
+ rc = NETRC_SYNTAX_ERROR;
+ }
+ if(want_literal) {
+ lexer->token = NETRC_TOK_LITERAL;
+ result = curlx_dyn_addn(&lexer->literal, start, slen);
+ rc = curl2netrc(result);
+ }
+ else if((slen == 7) && curl_strnequal(start, "machine", slen)) {
+ lexer->token = NETRC_TOK_MACHINE;
+ }
+ else if((slen == 7) && curl_strnequal(start, "default", slen)) {
+ lexer->token = NETRC_TOK_DEFAULT;
}
- if(!len)
- return NETRC_SYNTAX_ERROR;
- result = curlx_dyn_addn(token, tok, len);
- if(result)
- return curl2netrc(result);
+ else if((slen == 7) && curl_strnequal(start, "account", slen)) {
+ lexer->token = NETRC_TOK_ACCOUNT;
+ }
+ else if((slen == 5) && curl_strnequal(start, "login", slen)) {
+ lexer->token = NETRC_TOK_LOGIN;
+ }
+ else if((slen == 8) && curl_strnequal(start, "password", slen)) {
+ lexer->token = NETRC_TOK_PASSWD;
+ }
+ else if((slen == 6) && curl_strnequal(start, "macdef", slen)) {
+ lexer->token = NETRC_TOK_MACDEF;
+ }
+ else {
+ lexer->token = NETRC_TOK_JUNK;
+ }
+ break;
}
- *tok_endp = tok_end;
+out:
+#if NETRC_DEBUG
+ CURL_TRC_M(lexer->data, "[NETRC] token %s '%s', rc=%d",
+ netrc_tokenstr(lexer->token),
+ curlx_dyn_ptr(&lexer->literal), rc);
+#endif
+ lexer->pos = s;
+ return rc;
+}
- if(curlx_dyn_len(token))
- *tokp = curlx_dyn_ptr(token);
- else
- /* set it to blank to avoid NULL */
- *tokp = "";
+struct netrc_scanner {
+ struct netrc_lexer lexer;
+ const char *hostname; /* non-NULL, machine to scan for */
+ const char *user; /* maybe NULL, login to scan for */
+ char *login;
+ char *passwd;
+ struct Curl_creds *creds;
+ bool matches_host;
+ bool found;
+};
- return NETRC_OK;
+static void netrc_scan_reset(struct netrc_scanner *sc)
+{
+ curlx_safefree(sc->login);
+ curlx_safefree(sc->passwd);
+ sc->matches_host = FALSE;
}
-/*
- * 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)
+static void netrc_scan_init(struct netrc_scanner *sc,
+ struct Curl_easy *data,
+ const char *content,
+ const char *hostname,
+ const char *user)
{
- ns->keyword = NONE;
- ns->found = 0;
- ns->our_login = FALSE;
- curlx_safefree(ns->password);
- curlx_safefree(ns->login);
+ memset(sc, 0, sizeof(*sc));
+ netrc_lexer_init(&sc->lexer, data, content);
+ sc->hostname = hostname;
+ sc->user = (user && user[0]) ? user : NULL;
+ netrc_scan_reset(sc);
}
-/*
- * 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)
+static void netrc_scan_cleanup(struct netrc_scanner *sc)
{
- if(ns->keyword == LOGIN) {
- if(Curl_creds_has_user(ns->existing))
- ns->our_login = !Curl_timestrcmp(ns->existing->user, 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 */
- bool specific_login = Curl_creds_has_user(ns->existing);
-
- if((ns->found & FOUND_PASSWORD) &&
- /* a password was provided for this host */
- (!specific_login || ns->our_login ||
- /* and found a login that is suitable
- (either matched specific one or simply present) */
- (specific_login && !(ns->found & FOUND_LOGIN)))) {
- /* or we look for a specific login, but no login 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;
+ netrc_scan_reset(sc);
+ sc->hostname = NULL;
+ sc->user = NULL;
+ Curl_creds_unlink(&sc->creds);
+ netrc_lexer_cleanup(&sc->lexer);
}
-/*
- * 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)
+static NETRCcode netrc_scan_literal(struct netrc_scanner *sc,
+ char **pdest)
{
- 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;
+ NETRCcode rc = netrc_lexer_next(&sc->lexer, TRUE);
+ if(!rc) {
+ if(sc->lexer.token == NETRC_TOK_LITERAL) {
+ if(pdest && sc->matches_host) {
+ curlx_free(*pdest);
+ *pdest = curlx_strdup(curlx_dyn_ptr(&sc->lexer.literal));
+ if(!*pdest)
+ rc = NETRC_OUT_OF_MEMORY;
+ }
}
else
- ns->state = NOTHING;
- break;
- case HOSTVALID:
- return netrc_hostvalid(ns, tok);
+ netrc_lexer_push(&sc->lexer);
}
- return NETRC_OK;
+ return rc;
}
-/*
- * Finalize the parse result: fill in defaults and free
- * resources on error.
- */
-static NETRCcode netrc_finalize(struct netrc_state *ns,
- struct store_netrc *store,
- struct Curl_creds **pcreds)
+static NETRCcode netrc_scan_end_entry(struct netrc_scanner *sc)
{
- 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;
- goto out;
+ NETRCcode rc = NETRC_OK;
+#if NETRC_DEBUG
+ CURL_TRC_M(sc->lexer.data,
+ "[NETRC] entry matches_host=%d, login='%s', passwd='%s'",
+ sc->matches_host, sc->login, sc->passwd);
+#endif
+ if(sc->matches_host) {
+ if(sc->login) {
+ if(sc->user) {
+ if(Curl_timestrcmp(sc->user, sc->login))
+ goto out;
+ /* We look for a specific user,
+ * entry is only interesting with password */
+ sc->found = !!sc->passwd;
+ }
+ else {
+ sc->found = TRUE;
}
}
- else if(!ns->login && !ns->password) {
- /* a default with no credentials */
- retcode = NETRC_NO_MATCH;
- goto out;
+ else if(sc->passwd) {
+ /* found a passwd that applies to any user */
+ sc->found = TRUE;
}
- }
-
- if(!retcode) {
- /* success
- 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. */
- const char *login = Curl_creds_has_user(ns->existing) ?
- ns->existing->user : ns->login;
- /* success without a password, set a blank one */
- const char *passwd = ns->password ? ns->password : "";
-
- if(Curl_creds_create(login, passwd, NULL, NULL, NULL, CREDS_NETRC,
- pcreds)) {
- retcode = NETRC_OUT_OF_MEMORY;
- goto out;
+ else {
+ /* entry has nothing interesting */
+ }
+ if(sc->found) {
+#if NETRC_DEBUG
+ CURL_TRC_M(sc->lexer.data, "[NETRC] entry match found");
+#endif
+ if(Curl_creds_create(sc->user ? sc->user : sc->login, sc->passwd,
+ NULL, NULL, NULL, CREDS_NETRC, &sc->creds))
+ rc = NETRC_OUT_OF_MEMORY;
}
}
-
out:
- curlx_free(ns->login);
- curlx_free(ns->password);
- if(retcode) {
- curlx_dyn_free(&store->filebuf);
- store->loaded = FALSE;
- }
- return retcode;
+ netrc_scan_reset(sc);
+ return rc;
}
-/*
- * Returns zero on success.
- */
-static NETRCcode parsenetrc(struct store_netrc *store,
- const char *host,
- struct Curl_creds *existing,
- const char *netrcfile,
+static NETRCcode netrc_scan(struct Curl_easy *data,
+ const char *content,
+ const char *hostname,
+ const char *user,
struct Curl_creds **pcreds)
{
- const char *netrcbuffer;
- struct dynbuf token;
- struct dynbuf *filebuf = &store->filebuf;
- struct netrc_state ns;
-
- DEBUGASSERT(!existing || !Curl_creds_has_passwd(existing));
- memset(&ns, 0, sizeof(ns));
- ns.retcode = NETRC_NO_MATCH;
- ns.existing = existing;
-
- 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);
+ struct netrc_scanner sc;
+ NETRCcode rc = NETRC_OK;
- 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)
+ Curl_creds_unlink(pcreds);
+ netrc_scan_init(&sc, data, content, hostname, user);
+
+ while(!rc && !sc.found) {
+ rc = netrc_lexer_next(&sc.lexer, FALSE);
+ if(!rc) {
+ /* Does this token end any previous entry? */
+ switch(sc.lexer.token) {
+ case NETRC_TOK_EOF:
+ case NETRC_TOK_MACHINE:
+ case NETRC_TOK_DEFAULT:
+ case NETRC_TOK_MACDEF:
+ rc = netrc_scan_end_entry(&sc);
+ if(rc || sc.found)
+ goto out;
break;
+ default:
+ break;
+ }
- ret = netrc_handle_token(&ns, tok, host);
- if(ret) {
- ns.retcode = ret;
+ switch(sc.lexer.token) {
+ case NETRC_TOK_EOF:
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)
+ case NETRC_TOK_MACHINE:
+ rc = netrc_lexer_next(&sc.lexer, TRUE);
+ if(!rc) {
+ if(sc.lexer.token == NETRC_TOK_LITERAL) {
+ sc.matches_host = curl_strequal(
+ sc.hostname, curlx_dyn_ptr(&sc.lexer.literal));
+ }
+ else {
+ sc.matches_host = FALSE;
+ netrc_lexer_push(&sc.lexer);
+ }
+ }
+ break;
+ case NETRC_TOK_DEFAULT:
+ sc.matches_host = TRUE;
+ break;
+ case NETRC_TOK_ACCOUNT:
+ rc = netrc_scan_literal(&sc, NULL); /* ignore, not used */
+ break;
+ case NETRC_TOK_LOGIN:
+ rc = netrc_scan_literal(&sc, &sc.login);
break;
- /* point to next line */
- netrcbuffer = &nl[1];
+ case NETRC_TOK_PASSWD:
+ rc = netrc_scan_literal(&sc, &sc.passwd);
+ break;
+ case NETRC_TOK_MACDEF:
+ netrc_skip_to_empty_line(&sc.lexer);
+ break;
+ case NETRC_TOK_LITERAL:
+ case NETRC_TOK_JUNK:
+ default:
+ /* skip this */
+ break;
+ }
}
- } /* while !done */
+ }
out:
- curlx_dyn_free(&token);
- return netrc_finalize(&ns, store, pcreds);
+ if(!rc) {
+ if(sc.creds)
+ Curl_creds_link(pcreds, sc.creds);
+ else
+ rc = NETRC_NO_MATCH;
+ }
+ netrc_scan_cleanup(&sc);
+ return rc;
}
-const char *Curl_netrc_strerror(NETRCcode ret)
+static NETRCcode file2memory(const char *filename, struct dynbuf *filebuf)
{
- 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";
+ 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);
}
- /* never reached */
+ return ret;
+}
+
+static NETRCcode netrc_scan_file(struct Curl_easy *data,
+ struct store_netrc *store,
+ const char *hostname,
+ const char *user,
+ const char *netrcfile,
+ struct Curl_creds **pcreds)
+{
+ struct dynbuf *filebuf = &store->filebuf;
+
+ if(!store->loaded) {
+ NETRCcode ret = file2memory(netrcfile, filebuf);
+ if(ret) {
+ CURL_TRC_M(data, "[NETRC] could not load '%s'", netrcfile);
+ return ret;
+ }
+ store->loaded = TRUE;
+ }
+
+ return netrc_scan(data, curlx_dyn_ptr(filebuf), hostname, user, pcreds);
}
/*
* *loginp and *passwordp MUST be allocated if they are not NULL when passed
* in.
*/
-NETRCcode Curl_parsenetrc(struct store_netrc *store, const char *host,
- struct Curl_creds *existing,
+NETRCcode Curl_netrc_scan(struct Curl_easy *data,
+ struct store_netrc *store,
+ const char *hostname,
+ const char *user,
const char *netrcfile,
struct Curl_creds **pcreds)
{
NETRCcode retcode = NETRC_OK;
char *filealloc = NULL;
+ CURL_TRC_M(data, "[NETRC] scanning '%s' for host '%s' user '%s'",
+ netrcfile, hostname, user);
Curl_creds_unlink(pcreds);
if(!netrcfile) {
char *home = NULL;
goto out;
}
}
- retcode = parsenetrc(store, host, existing, filealloc, pcreds);
+ retcode = netrc_scan_file(
+ data, store, hostname, user, filealloc, pcreds);
curlx_free(filealloc);
#ifdef _WIN32
if(retcode == NETRC_FILE_MISSING) {
curlx_free(homea);
return NETRC_OUT_OF_MEMORY;
}
- retcode = parsenetrc(store, host, existing, filealloc, pcreds);
+ retcode = netrc_scan_file(
+ data, store, hostname, user, filealloc, pcreds);
curlx_free(filealloc);
}
#endif
curlx_free(homea);
}
else
- retcode = parsenetrc(store, host, existing, netrcfile, pcreds);
+ retcode = netrc_scan_file(
+ data, store, hostname, user, netrcfile, pcreds);
+
out:
if(retcode)
Curl_creds_unlink(pcreds);
curlx_dyn_free(&store->filebuf);
store->loaded = FALSE;
}
-#endif
+
+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 */
+}
+
+#endif /* !CURL_DISABLE_NETRC */
static CURLcode override_login(struct Curl_easy *data,
struct connectdata *conn)
{
- CURLUcode uc;
char **optionsp = &conn->options;
#ifndef CURL_DISABLE_NETRC
- struct Curl_creds *ncreds_in = NULL;
struct Curl_creds *ncreds_out = NULL;
#endif
CURLcode result = CURLE_OK;
- bool creds_changed = FALSE;
if(data->set.str[STRING_OPTIONS]) {
curlx_free(*optionsp);
}
#ifndef CURL_DISABLE_NETRC
- if(data->set.use_netrc) {
- /* Determine how to react on already existing credentials */
- if(data->set.use_netrc == CURL_NETRC_REQUIRED) {
- Curl_creds_unlink(&conn->creds);
- }
+ if(data->set.use_netrc) { /* not CURL_NETRC_IGNORED */
+ struct Curl_creds *ncreds_in = NULL;
+ bool scan_netrc = TRUE;
+ NETRCcode ret;
+ CURLUcode uc;
if(data->state.creds) {
switch(data->state.creds->source) {
case CREDS_OPTION:
- /* we never override credentials set via CURLOPT_* */
- goto out;
- case CREDS_URL:
+ /* we never override credentials set via CURLOPT_*, leave. */
+ scan_netrc = FALSE;
+ break;
+ case CREDS_URL: /* only apply when netrc is not required */
if(data->set.use_netrc == CURL_NETRC_REQUIRED) {
- /* use the URL user to search netrc */
- result = Curl_creds_create(
- data->state.creds->user, NULL, NULL, NULL, NULL, CREDS_URL,
- &ncreds_in);
- if(result)
- goto out;
+ /* We ignore password from URL */
+ ncreds_in = data->state.creds;
+ }
+ else if(!Curl_creds_has_user(data->state.creds) ||
+ !Curl_creds_has_passwd(data->state.creds)) {
+ /* We use netrc to complete what is missing */
+ ncreds_in = data->state.creds;
}
else
- /* only search when something is still missing */
- Curl_creds_link(&ncreds_in, data->state.creds);
+ scan_netrc = FALSE;
break;
- default:
- /* ignore credentials from other sources */
+ default: /* ignore credentials from other sources */
break;
}
}
- /* Only search in netrc when the creds are not already complete */
- if(!Curl_creds_has_passwd(ncreds_in)) {
- NETRCcode ret;
-
- CURL_TRC_M(data, "netrc: find credentials for %s, user %s",
- conn->origin->hostname,
- Curl_creds_has_user(ncreds_in) ? ncreds_in->user : "*");
- ret = Curl_parsenetrc(&data->state.netrc,
- conn->origin->hostname,
- ncreds_in,
- data->set.str[STRING_NETRC_FILE],
- &ncreds_out);
- DEBUGASSERT(!ret || !ncreds_out);
- if(ret == NETRC_OUT_OF_MEMORY) {
- result = CURLE_OUT_OF_MEMORY;
- goto out;
- }
- else if(ret && ((ret == NETRC_NO_MATCH) ||
- (data->set.use_netrc == CURL_NETRC_OPTIONAL))) {
- infof(data, "Could not find host %s in the %s file; using defaults",
- conn->origin->hostname,
- (data->set.str[STRING_NETRC_FILE] ?
- data->set.str[STRING_NETRC_FILE] : ".netrc"));
- }
- else if(ret) {
- const char *m = Curl_netrc_strerror(ret);
- failf(data, ".netrc error: %s", m);
- result = CURLE_READ_ERROR;
- goto out;
- }
- else if(ncreds_out) {
- if(!(conn->scheme->flags & PROTOPT_USERPWDCTRL)) {
- /* if the protocol cannot handle control codes in credentials, make
- sure there are none */
- if(str_has_ctrl(ncreds_out->user) ||
- str_has_ctrl(ncreds_out->passwd)) {
- failf(data, "control code detected in .netrc credentials");
- result = CURLE_READ_ERROR;
- goto out;
- }
- }
- CURL_TRC_M(data, "netrc: using credentials for %s as %s",
- conn->origin->hostname, ncreds_out->user);
- result = Curl_creds_merge(ncreds_out->user, ncreds_out->passwd,
- data->state.creds, CREDS_NETRC,
- &data->state.creds);
- if(result)
+ if(!scan_netrc)
+ goto out;
+
+ ret = Curl_netrc_scan(data, &data->state.netrc,
+ conn->origin->hostname,
+ Curl_creds_user(ncreds_in),
+ data->set.str[STRING_NETRC_FILE],
+ &ncreds_out);
+ DEBUGASSERT(!ret || !ncreds_out);
+ if(ret == NETRC_OUT_OF_MEMORY) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto out;
+ }
+ else if(ret && ((ret == NETRC_NO_MATCH) ||
+ (data->set.use_netrc == CURL_NETRC_OPTIONAL))) {
+ infof(data, "Could not find host %s in the %s file; using defaults",
+ conn->origin->hostname,
+ (data->set.str[STRING_NETRC_FILE] ?
+ data->set.str[STRING_NETRC_FILE] : ".netrc"));
+ }
+ else if(ret) {
+ const char *m = Curl_netrc_strerror(ret);
+ failf(data, ".netrc error: %s", m);
+ result = CURLE_READ_ERROR;
+ goto out;
+ }
+ else if(ncreds_out) {
+ if(!(conn->scheme->flags & PROTOPT_USERPWDCTRL)) {
+ /* if the protocol cannot handle control codes in credentials, make
+ sure there are none */
+ if(str_has_ctrl(ncreds_out->user) ||
+ str_has_ctrl(ncreds_out->passwd)) {
+ failf(data, "control code detected in .netrc credentials");
+ result = CURLE_READ_ERROR;
goto out;
- creds_changed = TRUE;
+ }
}
- else
- DEBUGASSERT(0);
+ CURL_TRC_M(data, "netrc: using credentials for %s as %s",
+ conn->origin->hostname, ncreds_out->user);
+ result = Curl_creds_merge(ncreds_out->user, ncreds_out->passwd,
+ data->state.creds, CREDS_NETRC,
+ &data->state.creds);
+ if(result)
+ goto out;
+ /* for updated strings, we update them in the URL */
+ uc = curl_url_set(data->state.uh, CURLUPART_USER,
+ Curl_creds_user(data->state.creds), CURLU_URLENCODE);
+ if(!uc)
+ uc = curl_url_set(data->state.uh, CURLUPART_PASSWORD,
+ Curl_creds_passwd(data->state.creds),
+ CURLU_URLENCODE);
+ if(uc)
+ result = Curl_uc_to_curlcode(uc);
}
+ else
+ DEBUGASSERT(0);
}
-
#endif
- if(creds_changed) {
- /* for updated strings, we update them in the URL */
- uc = curl_url_set(data->state.uh, CURLUPART_USER,
- Curl_creds_user(data->state.creds), CURLU_URLENCODE);
- if(!uc)
- uc = curl_url_set(data->state.uh, CURLUPART_PASSWORD,
- Curl_creds_passwd(data->state.creds), CURLU_URLENCODE);
- if(uc)
- result = Curl_uc_to_curlcode(uc);
- }
-
out:
#ifndef CURL_DISABLE_NETRC
- Curl_creds_unlink(&ncreds_in);
Curl_creds_unlink(&ncreds_out);
#endif
return result;
#include "netrc.h"
#include "creds.h"
-static void t1304_stop(struct Curl_creds **pc1, struct Curl_creds **pc2)
+static CURLcode t1304_setup(struct Curl_easy **easy)
{
- Curl_creds_unlink(pc1);
- Curl_creds_unlink(pc2);
+ CURLcode result = CURLE_OK;
+
+ global_init(CURL_GLOBAL_ALL);
+ *easy = curl_easy_init();
+ if(!*easy) {
+ curl_global_cleanup();
+ return CURLE_OUT_OF_MEMORY;
+ }
+ return result;
}
-static bool t1304_set_creds(const char *user, const char *passwd,
- struct Curl_creds **pcreds)
+static void t1304_stop(struct Curl_easy *easy)
{
- Curl_creds_unlink(pcreds);
- if(user || passwd)
- return !Curl_creds_create(user, passwd, NULL, NULL, NULL, CREDS_NONE,
- pcreds);
- else
- return TRUE;
+ curl_easy_cleanup(easy);
+ curl_global_cleanup();
}
static bool t1304_no_user(struct Curl_creds *creds)
static CURLcode test_unit1304(const char *arg)
{
- struct Curl_creds *cr_out = NULL, *cr_in = NULL;
-
- UNITTEST_BEGIN_SIMPLE
-
+ struct Curl_creds *cr_out = NULL;
+ struct Curl_easy *data;
int result;
struct store_netrc store;
+ UNITTEST_BEGIN(t1304_setup(&data))
+
/*
* Test a non existent host in our netrc file.
*/
Curl_netrc_init(&store);
- result = Curl_parsenetrc(&store, "test.example.com", NULL, arg, &cr_out);
+ result = Curl_netrc_scan(
+ data, &store, "test.example.com", NULL, arg, &cr_out);
fail_unless(result == 1, "expected no match");
- abort_unless(cr_out == NULL, "creds did not return NULL!");
+ fail_unless(cr_out == NULL, "creds did not return NULL!");
Curl_netrc_cleanup(&store);
/*
* Test a non existent login in our netrc file.
*/
- fail_unless(t1304_set_creds("me", NULL, &cr_in), "err set creds");
Curl_netrc_init(&store);
- result = Curl_parsenetrc(&store, "example.com", cr_in, arg, &cr_out);
+ result = Curl_netrc_scan(data, &store, "example.com", "me", arg, &cr_out);
fail_unless(result == 1, "expected no match");
- abort_unless(t1304_no_passwd(cr_out), "password is not NULL!");
+ fail_unless(t1304_no_passwd(cr_out), "password is not NULL!");
Curl_netrc_cleanup(&store);
/*
* Test a non existent login and host in our netrc file.
*/
- fail_unless(t1304_set_creds("me", NULL, &cr_in), "err set creds");
Curl_netrc_init(&store);
- result = Curl_parsenetrc(&store, "test.example.com", cr_in, arg, &cr_out);
+ result = Curl_netrc_scan(
+ data, &store, "test.example.com", "me", arg, &cr_out);
fail_unless(result == 1, "expected no match");
- abort_unless(t1304_no_passwd(cr_out), "password is not NULL!");
+ fail_unless(t1304_no_passwd(cr_out), "password is not NULL!");
Curl_netrc_cleanup(&store);
/*
* Test a non existent login (substring of an existing one) in our
* netrc file.
*/
- fail_unless(t1304_set_creds(
- "admi", NULL, &cr_in), "err set creds"); /* spellchecker:disable-line */
Curl_netrc_init(&store);
- result = Curl_parsenetrc(&store, "example.com", cr_in, arg, &cr_out);
+ result = Curl_netrc_scan(
+ data, &store, "example.com", "a", arg, &cr_out);
fail_unless(result == 1, "expected no match");
- abort_unless(t1304_no_passwd(cr_out), "password is not NULL!");
+ fail_unless(t1304_no_passwd(cr_out), "password is not NULL!");
Curl_netrc_cleanup(&store);
/*
* Test a non existent login (superstring of an existing one)
* in our netrc file.
*/
- fail_unless(t1304_set_creds("adminn", NULL, &cr_in), "err set creds");
Curl_netrc_init(&store);
- result = Curl_parsenetrc(&store, "example.com", cr_in, arg, &cr_out);
+ result = Curl_netrc_scan(
+ data, &store, "example.com", "administrator", arg, &cr_out);
fail_unless(result == 1, "expected no match");
- abort_unless(t1304_no_passwd(cr_out), "password is not NULL!");
+ fail_unless(t1304_no_passwd(cr_out), "password is not NULL!");
Curl_netrc_cleanup(&store);
/*
- * Test for the first existing host in our netrc file
- * with login[0] = 0.
+ * Test for the first existing host in our netrc file with no user
*/
- Curl_creds_unlink(&cr_in);
Curl_netrc_init(&store);
- result = Curl_parsenetrc(&store, "example.com", cr_in, arg, &cr_out);
+ result = Curl_netrc_scan(data, &store, "example.com", NULL, arg, &cr_out);
fail_unless(result == 0, "Host should have been found");
- abort_unless(!t1304_no_passwd(cr_out), "returned NULL!");
fail_unless(strncmp(Curl_creds_passwd(cr_out), "passwd", 6) == 0,
"password should be 'passwd'");
- abort_unless(!t1304_no_user(cr_out), "returned NULL!");
+ fail_unless(!t1304_no_user(cr_out), "returned NULL!");
fail_unless(strncmp(Curl_creds_user(cr_out), "admin", 5) == 0,
"login should be 'admin'");
Curl_netrc_cleanup(&store);
/*
- * Test for the first existing host in our netrc file
- * with login[0] != 0.
+ * Test for the second existing host in our netrc file with no user
*/
- Curl_creds_unlink(&cr_in);
Curl_netrc_init(&store);
- result = Curl_parsenetrc(&store, "example.com", cr_in, arg, &cr_out);
+ result = Curl_netrc_scan(
+ data, &store, "curl.example.com", NULL, arg, &cr_out);
fail_unless(result == 0, "Host should have been found");
- abort_unless(!t1304_no_passwd(cr_out), "returned NULL!");
- fail_unless(strncmp(Curl_creds_passwd(cr_out), "passwd", 6) == 0,
- "password should be 'passwd'");
- abort_unless(!t1304_no_user(cr_out), "returned NULL!");
- fail_unless(strncmp(Curl_creds_user(cr_out), "admin", 5) == 0,
- "login should be 'admin'");
- Curl_netrc_cleanup(&store);
-
- /*
- * Test for the second existing host in our netrc file
- * with login[0] = 0.
- */
- Curl_creds_unlink(&cr_in);
- Curl_netrc_init(&store);
- result = Curl_parsenetrc(&store, "curl.example.com", cr_in, arg, &cr_out);
- fail_unless(result == 0, "Host should have been found");
- abort_unless(!t1304_no_passwd(cr_out), "returned NULL!");
fail_unless(strncmp(Curl_creds_passwd(cr_out), "none", 4) == 0,
"password should be 'none'");
- abort_unless(!t1304_no_user(cr_out), "returned NULL!");
+ fail_unless(!t1304_no_user(cr_out), "returned NULL!");
fail_unless(strncmp(Curl_creds_user(cr_out), "none", 4) == 0,
"login should be 'none'");
Curl_netrc_cleanup(&store);
/*
- * Test for the second existing host in our netrc file
- * with login[0] != 0.
+ * Test for the last host where we do not want to see the password
+ * if the login does not match.
*/
- Curl_creds_unlink(&cr_in);
Curl_netrc_init(&store);
- result = Curl_parsenetrc(&store, "curl.example.com", cr_in, arg, &cr_out);
- fail_unless(result == 0, "Host should have been found");
- abort_unless(!t1304_no_passwd(cr_out), "returned NULL!");
- fail_unless(strncmp(Curl_creds_passwd(cr_out), "none", 4) == 0,
- "password should be 'none'");
- abort_unless(!t1304_no_user(cr_out), "returned NULL!");
- fail_unless(strncmp(Curl_creds_user(cr_out), "none", 4) == 0,
- "login should be 'none'");
+ result = Curl_netrc_scan(
+ data, &store, "curl.example.com", "hilarious", arg, &cr_out);
+ fail_unless(result == 1, "expect no match");
+ fail_unless(!Curl_creds_has_passwd(cr_out), "password must be NULL");
Curl_netrc_cleanup(&store);
- UNITTEST_END(t1304_stop(&cr_in, &cr_out))
+ Curl_creds_unlink(&cr_out);
+
+ UNITTEST_END(t1304_stop(data))
}
#else