- Markus Koetter provided a polished and updated version of Chad Monroe's TFTP

rework patch that now integrates TFTP properly into libcurl so that it can
  be used non-blocking with the multi interface and more. BLKSIZE also works.

  The --tftp-blksize option was added to allow setting the TFTP BLKSIZE from
  the command line.
This commit is contained in:
Daniel Stenberg 2009-11-27 23:46:29 +00:00
parent a240f4d1df
commit 6e38cc9048
5 changed files with 509 additions and 235 deletions

16
CHANGES
View File

@ -8,11 +8,19 @@
Daniel Stenberg (28 Nov 2009)
- Markus Koetter provided a polished and updated version of Chad Monroe's TFTP
rework patch that now integrates TFTP properly into libcurl so that it can
be used non-blocking with the multi interface and more. BLKSIZE also works.
The --tftp-blksize option was added to allow setting the TFTP BLKSIZE from
the command line.
Daniel Stenberg (26 Nov 2009) Daniel Stenberg (26 Nov 2009)
- Extended and fixed the change I did on Dec 11 for the the progress - Extended and fixed the change I did on Dec 11 for the the progress
meter/callback during FTP command/response sequences. It turned out it was meter/callback during FTP command/response sequences. It turned out it was
really lame before and now the progress meter SHOULD get called at least really lame before and now the progress meter SHOULD get called at least
once per second. once per second.
Daniel Stenberg (23 Nov 2009) Daniel Stenberg (23 Nov 2009)
- Bjorn Augustsson reported a bug which made curl not report any problems even - Bjorn Augustsson reported a bug which made curl not report any problems even

View File

@ -11,6 +11,7 @@ This release includes the following changes:
o support SSL_FILETYPE_ENGINE for client certificate o support SSL_FILETYPE_ENGINE for client certificate
o curl-config can now show the arguments used when building curl o curl-config can now show the arguments used when building curl
o non-blocking TFTP
This release includes the following bugfixes: This release includes the following bugfixes:
@ -26,6 +27,7 @@ This release includes the following bugfixes:
o HTTP proxy tunnel re-used connection even if tunnel got disabled o HTTP proxy tunnel re-used connection even if tunnel got disabled
o SSL lib post-close write o SSL lib post-close write
o curl failed to report write errors for tiny failed downloads o curl failed to report write errors for tiny failed downloads
o TFTP BLKSIZE
This release includes the following known bugs: This release includes the following known bugs:
@ -36,6 +38,7 @@ advice from friends like these:
Yang Tse, Kamil Dudka, Christian Schmitz, Constantine Sapuntzakis, Yang Tse, Kamil Dudka, Christian Schmitz, Constantine Sapuntzakis,
Marco Maggi, Camille Moncelier, Claes Jakobsson, Kevin Baughman, Marco Maggi, Camille Moncelier, Claes Jakobsson, Kevin Baughman,
Marc Kleine-Budde, Jad Chamcham, Bjorn Augustsson, David Byron Marc Kleine-Budde, Jad Chamcham, Bjorn Augustsson, David Byron,
Markus Koetter, Chad Monroe
Thanks! (and sorry if I forgot to mention someone) Thanks! (and sorry if I forgot to mention someone)

View File

