tool_getparam: verify that a file exists for some options

Passing the option as-is to libcurl is fine, but checking that the file
exists allows the tool to better provide a helpful message.

This now done for the following options:

  --cacert, --crlfile, --knownhosts, --netrc-file, --proxy-cacert amd
  --proxy-crlfile

Bonus: bail out properly on OOM errors in the --cert parser.

Reported-by: Wesley Moore
Fixes #19583
Closes #19585
This commit is contained in:
Daniel Stenberg 2025-11-18 09:04:42 +01:00
parent 54a3f63520
commit 88024c6d39
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
6 changed files with 121 additions and 79 deletions

View file

@ -385,20 +385,21 @@ static const struct LongShort aliases[]= {
#ifndef UNITTESTS
static
#endif
void parse_cert_parameter(const char *cert_parameter,
char **certname,
char **passphrase)
ParameterError parse_cert_parameter(const char *cert_parameter,
char **certname,
char **passphrase)
{
size_t param_length = strlen(cert_parameter);
size_t span;
const char *param_place = NULL;
char *certname_place = NULL;
ParameterError err = PARAM_OK;
*certname = NULL;
*passphrase = NULL;
/* most trivial assumption: cert_parameter is empty */
if(param_length == 0)
return;
return PARAM_BLANK_STRING;
/* next less trivial: cert_parameter starts 'pkcs11:' and thus
* looks like a RFC7512 PKCS#11 URI which can be used as-is.
@ -407,12 +408,16 @@ void parse_cert_parameter(const char *cert_parameter,
if(curl_strnequal(cert_parameter, "pkcs11:", 7) ||
!strpbrk(cert_parameter, ":\\")) {
*certname = strdup(cert_parameter);
return;
if(!*certname)
return PARAM_NO_MEM;
return PARAM_OK;
}
/* deal with escaped chars; find unescaped colon if it exists */
certname_place = malloc(param_length + 1);
if(!certname_place)
return;
if(!certname_place) {
err = PARAM_NO_MEM;
goto done;
}
*certname = certname_place;
param_place = cert_parameter;
@ -471,12 +476,19 @@ void parse_cert_parameter(const char *cert_parameter,
param_place++;
if(*param_place) {
*passphrase = strdup(param_place);
if(!*passphrase)
err = PARAM_NO_MEM;
}
goto done;
}
}
done:
*certname_place = '\0';
if(err) {
tool_safefree(*certname);
}
else
*certname_place = '\0';
return err;
}
/* Replace (in-place) '%20' by '+' according to RFC1866 */
@ -507,18 +519,22 @@ static size_t replace_url_encoded_space_by_plus(char *url)
return new_index; /* new size */
}
static void
static ParameterError
GetFileAndPassword(const char *nextarg, char **file, char **password)
{
char *certname, *passphrase;
ParameterError err;
/* nextarg is never NULL here */
parse_cert_parameter(nextarg, &certname, &passphrase);
free(*file);
*file = certname;
if(passphrase) {
free(*password);
*password = passphrase;
err = parse_cert_parameter(nextarg, &certname, &passphrase);
if(!err) {
free(*file);
*file = certname;
if(passphrase) {
free(*password);
*password = passphrase;
}
}
return err;
}
/* Get a size parameter for '--limit-rate' or '--max-filesize'.
@ -2164,6 +2180,19 @@ static ParameterError opt_bool(struct OperationConfig *config,
return PARAM_OK;
}
static ParameterError existingfile(char **store,
const struct LongShort *a,
const char *filename)
{
struct_stat info;
if(curlx_stat(filename, &info)) {
errorf("The file '%s' provided to --%s does not exist",
filename, a->lname);
return PARAM_BAD_USE;
}
return getstr(store, filename, DENY_BLANK);
}
/* opt_file handles file options */
static ParameterError opt_file(struct OperationConfig *config,
const struct LongShort *a,
@ -2181,13 +2210,13 @@ static ParameterError opt_file(struct OperationConfig *config,
err = getstr(&config->unix_socket_path, nextarg, DENY_BLANK);
break;
case C_CACERT: /* --cacert */
err = getstr(&config->cacert, nextarg, DENY_BLANK);
err = existingfile(&config->cacert, a, nextarg);
break;
case C_CAPATH: /* --capath */
err = getstr(&config->capath, nextarg, DENY_BLANK);
break;
case C_CERT: /* --cert */
GetFileAndPassword(nextarg, &config->cert, &config->key_passwd);
err = GetFileAndPassword(nextarg, &config->cert, &config->key_passwd);
break;
case C_CONFIG: /* --config */
if(--max_recursive < 0) {
@ -2200,7 +2229,7 @@ static ParameterError opt_file(struct OperationConfig *config,
}
break;
case C_CRLFILE: /* --crlfile */
err = getstr(&config->crlfile, nextarg, DENY_BLANK);
err = existingfile(&config->crlfile, a, nextarg);
break;
case C_DUMP_HEADER: /* --dump-header */
err = getstr(&config->headerfile, nextarg, DENY_BLANK);
@ -2225,26 +2254,26 @@ static ParameterError opt_file(struct OperationConfig *config,
err = getstr(&config->key, nextarg, DENY_BLANK);
break;
case C_KNOWNHOSTS: /* --knownhosts */
err = getstr(&config->knownhosts, nextarg, DENY_BLANK);
err = existingfile(&config->knownhosts, a, nextarg);
break;
case C_NETRC_FILE: /* --netrc-file */
err = getstr(&config->netrc_file, nextarg, DENY_BLANK);
err = existingfile(&config->netrc_file, a, nextarg);
break;
case C_OUTPUT: /* --output */
err = parse_output(config, nextarg);
break;
case C_PROXY_CACERT: /* --proxy-cacert */
err = getstr(&config->proxy_cacert, nextarg, DENY_BLANK);
err = existingfile(&config->proxy_cacert, a, nextarg);
break;
case C_PROXY_CAPATH: /* --proxy-capath */
err = getstr(&config->proxy_capath, nextarg, DENY_BLANK);
break;
case C_PROXY_CERT: /* --proxy-cert */
GetFileAndPassword(nextarg, &config->proxy_cert,
&config->proxy_key_passwd);
err = GetFileAndPassword(nextarg, &config->proxy_cert,
&config->proxy_key_passwd);
break;
case C_PROXY_CRLFILE: /* --proxy-crlfile */
err = getstr(&config->proxy_crlfile, nextarg, DENY_BLANK);
err = existingfile(&config->proxy_crlfile, a, nextarg);
break;
case C_PROXY_KEY: /* --proxy-key */
err = getstr(&config->proxy_key, nextarg, ALLOW_BLANK);

View file

@ -372,9 +372,9 @@ ParameterError getparameter(const char *flag, const char *nextarg,
int max_recursive);
#ifdef UNITTESTS
void parse_cert_parameter(const char *cert_parameter,
char **certname,
char **passphrase);
ParameterError parse_cert_parameter(const char *cert_parameter,
char **certname,
char **passphrase);
#endif
ParameterError parse_args(int argc, argv_item_t argv[]);