Skip to content
ftp.c 41 KiB
Newer Older
Daniel Stenberg's avatar
Daniel Stenberg committed
/*****************************************************************************
 *                                  _   _ ____  _     
 *  Project                     ___| | | |  _ \| |    
 *                             / __| | | | |_) | |    
 *                            | (__| |_| |  _ <| |___ 
 *                             \___|\___/|_| \_\_____|
 *
 *  The contents of this file are subject to the Mozilla Public License
 *  Version 1.0 (the "License"); you may not use this file except in
 *  compliance with the License. You may obtain a copy of the License at
 *  http://www.mozilla.org/MPL/
 *
 *  Software distributed under the License is distributed on an "AS IS"
 *  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 *  License for the specific language governing rights and limitations
 *  under the License.
 *
 *  The Original Code is Curl.
 *
 *  The Initial Developer of the Original Code is Daniel Stenberg.
 *
 *  Portions created by the Initial Developer are Copyright (C) 1998.
 *  All Rights Reserved.
 *
 * ------------------------------------------------------------
 * Main author:
Daniel Stenberg's avatar
Daniel Stenberg committed
 * - Daniel Stenberg <daniel@haxx.se>
Daniel Stenberg's avatar
Daniel Stenberg committed
 *
Daniel Stenberg's avatar
Daniel Stenberg committed
 * 	http://curl.haxx.se
Daniel Stenberg's avatar
Daniel Stenberg committed
 *
 * $Source$
 * $Revision$
 * $Date$
 * $Author$
 * $State$
 * $Locker$
 *
 * ------------------------------------------------------------
 ****************************************************************************/

#include "setup.h"

Daniel Stenberg's avatar
Daniel Stenberg committed
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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"
#include "download.h"
Daniel Stenberg's avatar
Daniel Stenberg committed
#include "escape.h"
Daniel Stenberg's avatar
Daniel Stenberg committed

#ifdef KRB4
#include "security.h"
#endif
/* The last #include file should be: */
#ifdef MALLOCDEBUG
#include "memdebug.h"
#endif
Daniel Stenberg's avatar
Daniel Stenberg committed
/* returns last node in linked list */
static struct curl_slist *slist_get_last(struct curl_slist *list)
{
	struct curl_slist	*item;

	/* if caller passed us a NULL, return now */
	if (!list)
		return NULL;

	/* loop through to find the last item */
	item = list;
	while (item->next) {
		item = item->next;
	}
	return item;
}

/* append a struct to the linked list. It always retunrs the address of the
 * first record, so that you can sure this function as an initialization
 * function as well as an append function. If you find this bothersome,
 * then simply create a separate _init function and call it appropriately from
 * within the proram. */
struct curl_slist *curl_slist_append(struct curl_slist *list, char *data)
{
	struct curl_slist	*last;
	struct curl_slist	*new_item;

	new_item = (struct curl_slist *) malloc(sizeof(struct curl_slist));
	if (new_item) {
		new_item->next = NULL;
		new_item->data = strdup(data);
	}
	else {
		fprintf(stderr, "Cannot allocate memory for QUOTE list.\n");
		exit(-1);
	}

	if (list) {
		last = slist_get_last(list);
		last->next = new_item;
		return list;
	}

	/* if this is the first item, then new_item *is* the list */
	return new_item;
}

/* be nice and clean up resources */
void curl_slist_free_all(struct curl_slist *list)
{
	struct curl_slist	*next;
	struct curl_slist	*item;

	if (!list)
		return;

	item = list;
	do {
		next = item->next;
		
		if (item->data) {
			free(item->data);
		}
		free(item);
		item = next;
	} while (next);
}