@ -70,6 +70,7 @@
#include "connect.h" #include "connect.h"
#include "strerror.h" #include "strerror.h"
#include "sockaddr.h" /* required for Curl_sockaddr_storage */ #include "sockaddr.h" /* required for Curl_sockaddr_storage */
#include "multiif.h"
#include "url.h" #include "url.h"
#include "rawstr.h" #include "rawstr.h"
@ -103,7 +104,8 @@ typedef enum {
} tftp_state_t; } tftp_state_t;
typedef enum { typedef enum {
TFTP_EVENT_INIT=0, TFTP_EVENT_NONE = -1,
TFTP_EVENT_INIT = 0,
TFTP_EVENT_RRQ = 1, TFTP_EVENT_RRQ = 1,
TFTP_EVENT_WRQ = 2, TFTP_EVENT_WRQ = 2,
TFTP_EVENT_DATA = 3, TFTP_EVENT_DATA = 3,
@ -137,20 +139,22 @@ typedef struct tftp_state_data {
tftp_state_t state; tftp_state_t state;
tftp_mode_t mode; tftp_mode_t mode;
tftp_error_t error; tftp_error_t error;
tftp_event_t event;
struct connectdata *conn; struct connectdata *conn;
curl_socket_t sockfd; curl_socket_t sockfd;
int retries; int retries;
time_t retry_time; int retry_time;
time_t retry_max; int retry_max;
time_t start_time; time_t start_time;
time_t max_time; time_t max_time;
time_t rx_time;
unsigned short block; unsigned short block;
struct Curl_sockaddr_storage local_addr; struct Curl_sockaddr_storage local_addr;
struct Curl_sockaddr_storage remote_addr; struct Curl_sockaddr_storage remote_addr;
curl_socklen_t remote_addrlen; socklen_t remote_addrlen;
ssize_t rbytes; int rbytes;
size_t sbytes; int sbytes;
size_t blksize; int blksize;
int requested_blksize; int requested_blksize;
tftp_packet_t rpacket; tftp_packet_t rpacket;
tftp_packet_t spacket; tftp_packet_t spacket;
@ -166,6 +170,11 @@ static CURLcode tftp_do(struct connectdata *conn, bool *done);
static CURLcode tftp_done(struct connectdata *conn, static CURLcode tftp_done(struct connectdata *conn,
CURLcode, bool premature); CURLcode, bool premature);
static CURLcode tftp_setup_connection(struct connectdata * conn); static CURLcode tftp_setup_connection(struct connectdata * conn);
static CURLcode tftp_multi_statemach(struct connectdata *conn, bool *done);
static CURLcode tftp_doing(struct connectdata *conn, bool *dophase_done);
static int tftp_getsock(struct connectdata *conn, curl_socket_t *socks,
int numsocks);
CURLcode tftp_translate_code(tftp_error_t error);
/* /*
@ -179,17 +188,16 @@ const struct Curl_handler Curl_handler_tftp = {
tftp_done, /* done */ tftp_done, /* done */
ZERO_NULL, /* do_more */ ZERO_NULL, /* do_more */
tftp_connect, /* connect_it */ tftp_connect, /* connect_it */
ZERO_NULL, /* connecting */ tftp_multi_statemach, /* connecting */
ZERO_NULL, /* doing */ tftp_doing, /* doing */
ZERO_NULL, /* proto_getsock */ tftp_getsock, /* proto_getsock */
ZERO_NULL, /* doing_getsock */ tftp_getsock, /* doing_getsock */
ZERO_NULL, /* perform_getsock */ ZERO_NULL, /* perform_getsock */
tftp_disconnect, /* disconnect */ tftp_disconnect, /* disconnect */
PORT_TFTP, /* defport */ PORT_TFTP, /* defport */
PROT_TFTP /* protocol */ PROT_TFTP /* protocol */
}; };
/********************************************************** /**********************************************************
* *
* tftp_set_timeouts - * tftp_set_timeouts -
@ -226,14 +234,14 @@ static CURLcode tftp_set_timeouts(tftp_state_data_t *state)
timeout = maxtime ; timeout = maxtime ;
/* Average restart after 5 seconds */ /* Average restart after 5 seconds */
state->retry_max = timeout/5; state->retry_max = (int)timeout/5;
if(state->retry_max < 1) if(state->retry_max < 1)
/* avoid division by zero below */ /* avoid division by zero below */
state->retry_max = 1; state->retry_max = 1;
/* Compute the re-start interval to suit the timeout */ /* Compute the re-start interval to suit the timeout */
state->retry_time = timeout/state->retry_max; state->retry_time = (int)timeout/state->retry_max;
if(state->retry_time<1) if(state->retry_time<1)
state->retry_time=1; state->retry_time=1;
@ -250,9 +258,9 @@ static CURLcode tftp_set_timeouts(tftp_state_data_t *state)
timeout = maxtime/10 ; timeout = maxtime/10 ;
/* Average reposting an ACK after 15 seconds */ /* Average reposting an ACK after 15 seconds */
state->retry_max = timeout/15; state->retry_max = (int)timeout/15;
} }
/* But bound the total number */ /* But bound the total number */
if(state->retry_max<3) if(state->retry_max<3)
state->retry_max=3; state->retry_max=3;
@ -269,6 +277,9 @@ static CURLcode tftp_set_timeouts(tftp_state_data_t *state)
state->state, (state->max_time-state->start_time), state->state, (state->max_time-state->start_time),
state->retry_time, state->retry_max); state->retry_time, state->retry_max);
/* init RX time */
time(&state->rx_time);
return CURLE_OK; return CURLE_OK;
} }
@ -352,9 +363,9 @@ static CURLcode tftp_parse_option_ack(tftp_state_data_t *state,
infof(data, "got option=(%s) value=(%s)\n", option, value); infof(data, "got option=(%s) value=(%s)\n", option, value);
if(checkprefix(option, TFTP_OPTION_BLKSIZE)) { if(checkprefix(option, TFTP_OPTION_BLKSIZE)) {
int blksize; long blksize;
blksize = (int)strtol( value, NULL, 10 ); blksize = strtol( value, NULL, 10 );
if(!blksize) { if(!blksize) {
failf(data, "invalid blocksize value in OACK packet"); failf(data, "invalid blocksize value in OACK packet");
@ -379,7 +390,7 @@ static CURLcode tftp_parse_option_ack(tftp_state_data_t *state,
return CURLE_TFTP_ILLEGAL; return CURLE_TFTP_ILLEGAL;
} }
state->blksize = blksize; state->blksize = (int)blksize;
infof(data, "%s (%d) %s (%d)\n", "blksize parsed from OACK", infof(data, "%s (%d) %s (%d)\n", "blksize parsed from OACK",
state->blksize, "requested", state->requested_blksize); state->blksize, "requested", state->requested_blksize);
} }
@ -402,13 +413,14 @@ static CURLcode tftp_parse_option_ack(tftp_state_data_t *state,
static size_t tftp_option_add(tftp_state_data_t *state, size_t csize, static size_t tftp_option_add(tftp_state_data_t *state, size_t csize,
char *buf, const char *option) char *buf, const char *option)
{ {
if( ( strlen(option) + csize + 1U ) > state->blksize ) if( ( strlen(option) + csize + 1 ) > (size_t)state->blksize )
return 0; return 0;
strcpy(buf, option); strcpy(buf, option);
return( strlen(option) + 1 ); return( strlen(option) + 1 );
} }
static CURLcode tftp_connect_for_tx(tftp_state_data_t *state, tftp_event_t event) static CURLcode tftp_connect_for_tx(tftp_state_data_t *state,
tftp_event_t event)
{ {
CURLcode res; CURLcode res;
#ifndef CURL_DISABLE_VERBOSE_STRINGS #ifndef CURL_DISABLE_VERBOSE_STRINGS
@ -423,7 +435,8 @@ static CURLcode tftp_connect_for_tx(tftp_state_data_t *state, tftp_event_t event
return tftp_tx(state, event); return tftp_tx(state, event);
} }
static CURLcode tftp_connect_for_rx(tftp_state_data_t *state, tftp_event_t event) static CURLcode tftp_connect_for_rx(tftp_state_data_t *state,
tftp_event_t event)
{ {
CURLcode res; CURLcode res;
#ifndef CURL_DISABLE_VERBOSE_STRINGS #ifndef CURL_DISABLE_VERBOSE_STRINGS
@ -441,6 +454,7 @@ static CURLcode tftp_connect_for_rx(tftp_state_data_t *state, tftp_event_t event
static CURLcode tftp_send_first(tftp_state_data_t *state, tftp_event_t event) static CURLcode tftp_send_first(tftp_state_data_t *state, tftp_event_t event)
{ {
size_t sbytes; size_t sbytes;
ssize_t senddata;
const char *mode = "octet"; const char *mode = "octet";
char *filename; char *filename;
char buf[64]; char buf[64];
@ -506,18 +520,19 @@ static CURLcode tftp_send_first(tftp_state_data_t *state, tftp_event_t event)
TFTP_OPTION_BLKSIZE); TFTP_OPTION_BLKSIZE);
sbytes += tftp_option_add(state, sbytes, sbytes += tftp_option_add(state, sbytes,
(char *)state->spacket.data+sbytes, buf ); (char *)state->spacket.data+sbytes, buf );
/* add timeout option */ /* add timeout option, this is the max time the session may live */
snprintf( buf, sizeof(buf), "%d", state->retry_time ); snprintf( buf, sizeof(buf), "%d", state->retry_time*state->retry_max );
sbytes += tftp_option_add(state, sbytes, sbytes += tftp_option_add(state, sbytes,
(char *)state->spacket.data+sbytes, (char *)state->spacket.data+sbytes,
TFTP_OPTION_INTERVAL); TFTP_OPTION_INTERVAL);
sbytes += tftp_option_add(state, sbytes, sbytes += tftp_option_add(state, sbytes,
(char *)state->spacket.data+sbytes, buf ); (char *)state->spacket.data+sbytes, buf );
if (sendto(state->sockfd, (void *)state->spacket.data, senddata = sendto(state->sockfd, (void *)state->spacket.data,
sbytes, 0, sbytes, 0,
state->conn->ip_addr->ai_addr, state->conn->ip_addr->ai_addr,
state->conn->ip_addr->ai_addrlen) < 0) { state->conn->ip_addr->ai_addrlen);
if(senddata != (ssize_t)sbytes) {
failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
} }
Curl_safefree(filename); Curl_safefree(filename);
@ -560,6 +575,7 @@ static CURLcode tftp_send_first(tftp_state_data_t *state, tftp_event_t event)
**********************************************************/ **********************************************************/
static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event) static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event)
{ {
ssize_t sbytes;
int rblock; int rblock;
struct SessionHandle *data = state->conn->data; struct SessionHandle *data = state->conn->data;
@ -584,10 +600,11 @@ static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event)
state->retries = 0; state->retries = 0;
setpacketevent(&state->spacket, TFTP_EVENT_ACK); setpacketevent(&state->spacket, TFTP_EVENT_ACK);
setpacketblock(&state->spacket, state->block); setpacketblock(&state->spacket, state->block);
if(sendto(state->sockfd, (void *)state->spacket.data, sbytes = sendto(state->sockfd, (void *)state->spacket.data,
4, SEND_4TH_ARG, 4, SEND_4TH_ARG,
(struct sockaddr *)&state->remote_addr, (struct sockaddr *)&state->remote_addr,
state->remote_addrlen) < 0) { state->remote_addrlen);
if(sbytes < 0) {
failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
return CURLE_SEND_ERROR; return CURLE_SEND_ERROR;
} }
@ -599,6 +616,7 @@ static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event)
else { else {
state->state = TFTP_STATE_RX; state->state = TFTP_STATE_RX;
} }
time(&state->rx_time);
break; break;
case TFTP_EVENT_OACK: case TFTP_EVENT_OACK:
@ -607,16 +625,18 @@ static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event)
state->retries = 0; state->retries = 0;
setpacketevent(&state->spacket, TFTP_EVENT_ACK); setpacketevent(&state->spacket, TFTP_EVENT_ACK);
setpacketblock(&state->spacket, state->block); setpacketblock(&state->spacket, state->block);
if(sendto(state->sockfd, (void *)state->spacket.data, sbytes = sendto(state->sockfd, (void *)state->spacket.data,
4, SEND_4TH_ARG, 4, SEND_4TH_ARG,
(struct sockaddr *)&state->remote_addr, (struct sockaddr *)&state->remote_addr,
state->remote_addrlen) < 0) { state->remote_addrlen);
if(sbytes < 0) {
failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
return CURLE_SEND_ERROR; return CURLE_SEND_ERROR;
} }
/* we're ready to RX data */ /* we're ready to RX data */
state->state = TFTP_STATE_RX; state->state = TFTP_STATE_RX;
time(&state->rx_time);
break; break;
case TFTP_EVENT_TIMEOUT: case TFTP_EVENT_TIMEOUT:
@ -629,11 +649,13 @@ static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event)
state->state = TFTP_STATE_FIN; state->state = TFTP_STATE_FIN;
} }
else { else {
/* Resend the previous ACK and check all sbytes were sent */ /* Resend the previous ACK */
if(sendto(state->sockfd, (void *)state->spacket.data, sbytes = sendto(state->sockfd, (void *)state->spacket.data,
4, SEND_4TH_ARG, 4, SEND_4TH_ARG,
(struct sockaddr *)&state->remote_addr, (struct sockaddr *)&state->remote_addr,
state->remote_addrlen) < 0) { state->remote_addrlen);
/* Check all sbytes were sent */
if(sbytes<0) {
failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
return CURLE_SEND_ERROR; return CURLE_SEND_ERROR;
} }
@ -641,6 +663,14 @@ static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event)
break; break;
case TFTP_EVENT_ERROR: case TFTP_EVENT_ERROR:
setpacketevent(&state->spacket, TFTP_EVENT_ERROR);
setpacketblock(&state->spacket, state->block);
sbytes = sendto(state->sockfd, (void *)state->spacket.data,
4, SEND_4TH_ARG,
(struct sockaddr *)&state->remote_addr,
state->remote_addrlen);
/* don't bother with the return code, but if the socket is still up we
* should be a good TFTP client and let the server know we're done */
state->state = TFTP_STATE_FIN; state->state = TFTP_STATE_FIN;
break; break;
@ -662,8 +692,8 @@ static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event)
static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event) static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event)
{ {
struct SessionHandle *data = state->conn->data; struct SessionHandle *data = state->conn->data;
ssize_t sbytes;
int rblock; int rblock;
int readcount;
CURLcode res = CURLE_OK; CURLcode res = CURLE_OK;
struct SingleRequest *k = &data->req; struct SingleRequest *k = &data->req;
@ -685,38 +715,40 @@ static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event)
res = CURLE_SEND_ERROR; res = CURLE_SEND_ERROR;
} }
else { else {
/* Re-send the data packet and check all sbytes were sent */ /* Re-send the data packet */
if(sendto(state->sockfd, (void *)&state->spacket, sbytes = sendto(state->sockfd, (void *)&state->spacket,
4+state->sbytes, SEND_4TH_ARG, 4+state->sbytes, SEND_4TH_ARG,
(struct sockaddr *)&state->remote_addr, (struct sockaddr *)&state->remote_addr,
state->remote_addrlen) < 0) { state->remote_addrlen);
/* Check all sbytes were sent */
if(sbytes<0) {
failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
res = CURLE_SEND_ERROR; res = CURLE_SEND_ERROR;
} }
} }
return res; return res;
} }
/* fall-through */
case TFTP_EVENT_OACK:
/* This is the expected packet. Reset the counters and send the next /* This is the expected packet. Reset the counters and send the next
block */ block */
time(&state->rx_time);
state->block++; state->block++;
state->retries = 0; state->retries = 0;
setpacketevent(&state->spacket, TFTP_EVENT_DATA); setpacketevent(&state->spacket, TFTP_EVENT_DATA);
setpacketblock(&state->spacket, state->block); setpacketblock(&state->spacket, state->block);
if(state->block > 1 && state->sbytes < state->blksize) { if(state->block > 1 && state->sbytes < (int)state->blksize) {
state->state = TFTP_STATE_FIN; state->state = TFTP_STATE_FIN;
return CURLE_OK; return CURLE_OK;
} }
res = Curl_fillreadbuffer(state->conn, (int)state->blksize, &readcount); res = Curl_fillreadbuffer(state->conn, (size_t)state->blksize,
state->sbytes = readcount; &state->sbytes);
if(res) if(res)
return res; return res;
/* Send the data packet and check all sbytes were sent */ sbytes = sendto(state->sockfd, (void *)state->spacket.data,
if(sendto(state->sockfd, (void *)state->spacket.data, 4+state->sbytes, SEND_4TH_ARG,
4+state->sbytes, SEND_4TH_ARG, (struct sockaddr *)&state->remote_addr,
(struct sockaddr *)&state->remote_addr, state->remote_addrlen);
state->remote_addrlen) < 0) { /* Check all sbytes were sent */
if(sbytes<0) {
failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
return CURLE_SEND_ERROR; return CURLE_SEND_ERROR;
} }
@ -736,11 +768,13 @@ static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event)
state->state = TFTP_STATE_FIN; state->state = TFTP_STATE_FIN;
} }
else { else {
/* Re-send the data packet and check all sbytes were sent */ /* Re-send the data packet */
if(sendto(state->sockfd, (void *)state->spacket.data, sbytes = sendto(state->sockfd, (void *)state->spacket.data,
4+state->sbytes, SEND_4TH_ARG, 4+state->sbytes, SEND_4TH_ARG,
(struct sockaddr *)&state->remote_addr, (struct sockaddr *)&state->remote_addr,
state->remote_addrlen) < 0) { state->remote_addrlen);
/* Check all sbytes were sent */
if(sbytes<0) {
failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO)); failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
return CURLE_SEND_ERROR; return CURLE_SEND_ERROR;
} }
@ -751,6 +785,15 @@ static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event)
case TFTP_EVENT_ERROR: case TFTP_EVENT_ERROR:
state->state = TFTP_STATE_FIN; state->state = TFTP_STATE_FIN;
setpacketevent(&state->spacket, TFTP_EVENT_ERROR);
setpacketblock(&state->spacket, state->block);
sbytes = sendto(state->sockfd, (void *)state->spacket.data,
4, SEND_4TH_ARG,
(struct sockaddr *)&state->remote_addr,
state->remote_addrlen);
/* don't bother with the return code, but if the socket is still up we
* should be a good TFTP client and let the server know we're done */
state->state = TFTP_STATE_FIN;
break; break;
default: default:
@ -761,6 +804,59 @@ static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event)
return res; return res;
} }
/**********************************************************
*
* tftp_translate_code
*
* Translate internal error codes to CURL error codes
*
**********************************************************/
CURLcode tftp_translate_code(tftp_error_t error)
{
CURLcode code = CURLE_OK;
if(error != TFTP_ERR_NONE) {
switch(error) {
case TFTP_ERR_NOTFOUND:
code = CURLE_TFTP_NOTFOUND;
break;
case TFTP_ERR_PERM:
code = CURLE_TFTP_PERM;
break;
case TFTP_ERR_DISKFULL:
code = CURLE_REMOTE_DISK_FULL;
break;
case TFTP_ERR_UNDEF:
case TFTP_ERR_ILLEGAL:
code = CURLE_TFTP_ILLEGAL;
break;
case TFTP_ERR_UNKNOWNID:
code = CURLE_TFTP_UNKNOWNID;
break;
case TFTP_ERR_EXISTS:
code = CURLE_REMOTE_FILE_EXISTS;
break;
case TFTP_ERR_NOSUCHUSER:
code = CURLE_TFTP_NOSUCHUSER;
break;
case TFTP_ERR_TIMEOUT:
code = CURLE_OPERATION_TIMEDOUT;
break;
case TFTP_ERR_NORESPONSE:
code = CURLE_COULDNT_CONNECT;
break;
default:
code= CURLE_ABORTED_BY_CALLBACK;
break;
}
}
else {
code = CURLE_OK;
}
return(code);
}
/********************************************************** /**********************************************************
* *
* tftp_state_machine * tftp_state_machine
@ -883,11 +979,11 @@ static CURLcode tftp_connect(struct connectdata *conn, bool *done)
/* If not already bound, bind to any interface, random UDP port. If it is /* If not already bound, bind to any interface, random UDP port. If it is
* reused or a custom local port was desired, this has already been done! * reused or a custom local port was desired, this has already been done!
* *
* We once used the size of the local_addr struct as the third argument for * We once used the size of the local_addr struct as the third argument
* bind() to better work with IPv6 or whatever size the struct could have, * for bind() to better work with IPv6 or whatever size the struct could
* but we learned that at least Tru64, AIX and IRIX *requires* the size of * have, but we learned that at least Tru64, AIX and IRIX *requires* the
* that argument to match the exact size of a 'sockaddr_in' struct when * size of that argument to match the exact size of a 'sockaddr_in' struct
* running IPv4-only. * when running IPv4-only.
* *
* Therefore we use the size from the address we connected to, which we * Therefore we use the size from the address we connected to, which we
* assume uses the same IP version and thus hopefully this works for both * assume uses the same IP version and thus hopefully this works for both
@ -920,38 +1016,342 @@ static CURLcode tftp_connect(struct connectdata *conn, bool *done)
static CURLcode tftp_done(struct connectdata *conn, CURLcode status, static CURLcode tftp_done(struct connectdata *conn, CURLcode status,
bool premature) bool premature)
{ {
CURLcode code = CURLE_OK;
tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc;
(void)status; /* unused */ (void)status; /* unused */
(void)premature; /* not used */ (void)premature; /* not used */
Curl_pgrsDone(conn); Curl_pgrsDone(conn);
return CURLE_OK; /* If we have encountered an error */
code = tftp_translate_code(state->error);
return code;
}
/**********************************************************
*
* tftp_getsock
*
* The getsock callback
*
**********************************************************/
static int tftp_getsock(struct connectdata *conn, curl_socket_t *socks,
int numsocks)
{
if(!numsocks)
return GETSOCK_BLANK;
socks[0] = conn->sock[FIRSTSOCKET];
return GETSOCK_READSOCK(0);
}
/**********************************************************
*
* tftp_receive_packet
*
* Called once select fires and data is ready on the socket
*
**********************************************************/
static CURLcode tftp_receive_packet(struct connectdata *conn)
{
struct Curl_sockaddr_storage fromaddr;
socklen_t fromlen;
CURLcode result = CURLE_OK;
struct SessionHandle *data = conn->data;
tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc;
struct SingleRequest *k = &data->req;
/* Receive the packet */
fromlen = sizeof(fromaddr);
state->rbytes = (int)recvfrom(state->sockfd,
(void *)state->rpacket.data,
state->blksize+4,
0,
(struct sockaddr *)&fromaddr,
&fromlen);
if(state->remote_addrlen==0) {
memcpy(&state->remote_addr, &fromaddr, fromlen);
state->remote_addrlen = fromlen;
}
/* Sanity check packet length */
if(state->rbytes < 4) {
failf(data, "Received too short packet");
/* Not a timeout, but how best to handle it? */
state->event = TFTP_EVENT_TIMEOUT;
}
else {
/* The event is given by the TFTP packet time */
state->event = (tftp_event_t)getrpacketevent(&state->rpacket);
switch(state->event) {
case TFTP_EVENT_DATA:
/* Don't pass to the client empty or retransmitted packets */
if(state->rbytes > 4 &&
((state->block+1) == getrpacketblock(&state->rpacket))) {
result = Curl_client_write(conn, CLIENTWRITE_BODY,
(char *)state->rpacket.data+4,
state->rbytes-4);
if(result) {
tftp_state_machine(state, TFTP_EVENT_ERROR);
return result;
}
k->bytecount += state->rbytes-4;
Curl_pgrsSetDownloadCounter(data, (curl_off_t) k->bytecount);
}
break;
case TFTP_EVENT_ERROR:
state->error = (tftp_error_t)getrpacketblock(&state->rpacket);
infof(data, "%s\n", (const char *)state->rpacket.data+4);
break;
case TFTP_EVENT_ACK:
break;
case TFTP_EVENT_OACK:
result = tftp_parse_option_ack(state,
(const char *)state->rpacket.data+2,
state->rbytes-2);
if(result)
return result;
break;
case TFTP_EVENT_RRQ:
case TFTP_EVENT_WRQ:
default:
failf(data, "%s", "Internal error: Unexpected packet");
break;
}
/* Update the progress meter */
if(Curl_pgrsUpdate(conn)) {
tftp_state_machine(state, TFTP_EVENT_ERROR);
return CURLE_ABORTED_BY_CALLBACK;
}
}
return result;
}
/**********************************************************
*
* tftp_state_timeout
*
* Check if timeouts have been reached
*
**********************************************************/
static long tftp_state_timeout(struct connectdata *conn, tftp_event_t *event)
{
time_t current;
struct SessionHandle *data = conn->data;
tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc;
if (event)
*event = TFTP_EVENT_NONE;
time(&current);
if(current > state->max_time) {
DEBUGF(infof(data, "timeout: %d > %d\n",
current, state->max_time));
state->error = TFTP_ERR_TIMEOUT;
state->state = TFTP_STATE_FIN;
return(0);
}
else if (current > state->rx_time+state->retry_time) {
if (event)
*event = TFTP_EVENT_TIMEOUT;
time(&state->rx_time); /* update even though we received nothing */
return(state->max_time-current);
}
else {
return(state->max_time-current);
}
}
/**********************************************************
*
* tftp_easy_statemach
*
* Handle easy request until completion
*
**********************************************************/
static CURLcode tftp_easy_statemach(struct connectdata *conn)
{
int rc;
int check_time = 0;
CURLcode result = CURLE_OK;
struct SessionHandle *data = conn->data;
tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc;
/* Run the TFTP State Machine */
for(;
(state->state != TFTP_STATE_FIN) && (result == CURLE_OK);
result=tftp_state_machine(state, state->event) ) {
/* Wait until ready to read or timeout occurs */
rc=Curl_socket_ready(state->sockfd, CURL_SOCKET_BAD,
state->retry_time * 1000);
if(rc == -1) {
/* bail out */
int error = SOCKERRNO;
failf(data, "%s", Curl_strerror(conn, error));
state->event = TFTP_EVENT_ERROR;
}
else if(rc==0) {
/* A timeout occured */
state->event = TFTP_EVENT_TIMEOUT;
/* Force a look at transfer timeouts */
check_time = 0;
}
else {
result = tftp_receive_packet(conn);
}
/* Check for transfer timeout every 10 blocks, or after timeout */
if(check_time%10==0) {
/* ignore the event here as Curl_socket_ready() handles
* retransmission timeouts inside the easy state mach */
tftp_state_timeout(conn, NULL);
}
if(result)
return(result);
}
/* Tell curl we're done */
result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
return(result);
}
/**********************************************************
*
* tftp_multi_statemach
*
* Handle single RX socket event and return
*
**********************************************************/
static CURLcode tftp_multi_statemach(struct connectdata *conn, bool *done)
{
int rc;
tftp_event_t event;
CURLcode result = CURLE_OK;
struct SessionHandle *data = conn->data;
tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc;
long timeout_ms = tftp_state_timeout(conn, &event);
*done = FALSE;
if(timeout_ms <= 0) {
failf(data, "TFTP response timeout");
return CURLE_OPERATION_TIMEDOUT;
}
else if (event != TFTP_EVENT_NONE) {
result = tftp_state_machine(state, event);
if(result != CURLE_OK)
return(result);
*done = (bool)(state->state == TFTP_STATE_FIN);
if(*done)
/* Tell curl we're done */
result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
}
else {
/* no timeouts to handle, check our socket */
rc = Curl_socket_ready(state->sockfd, CURL_SOCKET_BAD, 0);
if(rc == -1) {
/* bail out */
int error = SOCKERRNO;
failf(data, "%s", Curl_strerror(conn, error));
state->event = TFTP_EVENT_ERROR;
}
else if(rc != 0) {
result = tftp_receive_packet(conn);
if(result != CURLE_OK)
return(result);
result = tftp_state_machine(state, state->event);
if(result != CURLE_OK)
return(result);
*done = (bool)(state->state == TFTP_STATE_FIN);
if(*done)
/* Tell curl we're done */
result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
}
/* if rc == 0, then select() timed out */
}
return result;
}
/**********************************************************
*
* tftp_doing
*
* Called from multi.c while DOing
*
**********************************************************/
static CURLcode tftp_doing(struct connectdata *conn, bool *dophase_done)
{
CURLcode result;
result = tftp_multi_statemach(conn, dophase_done);
if(*dophase_done) {
DEBUGF(infof(conn->data, "DO phase is complete\n"));
}
return result;
}
/**********************************************************
*
* tftp_peform
*
* Entry point for transfer from tftp_do, sarts state mach
*
**********************************************************/
static CURLcode tftp_perform(struct connectdata *conn, bool *dophase_done)
{
CURLcode result = CURLE_OK;
tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc;
*dophase_done = FALSE;
result = tftp_state_machine(state, TFTP_EVENT_INIT);
if(state->state == TFTP_STATE_FIN || result != CURLE_OK)
return(result);
if(conn->data->state.used_interface == Curl_if_multi)
tftp_multi_statemach(conn, dophase_done);
else {
result = tftp_easy_statemach(conn);
*dophase_done = TRUE; /* with the easy interface we are done here */
}
if(*dophase_done)
DEBUGF(infof(conn->data, "DO phase is complete\n"));
return result;
} }
/********************************************************** /**********************************************************
* *
* tftp * tftp_do
* *
* The do callback * The do callback
* *
* This callback handles the entire TFTP transfer * This callback initiates the TFTP transfer
* *
**********************************************************/ **********************************************************/
static CURLcode tftp_do(struct connectdata *conn, bool *done) static CURLcode tftp_do(struct connectdata *conn, bool *done)
{ {
struct SessionHandle *data = conn->data;
tftp_state_data_t *state; tftp_state_data_t *state;
tftp_event_t event;
CURLcode code; CURLcode code;
int rc;
struct Curl_sockaddr_storage fromaddr;
curl_socklen_t fromlen;
int check_time = 0;
struct SingleRequest *k = &data->req;
*done = TRUE; *done = FALSE;
/* /*
Since connections can be re-used between SessionHandles, this might be a Since connections can be re-used between SessionHandles, this might be a
@ -968,156 +1368,11 @@ static CURLcode tftp_do(struct connectdata *conn, bool *done)
} }
state = (tftp_state_data_t *)conn->proto.tftpc; state = (tftp_state_data_t *)conn->proto.tftpc;
/* Run the TFTP State Machine */ code = tftp_perform(conn, done);
for(code=tftp_state_machine(state, TFTP_EVENT_INIT);
(state->state != TFTP_STATE_FIN) && (code == CURLE_OK);
code=tftp_state_machine(state, event) ) {
/* Wait until ready to read or timeout occurs */
rc=Curl_socket_ready(state->sockfd, CURL_SOCKET_BAD,
(int)(state->retry_time * 1000));
if(rc == -1) {
/* bail out */
int error = SOCKERRNO;
failf(data, "%s", Curl_strerror(conn, error));
event = TFTP_EVENT_ERROR;
}
else if(rc==0) {
/* A timeout occured */
event = TFTP_EVENT_TIMEOUT;
/* Force a look at transfer timeouts */
check_time = 0;
}
else {
/* Receive the packet */
fromlen = sizeof(fromaddr);
state->rbytes = (ssize_t)recvfrom(state->sockfd,
(void *)state->rpacket.data,
state->blksize+4,
0,
(struct sockaddr *)&fromaddr,
&fromlen);
if(state->remote_addrlen==0) {
memcpy(&state->remote_addr, &fromaddr, fromlen);
state->remote_addrlen = fromlen;
}
/* Sanity check packet length */
if(state->rbytes < 4) {
failf(data, "Received too short packet");
/* Not a timeout, but how best to handle it? */
event = TFTP_EVENT_TIMEOUT;
}
else {
/* The event is given by the TFTP packet time */
event = (tftp_event_t)getrpacketevent(&state->rpacket);
switch(event) {
case TFTP_EVENT_DATA:
/* Don't pass to the client empty or retransmitted packets */
if(state->rbytes > 4 &&
((state->block+1) == getrpacketblock(&state->rpacket))) {
code = Curl_client_write(conn, CLIENTWRITE_BODY,
(char *)state->rpacket.data+4,
state->rbytes-4);
if(code)
return code;
k->bytecount += state->rbytes-4;
Curl_pgrsSetDownloadCounter(data, (curl_off_t) k->bytecount);
}
break;
case TFTP_EVENT_ERROR:
state->error = (tftp_error_t)getrpacketblock(&state->rpacket);
infof(data, "%s\n", (const char *)state->rpacket.data+4);
break;
case TFTP_EVENT_ACK:
break;
case TFTP_EVENT_OACK:
code = tftp_parse_option_ack(state,
(const char *)state->rpacket.data+2,
(int)state->rbytes-2);
if(code)
return code;
break;
case TFTP_EVENT_RRQ:
case TFTP_EVENT_WRQ:
default:
failf(data, "%s", "Internal error: Unexpected packet");
break;
}
/* Update the progress meter */
if(Curl_pgrsUpdate(conn))
return CURLE_ABORTED_BY_CALLBACK;
}
}
/* Check for transfer timeout every 10 blocks, or after timeout */
if(check_time%10==0) {
time_t current;
time(&current);
if(current>state->max_time) {
DEBUGF(infof(data, "timeout: %d > %d\n",
current, state->max_time));
state->error = TFTP_ERR_TIMEOUT;
state->state = TFTP_STATE_FIN;
}
}
}
if(code)
return code;
/* Tell curl we're done */
code = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
if(code)
return code;
/* If we have encountered an error */ /* If we have encountered an error */
if(state->error != TFTP_ERR_NONE) { code = tftp_translate_code(state->error);
/* Translate internal error codes to curl error codes */
switch(state->error) {
case TFTP_ERR_NOTFOUND:
code = CURLE_TFTP_NOTFOUND;
break;
case TFTP_ERR_PERM:
code = CURLE_TFTP_PERM;
break;
case TFTP_ERR_DISKFULL:
code = CURLE_REMOTE_DISK_FULL;
break;
case TFTP_ERR_UNDEF:
case TFTP_ERR_ILLEGAL:
code = CURLE_TFTP_ILLEGAL;
break;
case TFTP_ERR_UNKNOWNID:
code = CURLE_TFTP_UNKNOWNID;
break;
case TFTP_ERR_EXISTS:
code = CURLE_REMOTE_FILE_EXISTS;
break;
case TFTP_ERR_NOSUCHUSER:
code = CURLE_TFTP_NOSUCHUSER;
break;
case TFTP_ERR_TIMEOUT:
code = CURLE_OPERATION_TIMEDOUT;
break;
case TFTP_ERR_NORESPONSE:
code = CURLE_COULDNT_CONNECT;
break;
default:
code= CURLE_ABORTED_BY_CALLBACK;
break;
}
}
else
code = CURLE_OK;
return code; return code;
} }

