CVE-2026-92389: TFTP direction confusion
What the report claims
curl's TFTP state machine accepts either direction's opening response regardless of which one was requested, allowing a malicious server to flip the client into transmit mode and drain data from a prior transfer.
Compare with curl's recorded outcome
Known vulnerability
A real vulnerability: CVE-2026-92389 TFTP direction confusion
Shown for comparison after analysis. The pipeline did not see this label.
ProveIt's finding
The available evidence does not settle the report. A maintainer should investigate.
See the withdrawn model reasoning
The following reasoning failed the citation check and does not support the final finding.
- tftp_send_first lacks direction checks for TFTP_EVENT_ACK and TFTP_EVENT_DATA (lines 775-781), while the OACK case correctly checks data->state.upload (lines 766-773).
- A malicious server can answer RRQ with ACK(0), forcing curl into transmit mode (tftp_connect_for_tx) without any upload request.
- In transmit mode, tftp_tx calls Curl_client_read (line 433), which reads from the sticky CURLOPT_READDATA/infilesize left by a prior transfer (e.g., HTTP POST).
- The attack requires only ordinary handle reuse across an HTTP POST and a TFTP download — no TFTP-specific options.
- The HMTFTP draft confirms ACK(0) from the server is not a valid first response to RRQ; the server must send OACK first.
How this analysis ran 5 claims · 12 citations verified
Evidence check: judge quoted an external source that does not say that (https://www.ietf.org/archive/id/draft-maurette-hmtftp-06.html)
Checked against curl master on 2026-09-11 (73d15fcb1a7d). Dev evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.
Inspect the withdrawn draft reply
This draft belongs to the withdrawn judgment. It may contain unsupported claims.
Thank you for the detailed report and PoC. The code confirms that tftp_send_first() accepts ACK and DATA as first replies without verifying the transfer direction (lines 775-781), while the OACK case does check data->state.upload (lines 766-773). A malicious server answering RRQ with ACK(0) forces curl into transmit mode, which then reads from the sticky CURLOPT_READDATA/infilesize. This is a valid confidentiality issue reachable via ordinary handle reuse. We will apply a fix adding the missing direction checks and a regression test.
Inspect all claims and source code 5 claims · 12 code excerpts
This model judgment was withdrawn. The claim assessments below are preserved for inspection and may be wrong.
Report claims
tftp_send_first() dispatches the peer's first reply without checking whether the direction matches what was requested
Read the reasoning
The OACK case (lines 766-773) checks data->state.upload to decide between transmit and receive. The ACK case (line 775-777) and DATA case (line 779-781) lack this check, so a peer's first reply can force the wrong direction.
case TFTP_EVENT_ACK and case TFTP_EVENT_DATA in lib/tftp.c
Read the reasoning
The two cases cited are present in tftp_send_first at the lines shown.
A malicious TFTP server can answer an RRQ with ACK block 0
Read the reasoning
When the client sends RRQ (download), a server reply of ACK block 0 produces TFTP_EVENT_ACK, which the state machine treats as 'connected for transmit' and enters tftp_tx, which reads from the upload source.
curl flips into transmit mode and drains whatever read source the handle's sticky CURLOPT_READDATA points at
Read the reasoning
Once in transmit mode, tftp_tx calls Curl_client_read (line 433), which reads from the sticky read source (CURLOPT_READFUNCTION/CURLOPT_READDATA) bounded by the sticky infilesize (set by prior CURLOPT_POSTFIELDS or left at -1).
run: sh reproduce.sh readdata
Read the reasoning
The PoC demonstrates the attack: HTTP POST sets CURLOPT_READDATA/infilesize, then TFTP download (RRQ) is answered with ACK(0), causing curl to drain the staged file.
Deterministic checks that failed
- quote 2 of 4 quoted lines appear nowhere in curl, e.g. 'case TFTP_EVENT_ACK: /* only valid reply to WRQ — no upload '
What the report says about other projects
confirmed ACK block 0 is the correct reply only to WRQ; DATA block 1 only to RRQ
“For RRQ: the client sends ACK(0) after receiving OACK, then the server starts with DATA(1). For WRQ: the client starts with DATA(1) after receiving OACK, and the server acknowledges each block with ACK(n).” source
Read the original report (excerpt)
Source code
| 651 | static CURLcode tftp_send_first(struct tftp_conn *state, |
| 652 | tftp_event_t event) |
| 653 | { |
| 654 | size_t sbytes; |
| 655 | ssize_t senddata; |
| 656 | const char *mode = "octet"; |
| 657 | char *filename; |
| 658 | struct Curl_easy *data = state->data; |
| 659 | const struct Curl_sockaddr_ex *remote_addr = NULL; |
| 660 | CURLcode result = CURLE_OK; |
| 661 | |
| 662 | /* Set ASCII mode if -B flag was used */ |
| 663 | if(data->state.prefer_ascii) |
| 664 | mode = "netascii"; |
| 665 | |
| 666 | switch(event) { |
| 667 | |
| 668 | case TFTP_EVENT_INIT: /* Send the first packet out */ |
| 669 | case TFTP_EVENT_TIMEOUT: /* Resend the first packet out */ |
| 670 | /* Increment the retry counter, quit if over the limit */ |
| 671 | state->retries++; |
| 672 | if(state->retries > state->retry_max) { |
| 673 | state->error = TFTP_ERR_NORESPONSE; |
| 674 | state->state = TFTP_STATE_FIN; |
| 675 | return result; |
| 676 | } |
| 677 | |
| 678 | if(data->state.upload) { |
| 679 | /* If we are uploading, send a WRQ */ |
| 680 | setpacketevent(&state->spacket, TFTP_EVENT_WRQ); |
| 681 | if(data->state.infilesize != -1) |
| 682 | Curl_pgrsSetUploadSize(data, data->state.infilesize); |
| 683 | } |
| 684 | else { |
| 685 | /* If we are downloading, send an RRQ */ |
| 686 | setpacketevent(&state->spacket, TFTP_EVENT_RRQ); |
| C5: Client sends RRQ for download when not uploading | |
| 687 | } |
| 688 | /* As RFC3617 describes the separator slash is not actually part of the |
| 689 | filename so we skip the always-present first letter of the path |
| 690 | string. */ |
| 691 | if(!state->data->state.up.path[1]) { |
| 692 | failf(data, "Missing filename"); |
| 693 | return CURLE_TFTP_ILLEGAL; |
| 694 | } |
| 695 | result = Curl_urldecode(&state->data->state.up.path[1], 0, |
| 696 | &filename, NULL, REJECT_ZERO); |
| 697 | if(result) |
| 698 | return result; |
| 699 | |
| 700 | if(strlen(filename) + strlen(mode) + 4 > state->blksize) { |
| 701 | failf(data, "TFTP filename too long"); |
| 702 | curlx_free(filename); |
| 703 | return CURLE_TFTP_ILLEGAL; /* too long filename field */ |
| 704 | } |
| 705 | |
| 706 | sbytes = 2 + |
| 707 | curl_msnprintf((char *)state->spacket.data + 2, |
| 708 | state->blksize, |
| 709 | "%s%c%s%c", filename, '\0', mode, '\0'); |
| 710 | curlx_free(filename); |
| 711 | |
| 712 | /* optional addition of TFTP options */ |
| 713 | if(!data->set.tftp_no_options) { |
| 714 | char buf[64]; |
| 715 | /* add tsize option */ |
| 716 | curl_msnprintf(buf, sizeof(buf), "%" FMT_OFF_T, |
| 717 | data->state.upload && (data->state.infilesize != -1) ? |
| 718 | data->state.infilesize : 0); |
| 719 | |
| 720 | result = tftp_option_add(state, &sbytes, sbytes, TFTP_OPTION_TSIZE); |
| 721 | if(result == CURLE_OK) |
| 722 | result = tftp_option_add(state, &sbytes, sbytes, buf); |
| 723 | |
| 724 | /* add blksize option */ |
| 725 | curl_msnprintf(buf, sizeof(buf), "%u", state->requested_blksize); |
| 726 | if(result == CURLE_OK) |
| 727 | result = tftp_option_add(state, &sbytes, sbytes, TFTP_OPTION_BLKSIZE); |
| 728 | if(result == CURLE_OK) |
| 729 | result = tftp_option_add(state, &sbytes, sbytes, buf); |
| 730 | |
| 731 | /* add timeout option */ |
| 732 | curl_msnprintf(buf, sizeof(buf), "%d", state->retry_time); |
| 733 | if(result == CURLE_OK) |
| 734 | result = tftp_option_add(state, &sbytes, sbytes, TFTP_OPTION_INTERVAL); |
| 735 | if(result == CURLE_OK) |
| 736 | result = tftp_option_add(state, &sbytes, sbytes, buf); |
| 737 | |
| 738 | if(result != CURLE_OK) { |
| 739 | failf(data, "TFTP buffer too small for options"); |
| 740 | return CURLE_TFTP_ILLEGAL; |
| 741 | } |
| 742 | } |
| 743 | |
| 744 | /* the typecast for the 3rd argument is mostly for systems that do |
| 745 | not have a size_t argument, like older unixes that want an 'int' */ |
| 746 | #ifdef __AMIGA__ |
| 747 | #define CURL_SENDTO_ARG5(x) CURL_UNCONST(x) |
| 748 | #else |
| 749 | #define CURL_SENDTO_ARG5(x) (x) |
| 750 | #endif |
| 751 | remote_addr = Curl_conn_get_remote_addr(data, FIRSTSOCKET); |
| 752 | if(!remote_addr) |
| 753 | return CURLE_FAILED_INIT; |
| 754 | |
| 755 | senddata = sendto(state->sockfd, (void *)state->spacket.data, |
| 756 | (SEND_TYPE_ARG3)sbytes, 0, |
| 757 | CURL_SENDTO_ARG5(&remote_addr->curl_sa_addr), |
| 758 | (curl_socklen_t)remote_addr->addrlen); |
| 759 | if(senddata != (ssize_t)sbytes) { |
| 760 | char buffer[STRERROR_LEN]; |
| 761 | failf(data, "%s", curlx_strerror(SOCKERRNO, buffer, sizeof(buffer))); |
| 762 | return CURLE_SEND_ERROR; |
| 763 | } |
| 764 | break; |
| 765 | |
| 766 | case TFTP_EVENT_OACK: |
| C1: case TFTP_EVENT_OACK correctly checks data->state.upload before choosing direction | |
| 767 | if(data->state.upload) { |
| 768 | result = tftp_connect_for_tx(state, event); |
| 769 | } |
| 770 | else { |
| 771 | result = tftp_connect_for_rx(state, event); |
| 772 | } |
| 773 | break; |
| 774 | |
| 775 | case TFTP_EVENT_ACK: /* Connected for transmit */ |
| C1: case TFTP_EVENT_ACK calls tftp_connect_for_tx without checking data->state.upload | |
| C2: TFTP_EVENT_ACK case at line 775 | |
| C3: TFTP_EVENT_ACK triggers tftp_connect_for_tx unconditionally | |
| C5: ACK reply forces transmit path | |
| 776 | result = tftp_connect_for_tx(state, event); |
| 777 | break; |
| 778 | |
| 779 | case TFTP_EVENT_DATA: /* Connected for receive */ |
| C1: case TFTP_EVENT_DATA calls tftp_connect_for_rx without checking data->state.upload | |
| C2: TFTP_EVENT_DATA case at line 779 | |
| 780 | result = tftp_connect_for_rx(state, event); |
| 781 | break; |
| 782 | |
| 783 | case TFTP_EVENT_ERROR: |
| 784 | state->state = TFTP_STATE_FIN; |
| 785 | break; |
| 786 | |
| 787 | default: |
| 788 | failf(state->data, "tftp_send_first: internal error"); |
| 789 | return CURLE_TFTP_ILLEGAL; |
| 790 | } |
| 791 | |
| 792 | return result; |
| 793 | } |
| 794 | |
| 795 | /********************************************************** |
| 796 | * |
| 797 | * tftp_translate_code |
| 798 | * |
| 799 | * Translate internal error codes to CURL error codes |
| 800 | * |
| 801 | **********************************************************/ |
| 802 | static CURLcode tftp_translate_code(tftp_error_t error) |
| 803 | { |
| 804 | CURLcode result = CURLE_OK; |
| 805 | |
| 806 | if(error != TFTP_ERR_NONE) { |
| 1127 | |
| 1128 | DEBUGASSERT(r); |
| 1129 | DEBUGASSERT(r->crt); |
| 1130 | DEBUGASSERT(r->phase == CURL_CR_CLIENT); |
| 1131 | DEBUGASSERT(!data->req.reader.stack); |
| 1132 | |
| 1133 | data->req.reader.stack = r; |
| 1134 | clen = r->crt->total_length(data, r); |
| 1135 | /* if we do not have 0 length init, and CRLF conversion is wanted, |
| 1136 | * add the reader for it */ |
| 1137 | if(clen && |
| 1138 | #ifdef CURL_PREFER_LF_LINEENDS |
| 1139 | (data->set.crlf || data->state.prefer_ascii) |
| 1140 | #else |
| 1141 | data->set.crlf |
| 1142 | #endif |
| 1143 | ) { |
| 1144 | result = cr_lc_add(data); |
| 1145 | if(result) |
| 1146 | return result; |
| 1147 | } |
| 1148 | |
| 1149 | return result; |
| 1150 | } |
| 1151 | |
| 1152 | CURLcode Curl_creader_set_fread(struct Curl_easy *data, curl_off_t len) |
| C4: Curl_creader_set_fread uses data->state.infilesize and the fread function/pointer from CURLOPT_READFUNCTION/CURLOPT_READDATA | |
| 1153 | { |
| 1154 | CURLcode result; |
| 1155 | struct Curl_creader *r; |
| 1156 | struct cr_in_ctx *ctx; |
| 1157 | |
| 1158 | result = Curl_creader_create(&r, data, &cr_in, CURL_CR_CLIENT); |
| 1159 | if(result || !r) |
| 1160 | goto out; |
| 1161 | ctx = r->ctx; |
| 1162 | ctx->total_len = len; |
| 1163 | |
| 1164 | cl_reset_reader(data); |
| 1165 | result = do_init_reader_stack(data, r); |
| 1166 | out: |
| 1167 | CURL_TRC_READ(data, "add fread reader, len=%" FMT_OFF_T " -> %d", |
| 1168 | len, (int)result); |
| 1169 | return result; |
| 1170 | } |
| 1171 | |
| 1172 | CURLcode Curl_creader_add(struct Curl_easy *data, |
| 1173 | struct Curl_creader *reader) |
| 1174 | { |
| 1175 | CURLcode result; |
| 1176 | struct Curl_creader **anchor = &data->req.reader.stack; |
| 1177 | |
| 1178 | if(!*anchor) { |
| 1179 | result = Curl_creader_set_fread(data, data->state.infilesize); |
| 1180 | if(result) |
| 1181 | return result; |
| 1182 | } |
| 1183 | |
| 1184 | /* Insert the writer as first in its phase. |
| 1185 | * Skip existing readers of lower phases. */ |
| 1186 | while(*anchor && (*anchor)->phase < reader->phase) |
| 1187 | anchor = &(*anchor)->next; |
| 1188 | reader->next = *anchor; |
| 1189 | *anchor = reader; |
| 1190 | return CURLE_OK; |
| 1191 | } |
| 1192 | |
| 1193 | CURLcode Curl_creader_set(struct Curl_easy *data, struct Curl_creader *r) |
| 1194 | { |
| 1195 | CURLcode result; |
| 1196 | |
| 1197 | DEBUGASSERT(r); |
| 1198 | DEBUGASSERT(r->crt); |
| 1199 | DEBUGASSERT(r->phase == CURL_CR_CLIENT); |
| 1200 | |
| 1201 | cl_reset_reader(data); |
| 1202 | result = do_init_reader_stack(data, r); |
| 1203 | if(result) |
| 1204 | Curl_creader_free(data, r); |
| 1208 | CURLcode Curl_client_read(struct Curl_easy *data, char *buf, size_t blen, |
| 1209 | size_t *nread, bool *eos) |
| 1210 | { |
| 1211 | CURLcode result; |
| 1212 | |
| 1213 | DEBUGASSERT(buf); |
| 1214 | DEBUGASSERT(blen); |
| 1215 | DEBUGASSERT(nread); |
| 1216 | DEBUGASSERT(eos); |
| 1217 | *nread = 0; |
| 1218 | |
| 1219 | if(!data->req.reader.stack) { |
| 1220 | result = Curl_creader_set_fread(data, data->state.infilesize); |
| C4: Curl_client_read uses data->state.infilesize as length limit | |
| 1221 | if(result) |
| 1222 | return result; |
| 1223 | DEBUGASSERT(data->req.reader.stack); |
| 1224 | } |
| 1225 | if(!data->req.reader_started) { |
| 1226 | Curl_rlimit_start(&data->progress.ul.rlimit, Curl_pgrs_now(data), -1); |
| 1227 | data->req.reader_started = TRUE; |
| 1228 | } |
| 1229 | |
| 1230 | if(Curl_rlimit_active(&data->progress.ul.rlimit)) { |
| 1231 | curl_off_t ul_avail = Curl_rlimit_avail(&data->progress.ul.rlimit, NULL); |
| 1232 | if(ul_avail <= 0) { |
| 1233 | result = CURLE_OK; |
| 1234 | *eos = FALSE; |
| 1235 | goto out; |
| 1236 | } |
| 1237 | if(ul_avail < (curl_off_t)blen) |
| 1238 | blen = (size_t)ul_avail; |
| 1239 | } |
| 1240 | result = Curl_creader_read(data, data->req.reader.stack, buf, blen, |
| 1241 | nread, eos); |
| 1242 | |
| 1243 | out: |
| 1244 | CURL_TRC_READ(data, "client_read(len=%zu) -> %d, nread=%zu, eos=%d", |
| 1245 | blen, (int)result, *nread, *eos); |
| 1246 | return result; |
| 1247 | } |
| 358 | static CURLcode tftp_tx(struct tftp_conn *state, tftp_event_t event) |
| 359 | { |
| 360 | struct Curl_easy *data = state->data; |
| 361 | ssize_t sbytes; |
| 362 | CURLcode result = CURLE_OK; |
| 363 | struct SingleRequest *k = &data->req; |
| 364 | size_t cb; /* Bytes currently read */ |
| 365 | char buffer[STRERROR_LEN]; |
| 366 | char *bufptr; |
| 367 | bool eos; |
| 368 | |
| 369 | switch(event) { |
| 370 | |
| 371 | case TFTP_EVENT_ACK: |
| 372 | case TFTP_EVENT_OACK: |
| 373 | if(event == TFTP_EVENT_ACK) { |
| C3: tftp_tx handles TFTP_EVENT_ACK and TFTP_EVENT_OACK, reading data via Curl_client_read | |
| 374 | /* Ack the packet */ |
| 375 | int rblock = getrpacketblock(&state->rpacket); |
| 376 | |
| 377 | if(rblock != state->block && |
| 378 | /* There is a bug in tftpd-hpa that causes it to send us an ACK for |
| 379 | * 65535 when the block number wraps to 0. To handle it, when we are |
| 380 | * expecting 0, also accept 65535. See |
| 381 | * https://www.syslinux.org/archives/2010-September/015612.html |
| 382 | * */ |
| 383 | !(state->block == 0 && rblock == 65535)) { |
| 384 | /* This is not the expected block. Log it and up the retry counter */ |
| 385 | infof(data, "Received ACK for block %d, expecting %d", |
| 386 | rblock, state->block); |
| 387 | state->retries++; |
| 388 | /* Bail out if over the maximum */ |
| 389 | if(state->retries > state->retry_max) { |
| 390 | failf(data, "tftp_tx: giving up waiting for block %d ack", |
| 391 | state->block); |
| 392 | result = CURLE_SEND_ERROR; |
| 393 | } |
| 394 | else { |
| 395 | /* Re-send the data packet */ |
| 396 | sbytes = sendto(state->sockfd, (void *)state->spacket.data, |
| 397 | 4 + (SEND_TYPE_ARG3)state->sbytes, SEND_4TH_ARG, |
| 398 | (struct sockaddr *)&state->remote_addr, |
| 399 | state->remote_addrlen); |
| 400 | /* Check all sbytes were sent */ |
| 401 | if(sbytes < 0) { |
| 402 | failf(data, "%s", |
| 403 | curlx_strerror(SOCKERRNO, buffer, sizeof(buffer))); |
| 404 | result = CURLE_SEND_ERROR; |
| 405 | } |
| 406 | } |
| 407 | |
| 408 | return result; |
| 409 | } |
| 410 | /* This is the expected packet. Reset the counters and send the next |
| 411 | block */ |
| 412 | state->rx_time = time(NULL); |
| 413 | state->block++; |
| 414 | } |
| 415 | else |
| 416 | state->block = 1; /* first data block is 1 when using OACK */ |
| 417 | |
| 418 | state->retries = 0; |
| 419 | setpacketevent(&state->spacket, TFTP_EVENT_DATA); |
| 420 | setpacketblock(&state->spacket, state->block); |
| 421 | if(state->block > 1 && state->sbytes < state->blksize) { |
| 422 | state->state = TFTP_STATE_FIN; |
| 423 | return CURLE_OK; |
| 424 | } |
| 425 | |
| 426 | /* TFTP considers data block size < 512 bytes as an end of session, so |
| 427 | * in some cases we must wait for additional data to build full (512 bytes) |
| 428 | * data block. |
| 429 | * */ |
| 430 | state->sbytes = 0; |
| 431 | bufptr = (char *)state->spacket.data + 4; |
| 432 | do { |
| 433 | result = Curl_client_read(data, bufptr, state->blksize - state->sbytes, |
| C4: tftp_tx calls Curl_client_read to read upload data | |
| 434 | &cb, &eos); |
| 435 | if(result) |
| 436 | return result; |
| 437 | state->sbytes += cb; |
| 438 | bufptr += cb; |
| 439 | } while(state->sbytes < state->blksize && cb); |
| 440 | |
| 441 | sbytes = sendto(state->sockfd, (void *)state->spacket.data, |
| 442 | 4 + (SEND_TYPE_ARG3)state->sbytes, SEND_4TH_ARG, |
| 443 | (struct sockaddr *)&state->remote_addr, |
| 444 | state->remote_addrlen); |
| 445 | /* Check all sbytes were sent */ |
| 446 | if(sbytes < 0) { |
| 447 | failf(data, "%s", curlx_strerror(SOCKERRNO, buffer, sizeof(buffer))); |
| 448 | return CURLE_SEND_ERROR; |
| 449 | } |
| 450 | /* Update the progress meter */ |
| 451 | k->writebytecount += state->sbytes; |
| 452 | Curl_pgrs_upload_inc(data, state->sbytes); |
| 453 | break; |
| 454 | |
| 455 | case TFTP_EVENT_TIMEOUT: |
| 456 | /* Increment the retry counter and log the timeout */ |
| 457 | state->retries++; |
| 458 | infof(data, "Timeout waiting for block %d ACK. " |
| 459 | "Retries = %d", NEXT_BLOCKNUM(state->block), state->retries); |
| 460 | /* Decide if we have had enough */ |
| 461 | if(state->retries > state->retry_max) { |
| 462 | state->error = TFTP_ERR_TIMEOUT; |
| 463 | state->state = TFTP_STATE_FIN; |
| 464 | } |
| 465 | else { |
| 466 | /* Re-send the data packet */ |
| 467 | sbytes = sendto(state->sockfd, (void *)state->spacket.data, |
| 468 | 4 + (SEND_TYPE_ARG3)state->sbytes, SEND_4TH_ARG, |
| 469 | (struct sockaddr *)&state->remote_addr, |
| 470 | state->remote_addrlen); |
| 471 | /* Check all sbytes were sent */ |
| 472 | if(sbytes < 0) { |
| 473 | failf(data, "%s", curlx_strerror(SOCKERRNO, buffer, sizeof(buffer))); |
| 474 | return CURLE_SEND_ERROR; |
| 475 | } |
| 476 | /* since this was a re-send, we remain at the still byte position */ |
| 477 | Curl_pgrsSetUploadCounter(data, k->writebytecount); |
| 478 | } |
| 479 | break; |
| 480 | |
| 481 | case TFTP_EVENT_ERROR: |
| 482 | state->state = TFTP_STATE_FIN; |
| 483 | setpacketevent(&state->spacket, TFTP_EVENT_ERROR); |
| 484 | setpacketblock(&state->spacket, state->block); |
| 485 | (void)sendto(state->sockfd, (void *)state->spacket.data, 4, SEND_4TH_ARG, |
| 486 | (struct sockaddr *)&state->remote_addr, |
| 487 | state->remote_addrlen); |
| 488 | /* do not bother with the return code, but if the socket is still up we |
| 489 | * should be a good TFTP client and let the server know we are done */ |
| 490 | state->state = TFTP_STATE_FIN; |
| 491 | break; |
| 492 | |
| 493 | default: |
| 494 | failf(data, "tftp_tx: internal error, event: %d", (int)event); |
| 495 | break; |
| 496 | } |
| 497 | |
| 498 | return result; |
| 499 | } |
Show 90 lines (not cited by the judge)
| 1036 | static CURLcode tftp_receive_packet(struct Curl_easy *data, |
| 1037 | struct tftp_conn *state) |
| 1038 | { |
| 1039 | CURLcode result = CURLE_OK; |
| 1040 | struct Curl_sockaddr_storage remote_addr; |
| 1041 | curl_socklen_t fromlen = sizeof(remote_addr); |
| 1042 | |
| 1043 | /* Receive the packet */ |
| 1044 | state->rbytes = (int)recvfrom(state->sockfd, |
| 1045 | (void *)state->rpacket.data, |
| 1046 | (RECV_TYPE_ARG3)state->blksize + 4, |
| 1047 | 0, |
| 1048 | (struct sockaddr *)&remote_addr, |
| 1049 | &fromlen); |
| 1050 | if((state->rbytes >= 0) && fromlen) { |
| 1051 | if(state->remote_pinned) { |
| 1052 | /* pinned, verify that it comes from the same address */ |
| 1053 | if((state->remote_addrlen != fromlen) || |
| 1054 | memcmp(&remote_addr, &state->remote_addr, fromlen)) { |
| 1055 | failf(data, "Data received from another address"); |
| 1056 | return CURLE_RECV_ERROR; |
| 1057 | } |
| 1058 | } |
| 1059 | else { |
| 1060 | /* pin address on first use */ |
| 1061 | state->remote_pinned = TRUE; |
| 1062 | state->remote_addrlen = fromlen; |
| 1063 | memcpy(&state->remote_addr, &remote_addr, fromlen); |
| 1064 | } |
| 1065 | } |
| 1066 | |
| 1067 | /* Sanity check packet length */ |
| 1068 | if(state->rbytes < 4) { |
| 1069 | failf(data, "Received too short packet"); |
| 1070 | /* Not a timeout, but how best to handle it? */ |
| 1071 | state->event = TFTP_EVENT_TIMEOUT; |
| 1072 | } |
| 1073 | else { |
| 1074 | /* The event is given by the TFTP packet time */ |
| 1075 | unsigned short event = getrpacketevent(&state->rpacket); |
| 1076 | state->event = (tftp_event_t)event; |
| 1077 | |
| 1078 | switch(state->event) { |
| 1079 | case TFTP_EVENT_DATA: |
| 1080 | /* Do not pass to the client empty or retransmitted packets */ |
| 1081 | if(state->rbytes > 4 && |
| 1082 | (NEXT_BLOCKNUM(state->block) == getrpacketblock(&state->rpacket))) { |
| 1083 | result = Curl_client_write(data, CLIENTWRITE_BODY, |
| 1084 | (const char *)state->rpacket.data + 4, |
| 1085 | state->rbytes - 4); |
| 1086 | if(result) { |
| 1087 | tftp_state_machine(state, TFTP_EVENT_ERROR); |
| 1088 | return result; |
| 1089 | } |
| 1090 | } |
| 1091 | break; |
| 1092 | case TFTP_EVENT_ERROR: { |
| 1093 | unsigned short error = getrpacketblock(&state->rpacket); |
| 1094 | const char *str = (const char *)state->rpacket.data + 4; |
| 1095 | size_t strn = state->rbytes - 4; |
| 1096 | state->error = (tftp_error_t)error; |
| 1097 | if(tftp_strnlen(str, strn) < strn) |
| 1098 | infof(data, "TFTP error: %s", str); |
| 1099 | break; |
| 1100 | } |
| 1101 | case TFTP_EVENT_ACK: |
| 1102 | break; |
| 1103 | case TFTP_EVENT_OACK: |
| 1104 | result = tftp_parse_option_ack(state, |
| 1105 | (const char *)state->rpacket.data + 2, |
| 1106 | state->rbytes-2); |
| 1107 | if(result) |
| 1108 | return result; |
| 1109 | break; |
| 1110 | case TFTP_EVENT_RRQ: |
| 1111 | case TFTP_EVENT_WRQ: |
| 1112 | default: |
| 1113 | failf(data, "%s", "Internal error: Unexpected packet"); |
| 1114 | break; |
| 1115 | } |
| 1116 | |
| 1117 | /* Update the progress meter */ |
| 1118 | result = Curl_pgrsUpdate(data); |
| 1119 | if(result) { |
| 1120 | tftp_state_machine(state, TFTP_EVENT_ERROR); |
| 1121 | return result; |
| 1122 | } |
| 1123 | } |
| 1124 | return result; |
| 1125 | } |
Show 55 lines (not cited by the judge)
| 1178 | timeout_ms = tftp_state_timeout(state, &event); |
| 1179 | if(timeout_ms < 0) { |
| 1180 | failf(data, "TFTP response timeout"); |
| 1181 | return CURLE_OPERATION_TIMEDOUT; |
| 1182 | } |
| 1183 | if(event != TFTP_EVENT_NONE) { |
| 1184 | result = tftp_state_machine(state, event); |
| 1185 | if(result) |
| 1186 | return result; |
| 1187 | *done = (state->state == TFTP_STATE_FIN); |
| 1188 | if(*done) |
| 1189 | /* Tell curl we are done */ |
| 1190 | Curl_xfer_setup_nop(data); |
| 1191 | } |
| 1192 | else { |
| 1193 | /* no timeouts to handle, check our socket */ |
| 1194 | int rc = SOCKET_READABLE(state->sockfd, 0); |
| 1195 | |
| 1196 | if(rc == -1) { |
| 1197 | /* bail out */ |
| 1198 | int sockerr = SOCKERRNO; |
| 1199 | char buffer[STRERROR_LEN]; |
| 1200 | failf(data, "%s", curlx_strerror(sockerr, buffer, sizeof(buffer))); |
| 1201 | state->event = TFTP_EVENT_ERROR; |
| 1202 | } |
| 1203 | else if(rc) { |
| 1204 | result = tftp_receive_packet(data, state); |
| 1205 | if(result) |
| 1206 | return result; |
| 1207 | result = tftp_state_machine(state, state->event); |
| 1208 | if(result) |
| 1209 | return result; |
| 1210 | *done = (state->state == TFTP_STATE_FIN); |
| 1211 | if(*done) |
| 1212 | /* Tell curl we are done */ |
| 1213 | Curl_xfer_setup_nop(data); |
| 1214 | } |
| 1215 | /* if rc == 0, then select() timed out */ |
| 1216 | } |
| 1217 | |
| 1218 | return result; |
| 1219 | } |
| 1220 | |
| 1221 | /********************************************************** |
| 1222 | * |
| 1223 | * tftp_doing |
| 1224 | * |
| 1225 | * Called from multi.c while DOing |
| 1226 | * |
| 1227 | **********************************************************/ |
| 1228 | static CURLcode tftp_doing(struct Curl_easy *data, bool *dophase_done) |
| 1229 | { |
| 1230 | CURLcode result; |
| 1231 | result = tftp_multi_statemach(data, dophase_done); |
| 1232 |
Show 2 lines (not cited by the judge)
| 71 | #define CURLPROTO_REDIR (CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP | \ |
| 72 | CURLPROTO_FTPS) |
Show 27 lines (not cited by the judge)
| 373 | CURLcode Curl_client_write(struct Curl_easy *data, int type, const char *buf, |
| 374 | size_t len) |
| 375 | { |
| 376 | CURLcode result; |
| 377 | |
| 378 | /* it is one of those, at least */ |
| 379 | DEBUGASSERT(type & |
| 380 | (CLIENTWRITE_BODY | CLIENTWRITE_HEADER | CLIENTWRITE_INFO)); |
| 381 | /* BODY is only BODY (with optional EOS) */ |
| 382 | DEBUGASSERT(!(type & CLIENTWRITE_BODY) || |
| 383 | ((type & ~(CLIENTWRITE_BODY | CLIENTWRITE_EOS)) == 0)); |
| 384 | /* INFO is only INFO (with optional EOS) */ |
| 385 | DEBUGASSERT(!(type & CLIENTWRITE_INFO) || |
| 386 | ((type & ~(CLIENTWRITE_INFO | CLIENTWRITE_EOS)) == 0)); |
| 387 | |
| 388 | if(!data->req.writer.stack) { |
| 389 | result = do_init_writer_stack(data); |
| 390 | if(result) |
| 391 | return result; |
| 392 | DEBUGASSERT(data->req.writer.stack); |
| 393 | } |
| 394 | |
| 395 | result = Curl_cwriter_write(data, data->req.writer.stack, type, buf, len); |
| 396 | CURL_TRC_WRITE(data, "client_write(type=%x, len=%zu) -> %d", |
| 397 | (unsigned int)type, len, (int)result); |
| 398 | return result; |
| 399 | } |
Show 20 lines (not cited by the judge)
| 2936 | CURLcode curl_easy_setopt(CURL *curl, CURLoption option, ...) |
| 2937 | { |
| 2938 | struct Curl_eapi_guard guard; |
| 2939 | CURLcode result; |
| 2940 | |
| 2941 | if(CURL_EAPI_ENTER(&guard, curl, easy_setopt, &result)) { |
| 2942 | struct Curl_easy *data = curl; |
| 2943 | va_list arg; |
| 2944 | |
| 2945 | va_start(arg, option); |
| 2946 | |
| 2947 | result = Curl_vsetopt(data, option, arg); |
| 2948 | |
| 2949 | va_end(arg); |
| 2950 | if(result == CURLE_BAD_FUNCTION_ARGUMENT) |
| 2951 | failf(data, "setopt 0x%x got bad argument", (unsigned int)option); |
| 2952 | } |
| 2953 | CURL_EAPI_LEAVE(&guard); |
| 2954 | return result; |
| 2955 | } |
Show 9 lines (not cited by the judge)
| 273 | int main(void) |
| 274 | { |
| 275 | size_t i; |
| 276 | |
| 277 | for(i = 0; i < CURL_ARRAYSIZE(disabled); i++) |
| 278 | puts(disabled[i]); |
| 279 | |
| 280 | return 0; |
| 281 | } |
Show 13 lines (not cited by the judge)
| 637 | static CURLcode tftp_connect_for_rx(struct tftp_conn *state, |
| 638 | tftp_event_t event) |
| 639 | { |
| 640 | CURLcode result; |
| 641 | |
| 642 | infof(state->data, "%s", "Connected for receive"); |
| 643 | |
| 644 | state->state = TFTP_STATE_RX; |
| 645 | result = tftp_set_timeouts(state); |
| 646 | if(result) |
| 647 | return result; |
| 648 | return tftp_rx(state, event); |
| 649 | } |
Show 13 lines (not cited by the judge)
| 501 | static CURLcode tftp_connect_for_tx(struct tftp_conn *state, |
| 502 | tftp_event_t event) |
| 503 | { |
| 504 | CURLcode result; |
| 505 | |
| 506 | infof(state->data, "%s", "Connected for transmit"); |
| 507 | |
| 508 | state->state = TFTP_STATE_TX; |
| 509 | result = tftp_set_timeouts(state); |
| 510 | if(result) |
| 511 | return result; |
| 512 | return tftp_tx(state, event); |
| 513 | } |