소스 검색

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 19 년 전
부모
커밋
56d9624b56
13개의 변경된 파일863개의 추가작업 그리고 8개의 파일을 삭제
  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