View File

@ -932,8 +932,8 @@ struct connectdata {
#define PROT_SSL (1<<22) /* protocol requires SSL */ #define PROT_SSL (1<<22) /* protocol requires SSL */
#define PROT_MISSING (1<<23) #define PROT_MISSING (1<<23)
#define PROT_CLOSEACTION PROT_FTP /* these ones need action before socket /* these ones need action before socket close */
close */ #define PROT_CLOSEACTION (PROT_FTP | PROT_TFTP)
#define PROT_DUALCHANNEL PROT_FTP /* these protocols use two connections */ #define PROT_DUALCHANNEL PROT_FTP /* these protocols use two connections */
/* 'dns_entry' is the particular host we use. This points to an entry in the /* 'dns_entry' is the particular host we use. This points to an entry in the

View File

@ -602,7 +602,7 @@ struct Configurable {
char *ftp_account; /* for ACCT */ char *ftp_account; /* for ACCT */
char *ftp_alternative_to_user; /* send command if USER/PASS fails */ char *ftp_alternative_to_user; /* send command if USER/PASS fails */
int ftp_filemethod; int ftp_filemethod;
long tftp_blksize; /* TFTP BLKSIZE option */
bool ignorecl; /* --ignore-content-length */ bool ignorecl; /* --ignore-content-length */
bool disable_sessionid; bool disable_sessionid;
@ -877,6 +877,7 @@ static void help(void)
" --stderr <file> Where to redirect stderr. - means stdout", " --stderr <file> Where to redirect stderr. - means stdout",
" --tcp-nodelay Use the TCP_NODELAY option", " --tcp-nodelay Use the TCP_NODELAY option",
" -t/--telnet-option <OPT=val> Set telnet option", " -t/--telnet-option <OPT=val> Set telnet option",
" --tftp-blksize <value> Set TFTP BLKSIZE option (must be >512)",
" -z/--time-cond <time> Transfer based on a time condition", " -z/--time-cond <time> Transfer based on a time condition",
" -1/--tlsv1 Use TLSv1 (SSL)", " -1/--tlsv1 Use TLSv1 (SSL)",
" --trace <file> Write a debug trace to the given file", " --trace <file> Write a debug trace to the given file",
@ -1732,12 +1733,13 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */
{"$3", "keepalive-time", TRUE}, {"$3", "keepalive-time", TRUE},
{"$4", "post302", FALSE}, {"$4", "post302", FALSE},
{"$5", "noproxy", TRUE}, {"$5", "noproxy", TRUE},
#if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI) #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
{"$6", "socks5-gssapi-service", TRUE}, {"$6", "socks5-gssapi-service", TRUE},
{"$7", "socks5-gssapi-nec", FALSE}, {"$7", "socks5-gssapi-nec", FALSE},
#endif #endif
{"$8", "proxy1.0", TRUE}, {"$8", "proxy1.0", TRUE},
{"$9", "tftp-blksize", TRUE},
{"0", "http1.0", FALSE}, {"0", "http1.0", FALSE},
{"1", "tlsv1", FALSE}, {"1", "tlsv1", FALSE},
{"2", "sslv2", FALSE}, {"2", "sslv2", FALSE},
@ -2264,6 +2266,9 @@ static ParameterError getparameter(char *flag, /* f or -long-flag */
GetStr(&config->proxy, nextarg); GetStr(&config->proxy, nextarg);
config->proxyver = CURLPROXY_HTTP_1_0; config->proxyver = CURLPROXY_HTTP_1_0;
break; break;
case '9': /* --tftp-blksize */
str2num(&config->tftp_blksize, nextarg);
break;
} }
break; break;
case '#': /* --progress-bar */ case '#': /* --progress-bar */
@ -5000,6 +5005,9 @@ operate(struct Configurable *config, int argc, argv_item_t argv[])
my_setopt(curl, CURLOPT_POSTREDIR, config->post301 | my_setopt(curl, CURLOPT_POSTREDIR, config->post301 |
(config->post302 ? CURL_REDIR_POST_302 : FALSE)); (config->post302 ? CURL_REDIR_POST_302 : FALSE));
if(config->tftp_blksize)
my_setopt(curl, CURLOPT_TFTP_BLKSIZE, config->tftp_blksize);
retry_numretries = config->req_retry; retry_numretries = config->req_retry;
retrystart = cutil_tvnow(); retrystart = cutil_tvnow();