| @@ -36,89 +36,156 @@ |
| #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 |
| @@ -126,374 +193,370 @@ struct netrc_state { |
| * |
| * 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); |
| } |
| |
| /* |
| @@ -502,14 +565,18 @@ const char *Curl_netrc_strerror(NETRCcode ret) |
| * *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; |
| @@ -559,7 +626,8 @@ NETRCcode Curl_parsenetrc(struct store_netrc *store, const char *host, |
| 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) { |
| @@ -569,14 +637,17 @@ NETRCcode Curl_parsenetrc(struct store_netrc *store, const char *host, |
| 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); |
| @@ -593,4 +664,22 @@ void Curl_netrc_cleanup(struct store_netrc *store) |
| 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 */ |