static CURLcode AllowServerConnect(struct UrlData *data,
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;

  switch ( select(sock+1, &rdset, NULL, NULL, &dt)) {
  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, (int *)&size);
      s=accept(sock, (struct sockaddr *) &add, (int *)&size);

      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");

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


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

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

int GetLastResponse(int sockfd, char *buf,
Daniel Stenberg's avatar
Daniel Stenberg committed
{
  int nread;
Daniel Stenberg's avatar
Daniel Stenberg committed
  char *ptr;
  int timeout = 3600; /* in seconds */
  struct timeval interval;
  fd_set rkeepfd;
  fd_set readfd;
  struct UrlData *data = conn->data;

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

  if(data->timeout) {
    /* if timeout is requested, find out how much remaining time we have */
    timeout = data->timeout - /* timeout time */
      (tvlong(tvnow()) - tvlong(conn->now)); /* 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;

Daniel Stenberg's avatar
Daniel Stenberg committed
  do {
    ptr=buf;

    /* get us a full line, terminated with a newline */
    nread=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;
        infof(data, "Transfer aborted due to timeout\n");
        failf(data, "Transfer aborted due to timeout");
        break;
      default:
Daniel Stenberg's avatar
Daniel Stenberg committed
#ifdef USE_SSLEAY
        if (data->ssl.use) {
          keepon = SSL_read(data->ssl.handle, ptr, 1);
Daniel Stenberg's avatar
Daniel Stenberg committed
#endif
Daniel Stenberg's avatar
Daniel Stenberg committed
#ifdef USE_SSLEAY
Daniel Stenberg's avatar
Daniel Stenberg committed
#endif /* USE_SSLEAY */

        if ((*ptr == '\n') || (*ptr == '\r'))
          keepon = FALSE;
      }
      if(keepon) {
        nread++;
        ptr++;
      }
Daniel Stenberg's avatar
Daniel Stenberg committed
    }
    *ptr=0; /* zero terminate */

#if KRB4
    { /* handle the security-oriented responses 6xx ***/
      /* FIXME: some errorchecking perhaps... ***/
      if(strncmp(buf, "631", 3) == 0)
        sec_read_msg(conn, buf, prot_safe);
      else if(strncmp(buf, "632", 3) == 0)
        sec_read_msg(conn, buf, prot_private);
      else if(strncmp(buf, "633", 3) == 0)
        sec_read_msg(conn, buf, prot_confidential);
      nread = strlen(buf);
    }
#endif

Daniel Stenberg's avatar
Daniel Stenberg committed
      fputs("< ", data->err);
      fwrite(buf, 1, nread, data->err);
      fputs("\n", data->err);
    }
Daniel Stenberg's avatar
Daniel Stenberg committed
	  (nread<4 || !lastline(buf)) );
Daniel Stenberg's avatar
Daniel Stenberg committed
  return nread;
}

/* -- who are we? -- */
Daniel Stenberg's avatar
Daniel Stenberg committed
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;
Daniel Stenberg's avatar
Daniel Stenberg committed
}

#if 0
/*
 * URLfix()
 *
 * This function returns a string converted FROM the input URL format to a
 * format that is more likely usable for the remote server. That is, all
 * special characters (found as %XX-codes) will be eascaped with \<letter>.
 */

static char *URLfix(char *string)
{
  /* The length of the new string can't be longer than twice the original
     string, if all letters are '+'... */
  int alloc = strlen(string)*2;
  char *ns = malloc(alloc);
  unsigned char in;
  int index=0;
  int hex;
   
  while(*string) {
    in = *string;
    switch(in) {
    case '+':
      ns[index++] = '\\';
      ns[index++] = ' ';
      string++;
      continue;

    case '%':
      /* encoded part */
      if(sscanf(string+1, "%02X", &hex)) {
        ns[index++] = '\\';
        ns[index++] = hex;
        string+=3;
        continue;
      }
      /* FALLTHROUGH */
    default:
      ns[index++] = in;
      string++;
    }
  }
  ns[index]=0; /* terminate it */
  return ns;
}
#endif

/* ftp_connect() should do everything that is to be considered a part
   of the connection phase. */
