Files
Marius CiepluchandClaude Fable 5.1 c3e841003c fix(pair-corpus-honesty): re-harvest curl/OpenSSL excerpts, restore Juliet fixtures
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
2026-09-05 10:45:42 +02:00

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;
}