Skip to content
ftp.c 57.8 KiB
Newer Older
Daniel Stenberg's avatar
Daniel Stenberg committed
/*****************************************************************************
 *                                  _   _ ____  _     
 *  Project                     ___| | | |  _ \| |    
 *                             / __| | | | |_) | |    
 *                            | (__| |_| |  _ <| |___ 
 *                             \___|\___/|_| \_\_____|
 *
 * Copyright (C) 2001, Daniel Stenberg, <daniel@haxx.se>, et al.
Daniel Stenberg's avatar
Daniel Stenberg committed
 *
Daniel Stenberg's avatar
Daniel Stenberg committed
 * In order to be useful for every potential user, curl and libcurl are
 * dual-licensed under the MPL and the MIT/X-derivate licenses.
Daniel Stenberg's avatar
Daniel Stenberg committed
 *
Daniel Stenberg's avatar
Daniel Stenberg committed
 * 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 MPL or the MIT/X-derivate
 * licenses. You may pick one of these licenses.
Daniel Stenberg's avatar
Daniel Stenberg committed
 *
Daniel Stenberg's avatar
Daniel Stenberg committed
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
Daniel Stenberg's avatar
Daniel Stenberg committed
 *
Daniel Stenberg's avatar
Daniel Stenberg committed
 * $Id$
 *****************************************************************************/
Daniel Stenberg's avatar
Daniel Stenberg committed

#include "setup.h"

Daniel Stenberg's avatar
Daniel Stenberg committed
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
Daniel Stenberg's avatar
Daniel Stenberg committed
#include <ctype.h>
#include <errno.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#if defined(WIN32) && !defined(__GNUC__) || defined(__MINGW32__)
#include <winsock.h>
#else /* some kind of unix */
Daniel Stenberg's avatar
Daniel Stenberg committed
#ifdef HAVE_SYS_SOCKET_H
Daniel Stenberg's avatar
Daniel Stenberg committed
#include <sys/socket.h>
Daniel Stenberg's avatar
Daniel Stenberg committed
#endif
#include <sys/types.h>
#ifdef HAVE_NETINET_IN_H
Daniel Stenberg's avatar
Daniel Stenberg committed
#include <netinet/in.h>
#endif
Daniel Stenberg's avatar
Daniel Stenberg committed
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#include <sys/utsname.h>
#ifdef HAVE_NETDB_H
Daniel Stenberg's avatar
Daniel Stenberg committed
#include <netdb.h>
#endif
#endif
Daniel Stenberg's avatar
Daniel Stenberg committed

#if defined(WIN32) && defined(__GNUC__) || defined(__MINGW32__)
#include <errno.h>
#endif

#include <curl/curl.h>
#include "urldata.h"
#include "sendf.h"

#include "if2ip.h"
#include "hostip.h"
#include "progress.h"
Daniel Stenberg's avatar
Daniel Stenberg committed
#include "escape.h"
#include "http.h" /* for HTTP proxy tunnel stuff */
Daniel Stenberg's avatar
Daniel Stenberg committed

#ifdef KRB4
#include "security.h"
#include "strequal.h"
Daniel Stenberg's avatar
Daniel Stenberg committed
#include "ssluse.h"
#include "connect.h"
Daniel Stenberg's avatar
Daniel Stenberg committed
#define _MPRINTF_REPLACE /* use our functions only */
#include <curl/mprintf.h>

/* The last #include file should be: */
#ifdef MALLOCDEBUG
#include "memdebug.h"
#endif
Sterling Hughes's avatar
Sterling Hughes committed
/* Local API functions */
static CURLcode ftp_sendquote(struct connectdata *conn, struct curl_slist *quote);
static CURLcode ftp_cwd(struct connectdata *conn, char *path);
#define FTPSENDF(x,y,z) if((result = Curl_ftpsendf(x,y,z))) return result
/***********************************************************************
 *
 * AllowServerConnect()
 *
 * When we've issue the PORT command, we have told the server to connect
 * to us. This function will sit and wait here until the server has
 * connected.
 *
 */
