mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
Tasks 3.3 and 4.1. - vfc curl and openssl-cve-2021-3449 excerpts re-harvested through the shared library: whole functions with upstream_start headers, the vendored key redacted code-safely, no truncated bodies. - vfc/recipes.toml carries mode, fix_path and function anchors; training/manifest.jsonl regenerated. - Juliet CWE-121 strcpy pair gets its buffer declarations back so the vulnerable side is a real overflow again. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
452 lines
14 KiB
C
452 lines
14 KiB
C
/* Provenance: curl/curl ftp_statemachine (fixed).
|
|
* repo: curl/curl
|
|
* commit: 364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
|
* parent: 43157490a5054bd24256fe12876931e8abc9df49
|
|
* commit_url: https://github.com/curl/curl/commit/364f174724ef115c63d5e5dc1d3342c8a43b1cca
|
|
* cve: CVE-2021-22946
|
|
* license: curl
|
|
* function: ftp_statemachine
|
|
* relpath: lib/ftp.c
|
|
* provenance: human
|
|
* mechanism: other
|
|
*/
|
|
|
|
static CURLcode ftp_statemachine(struct Curl_easy *data,
|
|
struct connectdata *conn)
|
|
{
|
|
CURLcode result;
|
|
curl_socket_t sock = conn->sock[FIRSTSOCKET];
|
|
int ftpcode;
|
|
struct ftp_conn *ftpc = &conn->proto.ftpc;
|
|
struct pingpong *pp = &ftpc->pp;
|
|
static const char ftpauth[][4] = { "SSL", "TLS" };
|
|
size_t nread = 0;
|
|
|
|
if(pp->sendleft)
|
|
return Curl_pp_flushsend(data, pp);
|
|
|
|
result = ftp_readresp(data, sock, pp, &ftpcode, &nread);
|
|
if(result)
|
|
return result;
|
|
|
|
if(ftpcode) {
|
|
/* we have now received a full FTP server response */
|
|
switch(ftpc->state) {
|
|
case FTP_WAIT220:
|
|
if(ftpcode == 230) {
|
|
/* 230 User logged in - already! Take as 220 if TLS required. */
|
|
if(data->set.use_ssl <= CURLUSESSL_TRY ||
|
|
conn->bits.ftp_use_control_ssl)
|
|
return ftp_state_user_resp(data, ftpcode, ftpc->state);
|
|
}
|
|
else if(ftpcode != 220) {
|
|
failf(data, "Got a %03d ftp-server response when 220 was expected",
|
|
ftpcode);
|
|
return CURLE_WEIRD_SERVER_REPLY;
|
|
}
|
|
|
|
/* We have received a 220 response fine, now we proceed. */
|
|
#ifdef HAVE_GSSAPI
|
|
if(data->set.krb) {
|
|
/* If not anonymous login, try a secure login. Note that this
|
|
procedure is still BLOCKING. */
|
|
|
|
Curl_sec_request_prot(conn, "private");
|
|
/* We set private first as default, in case the line below fails to
|
|
set a valid level */
|
|
Curl_sec_request_prot(conn, data->set.str[STRING_KRB_LEVEL]);
|
|
|
|
if(Curl_sec_login(data, conn))
|
|
infof(data, "Logging in with password in cleartext!");
|
|
else
|
|
infof(data, "Authentication successful");
|
|
}
|
|
#endif
|
|
|
|
if(data->set.use_ssl && !conn->bits.ftp_use_control_ssl) {
|
|
/* We don't have a SSL/TLS control connection yet, but FTPS is
|
|
requested. Try a FTPS connection now */
|
|
|
|
ftpc->count3 = 0;
|
|
switch(data->set.ftpsslauth) {
|
|
case CURLFTPAUTH_DEFAULT:
|
|
case CURLFTPAUTH_SSL:
|
|
ftpc->count2 = 1; /* add one to get next */
|
|
ftpc->count1 = 0;
|
|
break;
|
|
case CURLFTPAUTH_TLS:
|
|
ftpc->count2 = -1; /* subtract one to get next */
|
|
ftpc->count1 = 1;
|
|
break;
|
|
default:
|
|
failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d",
|
|
(int)data->set.ftpsslauth);
|
|
return CURLE_UNKNOWN_OPTION; /* we don't know what to do */
|
|
}
|
|
result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s",
|
|
ftpauth[ftpc->count1]);
|
|
if(!result)
|
|
state(data, FTP_AUTH);
|
|
}
|
|
else
|
|
result = ftp_state_user(data, conn);
|
|
break;
|
|
|
|
case FTP_AUTH:
|
|
/* we have gotten the response to a previous AUTH command */
|
|
|
|
/* RFC2228 (page 5) says:
|
|
*
|
|
* If the server is willing to accept the named security mechanism,
|
|
* and does not require any security data, it must respond with
|
|
* reply code 234/334.
|
|
*/
|
|
|
|
if((ftpcode == 234) || (ftpcode == 334)) {
|
|
/* Curl_ssl_connect is BLOCKING */
|
|
result = Curl_ssl_connect(data, conn, FIRSTSOCKET);
|
|
if(!result) {
|
|
conn->bits.ftp_use_data_ssl = FALSE; /* clear-text data */
|
|
conn->bits.ftp_use_control_ssl = TRUE; /* SSL on control */
|
|
result = ftp_state_user(data, conn);
|
|
}
|
|
}
|
|
else if(ftpc->count3 < 1) {
|
|
ftpc->count3++;
|
|
ftpc->count1 += ftpc->count2; /* get next attempt */
|
|
result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s",
|
|
ftpauth[ftpc->count1]);
|
|
/* remain in this same state */
|
|
}
|
|
else {
|
|
if(data->set.use_ssl > CURLUSESSL_TRY)
|
|
/* we failed and CURLUSESSL_CONTROL or CURLUSESSL_ALL is set */
|
|
result = CURLE_USE_SSL_FAILED;
|
|
else
|
|
/* ignore the failure and continue */
|
|
result = ftp_state_user(data, conn);
|
|
}
|
|
break;
|
|
|
|
case FTP_USER:
|
|
case FTP_PASS:
|
|
result = ftp_state_user_resp(data, ftpcode, ftpc->state);
|
|
break;
|
|
|
|
case FTP_ACCT:
|
|
result = ftp_state_acct_resp(data, ftpcode);
|
|
break;
|
|
|
|
case FTP_PBSZ:
|
|
result =
|
|
Curl_pp_sendf(data, &ftpc->pp, "PROT %c",
|
|
data->set.use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P');
|
|
if(!result)
|
|
state(data, FTP_PROT);
|
|
break;
|
|
|
|
case FTP_PROT:
|
|
if(ftpcode/100 == 2)
|
|
/* We have enabled SSL for the data connection! */
|
|
conn->bits.ftp_use_data_ssl =
|
|
(data->set.use_ssl != CURLUSESSL_CONTROL) ? TRUE : FALSE;
|
|
/* FTP servers typically responds with 500 if they decide to reject
|
|
our 'P' request */
|
|
else if(data->set.use_ssl > CURLUSESSL_CONTROL)
|
|
/* we failed and bails out */
|
|
return CURLE_USE_SSL_FAILED;
|
|
|
|
if(data->set.ftp_ccc) {
|
|
/* CCC - Clear Command Channel
|
|
*/
|
|
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "CCC");
|
|
if(!result)
|
|
state(data, FTP_CCC);
|
|
}
|
|
else
|
|
result = ftp_state_pwd(data, conn);
|
|
break;
|
|
|
|
case FTP_CCC:
|
|
if(ftpcode < 500) {
|
|
/* First shut down the SSL layer (note: this call will block) */
|
|
result = Curl_ssl_shutdown(data, conn, FIRSTSOCKET);
|
|
|
|
if(result)
|
|
failf(data, "Failed to clear the command channel (CCC)");
|
|
}
|
|
if(!result)
|
|
/* Then continue as normal */
|
|
result = ftp_state_pwd(data, conn);
|
|
break;
|
|
|
|
case FTP_PWD:
|
|
if(ftpcode == 257) {
|
|
char *ptr = &data->state.buffer[4]; /* start on the first letter */
|
|
const size_t buf_size = data->set.buffer_size;
|
|
char *dir;
|
|
bool entry_extracted = FALSE;
|
|
|
|
dir = malloc(nread + 1);
|
|
if(!dir)
|
|
return CURLE_OUT_OF_MEMORY;
|
|
|
|
/* Reply format is like
|
|
257<space>[rubbish]"<directory-name>"<space><commentary> and the
|
|
RFC959 says
|
|
|
|
The directory name can contain any character; embedded
|
|
double-quotes should be escaped by double-quotes (the
|
|
"quote-doubling" convention).
|
|
*/
|
|
|
|
/* scan for the first double-quote for non-standard responses */
|
|
while(ptr < &data->state.buffer[buf_size]
|
|
&& *ptr != '\n' && *ptr != '\0' && *ptr != '"')
|
|
ptr++;
|
|
|
|
if('\"' == *ptr) {
|
|
/* it started good */
|
|
char *store;
|
|
ptr++;
|
|
for(store = dir; *ptr;) {
|
|
if('\"' == *ptr) {
|
|
if('\"' == ptr[1]) {
|
|
/* "quote-doubling" */
|
|
*store = ptr[1];
|
|
ptr++;
|
|
}
|
|
else {
|
|
/* end of path */
|
|
entry_extracted = TRUE;
|
|
break; /* get out of this loop */
|
|
}
|
|
}
|
|
else
|
|
*store = *ptr;
|
|
store++;
|
|
ptr++;
|
|
}
|
|
*store = '\0'; /* null-terminate */
|
|
}
|
|
if(entry_extracted) {
|
|
/* If the path name does not look like an absolute path (i.e.: it
|
|
does not start with a '/'), we probably need some server-dependent
|
|
adjustments. For example, this is the case when connecting to
|
|
an OS400 FTP server: this server supports two name syntaxes,
|
|
the default one being incompatible with standard paths. In
|
|
addition, this server switches automatically to the regular path
|
|
syntax when one is encountered in a command: this results in
|
|
having an entrypath in the wrong syntax when later used in CWD.
|
|
The method used here is to check the server OS: we do it only
|
|
if the path name looks strange to minimize overhead on other
|
|
systems. */
|
|
|
|
if(!ftpc->server_os && dir[0] != '/') {
|
|
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SYST");
|
|
if(result) {
|
|
free(dir);
|
|
return result;
|
|
}
|
|
Curl_safefree(ftpc->entrypath);
|
|
ftpc->entrypath = dir; /* remember this */
|
|
infof(data, "Entry path is '%s'", ftpc->entrypath);
|
|
/* also save it where getinfo can access it: */
|
|
data->state.most_recent_ftp_entrypath = ftpc->entrypath;
|
|
state(data, FTP_SYST);
|
|
break;
|
|
}
|
|
|
|
Curl_safefree(ftpc->entrypath);
|
|
ftpc->entrypath = dir; /* remember this */
|
|
infof(data, "Entry path is '%s'", ftpc->entrypath);
|
|
/* also save it where getinfo can access it: */
|
|
data->state.most_recent_ftp_entrypath = ftpc->entrypath;
|
|
}
|
|
else {
|
|
/* couldn't get the path */
|
|
free(dir);
|
|
infof(data, "Failed to figure out path");
|
|
}
|
|
}
|
|
state(data, FTP_STOP); /* we are done with the CONNECT phase! */
|
|
DEBUGF(infof(data, "protocol connect phase DONE"));
|
|
break;
|
|
|
|
case FTP_SYST:
|
|
if(ftpcode == 215) {
|
|
char *ptr = &data->state.buffer[4]; /* start on the first letter */
|
|
char *os;
|
|
char *store;
|
|
|
|
os = malloc(nread + 1);
|
|
if(!os)
|
|
return CURLE_OUT_OF_MEMORY;
|
|
|
|
/* Reply format is like
|
|
215<space><OS-name><space><commentary>
|
|
*/
|
|
while(*ptr == ' ')
|
|
ptr++;
|
|
for(store = os; *ptr && *ptr != ' ';)
|
|
*store++ = *ptr++;
|
|
*store = '\0'; /* null-terminate */
|
|
|
|
/* Check for special servers here. */
|
|
|
|
if(strcasecompare(os, "OS/400")) {
|
|
/* Force OS400 name format 1. */
|
|
result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SITE NAMEFMT 1");
|
|
if(result) {
|
|
free(os);
|
|
return result;
|
|
}
|
|
/* remember target server OS */
|
|
Curl_safefree(ftpc->server_os);
|
|
ftpc->server_os = os;
|
|
state(data, FTP_NAMEFMT);
|
|
break;
|
|
}
|
|
/* Nothing special for the target server. */
|
|
/* remember target server OS */
|
|
Curl_safefree(ftpc->server_os);
|
|
ftpc->server_os = os;
|
|
}
|
|
else {
|
|
/* Cannot identify server OS. Continue anyway and cross fingers. */
|
|
}
|
|
|
|
state(data, FTP_STOP); /* we are done with the CONNECT phase! */
|
|
DEBUGF(infof(data, "protocol connect phase DONE"));
|
|
break;
|
|
|
|
case FTP_NAMEFMT:
|
|
if(ftpcode == 250) {
|
|
/* Name format change successful: reload initial path. */
|
|
ftp_state_pwd(data, conn);
|
|
break;
|
|
}
|
|
|
|
state(data, FTP_STOP); /* we are done with the CONNECT phase! */
|
|
DEBUGF(infof(data, "protocol connect phase DONE"));
|
|
break;
|
|
|
|
case FTP_QUOTE:
|
|
case FTP_POSTQUOTE:
|
|
case FTP_RETR_PREQUOTE:
|
|
case FTP_STOR_PREQUOTE:
|
|
if((ftpcode >= 400) && !ftpc->count2) {
|
|
/* failure response code, and not allowed to fail */
|
|
failf(data, "QUOT command failed with %03d", ftpcode);
|
|
result = CURLE_QUOTE_ERROR;
|
|
}
|
|
else
|
|
result = ftp_state_quote(data, FALSE, ftpc->state);
|
|
break;
|
|
|
|
case FTP_CWD:
|
|
if(ftpcode/100 != 2) {
|
|
/* failure to CWD there */
|
|
if(data->set.ftp_create_missing_dirs &&
|
|
ftpc->cwdcount && !ftpc->count2) {
|
|
/* try making it */
|
|
ftpc->count2++; /* counter to prevent CWD-MKD loops */
|
|
result = Curl_pp_sendf(data, &ftpc->pp, "MKD %s",
|
|
ftpc->dirs[ftpc->cwdcount - 1]);
|
|
if(!result)
|
|
state(data, FTP_MKD);
|
|
}
|
|
else {
|
|
/* return failure */
|
|
failf(data, "Server denied you to change to the given directory");
|
|
ftpc->cwdfail = TRUE; /* don't remember this path as we failed
|
|
to enter it */
|
|
result = CURLE_REMOTE_ACCESS_DENIED;
|
|
}
|
|
}
|
|
else {
|
|
/* success */
|
|
ftpc->count2 = 0;
|
|
if(++ftpc->cwdcount <= ftpc->dirdepth)
|
|
/* send next CWD */
|
|
result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s",
|
|
ftpc->dirs[ftpc->cwdcount - 1]);
|
|
else
|
|
result = ftp_state_mdtm(data);
|
|
}
|
|
break;
|
|
|
|
case FTP_MKD:
|
|
if((ftpcode/100 != 2) && !ftpc->count3--) {
|
|
/* failure to MKD the dir */
|
|
failf(data, "Failed to MKD dir: %03d", ftpcode);
|
|
result = CURLE_REMOTE_ACCESS_DENIED;
|
|
}
|
|
else {
|
|
state(data, FTP_CWD);
|
|
/* send CWD */
|
|
result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s",
|
|
ftpc->dirs[ftpc->cwdcount - 1]);
|
|
}
|
|
break;
|
|
|
|
case FTP_MDTM:
|
|
result = ftp_state_mdtm_resp(data, ftpcode);
|
|
break;
|
|
|
|
case FTP_TYPE:
|
|
case FTP_LIST_TYPE:
|
|
case FTP_RETR_TYPE:
|
|
case FTP_STOR_TYPE:
|
|
result = ftp_state_type_resp(data, ftpcode, ftpc->state);
|
|
break;
|
|
|
|
case FTP_SIZE:
|
|
case FTP_RETR_SIZE:
|
|
case FTP_STOR_SIZE:
|
|
result = ftp_state_size_resp(data, ftpcode, ftpc->state);
|
|
break;
|
|
|
|
case FTP_REST:
|
|
case FTP_RETR_REST:
|
|
result = ftp_state_rest_resp(data, conn, ftpcode, ftpc->state);
|
|
break;
|
|
|
|
case FTP_PRET:
|
|
if(ftpcode != 200) {
|
|
/* there only is this one standard OK return code. */
|
|
failf(data, "PRET command not accepted: %03d", ftpcode);
|
|
return CURLE_FTP_PRET_FAILED;
|
|
}
|
|
result = ftp_state_use_pasv(data, conn);
|
|
break;
|
|
|
|
case FTP_PASV:
|
|
result = ftp_state_pasv_resp(data, ftpcode);
|
|
break;
|
|
|
|
case FTP_PORT:
|
|
result = ftp_state_port_resp(data, ftpcode);
|
|
break;
|
|
|
|
case FTP_LIST:
|
|
case FTP_RETR:
|
|
result = ftp_state_get_resp(data, ftpcode, ftpc->state);
|
|
break;
|
|
|
|
case FTP_STOR:
|
|
result = ftp_state_stor_resp(data, ftpcode, ftpc->state);
|
|
break;
|
|
|
|
case FTP_QUIT:
|
|
/* fallthrough, just stop! */
|
|
default:
|
|
/* internal error */
|
|
state(data, FTP_STOP);
|
|
break;
|
|
}
|
|
} /* if(ftpcode) */
|
|
|
|
return result;
|
|
}
|