Newer
Older
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
Daniel Stenberg
committed
* Copyright (C) 1998 - 2009, 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 "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 {
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;
struct connectdata *conn;
int retries;
time_t retry_time;
time_t retry_max;
time_t start_time;
time_t max_time;
unsigned short block;
Daniel Stenberg
committed
struct Curl_sockaddr_storage local_addr;
struct Curl_sockaddr_storage remote_addr;
curl_socklen_t remote_addrlen;
ssize_t rbytes;
Daniel Stenberg
committed
size_t sbytes;
size_t blksize;
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);
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 */
ZERO_NULL, /* connecting */
ZERO_NULL, /* doing */
ZERO_NULL, /* proto_getsock */
ZERO_NULL, /* 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 */
state->retry_max = 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 */
state->retry_time = 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 */
state->retry_max = timeout/15;
}
/* 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;
Daniel Stenberg
committed
"set timeouts for state %d; Total %d, retry %d maxtry %d\n",
state->state, (state->max_time-state->start_time),
state->retry_time, state->retry_max);
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
306
307
308
309
310
311
312
313
314
315
316
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
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)) {
int blksize;
blksize = (int)strtol( value, NULL, 10 );
Daniel Stenberg
committed
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
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 */
failf(data, "%s (%d)",
"server requested blksize larger than allocated", blksize);
return CURLE_TFTP_ILLEGAL;
}
state->blksize = blksize;
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 value in OACK packet");
return CURLE_TFTP_ILLEGAL;
}
Curl_pgrsSetDownloadSize(data, tsize);
infof(data, "%s (%d)\n", "tsize parsed from OACK", tsize);
}
}
return CURLE_OK;
}
static size_t tftp_option_add(tftp_state_data_t *state, size_t csize,
char *buf, const char *option)
{
if( ( strlen(option) + csize + 1U ) > state->blksize )
return 0;
strcpy(buf, option);
return( strlen(option) + 1 );
}
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);
}
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)
{
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)
Loading
Loading full blame...