static CURLcode AllowServerConnect(struct SessionHandle *data,
                                   struct connectdata *conn,
Daniel Stenberg's avatar
Daniel Stenberg committed
                                   int sock)
{
  fd_set rdset;
  struct timeval dt;
  
  FD_ZERO(&rdset);

  FD_SET(sock, &rdset);

  /* we give the server 10 seconds to connect to us */
  dt.tv_sec = 10;
  dt.tv_usec = 0;

Sterling Hughes's avatar
Sterling Hughes committed
  switch (select(sock+1, &rdset, NULL, NULL, &dt)) {
Daniel Stenberg's avatar
Daniel Stenberg committed
  case -1: /* error */
    /* let's die here */
    failf(data, "Error while waiting for server connect");
    return CURLE_FTP_PORT_FAILED;
Daniel Stenberg's avatar
Daniel Stenberg committed
  case 0:  /* timeout */
    /* let's die here */
    failf(data, "Timeout while waiting for server connect");
    return CURLE_FTP_PORT_FAILED;
Daniel Stenberg's avatar
Daniel Stenberg committed
  default:
    /* we have received data here */
    {
      int s;
      size_t size = sizeof(struct sockaddr_in);
      struct sockaddr_in add;

      getsockname(sock, (struct sockaddr *) &add, (socklen_t *)&size);
      s=accept(sock, (struct sockaddr *) &add, (socklen_t *)&size);
Daniel Stenberg's avatar
Daniel Stenberg committed

      sclose(sock); /* close the first socket */

      if (-1 == s) {
        /* DIE! */
        failf(data, "Error accept()ing server connect");
      	return CURLE_FTP_PORT_FAILED;
Daniel Stenberg's avatar
Daniel Stenberg committed
      }
      infof(data, "Connection accepted from server\n");

      conn->secondarysocket = s;
Daniel Stenberg's avatar
Daniel Stenberg committed
    }
    break;
  }
  return CURLE_OK;
Daniel Stenberg's avatar
Daniel Stenberg committed
}


/* --- parse FTP server responses --- */

