Browse Source

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.

Daniel Stenberg 18 years ago
parent
commit
56d9624b56
13 changed files with 863 additions and 8 deletions
  1. 5 0
      CHANGES
  2. 15 0
      configure.ac
  3. 7 0
      include/curl/curl.h
  4. 2 2
      lib/Makefile.inc
  5. 7 2
      lib/connect.c
  6. 5 1
      lib/hostip4.c
  7. 6 1
      lib/hostip6.c
  8. 21 0
      lib/strerror.c
  9. 720 0
      lib/tftp.c
  10. 31 0
      lib/tftp.h
  11. 38 2
      lib/url.c
  12. 3 0
      lib/urldata.h
  13. 3 0
      lib/version.c

+ 5 - 0
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)

+ 15 - 0
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

+ 7 - 0
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;
 

+ 2 - 2
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
 

+ 7 - 2
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();
 

+ 5 - 1
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 */

+ 6 - 1
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);

+ 21 - 0
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 */

+ 720 - 0
lib/tftp.c

@@ -0,0 +1,720 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2005, 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 <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <ctype.h>
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#include <errno.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>
+#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
+#include <sys/ioctl.h>
+#include <signal.h>
+
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#endif
+
+
+#endif
+
+#include "urldata.h"
+#include <curl/curl.h>
+#include "transfer.h"
+#include "sendf.h"
+#include "tftp.h"
+#include "progress.h"
+
+#define _MPRINTF_REPLACE /* use our functions only */
+#include <curl/mprintf.h>
+
+#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(&current);
+      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

+ 31 - 0
lib/tftp.h

@@ -0,0 +1,31 @@
+#ifndef __TFTP_H
+#define __TFTP_H
+
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2005, 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$
+ ***************************************************************************/
+#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

+ 38 - 2
lib/url.c

@@ -77,9 +77,7 @@
 #error "We can't compile without socket() support!"
 #endif
 
-
 #endif
-
 #ifdef USE_LIBIDN
 #include <idna.h>
 #include <tld.h>
@@ -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=<typecode>" 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 {

+ 3 - 0
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;

+ 3 - 0
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