CURLcode ftp_connect(struct connectdata *conn)
Daniel Stenberg's avatar
Daniel Stenberg committed
{
  /* this is FTP and no proxy */
  struct UrlData *data=conn->data;
Daniel Stenberg's avatar
Daniel Stenberg committed
  char *buf = data->buffer; /* this is our buffer */
  struct FTP *ftp;
Daniel Stenberg's avatar
Daniel Stenberg committed

  myalarm(0); /* switch off the alarm stuff */

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

  memset(ftp, 0, sizeof(struct FTP));
  data->proto.ftp = ftp;

  /* get some initial data into the ftp struct */
  ftp->bytecountp = &conn->bytecount;
  ftp->user = data->user;
  ftp->passwd = data->passwd;
Daniel Stenberg's avatar
Daniel Stenberg committed

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

Daniel Stenberg's avatar
Daniel Stenberg committed
  /* The first thing we do is wait for the "220*" line: */
  nread = GetLastResponse(data->firstsocket, buf, conn);
  if(nread < 0)
    return CURLE_OPERATION_TIMEOUTED;
Daniel Stenberg's avatar
Daniel Stenberg committed
  if(strncmp(buf, "220", 3)) {
    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 */
  if(data->bits.krb4) {

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

    /* We set private first as default, in case the line below fails to
       set a valid level */
    sec_request_prot(conn, data->krb4_level);

    data->cmdchannel = fdopen(data->firstsocket, "w");

    if(sec_login(conn) != 0)
      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 */
  ftpsendf(data->firstsocket, conn, "USER %s", ftp->user);
Daniel Stenberg's avatar
Daniel Stenberg committed

  /* wait for feedback */
  nread = GetLastResponse(data->firstsocket, buf, conn);
  if(nread < 0)
    return CURLE_OPERATION_TIMEOUTED;
Daniel Stenberg's avatar
Daniel Stenberg committed

  if(!strncmp(buf, "530", 3)) {
    /* 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(!strncmp(buf, "331", 3)) {
    /* 331 Password required for ...
       (the server requires to send the user's password too) */
    ftpsendf(data->firstsocket, conn, "PASS %s", ftp->passwd);
    nread = GetLastResponse(data->firstsocket, buf, conn);
    if(nread < 0)
      return CURLE_OPERATION_TIMEOUTED;
Daniel Stenberg's avatar
Daniel Stenberg committed

    if(!strncmp(buf, "530", 3)) {
      /* 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(!strncmp(buf, "230", 3)) {
      /* 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
    }
  }
  else if(/*! strncmp(buf, "230", 3)***/ buf[0] == '2') {
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)
      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
     */
    if(conn->data->passwd && *conn->data->passwd)
      krb_kauth(conn);
#endif
Daniel Stenberg's avatar
Daniel Stenberg committed
  }
  else {
    failf(data, "Odd return code after USER");
    return CURLE_FTP_WEIRD_USER_REPLY;
  }

  return CURLE_OK;
}


/* argument is already checked for validity */
CURLcode ftp_done(struct connectdata *conn)
{
  struct UrlData *data = conn->data;
  struct FTP *ftp = data->proto.ftp;
  size_t nread;
  char *buf = data->buffer; /* this is our buffer */
  struct curl_slist *qitem; /* QUOTE item */

  if(data->bits.upload) {
    if((-1 != data->infilesize) && (data->infilesize != *ftp->bytecountp)) {
      failf(data, "Wrote only partial file (%d out of %d bytes)",
            *ftp->bytecountp, data->infilesize);
      return CURLE_PARTIAL_FILE;
    }
  }
  else {
    if((-1 != conn->size) && (conn->size != *ftp->bytecountp) &&
       (data->maxdownload != *ftp->bytecountp)) {
      failf(data, "Received only partial file");
      return CURLE_PARTIAL_FILE;
    }
    else if(!data->bits.no_body && (0 == *ftp->bytecountp)) {
      failf(data, "No data was received!");
      return CURLE_FTP_COULDNT_RETR_FILE;
    }
  }
#ifdef KRB4
  sec_fflush_fd(conn, data->secondarysocket);
#endif
  /* shut down the socket to inform the server we're done */
  sclose(data->secondarysocket);
  data->secondarysocket = -1;

  if(!data->bits.no_body) {  
    /* now let's see what the server says about the transfer we
       just performed: */
    nread = GetLastResponse(data->firstsocket, buf, conn);
    if(nread < 0)
      return CURLE_OPERATION_TIMEOUTED;

    /* 226 Transfer complete, 250 Requested file action okay, completed. */
    if(!strncmp(buf, "226", 3) && !strncmp(buf, "250", 3)) {
      failf(data, "%s", buf+4);
      return CURLE_FTP_WRITE_ERROR;
    }
  }

  /* Send any post-transfer QUOTE strings? */
  if(data->postquote) {
    qitem = data->postquote;
    /* Send all QUOTE strings in same order as on command-line */
    while (qitem) {
      /* Send string */
      if (qitem->data) {
        ftpsendf(data->firstsocket, conn, "%s", qitem->data);
        nread = GetLastResponse(data->firstsocket, buf, conn);
        if(nread < 0)
          return CURLE_OPERATION_TIMEOUTED;

        if (buf[0] != '2') {
          failf(data, "QUOT string not accepted: %s",
                qitem->data);
          return CURLE_FTP_QUOTE_ERROR;
        }
      }
      qitem = qitem->next;
    }
  if(ftp->file)
    free(ftp->file);
  if(ftp->dir)
    free(ftp->dir);

  free(ftp);
  data->proto.ftp=NULL; /* it is gone */

  return CURLE_OK;
}



static
CURLcode _ftp(struct connectdata *conn)
{
  /* this is FTP and no proxy */
  size_t nread;
  CURLcode result;
  struct UrlData *data=conn->data;
  char *buf = data->buffer; /* this is our buffer */
  /* for the ftp PORT mode */
  int portsock=-1;
  struct sockaddr_in serv_addr;
  char hostent_buf[8192];
Daniel Stenberg's avatar
Daniel Stenberg committed
#if defined (HAVE_INET_NTOA_R)
  char ntoa_buf[64];
#endif

  struct curl_slist *qitem; /* QUOTE item */
  /* the ftp struct is already inited in ftp_connect() */
  struct FTP *ftp = data->proto.ftp;

  long *bytecountp = ftp->bytecountp;

Daniel Stenberg's avatar
Daniel Stenberg committed
  /* Send any QUOTE strings? */
  if(data->quote) {
    qitem = data->quote;
    /* Send all QUOTE strings in same order as on command-line */
    while (qitem) {
      /* Send string */
      if (qitem->data) {
        ftpsendf(data->firstsocket, conn, "%s", qitem->data);
Daniel Stenberg's avatar
Daniel Stenberg committed

        nread = GetLastResponse(data->firstsocket, buf, conn);
        if(nread < 0)
          return CURLE_OPERATION_TIMEOUTED;
Daniel Stenberg's avatar
Daniel Stenberg committed

        if (buf[0] != '2') {
          failf(data, "QUOT string not accepted: %s",
                qitem->data);
          return CURLE_FTP_QUOTE_ERROR;
Daniel Stenberg's avatar
Daniel Stenberg committed
        }
      }
      qitem = qitem->next;
    }
  }

  /* change directory first! */
  if(ftp->dir && ftp->dir[0]) {
    ftpsendf(data->firstsocket, conn, "CWD %s", ftp->dir);
    nread = GetLastResponse(data->firstsocket, buf, conn);
    if(nread < 0)
      return CURLE_OPERATION_TIMEOUTED;

    if(strncmp(buf, "250", 3)) {
      failf(data, "Couldn't change to directory %s", ftp->dir);
      return CURLE_FTP_ACCESS_DENIED;
    }
  }

Daniel Stenberg's avatar
Daniel Stenberg committed
  /* If we have selected NOBODY, it means that we only want file information.
     Which in FTP can't be much more than the file size! */
  if(data->bits.no_body) {
Daniel Stenberg's avatar
Daniel Stenberg committed
    /* The SIZE command is _not_ RFC 959 specified, and therefor many servers
       may not support it! It is however the only way we have to get a file's
       size! */
    int filesize;

    /* Some servers return different sizes for different modes, and thus we
       must set the proper type before we check the size */
    ftpsendf(data->firstsocket, conn, "TYPE %s",
             (data->bits.ftp_ascii)?"A":"I");

    nread = GetLastResponse(data->firstsocket, buf, conn);
    if(nread < 0)
      return CURLE_OPERATION_TIMEOUTED;

    if(strncmp(buf, "200", 3)) {
      failf(data, "Couldn't set %s mode",
            (data->bits.ftp_ascii)?"ASCII":"binary");
      return (data->bits.ftp_ascii)? CURLE_FTP_COULDNT_SET_ASCII:
        CURLE_FTP_COULDNT_SET_BINARY;
    }

    ftpsendf(data->firstsocket, conn, "SIZE %s", ftp->file);
Daniel Stenberg's avatar
Daniel Stenberg committed

    nread = GetLastResponse(data->firstsocket, buf, conn);
    if(nread < 0)
      return CURLE_OPERATION_TIMEOUTED;
Daniel Stenberg's avatar
Daniel Stenberg committed

    if(strncmp(buf, "213", 3)) {
      failf(data, "Couldn't get file size: %s", buf+4);
      return CURLE_FTP_COULDNT_GET_SIZE;
Daniel Stenberg's avatar
Daniel Stenberg committed
    }
    /* get the size from the ascii string: */
    filesize = atoi(buf+4);

    sprintf(buf, "Content-Length: %d\n", filesize);

    if(strlen(buf) != data->fwrite(buf, 1, strlen(buf), data->out)) {
      failf (data, "Failed writing output");
      return CURLE_WRITE_ERROR;
Daniel Stenberg's avatar
Daniel Stenberg committed
    }
    if(data->writeheader) {
      /* the header is requested to be written to this file */
      if(strlen(buf) != data->fwrite (buf, 1, strlen(buf),
                                      data->writeheader)) {
Daniel Stenberg's avatar
Daniel Stenberg committed
        failf (data, "Failed writing output");
        return CURLE_WRITE_ERROR;
Daniel Stenberg's avatar
Daniel Stenberg committed
      }
    }
    return CURLE_OK;
Daniel Stenberg's avatar
Daniel Stenberg committed
  }

  /* We have chosen to use the PORT command */
  if(data->bits.ftp_use_port) {
Daniel Stenberg's avatar
Daniel Stenberg committed
    struct sockaddr_in sa;
    struct hostent *h=NULL;
    char *hostdataptr=NULL;
Daniel Stenberg's avatar
Daniel Stenberg committed
    size_t size;
    unsigned short porttouse;
Daniel Stenberg's avatar
Daniel Stenberg committed
    char myhost[256] = "";
Daniel Stenberg's avatar
Daniel Stenberg committed

    if(data->ftpport) {
Daniel Stenberg's avatar
Daniel Stenberg committed
      if(if2ip(data->ftpport, myhost, sizeof(myhost))) {
        h = GetHost(data, myhost, &hostdataptr);
Daniel Stenberg's avatar
Daniel Stenberg committed
      }
      else {
        if(strlen(data->ftpport)>1)
          h = GetHost(data, data->ftpport, &hostdataptr);
Daniel Stenberg's avatar
Daniel Stenberg committed
        if(h)
          strcpy(myhost, data->ftpport); /* buffer overflow risk */
Daniel Stenberg's avatar
Daniel Stenberg committed
      }
    }
Daniel Stenberg's avatar
Daniel Stenberg committed
    if(! *myhost) {
      h=GetHost(data, getmyhost(myhost, sizeof(myhost)), &hostdataptr);
Daniel Stenberg's avatar
Daniel Stenberg committed
    }
    infof(data, "We connect from %s\n", myhost);

    if ( h ) {
      if( (portsock = socket(AF_INET, SOCK_STREAM, 0)) >= 0 ) {
        memset((char *)&sa, 0, sizeof(sa));
        memcpy((char *)&sa.sin_addr,
               h->h_addr,
               h->h_length);
        sa.sin_family = AF_INET;
        sa.sin_addr.s_addr = INADDR_ANY;
        sa.sin_port = 0;
        size = sizeof(sa);

        if(bind(portsock, (struct sockaddr *)&sa, size) >= 0) {
          /* we succeeded to bind */
          struct sockaddr_in add;
          size = sizeof(add);

          if(getsockname(portsock, (struct sockaddr *) &add,
                         (int *)&size)<0) {
            failf(data, "getsockname() failed");
            return CURLE_FTP_PORT_FAILED;
Daniel Stenberg's avatar
Daniel Stenberg committed
          }
          porttouse = ntohs(add.sin_port);

          if ( listen(portsock, 1) < 0 ) {
            failf(data, "listen(2) failed on socket");
            free(hostdataptr);
            return CURLE_FTP_PORT_FAILED;
Daniel Stenberg's avatar
Daniel Stenberg committed
          }
        }
        else {
          failf(data, "bind(2) failed on socket");
          free(hostdataptr);
          return CURLE_FTP_PORT_FAILED;
Daniel Stenberg's avatar
Daniel Stenberg committed
        }
      }
      else {
        failf(data, "socket(2) failed (%s)");
        free(hostdataptr);
        return CURLE_FTP_PORT_FAILED;
Daniel Stenberg's avatar
Daniel Stenberg committed
      }
    }
    else {
      failf(data, "could't find my own IP address (%s)", myhost);
      return CURLE_FTP_PORT_FAILED;
Daniel Stenberg's avatar
Daniel Stenberg committed
    }
    {
      struct in_addr in;
      unsigned short ip[5];
      (void) memcpy(&in.s_addr, *h->h_addr_list, sizeof (in.s_addr));
Daniel Stenberg's avatar
Daniel Stenberg committed
#if defined (HAVE_INET_NTOA_R)
      /* ignore the return code from inet_ntoa_r() as it is int or
         char * depending on system */
      inet_ntoa_r(in, ntoa_buf, sizeof(ntoa_buf));
      sscanf( ntoa_buf, "%hu.%hu.%hu.%hu",
Daniel Stenberg's avatar
Daniel Stenberg committed
              &ip[0], &ip[1], &ip[2], &ip[3]);
#else
Daniel Stenberg's avatar
Daniel Stenberg committed
      sscanf( inet_ntoa(in), "%hu.%hu.%hu.%hu",
              &ip[0], &ip[1], &ip[2], &ip[3]);
Daniel Stenberg's avatar
Daniel Stenberg committed
#endif
      ftpsendf(data->firstsocket, conn, "PORT %d,%d,%d,%d,%d,%d",
Daniel Stenberg's avatar
Daniel Stenberg committed
            ip[0], ip[1], ip[2], ip[3],
            porttouse >> 8,
            porttouse & 255);
    }

    nread = GetLastResponse(data->firstsocket, buf, conn);
    if(nread < 0)
      return CURLE_OPERATION_TIMEOUTED;
Daniel Stenberg's avatar
Daniel Stenberg committed

    if(strncmp(buf, "200", 3)) {
      failf(data, "Server does not grok PORT, try without it!");
      return CURLE_FTP_PORT_FAILED;
Daniel Stenberg's avatar
Daniel Stenberg committed
    }     
  }
  else { /* we use the PASV command */

    ftpsendf(data->firstsocket, conn, "PASV");
Daniel Stenberg's avatar
Daniel Stenberg committed

    nread = GetLastResponse(data->firstsocket, buf, conn);
    if(nread < 0)
      return CURLE_OPERATION_TIMEOUTED;
Daniel Stenberg's avatar
Daniel Stenberg committed

    if(strncmp(buf, "227", 3)) {
      failf(data, "Odd return code after PASV");
      return CURLE_FTP_WEIRD_PASV_REPLY;
Daniel Stenberg's avatar
Daniel Stenberg committed
    }
    else {
      int ip[4];
      int port[2];
      unsigned short newport; /* remote port, not necessary the local one */
      unsigned short connectport; /* the local port connect() should use! */
Daniel Stenberg's avatar
Daniel Stenberg committed
      char newhost[32];
      struct hostent *he;
Daniel Stenberg's avatar
Daniel Stenberg committed
      char *str=buf,*ip_addr;
      char *hostdataptr=NULL;
Daniel Stenberg's avatar
Daniel Stenberg committed

      /*
       * New 227-parser June 3rd 1999.
       * It now scans for a sequence of six comma-separated numbers and
       * will take them as IP+port indicators.
       *
       * Found reply-strings include:
       * "227 Entering Passive Mode (127,0,0,1,4,51)"
       * "227 Data transfer will passively listen to 127,0,0,1,4,51"
       * "227 Entering passive mode. 127,0,0,1,4,51"
       */
      
      while(*str) {
	 if (6 == sscanf(str, "%d,%d,%d,%d,%d,%d",
			 &ip[0], &ip[1], &ip[2], &ip[3],
			 &port[0], &port[1]))
	    break;
	 str++;
      }
      if(!*str) {
	 failf(data, "Couldn't interpret this 227-reply: %s", buf);
	 return CURLE_FTP_WEIRD_227_FORMAT;
      sprintf(newhost, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
      newport = (port[0]<<8) + port[1];
      if(data->bits.httpproxy) {
        /*
         * This is a tunnel through a http proxy and we need to connect to the
         * proxy again here. We already have the name info for it since the
         * previous lookup.
         */
        he = conn->hp;
        connectport = data->port; /* we connect to the proxy's port */
      }
      else {
        /* normal, direct, ftp connection */
        he = GetHost(data, newhost, &hostdataptr);
        if(!he) {
          failf(data, "Can't resolve new host %s", newhost);
          return CURLE_FTP_CANT_GET_HOST;
        }
        connectport = newport; /* we connect to the remote port */
Daniel Stenberg's avatar
Daniel Stenberg committed
	
      data->secondarysocket = socket(AF_INET, SOCK_STREAM, 0);

      memset((char *) &serv_addr, '\0', sizeof(serv_addr));
      memcpy((char *)&(serv_addr.sin_addr), he->h_addr, he->h_length);
      serv_addr.sin_family = he->h_addrtype;
      serv_addr.sin_port = htons(connectport);
Daniel Stenberg's avatar
Daniel Stenberg committed

      if(data->bits.verbose) {
Daniel Stenberg's avatar
Daniel Stenberg committed
        struct in_addr in;
        struct hostent * answer;

Daniel Stenberg's avatar
Daniel Stenberg committed
        unsigned long address;
# if defined(HAVE_GETHOSTBYADDR_R)
Daniel Stenberg's avatar
Daniel Stenberg committed
        int h_errnop;
Daniel Stenberg's avatar
Daniel Stenberg committed
        address = inet_addr(newhost);
# ifdef HAVE_GETHOSTBYADDR_R

#  ifdef HAVE_GETHOSTBYADDR_R_5
        /* AIX, Digital Unix 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! */

        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 */
Daniel Stenberg's avatar
Daniel Stenberg committed
        answer = gethostbyaddr_r((char *) &address, sizeof(address), AF_INET,
                                 (struct hostent *)hostent_buf,
                                 hostent_buf + sizeof(*answer),
                                 sizeof(hostent_buf) - sizeof(*answer),
                                 &h_errnop);
        /* 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
Daniel Stenberg's avatar
Daniel Stenberg committed
        answer = gethostbyaddr((char *) &address, sizeof(address), AF_INET);
Daniel Stenberg's avatar
Daniel Stenberg committed
#else
        answer = NULL;
#endif
        (void) memcpy(&in.s_addr, *he->h_addr_list, sizeof (in.s_addr));
        infof(data, "Connecting to %s (%s) port %u\n",
Daniel Stenberg's avatar
Daniel Stenberg committed
              answer?answer->h_name:newhost,
#if defined(HAVE_INET_NTOA_R)
              inet_ntoa_r(in, ip_addr=ntoa_buf, sizeof(ntoa_buf)),
Daniel Stenberg's avatar
Daniel Stenberg committed
#else
Daniel Stenberg's avatar
Daniel Stenberg committed
              ip_addr = inet_ntoa(in),
Daniel Stenberg's avatar
Daniel Stenberg committed
#endif
              connectport);
      if(hostdataptr)
        free(hostdataptr);

Daniel Stenberg's avatar
Daniel Stenberg committed
      if (connect(data->secondarysocket, (struct sockaddr *) &serv_addr,
                  sizeof(serv_addr)) < 0) {
        switch(errno) {
#ifdef ECONNREFUSED
          /* this should be made nicer */
        case ECONNREFUSED:
          failf(data, "Connection refused by ftp server");
          break;
#endif
#ifdef EINTR
        case EINTR:
          failf(data, "Connection timeouted to ftp server");
          break;
#endif
        default:
          failf(data, "Can't connect to ftp server");
          break;
        }
        return CURLE_FTP_CANT_RECONNECT;
      if (data->bits.tunnel_thru_httpproxy) {
        /* We want "seamless" FTP operations through HTTP proxy tunnel */
        result = GetHTTPProxyTunnel(data, data->secondarysocket,
                                    newhost, newport);
        if(CURLE_OK != result)
          return result;
      }
    }
Daniel Stenberg's avatar
Daniel Stenberg committed
  }
  /* we have the (new) data connection ready */
  infof(data, "Connected the data stream!\n");
Daniel Stenberg's avatar
Daniel Stenberg committed

  if(data->bits.upload) {
Daniel Stenberg's avatar
Daniel Stenberg committed

    /* Set type to binary (unless specified ASCII) */
    ftpsendf(data->firstsocket, conn, "TYPE %s",
          (data->bits.ftp_ascii)?"A":"I");
Daniel Stenberg's avatar
Daniel Stenberg committed

    nread = GetLastResponse(data->firstsocket, buf, conn);
    if(nread < 0)
      return CURLE_OPERATION_TIMEOUTED;
Daniel Stenberg's avatar
Daniel Stenberg committed

    if(strncmp(buf, "200", 3)) {
      failf(data, "Couldn't set %s mode",
            (data->bits.ftp_ascii)?"ASCII":"binary");
      return (data->bits.ftp_ascii)? CURLE_FTP_COULDNT_SET_ASCII:
        CURLE_FTP_COULDNT_SET_BINARY;
Daniel Stenberg's avatar
Daniel Stenberg committed
    }

    if(data->resume_from) {