/*
 * Curl_GetFTPResponse() is supposed to be invoked after each command sent to
 * a remote FTP server. This function will wait and read all lines of the
 * response and extract the relevant return code for the invoking function.
 */
                        struct connectdata *conn,
                        int *ftpcode)
{
  /* Brand new implementation.
   * We cannot read just one byte per read() and then go back to select()
   * as it seems that the OpenSSL read() stuff doesn't grok that properly.
   *
   * Alas, read as much as possible, split up into lines, use the ending
   * line in a response or continue reading.  */
  int nread;   /* total size read */
  int perline; /* count bytes per line */
  bool keepon=TRUE;
  ssize_t gotbytes;
  char *ptr;
  int timeout = 3600; /* default timeout in seconds */
  struct timeval interval;
  fd_set rkeepfd;
  fd_set readfd;
  struct SessionHandle *data = conn->data;
  int code=0; /* default "error code" to return */

#define SELECT_OK      0
#define SELECT_ERROR   1
#define SELECT_TIMEOUT 2
  int error = SELECT_OK;

  if (ftpcode)
    *ftpcode = 0; /* 0 for errors */
    /* if timeout is requested, find out how much remaining time we have */
    timeout = data->set.timeout - /* timeout time */
      Curl_tvdiff(Curl_tvnow(), conn->now)/1000; /* spent time */
    if(timeout <=0 ) {
      failf(data, "Transfer aborted due to timeout");
      return -SELECT_TIMEOUT; /* already too little time */
    }
  }

  FD_ZERO (&readfd);		/* clear it */
  FD_SET (sockfd, &readfd);     /* read socket */

  /* get this in a backup variable to be able to restore it on each lap in the
     select() loop */
  rkeepfd = readfd;

  ptr=buf;
  line_start = buf;

  nread=0;
  perline=0;
  keepon=TRUE;

  while((nread<BUFSIZE) && (keepon && !error)) {
    readfd = rkeepfd;		   /* set every lap */
    interval.tv_sec = timeout;
    interval.tv_usec = 0;

    switch (select (sockfd+1, &readfd, NULL, NULL, &interval)) {
    case -1: /* select() error, stop reading */
      error = SELECT_ERROR;
      failf(data, "Transfer aborted due to select() error");
      break;
    case 0: /* timeout */
      error = SELECT_TIMEOUT;
      failf(data, "Transfer aborted due to timeout");
      break;
    default:
      /*
       * This code previously didn't use the kerberos sec_read() code
       * to read, but when we use Curl_read() it may do so. Do confirm
       * that this is still ok and then remove this comment!
       */
      if(CURLE_OK != Curl_read(conn, sockfd, ptr, BUFSIZE-nread, &gotbytes))
        keepon = FALSE;
      else if(gotbytes <= 0) {
        keepon = FALSE;
        error = SELECT_ERROR;
        failf(data, "Connection aborted");
      }
      else {
        /* we got a whole chunk of data, which can be anything from one
         * byte to a set of lines and possible just a piece of the last
         * line */
        int i;

        nread += gotbytes;
        for(i = 0; i < gotbytes; ptr++, i++) {
          perline++;
          if(*ptr=='\n') {
            /* a newline is CRLF in ftp-talk, so the CR is ignored as
               the line isn't really terminated until the LF comes */

            /* output debug output if that is requested */
            if(data->set.verbose) {
              fputs("< ", data->set.err);
              fwrite(line_start, perline, 1, data->set.err);
              /* no need to output LF here, it is part of the data */
            }

#define lastline(line) (isdigit((int)line[0]) && isdigit((int)line[1]) && \
			isdigit((int)line[2]) && (' ' == line[3]))

            if(perline>3 && lastline(line_start)) {
              /* This is the end of the last line, copy the last
               * line to the start of the buffer and zero terminate,
               * for old times sake (and krb4)! */
Sterling Hughes's avatar
Sterling Hughes committed
              char *meow;
Sterling Hughes's avatar
Sterling Hughes committed
              for(meow=line_start, i=0; meow<ptr; meow++, i++)
                buf[i] = *meow;
Daniel Stenberg's avatar
Daniel Stenberg committed
              *meow=0; /* zero terminate */
              keepon=FALSE;
              break;
            }
            perline=0; /* line starts over here */
            line_start = ptr+1;
          }
        }
      }
      break;
    } /* switch */
  } /* while there's buffer left and loop is requested */

  if(!error)
    code = atoi(buf);

#if KRB4
  /* handle the security-oriented responses 6xx ***/
  /* FIXME: some errorchecking perhaps... ***/
  switch(code) {
  case 631:
    Curl_sec_read_msg(conn, buf, prot_safe);
    Curl_sec_read_msg(conn, buf, prot_private);
    Curl_sec_read_msg(conn, buf, prot_confidential);
    break;
  default:
    /* normal ftp stuff we pass through! */
    break;
  }
#endif

  if(error)
    return -error;

  if(ftpcode)
    *ftpcode=code; /* return the initial number like this */

  return nread; /* total amount of bytes read */
}

#ifndef ENABLE_IPV6
/*
 * This function is only used by code that works on IPv4. When we add proper
 * support for that functionality with IPv6, this function can go in again.
 */
