diff --git a/CHANGES b/CHANGES index cb61019c8..7b209cb88 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,11 @@ Changelog +Daniel (1 September 2005) +- John Kelly added TFTP support to libcurl. A bunch of new error codes was + added. TODO: add them to docs. add TFTP server to test suite. add TFTP to + list of protocols whereever those are mentioned. + Version 7.14.1 (1 September 2005) Daniel (29 August 2005) diff --git a/configure.ac b/configure.ac index 494c2048a..1f2eedde3 100644 --- a/configure.ac +++ b/configure.ac @@ -251,6 +251,21 @@ AC_HELP_STRING([--disable-telnet],[Disable TELNET support]), esac ], AC_MSG_RESULT(yes) ) +AC_MSG_CHECKING([whether to support tftp]) +AC_ARG_ENABLE(tftp, +AC_HELP_STRING([--enable-tftp],[Enable TFTP support]) +AC_HELP_STRING([--disable-tftp],[Disable TFTP support]), +[ case "$enableval" in + no) + AC_MSG_RESULT(no) + AC_DEFINE(CURL_DISABLE_TFTP, 1, [to disable TFTP]) + AC_SUBST(CURL_DISABLE_TFTP, [1]) + ;; + *) AC_MSG_RESULT(yes) + ;; + esac ], + AC_MSG_RESULT(yes) +) dnl ********************************************************************** dnl Check for built-in manual diff --git a/include/curl/curl.h b/include/curl/curl.h index e882bd4c6..daacf781a 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -309,6 +309,13 @@ typedef enum { CURLE_SSL_ENGINE_INITFAILED, /* 66 - failed to initialise ENGINE */ CURLE_LOGIN_DENIED, /* 67 - user, password or similar was not accepted and we failed to login */ + CURLE_TFTP_NOTFOUND, /* 68 - file not found on server */ + CURLE_TFTP_PERM, /* 69 - permission problem on server */ + CURLE_TFTP_DISKFULL, /* 70 - out of disk space on server */ + CURLE_TFTP_ILLEGAL, /* 71 - Illegal TFTP operation */ + CURLE_TFTP_UNKNOWNID, /* 72 - Unknown transfer ID */ + CURLE_TFTP_EXISTS, /* 73 - File already exists */ + CURLE_TFTP_NOSUCHUSER, /* 74 - No such user */ CURL_LAST /* never use! */ } CURLcode; diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 3ab3d1f9d..f204a73f7 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -8,7 +8,7 @@ CSOURCES = file.c timeval.c base64.c hostip.c progress.c formdata.c \ content_encoding.c share.c http_digest.c md5.c http_negotiate.c \ http_ntlm.c inet_pton.c strtoofft.c strerror.c hostares.c hostasyn.c \ hostip4.c hostip6.c hostsyn.c hostthre.c inet_ntop.c parsedate.c \ - select.c gtls.c sslgen.c + select.c gtls.c sslgen.c tftp.c HHEADERS = arpa_telnet.h netrc.h file.h timeval.h base64.h hostip.h \ progress.h formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h \ @@ -18,5 +18,5 @@ HHEADERS = arpa_telnet.h netrc.h file.h timeval.h base64.h hostip.h \ share.h md5.h http_digest.h http_negotiate.h http_ntlm.h ca-bundle.h \ inet_pton.h strtoofft.h strerror.h inet_ntop.h curlx.h memory.h \ setup.h transfer.h select.h easyif.h multiif.h parsedate.h sslgen.h \ - gtls.h + gtls.h tftp.h diff --git a/lib/connect.c b/lib/connect.c index 50f6684b6..5d9cf65ef 100644 --- a/lib/connect.c +++ b/lib/connect.c @@ -660,8 +660,13 @@ singleipconnect(struct connectdata *conn, /* set socket non-blocking */ Curl_nonblock(sockfd, TRUE); - rc = connect(sockfd, ai->ai_addr, (socklen_t)ai->ai_addrlen); - + /* Connect TCP sockets, bind UDP */ + if(ai->ai_socktype==SOCK_STREAM) { + rc = connect(sockfd, ai->ai_addr, (socklen_t)ai->ai_addrlen); + } else { + rc = 0; + } + if(-1 == rc) { error = Curl_ourerrno(); diff --git a/lib/hostip4.c b/lib/hostip4.c index 1244a47bf..d9277b960 100644 --- a/lib/hostip4.c +++ b/lib/hostip4.c @@ -423,7 +423,11 @@ Curl_addrinfo *Curl_he2ai(struct hostent *he, int port) prevai->ai_next = ai; ai->ai_family = AF_INET; /* we only support this */ - ai->ai_socktype = SOCK_STREAM; /* we only support this */ + if(port == PORT_TFTP) + ai->ai_socktype = SOCK_DGRAM; + else + ai->ai_socktype = SOCK_STREAM; + ai->ai_addrlen = sizeof(struct sockaddr_in); /* make the ai_addr point to the address immediately following this struct and use that area to store the address */ diff --git a/lib/hostip6.c b/lib/hostip6.c index b273efe00..4624e00b8 100644 --- a/lib/hostip6.c +++ b/lib/hostip6.c @@ -251,7 +251,12 @@ Curl_addrinfo *Curl_getaddrinfo(struct connectdata *conn, memset(&hints, 0, sizeof(hints)); hints.ai_family = pf; - hints.ai_socktype = SOCK_STREAM; + + if(conn->protocol & PROT_TFTP) + hints.ai_socktype = SOCK_DGRAM; + else + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = ai_flags; snprintf(sbuf, sizeof(sbuf), "%d", port); error = getaddrinfo(hostname, sbuf, &hints, &res); diff --git a/lib/strerror.c b/lib/strerror.c index 2d0b34270..4edd28ac7 100644 --- a/lib/strerror.c +++ b/lib/strerror.c @@ -245,6 +245,27 @@ curl_easy_strerror(CURLcode error) case CURLE_LOGIN_DENIED: return "FTP: login denied";; + case CURLE_TFTP_NOTFOUND: + return "TFTP: File Not Found";; + + case CURLE_TFTP_PERM: + return "TFTP: Access Violation";; + + case CURLE_TFTP_DISKFULL: + return "TFTP: Disk full or allocation exceeded";; + + case CURLE_TFTP_ILLEGAL: + return "TFTP: Illegal operation";; + + case CURLE_TFTP_UNKNOWNID: + return "TFTP: Unknown transfer ID";; + + case CURLE_TFTP_EXISTS: + return "TFTP: File already exists";; + + case CURLE_TFTP_NOSUCHUSER: + return "TFTP: No such user";; + case CURLE_URL_MALFORMAT_USER: /* not used by current libcurl */ case CURLE_MALFORMAT_USER: /* not used by current libcurl */ case CURLE_BAD_CALLING_ORDER: /* not used by current libcurl */ diff --git a/lib/tftp.c b/lib/tftp.c new file mode 100644 index 000000000..675cdec51 --- /dev/null +++ b/lib/tftp.c @@ -0,0 +1,720 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2005, Daniel Stenberg, , 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 +#include +#include +#include +#include +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#include + +#if defined(WIN32) +#include +#include +#else +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef HAVE_NET_IF_H +#include +#endif +#include +#include + +#ifdef HAVE_SYS_PARAM_H +#include +#endif + + +#endif + +#include "urldata.h" +#include +#include "transfer.h" +#include "sendf.h" +#include "tftp.h" +#include "progress.h" + +#define _MPRINTF_REPLACE /* use our functions only */ +#include + +#include "memory.h" +#include "select.h" + +/* The last #include file should be: */ +#include "memdebug.h" + +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +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, + 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, + TFTP_ERR_NOSUCHUSER, + TFTP_ERR_TIMEOUT, + TFTP_ERR_NORESPONSE +} tftp_error_t; + +typedef struct tftp_packet { + unsigned short event; + union { + struct { + unsigned char data[512]; + } request; + struct { + unsigned short block; + unsigned char data[512]; + } data; + struct { + unsigned short block; + } ack; + struct { + unsigned short code; + unsigned char data[512]; + } error; + } u; +} tftp_packet_t; + +typedef struct tftp_state_data { + tftp_state_t state; + tftp_mode_t mode; + tftp_error_t error; + struct connectdata *conn; + int sockfd; + int retries; + int retry_time; + int retry_max; + time_t start_time; + time_t max_time; + unsigned short block; + struct sockaddr local_addr; + socklen_t local_addrlen; + struct sockaddr remote_addr; + socklen_t remote_addrlen; + int rbytes; + int sbytes; + tftp_packet_t rpacket; + tftp_packet_t spacket; +} tftp_state_data_t; + + +/* Forward declarations */ +static void tftp_rx(tftp_state_data_t *state, tftp_event_t event) ; +static void tftp_tx(tftp_state_data_t *state, tftp_event_t event) ; +void tftp_set_timeouts(tftp_state_data_t *state) ; + +/********************************************************** + * + * 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 + * + * + **********************************************************/ +void tftp_set_timeouts(tftp_state_data_t *state) +{ + + struct SessionHandle *data = state->conn->data; + unsigned long maxtime, timeout; + + time(&state->start_time); + if(state->state == TFTP_STATE_START) { + /* Compute drop-dead time */ + maxtime = data->set.connecttimeout?data->set.connecttimeout:30; + 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; + + /* 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 { + + /* Compute drop-dead time */ + maxtime = data->set.timeout?data->set.timeout: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; + + infof(data, "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); +} + +/********************************************************** + * + * tftp_set_send_first + * + * Event handler for the START state + * + **********************************************************/ + +static void tftp_send_first(tftp_state_data_t *state, tftp_event_t event) +{ + int sbytes; + const char *mode = "octet"; + char *filename = state->conn->path; + struct SessionHandle *data = state->conn->data; + + /* Set ascii mode if -B flag was used */ + if(data->set.ftp_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; + } + + if(data->set.upload) { + /* If we are uploading, send an WRQ */ + state->spacket.event = htons(TFTP_EVENT_WRQ); + filename = curl_unescape(filename, strlen(filename)); + state->conn->upload_fromhere = (char *)state->spacket.u.data.data; + if(data->set.infilesize != -1) { + Curl_pgrsSetUploadSize(data, data->set.infilesize); + } + } + else { + /* If we are downloading, send an RRQ */ + state->spacket.event = htons(TFTP_EVENT_RRQ); + } + sprintf((char *)state->spacket.u.request.data, "%s%c%s%c", + filename, '\0', mode, '\0'); + sbytes = 4 + strlen(filename) + strlen(mode); + sbytes = sendto(state->sockfd, &state->spacket, sbytes, 0, + state->conn->ip_addr->ai_addr, + state->conn->ip_addr->ai_addrlen); + if(sbytes < 0) { + failf(data, "%s\n", strerror(errno)); + } + break; + + case TFTP_EVENT_ACK: /* Connected for transmit */ + infof(data, "%s\n", "Connected for transmit"); + state->state = TFTP_STATE_TX; + tftp_set_timeouts(state); + tftp_tx(state, event); + break; + + case TFTP_EVENT_DATA: /* connected for receive */ + infof(data, "%s\n", "Connected for receive"); + state->state = TFTP_STATE_RX; + tftp_set_timeouts(state); + tftp_rx(state, event); + break; + + case TFTP_EVENT_ERROR: + state->state = TFTP_STATE_FIN; + break; + + default: + failf(state->conn->data, "tftp_send_first: internal error\n"); + break; + } +} + +/********************************************************** + * + * tftp_rx + * + * Event handler for the RX state + * + **********************************************************/ +static void tftp_rx(tftp_state_data_t *state, tftp_event_t event) +{ + int sbytes; + int rblock; + + switch(event) { + + case TFTP_EVENT_DATA: + + /* Is this the block we expect? */ + rblock = ntohs(state->rpacket.u.data.block); + if ((state->block+1) != rblock) { + /* No, log it, up the retry count and fail if over the limit */ + infof(state->conn->data, + "Received unexpected DATA packet block %d\n", rblock); + state->retries++; + if (state->retries>state->retry_max) { + failf(state->conn->data, "tftp_rx: giving up waiting for block %d\n", + state->block+1); + return; + } + } + /* This is the expected block. Reset counters and ACK it. */ + state->block = rblock; + state->retries = 0; + state->spacket.event = htons(TFTP_EVENT_ACK); + state->spacket.u.ack.block = htons(state->block); + sbytes = sendto(state->sockfd, &state->spacket, 4, MSG_NOSIGNAL, + &state->remote_addr, state->remote_addrlen); + if(sbytes < 0) { + failf(state->conn->data, "%s\n", strerror(errno)); + } + + /* Check if completed (That is, a less than full packet is recieved) */ + if (state->rbytes < (int)sizeof(state->spacket)){ + state->state = TFTP_STATE_FIN; + } + else { + state->state = TFTP_STATE_RX; + } + break; + + case TFTP_EVENT_TIMEOUT: + /* Increment the retry count and fail if over the limit */ + state->retries++; + infof(state->conn->data, + "Timeout waiting for block %d ACK. Retries = %d\n", state->retries); + if(state->retries > state->retry_max) { + state->error = TFTP_ERR_TIMEOUT; + state->state = TFTP_STATE_FIN; + } else { + /* Resend the previous ACK */ + sbytes = sendto(state->sockfd, &state->spacket, + 4, MSG_NOSIGNAL, + &state->remote_addr, state->remote_addrlen); + /* Check all sbytes were sent */ + if(sbytes<0) { + failf(state->conn->data, "%s\n", strerror(errno)); + } + } + break; + + case TFTP_EVENT_ERROR: + state->state = TFTP_STATE_FIN; + break; + + default: + failf(state->conn->data, "%s\n", "tftp_rx: internal error"); + break; + } + Curl_pgrsSetDownloadCounter(state->conn->data, + (curl_off_t) state->block*512); +} + +/********************************************************** + * + * tftp_tx + * + * Event handler for the TX state + * + **********************************************************/ +static void tftp_tx(tftp_state_data_t *state, tftp_event_t event) +{ + int sbytes; + int rblock; + + switch(event) { + + + case TFTP_EVENT_ACK: + /* Ack the packet */ + rblock = ntohs(state->rpacket.u.data.block); + + if(rblock != state->block) { + /* This isn't the expected block. Log it and up the retry counter */ + infof(state->conn->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(state->conn->data, "%s\n", + "tftp_tx: giving up waiting for block %d ack", + state->block); + } + return; + } + /* This is the expected packet. Reset the counters and send the next + block */ + state->block++; + state->retries = 0; + state->spacket.event = htons(TFTP_EVENT_DATA); + state->spacket.u.ack.block = htons(state->block); + if(state->block > 1 && state->sbytes < 512) { + state->state = TFTP_STATE_FIN; + return; + } + Curl_fillreadbuffer(state->conn, 512, &state->sbytes); + sbytes = sendto(state->sockfd, &state->spacket, + 4+state->sbytes, MSG_NOSIGNAL, + &state->remote_addr, state->remote_addrlen); + /* Check all sbytes were sent */ + if(sbytes<0) { + failf(state->conn->data, "%s\n", strerror(errno)); + } + break; + + + + case TFTP_EVENT_TIMEOUT: + /* Increment the retry counter and log the timeout */ + state->retries++; + infof(state->conn->data, "Timeout waiting for block %d ACK. " + " Retries = %d\n", state->retries); + /* Decide if we've had enough */ + if(state->retries > state->retry_max) { + state->error = TFTP_ERR_TIMEOUT; + state->state = TFTP_STATE_FIN; + } else { + /* Re-send the data packet */ + sbytes = sendto(state->sockfd, &state->spacket, + 4+state->sbytes, MSG_NOSIGNAL, + &state->remote_addr, state->remote_addrlen); + /* Check all sbytes were sent */ + if(sbytes<0) { + failf(state->conn->data, "%s\n", strerror(errno)); + } + } + break; + + + + case TFTP_EVENT_ERROR: + state->state = TFTP_STATE_FIN; + break; + + + + default: + failf(state->conn->data, "%s\n", "tftp_tx: internal error"); + break; + } + + /* Update the progress meter */ + Curl_pgrsSetUploadCounter(state->conn->data, (curl_off_t) state->block*512); +} + +/********************************************************** + * + * tftp_state_machine + * + * The tftp state machine event dispatcher + * + **********************************************************/ +static CURLcode tftp_state_machine(tftp_state_data_t *state, + tftp_event_t event) +{ + + switch(state->state) { + case TFTP_STATE_START: + tftp_send_first(state, event); + break; + case TFTP_STATE_RX: + tftp_rx(state, event); + break; + case TFTP_STATE_TX: + tftp_tx(state, event); + break; + case TFTP_STATE_FIN: + infof(state->conn->data, "%s\n", "TFTP finished"); + break; + default: + failf(state->conn->data, "%s\n", "Internal state machine error"); + break; + } + return CURLE_OK; +} + + +/********************************************************** + * + * Curl_tftp_connect + * + * The connect callback + * + **********************************************************/ +CURLcode Curl_tftp_connect(struct connectdata *conn, bool *done) +{ + CURLcode code; + tftp_state_data_t *state; + int rc; + + if((state = conn->proto.tftp = calloc(sizeof(tftp_state_data_t), 1))==NULL) { + return CURLE_OUT_OF_MEMORY; + } + + state->conn = conn; + state->sockfd = state->conn->sock[FIRSTSOCKET]; + state->state = TFTP_STATE_START; + + tftp_set_timeouts(state); + + /* Bind to any interface, random UDP port */ + rc = bind(state->sockfd, &state->local_addr, sizeof(state->local_addr)); + + Curl_pgrsStartNow(conn->data); + + *done = TRUE; + code = CURLE_OK; + return(code); +} + +/********************************************************** + * + * Curl_tftp_done + * + * The done callback + * + **********************************************************/ +CURLcode Curl_tftp_done(struct connectdata *conn, CURLcode status) +{ + (void)status; /* unused */ + + free(conn->proto.tftp); + conn->proto.tftp = NULL; + Curl_pgrsDone(conn); + + return CURLE_OK; +} + + +/********************************************************** + * + * Curl_tftp + * + * The do callback + * + * This callback handles the entire TFTP transfer + * + **********************************************************/ + +CURLcode Curl_tftp(struct connectdata *conn, bool *done) +{ + struct SessionHandle *data = conn->data; + tftp_state_data_t *state = (tftp_state_data_t *)(conn->proto.tftp); + tftp_event_t event; + fd_set readset; + struct timeval tv; + CURLcode code; + int rc; + struct sockaddr fromaddr; + socklen_t fromlen; + int check_time = 0; + + (void)done; /* prevent compiler warning */ + + /* Run the TFTP State Machine */ + for(tftp_state_machine(state, TFTP_EVENT_INIT); + state->state != TFTP_STATE_FIN; + tftp_state_machine(state, event) ) { + + /* Update the progress meter */ + Curl_pgrsUpdate(conn); + + /* Waiting on event from network or OS */ + FD_ZERO(&readset); + FD_SET(state->sockfd, &readset); + tv.tv_sec=state->retry_time; tv.tv_usec=0; + + restart: + + /* Wait until ready to read or timeout occurs */ + rc=select(state->sockfd+1, &readset, NULL, NULL, &tv); + + if(rc == -1) { + /* Restart if a signal interrupt occured */ + if(errno == EINTR) { + goto restart; + } + + /* Otherwise, bail out */ + failf(state->conn->data, "%s\n", strerror(errno)); + 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 = recvfrom(state->sockfd, + (void *)&state->rpacket, sizeof(state->rpacket), + 0, &fromaddr, &fromlen); + if(state->remote_addrlen==0) { + memcpy(&state->remote_addr, &fromaddr, fromlen); + state->remote_addrlen = fromlen; + } + + /* The event is given by the TFTP packet time */ + event = ntohs(state->rpacket.event); + + switch(event) { + case TFTP_EVENT_DATA: + Curl_client_write(data, CLIENTWRITE_BODY, + (char *)state->rpacket.u.data.data, state->rbytes-4); + break; + case TFTP_EVENT_ERROR: + state->error = ntohs(state->rpacket.u.error.code); + infof(conn->data, "%s\n", (char *)state->rpacket.u.error.data); + break; + case TFTP_EVENT_ACK: + break; + case TFTP_EVENT_RRQ: + case TFTP_EVENT_WRQ: + default: + failf(conn->data, "%s\n", "Internal error: Unexpected packet"); + break; + } + } + + /* Check for transfer timeout every 10 blocks, or after timeout */ + if(check_time%10==0) { + time_t current; + time(¤t); + if(current>state->max_time) { + state->error = TFTP_ERR_TIMEOUT; + state->state = TFTP_STATE_FIN; + } + } + + + } + + /* Tell curl we're done */ + Curl_Transfer(conn, -1, -1, FALSE, NULL, -1, NULL); + + /* If we have encountered an error */ + if(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_TFTP_DISKFULL; + break; + case TFTP_ERR_ILLEGAL: + code = CURLE_TFTP_ILLEGAL; + break; + case TFTP_ERR_UNKNOWNID: + code = CURLE_TFTP_UNKNOWNID; + break; + case TFTP_ERR_EXISTS: + code = CURLE_TFTP_EXISTS; + break; + case TFTP_ERR_NOSUCHUSER: + code = CURLE_TFTP_NOSUCHUSER; + break; + case TFTP_ERR_TIMEOUT: + code = CURLE_OPERATION_TIMEOUTED; + break; + case TFTP_ERR_NORESPONSE: + code = CURLE_COULDNT_CONNECT; + break; + default: + code= CURLE_ABORTED_BY_CALLBACK; + break; + } + } + else + code = CURLE_OK; + return code; +} +#endif diff --git a/lib/tftp.h b/lib/tftp.h new file mode 100644 index 000000000..1120e26a5 --- /dev/null +++ b/lib/tftp.h @@ -0,0 +1,31 @@ +#ifndef __TFTP_H +#define __TFTP_H + +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2005, Daniel Stenberg, , 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$ + ***************************************************************************/ +#ifndef CURL_DISABLE_TFTP +CURLcode Curl_tftp_connect(struct connectdata *conn, bool *done); +CURLcode Curl_tftp(struct connectdata *conn, bool *done); +CURLcode Curl_tftp_done(struct connectdata *conn, CURLcode); +#endif +#endif diff --git a/lib/url.c b/lib/url.c index 92d81caa4..06784fe34 100644 --- a/lib/url.c +++ b/lib/url.c @@ -77,9 +77,7 @@ #error "We can't compile without socket() support!" #endif - #endif - #ifdef USE_LIBIDN #include #include @@ -123,6 +121,7 @@ void idn_free (void *ptr); /* prototype from idn-free.h, not provided by #include "ftp.h" #include "dict.h" #include "telnet.h" +#include "tftp.h" #include "http.h" #include "file.h" #include "ldap.h" @@ -2915,6 +2914,43 @@ static CURLcode CreateConnection(struct SessionHandle *data, #else failf(data, LIBCURL_NAME " was built with FILE disabled!"); +#endif + } + else if (strequal(conn->protostr, "TFTP")) { +#ifndef CURL_DISABLE_TFTP + char *type; + conn->protocol |= PROT_TFTP; + conn->port = PORT_TFTP; + conn->remote_port = PORT_TFTP; + conn->curl_connect = Curl_tftp_connect; + conn->curl_do = Curl_tftp; + conn->curl_done = Curl_tftp_done; + /* TFTP URLs support an extension like ";mode=" that + * we'll try to get now! */ + type=strstr(conn->path, ";mode="); + if(!type) { + type=strstr(conn->host.rawalloc, ";mode="); + } + if(type) { + char command; + *type=0; /* it was in the middle of the hostname */ + command = toupper((int)type[6]); + switch(command) { + case 'A': /* ASCII mode */ + case 'N': /* NETASCII mode */ + data->set.ftp_ascii = 1; + break; + case 'O': /* octet mode */ + case 'I': /* binary mode */ + default: + /* switch off ASCII */ + data->set.ftp_ascii = 0; + break; + } + } +#else + failf(data, LIBCURL_NAME + " was built with TFTP disabled!"); #endif } else { diff --git a/lib/urldata.h b/lib/urldata.h index d54250a5d..3436b17da 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -35,6 +35,7 @@ #define PORT_HTTPS 443 #define PORT_DICT 2628 #define PORT_LDAP 389 +#define PORT_TFTP 69 #define DICT_MATCH "/MATCH:" #define DICT_MATCH2 "/M:" @@ -540,6 +541,7 @@ struct connectdata { #define PROT_DICT (1<<6) #define PROT_LDAP (1<<7) #define PROT_FILE (1<<8) +#define PROT_TFTP (1<<11) #define PROT_FTPS (1<<9) #define PROT_SSL (1<<10) /* protocol requires SSL */ @@ -695,6 +697,7 @@ struct connectdata { struct HTTP *gopher; /* alias, just for the sake of being more readable */ struct HTTP *https; /* alias, just for the sake of being more readable */ struct FTP *ftp; + void *tftp; /* private for tftp.c-eyes only */ struct FILEPROTO *file; void *telnet; /* private for telnet.c-eyes only */ void *generic; diff --git a/lib/version.c b/lib/version.c index f7fe757ec..42bdfd99c 100644 --- a/lib/version.c +++ b/lib/version.c @@ -81,6 +81,9 @@ char *curl_version(void) /* data for curl_version_info */ static const char * const protocols[] = { +#ifndef CURL_DISABLE_TFTP + "tftp", +#endif #ifndef CURL_DISABLE_FTP "ftp", #endif