Newer
Older
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2010, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://curl.haxx.se/docs/copyright.html.
*
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
* copies of the Software, and permit persons to whom the Software is
* furnished to do so, under the terms of the COPYING file.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
* $Id$
***************************************************************************/
#include "setup.h"
#ifndef CURL_DISABLE_TFTP
/* -- WIN32 approved -- */
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <ctype.h>
#if defined(WIN32)
#include <time.h>
#include <io.h>
#else
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#include <netinet/in.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <netdb.h>
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#endif /* WIN32 */
#include "urldata.h"
#include <curl/curl.h>
#include "transfer.h"
#include "sendf.h"
#include "tftp.h"
#include "progress.h"
#include "connect.h"
#include "strerror.h"
Daniel Stenberg
committed
#include "sockaddr.h" /* required for Curl_sockaddr_storage */
Daniel Stenberg
committed
#include "multiif.h"
Daniel Stenberg
committed
#include "url.h"
#include "rawstr.h"
#define _MPRINTF_REPLACE /* use our functions only */
#include <curl/mprintf.h>
#include "select.h"
/* The last #include file should be: */
#include "memdebug.h"
Daniel Stenberg
committed
/* RFC2348 allows the block size to be negotiated */
#define TFTP_BLKSIZE_DEFAULT 512
#define TFTP_BLKSIZE_MIN 8
#define TFTP_BLKSIZE_MAX 65464
#define TFTP_OPTION_BLKSIZE "blksize"
#define TFTP_OPTION_TSIZE "tsize"
#define TFTP_OPTION_INTERVAL "interval"
typedef enum {
TFTP_MODE_NETASCII=0,
TFTP_MODE_OCTET
} tftp_mode_t;
typedef enum {
TFTP_STATE_START=0,
TFTP_STATE_RX,
TFTP_STATE_TX,
TFTP_STATE_FIN
} tftp_state_t;
typedef enum {
Daniel Stenberg
committed
TFTP_EVENT_NONE = -1,
TFTP_EVENT_INIT = 0,
TFTP_EVENT_RRQ = 1,
TFTP_EVENT_WRQ = 2,
TFTP_EVENT_DATA = 3,
TFTP_EVENT_ACK = 4,
TFTP_EVENT_ERROR = 5,
Daniel Stenberg
committed
TFTP_EVENT_OACK = 6,
TFTP_EVENT_TIMEOUT
} tftp_event_t;
typedef enum {
TFTP_ERR_UNDEF=0,
TFTP_ERR_NOTFOUND,
TFTP_ERR_PERM,
TFTP_ERR_DISKFULL,
TFTP_ERR_ILLEGAL,
TFTP_ERR_UNKNOWNID,
TFTP_ERR_EXISTS,
Daniel Stenberg
committed
TFTP_ERR_NOSUCHUSER, /* This will never be triggered by this code */
/* The remaining error codes are internal to curl */
TFTP_ERR_NONE = -100,
TFTP_ERR_TIMEOUT,
TFTP_ERR_NORESPONSE
} tftp_error_t;
typedef struct tftp_packet {
Daniel Stenberg
committed
unsigned char *data;
} tftp_packet_t;
typedef struct tftp_state_data {
tftp_state_t state;
tftp_mode_t mode;
tftp_error_t error;
Daniel Stenberg
committed
tftp_event_t event;
struct connectdata *conn;
int retries;
Daniel Stenberg
committed
int retry_time;
int retry_max;
time_t start_time;
time_t max_time;
Daniel Stenberg
committed
time_t rx_time;
unsigned short block;
Daniel Stenberg
committed
struct Curl_sockaddr_storage local_addr;
struct Curl_sockaddr_storage remote_addr;
Daniel Stenberg
committed
int rbytes;
int sbytes;
int blksize;
Daniel Stenberg
committed
int requested_blksize;
tftp_packet_t rpacket;
tftp_packet_t spacket;
} tftp_state_data_t;
/* Forward declarations */
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_connect(struct connectdata *conn, bool *done);
Daniel Stenberg
committed
static CURLcode tftp_disconnect(struct connectdata *conn);
static CURLcode tftp_do(struct connectdata *conn, bool *done);
static CURLcode tftp_done(struct connectdata *conn,
Patrick Monnerat
committed
CURLcode, bool premature);
static CURLcode tftp_setup_connection(struct connectdata * conn);
Daniel Stenberg
committed
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);
static CURLcode tftp_translate_code(tftp_error_t error);
Patrick Monnerat
committed
/*
* TFTP protocol handler.
*/
const struct Curl_handler Curl_handler_tftp = {
"TFTP", /* scheme */
tftp_setup_connection, /* setup_connection */
tftp_do, /* do_it */
tftp_done, /* done */
ZERO_NULL, /* do_more */
tftp_connect, /* connect_it */
Daniel Stenberg
committed
tftp_multi_statemach, /* connecting */
tftp_doing, /* doing */
tftp_getsock, /* proto_getsock */
tftp_getsock, /* doing_getsock */
Daniel Stenberg
committed
ZERO_NULL, /* perform_getsock */
Daniel Stenberg
committed
tftp_disconnect, /* disconnect */
Patrick Monnerat
committed
PORT_TFTP, /* defport */
PROT_TFTP /* protocol */
};
/**********************************************************
*
* tftp_set_timeouts -
*
* Set timeouts based on state machine state.
* Use user provided connect timeouts until DATA or ACK
* packet is received, then use user-provided transfer timeouts
*
*
**********************************************************/
Daniel Stenberg
committed
static CURLcode tftp_set_timeouts(tftp_state_data_t *state)
{
time_t maxtime, timeout;
Daniel Stenberg
committed
long timeout_ms;
bool start = (bool)(state->state == TFTP_STATE_START);
time(&state->start_time);
Daniel Stenberg
committed
Daniel Stenberg
committed
/* Compute drop-dead time */
timeout_ms = Curl_timeleft(state->conn, NULL, start);
Daniel Stenberg
committed
if(timeout_ms < 0) {
/* time-out, bail out, go home */
failf(state->conn->data, "Connection time-out");
return CURLE_OPERATION_TIMEDOUT;
}
Daniel Stenberg
committed
Daniel Stenberg
committed
maxtime = (time_t)(timeout_ms + 500) / 1000;
state->max_time = state->start_time+maxtime;
/* Set per-block timeout to total */
timeout = maxtime ;
/* Average restart after 5 seconds */
Daniel Stenberg
committed
state->retry_max = (int)timeout/5;
if(state->retry_max < 1)
/* avoid division by zero below */
state->retry_max = 1;
/* Compute the re-start interval to suit the timeout */
Daniel Stenberg
committed
state->retry_time = (int)timeout/state->retry_max;
if(state->retry_time<1)
state->retry_time=1;
}
else {
Daniel Stenberg
committed
if(timeout_ms > 0)
maxtime = (time_t)(timeout_ms + 500) / 1000;
else
maxtime = 3600;
state->max_time = state->start_time+maxtime;
/* Set per-block timeout to 10% of total */
timeout = maxtime/10 ;
/* Average reposting an ACK after 15 seconds */
Daniel Stenberg
committed
state->retry_max = (int)timeout/15;
}
Daniel Stenberg
committed
/* But bound the total number */
if(state->retry_max<3)
state->retry_max=3;
if(state->retry_max>50)
state->retry_max=50;
/* Compute the re-ACK interval to suit the timeout */
state->retry_time = timeout/state->retry_max;
if(state->retry_time<1)
state->retry_time=1;
state->retry_time, state->retry_max);
Daniel Stenberg
committed
Daniel Stenberg
committed
/* init RX time */
time(&state->rx_time);
Daniel Stenberg
committed
return CURLE_OK;
}
/**********************************************************
*
* tftp_set_send_first
*
* Event handler for the START state
*
**********************************************************/
static void setpacketevent(tftp_packet_t *packet, unsigned short num)
{
packet->data[0] = (unsigned char)(num >> 8);
packet->data[1] = (unsigned char)(num & 0xff);
}
static void setpacketblock(tftp_packet_t *packet, unsigned short num)
{
packet->data[2] = (unsigned char)(num >> 8);
packet->data[3] = (unsigned char)(num & 0xff);
}
static unsigned short getrpacketevent(const tftp_packet_t *packet)
{
return (unsigned short)((packet->data[0] << 8) | packet->data[1]);
}
static unsigned short getrpacketblock(const tftp_packet_t *packet)
{
return (unsigned short)((packet->data[2] << 8) | packet->data[3]);
}
Daniel Stenberg
committed
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
static size_t Curl_strnlen(const char *string, size_t maxlen)
{
const char *end = memchr (string, '\0', maxlen);
return end ? (size_t) (end - string) : maxlen;
}
static const char *tftp_option_get(const char *buf, size_t len,
const char **option, const char **value)
{
size_t loc;
loc = Curl_strnlen( buf, len );
loc++; /* NULL term */
if (loc >= len)
return NULL;
*option = buf;
loc += Curl_strnlen( buf+loc, len-loc );
loc++; /* NULL term */
if (loc > len)
return NULL;
*value = &buf[strlen(*option) + 1];
return &buf[loc];
}
static CURLcode tftp_parse_option_ack(tftp_state_data_t *state,
const char *ptr, int len)
{
const char *tmp = ptr;
struct SessionHandle *data = state->conn->data;
/* if OACK doesn't contain blksize option, the default (512) must be used */
state->blksize = TFTP_BLKSIZE_DEFAULT;
while (tmp < ptr + len) {
const char *option, *value;
tmp = tftp_option_get(tmp, ptr + len - tmp, &option, &value);
if(tmp == NULL) {
failf(data, "Malformed ACK packet, rejecting");
return CURLE_TFTP_ILLEGAL;
}
infof(data, "got option=(%s) value=(%s)\n", option, value);
if(checkprefix(option, TFTP_OPTION_BLKSIZE)) {
Daniel Stenberg
committed
long blksize;
Daniel Stenberg
committed
Daniel Stenberg
committed
blksize = strtol( value, NULL, 10 );
Daniel Stenberg
committed
if(!blksize) {
failf(data, "invalid blocksize value in OACK packet");
return CURLE_TFTP_ILLEGAL;
}
else if(blksize > TFTP_BLKSIZE_MAX) {
failf(data, "%s (%d)", "blksize is larger than max supported",
TFTP_BLKSIZE_MAX);
return CURLE_TFTP_ILLEGAL;
}
else if(blksize < TFTP_BLKSIZE_MIN) {
failf(data, "%s (%d)", "blksize is smaller than min supported",
TFTP_BLKSIZE_MIN);
return CURLE_TFTP_ILLEGAL;
}
else if (blksize > state->requested_blksize) {
/* could realloc pkt buffers here, but the spec doesn't call out
* support for the server requesting a bigger blksize than the client
* requests */
Daniel Stenberg
committed
"server requested blksize larger than allocated", blksize);
return CURLE_TFTP_ILLEGAL;
}
Daniel Stenberg
committed
state->blksize = (int)blksize;
Daniel Stenberg
committed
infof(data, "%s (%d) %s (%d)\n", "blksize parsed from OACK",
state->blksize, "requested", state->requested_blksize);
}
else if(checkprefix(option, TFTP_OPTION_TSIZE)) {
long tsize = 0;
tsize = strtol( value, NULL, 10 );
if(!tsize) {
failf(data, "invalid tsize -:%s:- value in OACK packet", value);
Daniel Stenberg
committed
return CURLE_TFTP_ILLEGAL;
}
Curl_pgrsSetDownloadSize(data, tsize);
Daniel Stenberg
committed
}
}
return CURLE_OK;
}
static size_t tftp_option_add(tftp_state_data_t *state, size_t csize,
char *buf, const char *option)
{
Daniel Stenberg
committed
if( ( strlen(option) + csize + 1 ) > (size_t)state->blksize )
return 0;
Daniel Stenberg
committed
strcpy(buf, option);
return( strlen(option) + 1 );
}
Daniel Stenberg
committed
static CURLcode tftp_connect_for_tx(tftp_state_data_t *state,
tftp_event_t event)
Daniel Stenberg
committed
{
Daniel Stenberg
committed
struct SessionHandle *data = state->conn->data;
infof(data, "%s\n", "Connected for transmit");
Daniel Stenberg
committed
state->state = TFTP_STATE_TX;
res = tftp_set_timeouts(state);
if(res != CURLE_OK)
Daniel Stenberg
committed
return(res);
return tftp_tx(state, event);
}
Daniel Stenberg
committed
static CURLcode tftp_connect_for_rx(tftp_state_data_t *state,
tftp_event_t event)
Daniel Stenberg
committed
{
Daniel Stenberg
committed
struct SessionHandle *data = state->conn->data;
infof(data, "%s\n", "Connected for receive");
Daniel Stenberg
committed
state->state = TFTP_STATE_RX;
res = tftp_set_timeouts(state);
if(res != CURLE_OK)
Daniel Stenberg
committed
return(res);
return tftp_rx(state, event);
}
static CURLcode tftp_send_first(tftp_state_data_t *state, tftp_event_t event)
{
Daniel Stenberg
committed
ssize_t senddata;
const char *mode = "octet";
char *filename;
Daniel Stenberg
committed
char buf[64];
struct SessionHandle *data = state->conn->data;
/* Set ascii mode if -B flag was used */
Daniel Stenberg
committed
if(data->set.prefer_ascii)
mode = "netascii";
switch(event) {
case TFTP_EVENT_INIT: /* Send the first packet out */
case TFTP_EVENT_TIMEOUT: /* Resend the first packet out */
/* Increment the retry counter, quit if over the limit */
state->retries++;
if(state->retries>state->retry_max) {
state->error = TFTP_ERR_NORESPONSE;
state->state = TFTP_STATE_FIN;
return res;
}
if(data->set.upload) {
/* If we are uploading, send an WRQ */
setpacketevent(&state->spacket, TFTP_EVENT_WRQ);
Daniel Stenberg
committed
state->conn->data->req.upload_fromhere =
Daniel Stenberg
committed
(char *)state->spacket.data+4;
if(data->set.infilesize != -1)
Curl_pgrsSetUploadSize(data, data->set.infilesize);
}
else {
/* If we are downloading, send an RRQ */
setpacketevent(&state->spacket, TFTP_EVENT_RRQ);
}
/* As RFC3617 describes the separator slash is not actually part of the
Daniel Stenberg
committed
file name so we skip the always-present first letter of the path
string. */
Daniel Stenberg
committed
filename = curl_easy_unescape(data, &state->conn->data->state.path[1], 0,
Daniel Stenberg
committed
NULL);
return CURLE_OUT_OF_MEMORY;
Daniel Stenberg
committed
snprintf((char *)state->spacket.data+2,
state->blksize,
"%s%c%s%c", filename, '\0', mode, '\0');
Daniel Stenberg
committed
sbytes = 4 + strlen(filename) + strlen(mode);
/* add tsize option */
Daniel Stenberg
committed
if(data->set.upload && (data->set.infilesize != -1))
snprintf( buf, sizeof(buf), "%" FORMAT_OFF_T, data->set.infilesize );
else
strcpy(buf, "0"); /* the destination is large enough */
Daniel Stenberg
committed
sbytes += tftp_option_add(state, sbytes,
(char *)state->spacket.data+sbytes,
TFTP_OPTION_TSIZE);
sbytes += tftp_option_add(state, sbytes,
Daniel Stenberg
committed
(char *)state->spacket.data+sbytes, buf);
Daniel Stenberg
committed
/* add blksize option */
snprintf( buf, sizeof(buf), "%d", state->requested_blksize );
sbytes += tftp_option_add(state, sbytes,
(char *)state->spacket.data+sbytes,
TFTP_OPTION_BLKSIZE);
sbytes += tftp_option_add(state, sbytes,
(char *)state->spacket.data+sbytes, buf );
Daniel Stenberg
committed
/* add timeout option, this is the max time the session may live */
snprintf( buf, sizeof(buf), "%d", state->retry_time*state->retry_max );
Daniel Stenberg
committed
sbytes += tftp_option_add(state, sbytes,
(char *)state->spacket.data+sbytes,
TFTP_OPTION_INTERVAL);
sbytes += tftp_option_add(state, sbytes,
(char *)state->spacket.data+sbytes, buf );
Daniel Stenberg
committed
senddata = sendto(state->sockfd, (void *)state->spacket.data,
sbytes, 0,
state->conn->ip_addr->ai_addr,
state->conn->ip_addr->ai_addrlen);
if(senddata != (ssize_t)sbytes) {
Daniel Stenberg
committed
failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
}
Curl_safefree(filename);
break;
Daniel Stenberg
committed
case TFTP_EVENT_OACK:
if(data->set.upload) {
res = tftp_connect_for_tx(state, event);
}
else {
res = tftp_connect_for_rx(state, event);
}
break;
case TFTP_EVENT_ACK: /* Connected for transmit */
Daniel Stenberg
committed
res = tftp_connect_for_tx(state, event);
break;
Daniel Stenberg
committed
case TFTP_EVENT_DATA: /* Connected for receive */
res = tftp_connect_for_rx(state, event);
break;
case TFTP_EVENT_ERROR:
state->state = TFTP_STATE_FIN;
break;
default:
Daniel Stenberg
committed
failf(state->conn->data, "tftp_send_first: internal error");
break;
}
return res;
}
/**********************************************************
*
* tftp_rx
*
* Event handler for the RX state
*
**********************************************************/
static CURLcode tftp_rx(tftp_state_data_t *state, tftp_event_t event)
{
Daniel Stenberg
committed
ssize_t sbytes;
int rblock;
struct SessionHandle *data = state->conn->data;
switch(event) {
case TFTP_EVENT_DATA:
/* Is this the block we expect? */
rblock = getrpacketblock(&state->rpacket);
/* No, log it, up the retry count and fail if over the limit */
"Received unexpected DATA packet block %d\n", rblock);
state->retries++;
Daniel Stenberg
committed
failf(data, "tftp_rx: giving up waiting for block %d",
state->block+1);
return CURLE_TFTP_ILLEGAL;
}
}
/* This is the expected block. Reset counters and ACK it. */
state->retries = 0;
setpacketevent(&state->spacket, TFTP_EVENT_ACK);
setpacketblock(&state->spacket, state->block);
Daniel Stenberg
committed
sbytes = sendto(state->sockfd, (void *)state->spacket.data,
4, SEND_4TH_ARG,
(struct sockaddr *)&state->remote_addr,
state->remote_addrlen);
if(sbytes < 0) {
Daniel Stenberg
committed
failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
}
/* Check if completed (That is, a less than full packet is received) */
Daniel Stenberg
committed
if(state->rbytes < (ssize_t)state->blksize+4){
state->state = TFTP_STATE_FIN;
}
else {
state->state = TFTP_STATE_RX;
}
Daniel Stenberg
committed
time(&state->rx_time);
break;
Daniel Stenberg
committed
case TFTP_EVENT_OACK:
/* ACK option acknowledgement so we can move on to data */
state->block = 0;
state->retries = 0;
setpacketevent(&state->spacket, TFTP_EVENT_ACK);
setpacketblock(&state->spacket, state->block);
Daniel Stenberg
committed
sbytes = sendto(state->sockfd, (void *)state->spacket.data,
4, SEND_4TH_ARG,
(struct sockaddr *)&state->remote_addr,
state->remote_addrlen);
if(sbytes < 0) {
Daniel Stenberg
committed
failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
return CURLE_SEND_ERROR;
}
/* we're ready to RX data */
state->state = TFTP_STATE_RX;
Daniel Stenberg
committed
time(&state->rx_time);
Daniel Stenberg
committed
break;
case TFTP_EVENT_TIMEOUT:
/* Increment the retry count and fail if over the limit */
state->retries++;
"Timeout waiting for block %d ACK. Retries = %d\n",
state->block+1, state->retries);
if(state->retries > state->retry_max) {
state->error = TFTP_ERR_TIMEOUT;
state->state = TFTP_STATE_FIN;
}
else {
Daniel Stenberg
committed
/* Resend the previous ACK */
sbytes = sendto(state->sockfd, (void *)state->spacket.data,
4, SEND_4TH_ARG,
(struct sockaddr *)&state->remote_addr,
state->remote_addrlen);
/* Check all sbytes were sent */
if(sbytes<0) {
Daniel Stenberg
committed
failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
}
}
break;
case TFTP_EVENT_ERROR:
Daniel Stenberg
committed
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;
default:
Daniel Stenberg
committed
failf(data, "%s", "tftp_rx: internal error");
return CURLE_TFTP_ILLEGAL; /* not really the perfect return code for
this */
}
return CURLE_OK;
}
/**********************************************************
*
* tftp_tx
*
* Event handler for the TX state
*
**********************************************************/
static CURLcode tftp_tx(tftp_state_data_t *state, tftp_event_t event)
{
struct SessionHandle *data = state->conn->data;
Daniel Stenberg
committed
ssize_t sbytes;
int rblock;
CURLcode res = CURLE_OK;
Daniel Stenberg
committed
struct SingleRequest *k = &data->req;
switch(event) {
case TFTP_EVENT_ACK:
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
case TFTP_EVENT_OACK:
if (event == TFTP_EVENT_ACK) {
/* Ack the packet */
rblock = getrpacketblock(&state->rpacket);
if(rblock != state->block) {
/* This isn't the expected block. Log it and up the retry counter */
infof(data, "Received ACK for block %d, expecting %d\n",
rblock, state->block);
state->retries++;
/* Bail out if over the maximum */
if(state->retries>state->retry_max) {
failf(data, "tftp_tx: giving up waiting for block %d ack",
state->block);
res = CURLE_SEND_ERROR;
}
else {
/* Re-send the data packet */
sbytes = sendto(state->sockfd, (void *)&state->spacket,
4+state->sbytes, SEND_4TH_ARG,
(struct sockaddr *)&state->remote_addr,
state->remote_addrlen);
/* Check all sbytes were sent */
if(sbytes<0) {
failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
res = CURLE_SEND_ERROR;
}
}
return res;
}
/* This is the expected packet. Reset the counters and send the next
block */
time(&state->rx_time);
state->block++;
}
else {
state->block = 1; /* first data block is 1 when using OACK */
}
state->retries = 0;
setpacketevent(&state->spacket, TFTP_EVENT_DATA);
setpacketblock(&state->spacket, state->block);
Daniel Stenberg
committed
if(state->block > 1 && state->sbytes < (int)state->blksize) {
state->state = TFTP_STATE_FIN;
return CURLE_OK;
}
Daniel Stenberg
committed
res = Curl_fillreadbuffer(state->conn, (size_t)state->blksize,
&state->sbytes);
if(res)
return res;
Daniel Stenberg
committed
sbytes = sendto(state->sockfd, (void *)state->spacket.data,
4+state->sbytes, SEND_4TH_ARG,
(struct sockaddr *)&state->remote_addr,
state->remote_addrlen);
/* Check all sbytes were sent */
if(sbytes<0) {
Daniel Stenberg
committed
failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
return CURLE_SEND_ERROR;
}
/* Update the progress meter */
k->writebytecount += state->sbytes;
Curl_pgrsSetUploadCounter(data, k->writebytecount);
break;
case TFTP_EVENT_TIMEOUT:
/* Increment the retry counter and log the timeout */
state->retries++;
infof(data, "Timeout waiting for block %d ACK. "
/* Decide if we've had enough */
if(state->retries > state->retry_max) {
state->error = TFTP_ERR_TIMEOUT;
state->state = TFTP_STATE_FIN;
}
else {
Daniel Stenberg
committed
/* Re-send the data packet */
sbytes = sendto(state->sockfd, (void *)state->spacket.data,
4+state->sbytes, SEND_4TH_ARG,
(struct sockaddr *)&state->remote_addr,
state->remote_addrlen);
/* Check all sbytes were sent */
if(sbytes<0) {
Daniel Stenberg
committed
failf(data, "%s", Curl_strerror(state->conn, SOCKERRNO));
return CURLE_SEND_ERROR;
}
/* since this was a re-send, we remain at the still byte position */
Curl_pgrsSetUploadCounter(data, k->writebytecount);
}
break;
case TFTP_EVENT_ERROR:
state->state = TFTP_STATE_FIN;
Daniel Stenberg
committed
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;
default:
failf(data, "tftp_tx: internal error, event: %i", (int)(event));
break;
}
return res;
}
Daniel Stenberg
committed
/**********************************************************
*
* tftp_translate_code
*
* Translate internal error codes to CURL error codes
*
**********************************************************/
static CURLcode tftp_translate_code(tftp_error_t error)
Daniel Stenberg
committed
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
{
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
*
* The tftp state machine event dispatcher
*
**********************************************************/
static CURLcode tftp_state_machine(tftp_state_data_t *state,
tftp_event_t event)
{
CURLcode res = CURLE_OK;
struct SessionHandle *data = state->conn->data;
switch(state->state) {
case TFTP_STATE_START:
DEBUGF(infof(data, "TFTP_STATE_START\n"));
res = tftp_send_first(state, event);
break;
case TFTP_STATE_RX:
DEBUGF(infof(data, "TFTP_STATE_RX\n"));
res = tftp_rx(state, event);
break;
case TFTP_STATE_TX:
DEBUGF(infof(data, "TFTP_STATE_TX\n"));
res = tftp_tx(state, event);
break;
case TFTP_STATE_FIN:
infof(data, "%s\n", "TFTP finished");
break;
default:
DEBUGF(infof(data, "STATE: %d\n", state->state));
Daniel Stenberg
committed
failf(data, "%s", "Internal state machine error");
res = CURLE_TFTP_ILLEGAL;
break;
}
return res;
}
Daniel Stenberg
committed
/**********************************************************
*
* tftp_disconnect
*
* The disconnect callback
*
**********************************************************/
static CURLcode tftp_disconnect(struct connectdata *conn)
{
tftp_state_data_t *state = conn->proto.tftpc;
/* done, free dynamically allocated pkt buffers */
if(state) {
Curl_safefree(state->rpacket.data);
Curl_safefree(state->spacket.data);
free(state);
}
return CURLE_OK;
}
/**********************************************************
*
* tftp_connect
*
* The connect callback
*
**********************************************************/
static CURLcode tftp_connect(struct connectdata *conn, bool *done)
{
CURLcode code;
tftp_state_data_t *state;
Daniel Stenberg
committed
int blksize, rc;
blksize = TFTP_BLKSIZE_DEFAULT;
/* If there already is a protocol-specific struct allocated for this
sessionhandle, deal with it */
Curl_reset_reqproto(conn);
state = conn->proto.tftpc = calloc(1, sizeof(tftp_state_data_t));
Daniel Stenberg
committed
if(!state)
return CURLE_OUT_OF_MEMORY;
/* alloc pkt buffers based on specified blksize */
if(conn->data->set.tftp_blksize) {
blksize = (int)conn->data->set.tftp_blksize;
Daniel Stenberg
committed
if(blksize > TFTP_BLKSIZE_MAX || blksize < TFTP_BLKSIZE_MIN )
return CURLE_TFTP_ILLEGAL;
}
if(!state->rpacket.data) {
state->rpacket.data = calloc(1, blksize + 2 + 2);
if(!state->rpacket.data)
return CURLE_OUT_OF_MEMORY;
}
if(!state->spacket.data) {
state->spacket.data = calloc(1, blksize + 2 + 2);
if(!state->spacket.data)
Daniel Stenberg
committed
conn->bits.close = TRUE; /* we don't keep TFTP connections up bascially
because there's none or very little gain for UDP
*/
Daniel Stenberg
committed
state->conn = conn;
state->sockfd = state->conn->sock[FIRSTSOCKET];
state->state = TFTP_STATE_START;
state->error = TFTP_ERR_NONE;
Daniel Stenberg
committed
state->blksize = TFTP_BLKSIZE_DEFAULT;
state->requested_blksize = blksize;
((struct sockaddr *)&state->local_addr)->sa_family =
tftp_set_timeouts(state);
if(!conn->bits.bound) {
/* 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!
Daniel Stenberg
committed
*
Daniel Stenberg
committed
* We once used the size of the local_addr struct as the third argument
* for bind() to better work with IPv6 or whatever size the struct could
* have, but we learned that at least Tru64, AIX and IRIX *requires* the
* size of that argument to match the exact size of a 'sockaddr_in' struct
* when running IPv4-only.
Daniel Stenberg
committed
*
* 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
* IPv4 and IPv6...
*/
rc = bind(state->sockfd, (struct sockaddr *)&state->local_addr,
conn->ip_addr->ai_addrlen);
if(rc) {
Daniel Stenberg
committed
failf(conn->data, "bind() failed; %s",
Curl_strerror(conn, SOCKERRNO));
Daniel Stenberg
committed
return CURLE_COULDNT_CONNECT;
}
conn->bits.bound = TRUE;
Curl_pgrsStartNow(conn->data);
*done = TRUE;
code = CURLE_OK;
return(code);
}
/**********************************************************
*
* tftp_done
*
* The done callback
*
**********************************************************/
static CURLcode tftp_done(struct connectdata *conn, CURLcode status,
Patrick Monnerat
committed
bool premature)
{
Daniel Stenberg
committed
CURLcode code = CURLE_OK;
tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc;
(void)status; /* unused */
Daniel Stenberg
committed
(void)premature; /* not used */
Curl_pgrsDone(conn);
Daniel Stenberg
committed
/* If we have encountered an error */
code = tftp_translate_code(state->error);
Daniel Stenberg
committed
return code;
}
/**********************************************************
*
Daniel Stenberg
committed
* tftp_getsock
*
Daniel Stenberg
committed
* The getsock callback
*
**********************************************************/
Daniel Stenberg
committed
static int tftp_getsock(struct connectdata *conn, curl_socket_t *socks,
int numsocks)
{
if(!numsocks)
return GETSOCK_BLANK;
Daniel Stenberg
committed
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)
{
Daniel Stenberg
committed
struct Curl_sockaddr_storage fromaddr;
Daniel Stenberg
committed
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;
}
Daniel Stenberg
committed
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
/* 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;
}
Daniel Stenberg
committed
Daniel Stenberg
committed
/* Update the progress meter */
if(Curl_pgrsUpdate(conn)) {
tftp_state_machine(state, TFTP_EVENT_ERROR);
return CURLE_ABORTED_BY_CALLBACK;
}
}
return result;
}
Daniel Stenberg
committed
Daniel Stenberg
committed
/**********************************************************
*
* tftp_state_timeout
*
* Check if timeouts have been reached
*
**********************************************************/
static long tftp_state_timeout(struct connectdata *conn, tftp_event_t *event)
{
time_t current;
tftp_state_data_t *state = (tftp_state_data_t *)conn->proto.tftpc;
if (event)
*event = TFTP_EVENT_NONE;
time(¤t);
if(current > state->max_time) {
DEBUGF(infof(conn->data, "timeout: %ld > %ld\n",
(long)current, (long)state->max_time));
Daniel Stenberg
committed
state->error = TFTP_ERR_TIMEOUT;
state->state = TFTP_STATE_FIN;
return(0);
Daniel Stenberg
committed
}
Daniel Stenberg
committed
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);
}
}
static long sleep_time(curl_off_t rate_bps, curl_off_t cur_rate_bps,
int pkt_size)
Daniel Stenberg
committed
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
{
curl_off_t min_sleep = 0;
curl_off_t rv = 0;
if (rate_bps == 0)
return 0;
if (cur_rate_bps > (rate_bps + (rate_bps >> 10))) {
/* running too fast */
rate_bps -= rate_bps >> 6;
min_sleep = 1;
}
else if (cur_rate_bps < (rate_bps - (rate_bps >> 10))) {
/* running too slow */
rate_bps += rate_bps >> 6;
}
rv = ((curl_off_t)((pkt_size * 8) * 1000) / rate_bps);
if (rv < min_sleep)
rv = min_sleep;
if(rv > 0x7fffffff)
rv = 0x7fffffff;
return (long)rv;
Daniel Stenberg
committed
}
Daniel Stenberg
committed
/**********************************************************
*
* 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;
Daniel Stenberg
committed
int fd_read;
long timeout_ms;
Daniel Stenberg
committed
struct SingleRequest *k = &data->req;
struct timeval transaction_start = Curl_tvnow();
k->start = transaction_start;
k->now = transaction_start;
/* Run the TFTP State Machine */
Daniel Stenberg
committed
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
for(; (state->state != TFTP_STATE_FIN) && (result == CURLE_OK); ) {
timeout_ms = state->retry_time * 1000;
if (data->set.upload) {
if (data->set.max_send_speed &&
(data->progress.ulspeed > data->set.max_send_speed)) {
fd_read = CURL_SOCKET_BAD;
timeout_ms = sleep_time(data->set.max_send_speed,
data->progress.ulspeed, state->blksize);
}
else {
fd_read = state->sockfd;
}
}
else {
if (data->set.max_recv_speed &&
(data->progress.dlspeed > data->set.max_recv_speed)) {
fd_read = CURL_SOCKET_BAD;
timeout_ms = sleep_time(data->set.max_recv_speed,
data->progress.dlspeed, state->blksize);
}
else {
fd_read = state->sockfd;
}
}
if(data->set.timeout) {
timeout_ms = data->set.timeout - Curl_tvdiff(k->now, k->start);
if (timeout_ms > state->retry_time * 1000)
timeout_ms = state->retry_time * 1000;
else if(timeout_ms < 0)
timeout_ms = 0;
}
/* Wait until ready to read or timeout occurs */
Daniel Stenberg
committed
rc=Curl_socket_ready(fd_read, CURL_SOCKET_BAD, timeout_ms);
k->now = Curl_tvnow();
/* Force a progress callback if it's been too long */
if (Curl_tvdiff(k->now, k->start) >= data->set.timeout) {
if(Curl_pgrsUpdate(conn)) {
tftp_state_machine(state, TFTP_EVENT_ERROR);
return CURLE_ABORTED_BY_CALLBACK;
}
k->start = k->now;
}
if(rc == -1) {
/* bail out */
int error = SOCKERRNO;
Daniel Stenberg
committed
failf(data, "%s", Curl_strerror(conn, error));
Daniel Stenberg
committed
state->event = TFTP_EVENT_ERROR;
}
else {
Daniel Stenberg
committed
if(rc==0) {
/* A timeout occured, but our timeout is variable, so maybe
just continue? */
long rtms = state->retry_time * 1000;
if (Curl_tvdiff(k->now, transaction_start) > rtms) {
state->event = TFTP_EVENT_TIMEOUT;
/* Force a look at transfer timeouts */
check_time = 1;
}
else {
continue; /* skip state machine */
}
}
else {
Daniel Stenberg
committed
result = tftp_receive_packet(conn);
Daniel Stenberg
committed
if (result == CURLE_OK)
transaction_start = Curl_tvnow();
if(k->bytecountp)
*k->bytecountp = k->bytecount; /* read count */
if(k->writebytecountp)
*k->writebytecountp = k->writebytecount; /* write count */
}
}
Daniel Stenberg
committed
if(check_time) {
Daniel Stenberg
committed
tftp_state_timeout(conn, NULL);
Daniel Stenberg
committed
check_time = 0;
}
Daniel Stenberg
committed
if(result)
return(result);
Daniel Stenberg
committed
result = tftp_state_machine(state, state->event);
}
/* Tell curl we're done */
Daniel Stenberg
committed
result = Curl_setup_transfer(conn, -1, -1, FALSE, NULL, -1, NULL);
Daniel Stenberg
committed
return(result);
}
Daniel Stenberg
committed
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
/**********************************************************
*
* 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;
}
Daniel Stenberg
committed
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 */
}
Daniel Stenberg
committed
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
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_do
*
* The do callback
*
* This callback initiates the TFTP transfer
*
**********************************************************/
static CURLcode tftp_do(struct connectdata *conn, bool *done)
{
tftp_state_data_t *state;
CURLcode code;
*done = FALSE;
/*
Since connections can be re-used between SessionHandles, this might be a
connection already existing but on a fresh SessionHandle struct so we must
make sure we have a good 'struct TFTP' to play with. For new connections,
the struct TFTP is allocated and setup in the tftp_connect() function.
*/
Curl_reset_reqproto(conn);
if(!conn->proto.tftpc) {
code = tftp_connect(conn, done);
if(code)
return code;
}
state = (tftp_state_data_t *)conn->proto.tftpc;
code = tftp_perform(conn, done);
/* If we have encountered an error */
code = tftp_translate_code(state->error);
return code;
}
Patrick Monnerat
committed
static CURLcode tftp_setup_connection(struct connectdata * conn)
Patrick Monnerat
committed
{
struct SessionHandle *data = conn->data;
char * type;
char command;
conn->socktype = SOCK_DGRAM; /* UDP datagram based */
/* TFTP URLs support an extension like ";mode=<typecode>" that
* we'll try to get now! */
Daniel Stenberg
committed
type = strstr(data->state.path, ";mode=");
Patrick Monnerat
committed
Patrick Monnerat
committed
type = strstr(conn->host.rawalloc, ";mode=");
Patrick Monnerat
committed
*type = 0; /* it was in the middle of the hostname */
command = Curl_raw_toupper(type[6]);
Patrick Monnerat
committed
switch (command) {
case 'A': /* ASCII mode */
case 'N': /* NETASCII mode */
data->set.prefer_ascii = TRUE;
break;
case 'O': /* octet mode */
case 'I': /* binary mode */
default:
/* switch off ASCII */
data->set.prefer_ascii = FALSE;
break;
}
}
return CURLE_OK;
}