Daniel Stenberg's avatar
Daniel Stenberg committed
/* -- who are we? -- */
static char *getmyhost(char *buf, int buf_size)
Daniel Stenberg's avatar
Daniel Stenberg committed
{
Daniel Stenberg's avatar
Daniel Stenberg committed
#if defined(HAVE_GETHOSTNAME)
  gethostname(buf, buf_size);
#elif defined(HAVE_UNAME)
Daniel Stenberg's avatar
Daniel Stenberg committed
  struct utsname ugnm;
Daniel Stenberg's avatar
Daniel Stenberg committed
  strncpy(buf, uname(&ugnm) < 0 ? "localhost" : ugnm.nodename, buf_size - 1);
  buf[buf_size - 1] = '\0';
#else
  /* We have no means of finding the local host name! */
Daniel Stenberg's avatar
Daniel Stenberg committed
  strncpy(buf, "localhost", buf_size);
  buf[buf_size - 1] = '\0';
Daniel Stenberg's avatar
Daniel Stenberg committed
#endif
Daniel Stenberg's avatar
Daniel Stenberg committed
  return buf;
/* ftp_connect() should do everything that is to be considered a part
   of the connection phase. */
CURLcode Curl_ftp_connect(struct connectdata *conn)
Daniel Stenberg's avatar
Daniel Stenberg committed
{
  /* this is FTP and no proxy */
  struct SessionHandle *data=conn->data;
  char *buf = data->state.buffer; /* this is our buffer */
  struct FTP *ftp;
Daniel Stenberg's avatar
Daniel Stenberg committed

  ftp = (struct FTP *)malloc(sizeof(struct FTP));
  if(!ftp)
    return CURLE_OUT_OF_MEMORY;

  memset(ftp, 0, sizeof(struct FTP));
  conn->proto.ftp = ftp;
  /* We always support persistant connections on ftp */
  conn->bits.close = FALSE;

  /* get some initial data into the ftp struct */
  ftp->bytecountp = &conn->bytecount;
  /* no need to duplicate them, the data struct won't change */
  ftp->user = data->state.user;
  ftp->passwd = data->state.passwd;
Daniel Stenberg's avatar
Daniel Stenberg committed

  if (data->set.tunnel_thru_httpproxy) {
    /* We want "seamless" FTP operations through HTTP proxy tunnel */
    result = Curl_ConnectHTTPProxyTunnel(conn, conn->firstsocket,
                                         conn->hostname, conn->remote_port);
    if(CURLE_OK != result)
      return result;
  }

Daniel Stenberg's avatar
Daniel Stenberg committed
  if(conn->protocol & PROT_FTPS) {
    /* FTPS is simply ftp with SSL for the control channel */
    /* now, perform the SSL initialization for this socket */
    result = Curl_SSLConnect(conn);
    if(result)
      return result;
Daniel Stenberg's avatar
Daniel Stenberg committed
  /* The first thing we do is wait for the "220*" line: */
  nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
  if(nread < 0)
    return CURLE_OPERATION_TIMEOUTED;
Daniel Stenberg's avatar
Daniel Stenberg committed
    failf(data, "This doesn't seem like a nice ftp-server response");
    return CURLE_FTP_WEIRD_SERVER_REPLY;
#ifdef KRB4
  /* if not anonymous login, try a secure login */

    /* request data protection level (default is 'clear') */
    Curl_sec_request_prot(conn, "private");

    /* We set private first as default, in case the line below fails to
       set a valid level */
    Curl_sec_request_prot(conn, data->set.krb4_level);
      infof(data, "Logging in with password in cleartext!\n");
    else
      infof(data, "Authentication successful\n");
  }
#endif
  
Daniel Stenberg's avatar
Daniel Stenberg committed
  /* send USER */
Daniel Stenberg's avatar
Daniel Stenberg committed

  /* wait for feedback */
  nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
  if(nread < 0)
    return CURLE_OPERATION_TIMEOUTED;
Daniel Stenberg's avatar
Daniel Stenberg committed

Daniel Stenberg's avatar
Daniel Stenberg committed
    /* 530 User ... access denied
       (the server denies to log the specified user) */
    failf(data, "Access denied: %s", &buf[4]);
    return CURLE_FTP_ACCESS_DENIED;
Daniel Stenberg's avatar
Daniel Stenberg committed
  }
  else if(ftpcode == 331) {
Daniel Stenberg's avatar
Daniel Stenberg committed
    /* 331 Password required for ...
       (the server requires to send the user's password too) */
    FTPSENDF(conn, "PASS %s", ftp->passwd);
    nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
    if(nread < 0)
      return CURLE_OPERATION_TIMEOUTED;
Daniel Stenberg's avatar
Daniel Stenberg committed

Daniel Stenberg's avatar
Daniel Stenberg committed
      /* 530 Login incorrect.
         (the username and/or the password are incorrect) */
      failf(data, "the username and/or the password are incorrect");
      return CURLE_FTP_USER_PASSWORD_INCORRECT;
Daniel Stenberg's avatar
Daniel Stenberg committed
    }
    else if(ftpcode == 230) {
Daniel Stenberg's avatar
Daniel Stenberg committed
      /* 230 User ... logged in.
         (user successfully logged in) */
        
      infof(data, "We have successfully logged in\n");
    }
    else {
      failf(data, "Odd return code after PASS");
      return CURLE_FTP_WEIRD_PASS_REPLY;
Daniel Stenberg's avatar
Daniel Stenberg committed
    }
  }
Daniel Stenberg's avatar
Daniel Stenberg committed
    /* 230 User ... logged in.
       (the user logged in without password) */
    infof(data, "We have successfully logged in\n");
#ifdef KRB4
	/* we are logged in (with Kerberos)
	 * now set the requested protection level
	 */
    if(conn->sec_complete)
      Curl_sec_set_protection_level(conn);

    /* we may need to issue a KAUTH here to have access to the files
     * do it if user supplied a password
     */
Daniel Stenberg's avatar
Daniel Stenberg committed
    if(data->state.passwd && *data->state.passwd)
Daniel Stenberg's avatar
Daniel Stenberg committed
  }
  else {
    failf(data, "Odd return code after USER");
    return CURLE_FTP_WEIRD_USER_REPLY;
  }

  /* send PWD to discover our entry point */

  /* wait for feedback */
  nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
  if(nread < 0)
    return CURLE_OPERATION_TIMEOUTED;

  if(ftpcode == 257) {
    char *dir = (char *)malloc(nread+1);
    char *store=dir;
    char *ptr=&buf[4]; /* start on the first letter */
    
    /* Reply format is like
       257<space>"<directory-name>"<space><commentary> and the RFC959 says

       The directory name can contain any character; embedded double-quotes
       should be escaped by double-quotes (the "quote-doubling" convention).
    */
    if('\"' == *ptr) {
      /* it started good */
      ptr++;
      while(ptr && *ptr) {
        if('\"' == *ptr) {
          if('\"' == ptr[1]) {
            /* "quote-doubling" */
            *store = ptr[1];
            ptr++;
          }
          else {
            /* end of path */
            *store = '\0'; /* zero terminate */
            break; /* get out of this loop */
          }
        }
        else
          *store = *ptr;
        store++;
        ptr++;
      }
      ftp->entrypath =dir; /* remember this */
      infof(data, "Entry path is '%s'\n", ftp->entrypath);
    }
    else {
      /* couldn't get the path */
    }

  }
  else {
    /* We couldn't read the PWD response! */
  }

/***********************************************************************
 *
 * Curl_ftp_done()
 *
 * The DONE function. This does what needs to be done after a single DO has
 * performed.
 *
 * Input argument is already checked for validity.
 */
CURLcode Curl_ftp_done(struct connectdata *conn)
  struct SessionHandle *data = conn->data;
  struct FTP *ftp = conn->proto.ftp;
  char *buf = data->state.buffer; /* this is our buffer */
Daniel Stenberg's avatar
Daniel Stenberg committed
  CURLcode result=CURLE_OK;
  if(data->set.upload) {
    if((-1 != data->set.infilesize) && (data->set.infilesize != *ftp->bytecountp)) {
      failf(data, "Wrote only partial file (%d out of %d bytes)",
            *ftp->bytecountp, data->set.infilesize);
      return CURLE_PARTIAL_FILE;
    }
  }
  else {
    if((-1 != conn->size) && (conn->size != *ftp->bytecountp) &&
       (conn->maxdownload != *ftp->bytecountp)) {
      failf(data, "Received only partial file: %d bytes", *ftp->bytecountp);
      return CURLE_PARTIAL_FILE;
    }
      /* We consider this an error, but there's no true FTP error received
         why we need to continue to "read out" the server response too.
         We don't want to leave a "waiting" server reply if we'll get told
         to make a second request on this same connection! */
      failf(data, "No data was received!");
  Curl_sec_fflush_fd(conn, conn->secondarysocket);
  /* shut down the socket to inform the server we're done */
  sclose(conn->secondarysocket);
  conn->secondarysocket = -1;
  if(!data->set.no_body && !conn->bits.resume_done) {  
    /* now let's see what the server says about the transfer we
       just performed: */
    nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
    if(nread < 0)
      return CURLE_OPERATION_TIMEOUTED;

    /* 226 Transfer complete, 250 Requested file action okay, completed. */
    if((ftpcode != 226) && (ftpcode != 250)) {
      failf(data, "server did not report OK, got %d", ftpcode);
      return CURLE_FTP_WRITE_ERROR;
    }
  conn->bits.resume_done = FALSE; /* clean this for next connection */

  /* Send any post-transfer QUOTE strings? */
  if(!result && data->set.postquote)
    result = ftp_sendquote(conn, data->set.postquote);
Daniel Stenberg's avatar
Daniel Stenberg committed

/***********************************************************************
 *
 * ftp_sendquote()
 *
 * Where a 'quote' means a list of custom commands to send to the server.
 * The quote list is passed as an argument.
 */
Sterling Hughes's avatar
Sterling Hughes committed
static 
CURLcode ftp_sendquote(struct connectdata *conn, struct curl_slist *quote)
{
  struct curl_slist *item;

  item = quote;
  while (item) {
    if (item->data) {
      nread = Curl_GetFTPResponse(conn->data->state.buffer, conn, &ftpcode);
      if (nread < 0)
        return CURLE_OPERATION_TIMEOUTED;

      if (ftpcode >= 400) {
        failf(conn->data, "QUOT string not accepted: %s", item->data);
        return CURLE_FTP_QUOTE_ERROR;
      }
    }

/***********************************************************************
 *
 * ftp_cwd()
 *
 * Send 'CWD' to the remote server to Change Working Directory.
 * It is the ftp version of the unix 'cd' command.
 */
Sterling Hughes's avatar
Sterling Hughes committed
static 
CURLcode ftp_cwd(struct connectdata *conn, char *path)
Sterling Hughes's avatar
Sterling Hughes committed
{
  ssize_t nread;
  int     ftpcode;
                              conn->data->state.buffer, conn, &ftpcode);
Sterling Hughes's avatar
Sterling Hughes committed
  if (nread < 0)
    return CURLE_OPERATION_TIMEOUTED;

  if (ftpcode != 250) {
    failf(conn->data, "Couldn't cd to %s", path);
Sterling Hughes's avatar
Sterling Hughes committed
    return CURLE_FTP_ACCESS_DENIED;
  }
Sterling Hughes's avatar
Sterling Hughes committed

  return CURLE_OK;
/***********************************************************************
 *
 * ftp_getfiletime()
 *
 * Get the timestamp of the given file.
 */
CURLcode ftp_getfiletime(struct connectdata *conn, char *file)
{
  CURLcode result=CURLE_OK;
  int ftpcode; /* for ftp status */
  ssize_t nread;
  char *buf = conn->data->state.buffer;

  /* we have requested to get the modified-time of the file, this is yet
     again a grey area as the MDTM is not kosher RFC959 */
  nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
  if(nread < 0)
    return CURLE_OPERATION_TIMEOUTED;

  if(ftpcode == 213) {
    /* we got a time. Format should be: "YYYYMMDDHHMMSS[.sss]" where the
       last .sss part is optional and means fractions of a second */
    int year, month, day, hour, minute, second;
    if(6 == sscanf(buf+4, "%04d%02d%02d%02d%02d%02d",
                   &year, &month, &day, &hour, &minute, &second)) {
      /* we have a time, reformat it */
      time_t secs=time(NULL);
      sprintf(buf, "%04d%02d%02d %02d:%02d:%02d",
              year, month, day, hour, minute, second);
      /* now, convert this into a time() value: */
      conn->data->info.filetime = curl_getdate(buf, &secs);
    }
    else {
      infof(conn->data, "unsupported MDTM reply format\n");
    }
  }
  return  result;
}

/***********************************************************************
 *
 * ftp_transfertype()
 *
 * Set transfer type. We only deal with ASCII or BINARY so this function
 * sets one of them.
 */
static CURLcode ftp_transfertype(struct connectdata *conn,
  struct SessionHandle *data = conn->data;
  FTPSENDF(conn, "TYPE %s", ascii?"A":"I");
  nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
  if(nread < 0)
    return CURLE_OPERATION_TIMEOUTED;
  
  if(ftpcode != 200) {
    failf(data, "Couldn't set %s mode",
          ascii?"ASCII":"binary");
    return ascii? CURLE_FTP_COULDNT_SET_ASCII:CURLE_FTP_COULDNT_SET_BINARY;
  }

  return CURLE_OK;
}

/***********************************************************************
 *
 * ftp_getsize()
 *
 * Returns the file size (in bytes) of the given remote file.
 */

CURLcode ftp_getsize(struct connectdata *conn, char *file,
  struct SessionHandle *data = conn->data;
  nread = Curl_GetFTPResponse(buf, conn, &ftpcode);
  if(nread < 0)
    return CURLE_OPERATION_TIMEOUTED;

  if(ftpcode == 213) {
    /* get the size from the ascii string: */
    *size = atoi(buf+4);
  }
  else
    return CURLE_FTP_COULDNT_GET_SIZE;

  return CURLE_OK;
}

/***************************************************************************
 *
 * ftp_pasv_verbose()
 *
 * This function only outputs some informationals about this second connection
 * when we've issued a PASV command before and thus we have connected to a
 * possibly new IP address.
 *
 */
static void
ftp_pasv_verbose(struct connectdata *conn,
                 Curl_ipconnect *addr,
                 char *newhost, /* ascii version */
                 int port)
{
#ifndef ENABLE_IPV6
  /*****************************************************************
   *
   * IPv4-only code section
   */

  struct in_addr in;
  struct hostent * answer;

# if defined(HAVE_GETHOSTBYADDR_R)
  int h_errnop;
# endif

  address = inet_addr(newhost);
# ifdef HAVE_GETHOSTBYADDR_R

#  ifdef HAVE_GETHOSTBYADDR_R_5
  /* AIX, Digital Unix (OSF1, Tru64) style:
     extern int gethostbyaddr_r(char *addr, size_t len, int type,
     struct hostent *htent, struct hostent_data *ht_data); */

  /* Fred Noz helped me try this out, now it at least compiles! */

  /* Bjorn Reese (November 28 2001):
     The Tru64 man page on gethostbyaddr_r() says that
     the hostent struct must be filled with zeroes before the call to
     gethostbyaddr_r(). */

  memset(hostent_buf, 0, sizeof(struct hostent));

  if(gethostbyaddr_r((char *) &address,
                     sizeof(address), AF_INET,
                     (struct hostent *)hostent_buf,
                     hostent_buf + sizeof(*answer)))
    answer=NULL;
                           
#  endif
#  ifdef HAVE_GETHOSTBYADDR_R_7
  /* Solaris and IRIX */
  answer = gethostbyaddr_r((char *) &address, sizeof(address), AF_INET,
                           (struct hostent *)hostent_buf,
                           hostent_buf + sizeof(*answer),
                           sizeof(hostent_buf) - sizeof(*answer),
                           &h_errnop);
#  endif
#  ifdef HAVE_GETHOSTBYADDR_R_8
  /* Linux style */
  if(gethostbyaddr_r((char *) &address, sizeof(address), AF_INET,
                     (struct hostent *)hostent_buf,
                     hostent_buf + sizeof(*answer),
                     sizeof(hostent_buf) - sizeof(*answer),
                     &answer,
                     &h_errnop))
    answer=NULL; /* error */
#  endif
        
# else
  answer = gethostbyaddr((char *) &address, sizeof(address), AF_INET);
# endif
#else
  answer = NULL;
#endif
  (void) memcpy(&in.s_addr, addr, sizeof (Curl_ipconnect));
  infof(conn->data, "Connecting to %s (%s) port %u\n",
        answer?answer->h_name:newhost,
#if defined(HAVE_INET_NTOA_R)
        inet_ntoa_r(in, ntoa_buf, sizeof(ntoa_buf)),

#else
  /*****************************************************************
   *
   * IPv6-only code section
   */
  char hbuf[NI_MAXHOST]; /* ~1KB */
  char nbuf[NI_MAXHOST]; /* ~1KB */
  char sbuf[NI_MAXSERV]; /* around 32 */
#ifdef NI_WITHSCOPEID
  const int niflags = NI_NUMERICHOST | NI_NUMERICSERV | NI_WITHSCOPEID;
#else
  const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
#endif
  port = 0; /* unused, prevent warning */
  if (getnameinfo(addr->ai_addr, addr->ai_addrlen,
                  nbuf, sizeof(nbuf), sbuf, sizeof(sbuf), niflags)) {
    snprintf(nbuf, sizeof(nbuf), "?");
    snprintf(sbuf, sizeof(sbuf), "?");
  }
        
  if (getnameinfo(addr->ai_addr, addr->ai_addrlen,
                  hbuf, sizeof(hbuf), NULL, 0, 0)) {
    infof(conn->data, "Connecting to %s (%s) port %s\n", nbuf, newhost, sbuf);
  }
  else {
    infof(conn->data, "Connecting to %s (%s) port %s\n", hbuf, nbuf, sbuf);
  }
#endif
}

/***********************************************************************
 *
 * ftp_use_port()
 *
 * Send the proper PORT command. PORT is the ftp client's way of telling the
 * server that *WE* open a port that we listen on an awaits the server to
 * connect to. This is the opposite of PASV.
CURLcode ftp_use_port(struct connectdata *conn)
  struct SessionHandle *data=conn->data;
  int portsock=-1;
  ssize_t nread;
  char *buf = data->state.buffer; /* this is our buffer */
  int ftpcode; /* receive FTP response codes in this */
Daniel Stenberg's avatar
Daniel Stenberg committed

#ifdef ENABLE_IPV6
  /******************************************************************
   *
   * Here's a piece of IPv6-specific code coming up
   *
   */
  struct addrinfo hints, *res, *ai;
  struct sockaddr_storage ss;
  socklen_t sslen;
  char hbuf[NI_MAXHOST];

  struct sockaddr *sa=(struct sockaddr *)&ss;
#ifdef NI_WITHSCOPEID
  const int niflags = NI_NUMERICHOST | NI_NUMERICSERV | NI_WITHSCOPEID;
  const int niflags = NI_NUMERICHOST | NI_NUMERICSERV;
  unsigned char *ap;
  unsigned char *pp;
  int alen, plen;
  char portmsgbuf[4096], tmp[4096];
  const char *mode[] = { "EPRT", "LPRT", "PORT", NULL };
  char **modep;
  /*
   * we should use Curl_if2ip?  given pickiness of recent ftpd,
   * I believe we should use the same address as the control connection.
   */
  sslen = sizeof(ss);
  if (getsockname(conn->firstsocket, (struct sockaddr *)&ss, &sslen) < 0)
    return CURLE_FTP_PORT_FAILED;
  
  if (getnameinfo((struct sockaddr *)&ss, sslen, hbuf, sizeof(hbuf), NULL, 0,
                  niflags))
    return CURLE_FTP_PORT_FAILED;
  memset(&hints, 0, sizeof(hints));
  hints.ai_family = sa->sa_family;
  /*hints.ai_family = ss.ss_family;
    this way can be used if sockaddr_storage is properly defined, as glibc 
    2.1.X doesn't do*/
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_flags = AI_PASSIVE;
Daniel Stenberg's avatar
Daniel Stenberg committed

  if (getaddrinfo(hbuf, (char *)"0", &hints, &res))
    return CURLE_FTP_PORT_FAILED;
  
  portsock = -1;
  for (ai = res; ai; ai = ai->ai_next) {
    portsock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
    if (portsock < 0)
      continue;
    if (bind(portsock, ai->ai_addr, ai->ai_addrlen) < 0) {
      sclose(portsock);
      portsock = -1;
      continue;
      
    if (listen(portsock, 1) < 0) {
      sclose(portsock);
      portsock = -1;
      continue;
Daniel Stenberg's avatar
Daniel Stenberg committed
  freeaddrinfo(res);