text: /*
     +----------------------------------------------------------------------+
     | PHP Version 7                                                        |
     +----------------------------------------------------------------------+
     | Copyright (c) 1997-2017 The PHP Group                                |
     +----------------------------------------------------------------------+
     | This source file is subject to version 3.01 of the PHP license,      |
     | that is bundled with this package in the file LICENSE, and is        |
     | available through the world-wide-web at the following url:           |
     | http://www.php.net/license/3_01.txt                                  |
     | If you did not receive a copy of the PHP license and are unable to   |
     | obtain it through the world-wide-web, please send a note to          |
     | license@php.net so we can mail you a copy immediately.               |
     +----------------------------------------------------------------------+
     | Authors: Andrew Skalski <askalski@chek.com>                          |
     |          Stefan Esser <sesser@php.net> (resume functions)            |
     +----------------------------------------------------------------------+
   */
  
  /* $Id$ */
  
  #ifdef HAVE_CONFIG_H
  #include "config.h"
  #endif
  
  #include "php.h"
  
  #if HAVE_FTP
  
  #include <stdio.h>
  #include <ctype.h>
  #include <stdlib.h>
  #ifdef HAVE_UNISTD_H
  #include <unistd.h>
  #endif
  #include <fcntl.h>
  #include <string.h>
  #include <time.h>
  #ifdef PHP_WIN32
  #include <winsock2.h>
  #else
  #ifdef HAVE_SYS_TYPES_H
  #include <sys/types.h>
  #endif
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  #include <netdb.h>
  #endif
  #include <errno.h>
  
  #if HAVE_SYS_TIME_H
  #include <sys/time.h>
  #endif
  
  #ifdef HAVE_SYS_SELECT_H
  #include <sys/select.h>
  #endif
  
  #ifdef HAVE_FTP_SSL
  #include <openssl/ssl.h>
  #endif
  
  #include "ftp.h"
  #include "ext/standard/fsock.h"
  
  /* sends an ftp command, returns true on success, false on error.
   * it sends the string "cmd args\r\n" if args is non-null, or
   * "cmd\r\n" if args is null
   */
  static int		ftp_putcmd(	ftpbuf_t *ftp,
  					const char *cmd,
  					const size_t cmd_len, 
  					const char *args, 
  					const size_t args_len);
  
  /* wrapper around send/recv to handle timeouts */
  static int		my_send(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t len);
  static int		my_recv(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t len);
  static int		my_accept(ftpbuf_t *ftp, php_socket_t s, struct sockaddr *addr, socklen_t *addrlen);
  
  /* reads a line the socket , returns true on success, false on error */
  static int		ftp_readline(ftpbuf_t *ftp);
  
  /* reads an ftp response, returns true on success, false on error */
  static int		ftp_getresp(ftpbuf_t *ftp);
  
  /* sets the ftp transfer type */
  static int		ftp_type(ftpbuf_t *ftp, ftptype_t type);
  
  /* opens up a data stream */
  static databuf_t*	ftp_getdata(ftpbuf_t *ftp);
  
  /* accepts the data connection, returns updated data buffer */
  static databuf_t*	data_accept(databuf_t *data, ftpbuf_t *ftp);
  
  /* closes the data connection, returns NULL */
  static databuf_t*	data_close(ftpbuf_t *ftp, databuf_t *data);
  
  /* generic file lister */
  static char**		ftp_genlist(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len, const char *path, const size_t path_len);
  
  /* IP and port conversion box */
  union ipbox {
  	struct in_addr	ia[2];
  	unsigned short	s[4];
  	unsigned char	c[8];
  };
  
  /* {{{ ftp_open
   */
  ftpbuf_t*
  ftp_open(const char *host, short port, zend_long timeout_sec)
  {
  	ftpbuf_t		*ftp;
  	socklen_t		 size;
  	struct timeval tv;
  
  
  	/* alloc the ftp structure */
  	ftp = ecalloc(1, sizeof(*ftp));
  
  	tv.tv_sec = timeout_sec;
  	tv.tv_usec = 0;
  
  	ftp->fd = php_network_connect_socket_to_host(host,
  			(unsigned short) (port ? port : 21), SOCK_STREAM,
  			0, &tv, NULL, NULL, NULL, 0, STREAM_SOCKOP_NONE);
  	if (ftp->fd == -1) {
  		goto bail;
  	}
  
  	/* Default Settings */
  	ftp->timeout_sec = timeout_sec;
  	ftp->nb = 0;
  
  	size = sizeof(ftp->localaddr);
  	memset(&ftp->localaddr, 0, size);
  	if (getsockname(ftp->fd, (struct sockaddr*) &ftp->localaddr, &size) != 0) {
  		php_error_docref(NULL, E_WARNING, "getsockname failed: %s (%d)", strerror(errno), errno);
  		goto bail;
  	}
  
  	if (!ftp_getresp(ftp) || ftp->resp != 220) {
  		goto bail;
  	}
  
  	return ftp;
  
  bail:
  	if (ftp->fd != -1) {
  		closesocket(ftp->fd);
  	}
  	efree(ftp);
  	return NULL;
  }
  /* }}} */
  
  /* {{{ ftp_close
   */
  ftpbuf_t*
  ftp_close(ftpbuf_t *ftp)
  {
  	if (ftp == NULL) {
  		return NULL;
  	}
  	if (ftp->data) {
  		data_close(ftp, ftp->data);
  	}
  	if (ftp->stream && ftp->closestream) {
  			php_stream_close(ftp->stream);
  	}
  	if (ftp->fd != -1) {
  #ifdef HAVE_FTP_SSL
  		if (ftp->ssl_active) {
  			SSL_shutdown(ftp->ssl_handle);
  			SSL_free(ftp->ssl_handle);
  		}
  #endif
  		closesocket(ftp->fd);
  	}
  	ftp_gc(ftp);
  	efree(ftp);
  	return NULL;
  }
  /* }}} */
  
  /* {{{ ftp_gc
   */
  void
  ftp_gc(ftpbuf_t *ftp)
  {
  	if (ftp == NULL) {
  		return;
  	}
  	if (ftp->pwd) {
  		efree(ftp->pwd);
  		ftp->pwd = NULL;
  	}
  	if (ftp->syst) {
  		efree(ftp->syst);
  		ftp->syst = NULL;
  	}
  }
  /* }}} */
  
  /* {{{ ftp_quit
   */
  int
  ftp_quit(ftpbuf_t *ftp)
  {
  	if (ftp == NULL) {
  		return 0;
  	}
  
  	if (!ftp_putcmd(ftp, "QUIT", sizeof("QUIT")-1, NULL, (size_t) 0)) {
  		return 0;
  	}
  	if (!ftp_getresp(ftp) || ftp->resp != 221) {
  		return 0;
  	}
  
  	if (ftp->pwd) {
  		efree(ftp->pwd);
  		ftp->pwd = NULL;
  	}
  
  	return 1;
  }
  /* }}} */
  
  /* {{{ ftp_login
   */
  int
  ftp_login(ftpbuf_t *ftp, const char *user, const size_t user_len, const char *pass, const size_t pass_len)
  {
  #ifdef HAVE_FTP_SSL
  	SSL_CTX	*ctx = NULL;
  	long ssl_ctx_options = SSL_OP_ALL;
  	int err, res;
  	zend_bool retry;
  #endif
  	if (ftp == NULL) {
  		return 0;
  	}
  
  #ifdef HAVE_FTP_SSL
  	if (ftp->use_ssl && !ftp->ssl_active) {
  		if (!ftp_putcmd(ftp, "AUTH", sizeof("AUTH")-1, "TLS", sizeof("TLS")-1)) {
  			return 0;
  		}
  		if (!ftp_getresp(ftp)) {
  			return 0;
  		}
  
  		if (ftp->resp != 234) {
  			if (!ftp_putcmd(ftp, "AUTH", sizeof("AUTH")-1, "SSL", sizeof("SSL")-1)) {
  				return 0;
  			}
  			if (!ftp_getresp(ftp)) {
  				return 0;
  			}
  
  			if (ftp->resp != 334) {
  				return 0;
  			} else {
  				ftp->old_ssl = 1;
  				ftp->use_ssl_for_data = 1;
  			}
  		}
  
  		ctx = SSL_CTX_new(SSLv23_client_method());
  		if (ctx == NULL) {
  			php_error_docref(NULL, E_WARNING, "failed to create the SSL context");
  			return 0;
  		}
  
  #if OPENSSL_VERSION_NUMBER >= 0x0090605fL
  		ssl_ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
  #endif
  		SSL_CTX_set_options(ctx, ssl_ctx_options);
  
  		/* allow SSL to re-use sessions */
  		SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_BOTH);
  
  		ftp->ssl_handle = SSL_new(ctx);
  		if (ftp->ssl_handle == NULL) {
  			php_error_docref(NULL, E_WARNING, "failed to create the SSL handle");
  			SSL_CTX_free(ctx);
  			return 0;
  		}
  
  		SSL_set_fd(ftp->ssl_handle, ftp->fd);
  
  		do {
  			res = SSL_connect(ftp->ssl_handle);
  			err = SSL_get_error(ftp->ssl_handle, res);
  
  			/* TODO check if handling other error codes would make sense */
  			switch (err) {
  				case SSL_ERROR_NONE:
  					retry = 0;
  					break;
  
  				case SSL_ERROR_ZERO_RETURN:
  					retry = 0;
  					SSL_shutdown(ftp->ssl_handle);
  					break;
  
  				case SSL_ERROR_WANT_READ:
  				case SSL_ERROR_WANT_WRITE: {
  						php_pollfd p;
  						int i;
  
  						p.fd = ftp->fd;
  						p.events = (err == SSL_ERROR_WANT_READ) ? (POLLIN|POLLPRI) : POLLOUT;
  						p.revents = 0;
  
  						i = php_poll2(&p, 1, 300);
  
  						retry = i > 0;
  					}
  					break;
  
  				default:
  					php_error_docref(NULL, E_WARNING, "SSL/TLS handshake failed");
  					SSL_shutdown(ftp->ssl_handle);
  					SSL_free(ftp->ssl_handle);
  					return 0;
  			}
  		} while (retry);
  
  		ftp->ssl_active = 1;
  
  		if (!ftp->old_ssl) {
  
  			/* set protection buffersize to zero */
  			if (!ftp_putcmd(ftp, "PBSZ", sizeof("PBSZ")-1, "0", sizeof("0")-1)) {
  				return 0;
  			}
  			if (!ftp_getresp(ftp)) {
  				return 0;
  			}
  
  			/* enable data conn encryption */
  			if (!ftp_putcmd(ftp, "PROT", sizeof("PROT")-1, "P", sizeof("P")-1)) {
  				return 0;
  			}
  			if (!ftp_getresp(ftp)) {
  				return 0;
  			}
  
  			ftp->use_ssl_for_data = (ftp->resp >= 200 && ftp->resp <=299);
  		}
  	}
  #endif
  
  	if (!ftp_putcmd(ftp, "USER", sizeof("USER")-1, user, user_len)) {
  		return 0;
  	}
  	if (!ftp_getresp(ftp)) {
  		return 0;
  	}
  	if (ftp->resp == 230) {
  		return 1;
  	}
  	if (ftp->resp != 331) {
  		return 0;
  	}
  	if (!ftp_putcmd(ftp, "PASS", sizeof("PASS")-1, pass, pass_len)) {
  		return 0;
  	}
  	if (!ftp_getresp(ftp)) {
  		return 0;
  	}
  	return (ftp->resp == 230);
  }
  /* }}} */
  
  /* {{{ ftp_reinit
   */
  int
  ftp_reinit(ftpbuf_t *ftp)
  {
  	if (ftp == NULL) {
  		return 0;
  	}
  
  	ftp_gc(ftp);
  
  	ftp->nb = 0;
  
  	if (!ftp_putcmd(ftp, "REIN", sizeof("REIN")-1, NULL, (size_t) 0)) {
  		return 0;
  	}
  	if (!ftp_getresp(ftp) || ftp->resp != 220) {
  		return 0;
  	}
  
  	return 1;
  }
  /* }}} */
  
  /* {{{ ftp_syst
   */
  const char*
  ftp_syst(ftpbuf_t *ftp)
  {
  	char *syst, *end;
  
  	if (ftp == NULL) {
  		return NULL;
  	}
  
  	/* default to cached value */
  	if (ftp->syst) {
  		return ftp->syst;
  	}
  	if (!ftp_putcmd(ftp, "SYST", sizeof("SYST")-1, NULL, (size_t) 0)) {
  		return NULL;
  	}
  	if (!ftp_getresp(ftp) || ftp->resp != 215) {
  		return NULL;
  	}
  	syst = ftp->inbuf;
  	while (*syst == ' ') {
  		syst++;
  	}
  	if ((end = strchr(syst, ' '))) {
  		*end = 0;
  	}
  	ftp->syst = estrdup(syst);
  	if (end) {
  		*end = ' ';
  	}
  	return ftp->syst;
  }
  /* }}} */
  
  /* {{{ ftp_pwd
   */
  const char*
  ftp_pwd(ftpbuf_t *ftp)
  {
  	char *pwd, *end;
  
  	if (ftp == NULL) {
  		return NULL;
  	}
  
  	/* default to cached value */
  	if (ftp->pwd) {
  		return ftp->pwd;
  	}
  	if (!ftp_putcmd(ftp, "PWD", sizeof("PWD")-1, NULL, (size_t) 0)) {
  		return NULL;
  	}
  	if (!ftp_getresp(ftp) || ftp->resp != 257) {
  		return NULL;
  	}
  	/* copy out the pwd from response */
  	if ((pwd = strchr(ftp->inbuf, '"')) == NULL) {
  		return NULL;
  	}
  	if ((end = strrchr(++pwd, '"')) == NULL) {
  		return NULL;
  	}
  	ftp->pwd = estrndup(pwd, end - pwd);
  
  	return ftp->pwd;
  }
  /* }}} */
  
  /* {{{ ftp_exec
   */
  int
  ftp_exec(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len)
  {
  	if (ftp == NULL) {
  		return 0;
  	}
  	if (!ftp_putcmd(ftp, "SITE EXEC", sizeof("SITE EXEC")-1, cmd, cmd_len)) {
  		return 0;
  	}
  	if (!ftp_getresp(ftp) || ftp->resp != 200) {
  		return 0;
  	}
  
  	return 1;
  }
  /* }}} */
  
  /* {{{ ftp_raw
   */
  void
  ftp_raw(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len, zval *return_value)
  {
  	if (ftp == NULL || cmd == NULL) {
  		RETURN_NULL();
  	}
  	if (!ftp_putcmd(ftp, cmd, cmd_len, NULL, (size_t) 0)) {
  		RETURN_NULL();
  	}
  	array_init(return_value);
  	while (ftp_readline(ftp)) {
  		add_next_index_string(return_value, ftp->inbuf);
  		if (isdigit(ftp->inbuf[0]) && isdigit(ftp->inbuf[1]) && isdigit(ftp->inbuf[2]) && ftp->inbuf[3] == ' ') {
  			return;
  		}
  	}
  }
  /* }}} */
  
  /* {{{ ftp_chdir
   */
  int
  ftp_chdir(ftpbuf_t *ftp, const char *dir, const size_t dir_len)
  {
  	if (ftp == NULL) {
  		return 0;
  	}
  
  	if (ftp->pwd) {
  		efree(ftp->pwd);
  		ftp->pwd = NULL;
  	}
  
  	if (!ftp_putcmd(ftp, "CWD", sizeof("CWD")-1, dir, dir_len)) {
  		return 0;
  	}
  	if (!ftp_getresp(ftp) || ftp->resp != 250) {
  		return 0;
  	}
  	return 1;
  }
  /* }}} */
  
  /* {{{ ftp_cdup
   */
  int
  ftp_cdup(ftpbuf_t *ftp)
  {
  	if (ftp == NULL) {
  		return 0;
  	}
  
  	if (ftp->pwd) {
  		efree(ftp->pwd);
  		ftp->pwd = NULL;
  	}
  
  	if (!ftp_putcmd(ftp, "CDUP", sizeof("CDUP")-1, NULL, (size_t) 0)) {
  		return 0;
  	}
  	if (!ftp_getresp(ftp) || ftp->resp != 250) {
  		return 0;
  	}
  	return 1;
  }
  /* }}} */
  
  /* {{{ ftp_mkdir
   */
  zend_string*
  ftp_mkdir(ftpbuf_t *ftp, const char *dir, const size_t dir_len)
  {
  	char *mkd, *end;
  	zend_string *ret;
  
  	if (ftp == NULL) {
  		return NULL;
  	}
  	if (!ftp_putcmd(ftp, "MKD", sizeof("MKD")-1, dir, dir_len)) {
  		return NULL;
  	}
  	if (!ftp_getresp(ftp) || ftp->resp != 257) {
  		return NULL;
  	}
  	/* copy out the dir from response */
  	if ((mkd = strchr(ftp->inbuf, '"')) == NULL) {
  		return zend_string_init(dir, dir_len, 0);
  	}
  	if ((end = strrchr(++mkd, '"')) == NULL) {
  		return NULL;
  	}
  	*end = 0;
  	ret = zend_string_init(mkd, end - mkd, 0);
  	*end = '"';
  
  	return ret;
  }
  /* }}} */
  
  /* {{{ ftp_rmdir
   */
  int
  ftp_rmdir(ftpbuf_t *ftp, const char *dir, const size_t dir_len)
  {
  	if (ftp == NULL) {
  		return 0;
  	}
  	if (!ftp_putcmd(ftp, "RMD", sizeof("RMD")-1, dir, dir_len)) {
  		return 0;
  	}
  	if (!ftp_getresp(ftp) || ftp->resp != 250) {
  		return 0;
  	}
  	return 1;
  }
  /* }}} */
  
  /* {{{ ftp_chmod
   */
  int
  ftp_chmod(ftpbuf_t *ftp, const int mode, const char *filename, const int filename_len)
  {
  	char *buffer;
  	size_t buffer_len;
  
  	if (ftp == NULL || filename_len <= 0) {
  		return 0;
  	}
  
  	buffer_len = spprintf(&buffer, 0, "CHMOD %o %s", mode, filename);
  
  	if (!buffer) {
  		return 0;
  	}
  
  	if (!ftp_putcmd(ftp, "SITE", sizeof("SITE")-1, buffer, buffer_len)) {
  		efree(buffer);
  		return 0;
  	}
  
  	efree(buffer);
  
  	if (!ftp_getresp(ftp) || ftp->resp != 200) {
  		return 0;
  	}
  
  	return 1;
  }
  /* }}} */
  
  /* {{{ ftp_alloc
   */
  int
  ftp_alloc(ftpbuf_t *ftp, const zend_long size, zend_string **response)
  {
  	char buffer[64];
  	int buffer_len;
  
  	if (ftp == NULL || size <= 0) {
  		return 0;
  	}
  
  	buffer_len = snprintf(buffer, sizeof(buffer) - 1, ZEND_LONG_FMT, size);
  
  	if (buffer_len < 0) {
  		return 0;
  	}
  
  	if (!ftp_putcmd(ftp, "ALLO", sizeof("ALLO")-1, buffer, buffer_len)) {
  		return 0;
  	}
  
  	if (!ftp_getresp(ftp)) {
  		return 0;
  	}
  
  	if (response) {
  		*response = zend_string_init(ftp->inbuf, strlen(ftp->inbuf), 0);
  	}
  
  	if (ftp->resp < 200 || ftp->resp >= 300) {
  		return 0;
  	}
  
  	return 1;
  }
  /* }}} */
  
  /* {{{ ftp_nlist
   */
  char**
  ftp_nlist(ftpbuf_t *ftp, const char *path, const size_t path_len)
  {
  	return ftp_genlist(ftp, "NLST", sizeof("NLST")-1, path, path_len);
  }
  /* }}} */
  
  /* {{{ ftp_list
   */
  char**
  ftp_list(ftpbuf_t *ftp, const char *path, const size_t path_len, int recursive)
  {
  	return ftp_genlist(ftp, ((recursive) ? "LIST -R" : "LIST"), ((recursive) ? sizeof("LIST -R")-1 : sizeof("LIST")-1), path, path_len);
  }
  /* }}} */
  
  /* {{{ ftp_mlsd
   */
  char**
  ftp_mlsd(ftpbuf_t *ftp, const char *path, const size_t path_len)
  {
  	return ftp_genlist(ftp, "MLSD", sizeof("MLSD")-1, path, path_len);
  }
  /* }}} */
  
  /* {{{ ftp_mlsd_parse_line
   */
  int
  ftp_mlsd_parse_line(HashTable *ht, const char *input) {
  
  	zval zstr;
  	const char *end = input + strlen(input);
  
  	const char *sp = memchr(input, ' ', end - input);
  	if (!sp) {
  		php_error_docref(NULL, E_WARNING, "Missing pathname in MLSD response");
  		return FAILURE;
  	}
  
  	/* Extract pathname */
  	ZVAL_STRINGL(&zstr, sp + 1, end - sp - 1);
  	zend_hash_str_update(ht, "name", sizeof("name")-1, &zstr);
  	end = sp;
  
  	while (input < end) {
  		const char *semi, *eq;
  
  		/* Find end of fact */
  		semi = memchr(input, ';', end - input);
  		if (!semi) {
  			php_error_docref(NULL, E_WARNING, "Malformed fact in MLSD response");
  			return FAILURE;
  		}
  
  		/* Separate fact key and value */
  		eq = memchr(input, '=', semi - input);
  		if (!eq) {
  			php_error_docref(NULL, E_WARNING, "Malformed fact in MLSD response");
  			return FAILURE;
  		}
  
  		ZVAL_STRINGL(&zstr, eq + 1, semi - eq - 1);
  		zend_hash_str_update(ht, input, eq - input, &zstr);
  		input = semi + 1;
  	}
  
  	return SUCCESS;
  }
  /* }}} */
  
  /* {{{ ftp_type
   */
  int
  ftp_type(ftpbuf_t *ftp, ftptype_t type)
  {
  	const char *typechar;
  
  	if (ftp == NULL) {
  		return 0;
  	}
  	if (type == ftp->type) {
  		return 1;
  	}
  	if (type == FTPTYPE_ASCII) {
  		typechar = "A";
  	} else if (type == FTPTYPE_IMAGE) {
  		typechar = "I";
  	} else {
  		return 0;
  	}
  	if (!ftp_putcmd(ftp, "TYPE", sizeof("TYPE")-1, typechar, 1)) {
  		return 0;
  	}
  	if (!ftp_getresp(ftp) || ftp->resp != 200) {
  		return 0;
  	}
  	ftp->type = type;
  
  	return 1;
  }
  /* }}} */
  
  /* {{{ ftp_pasv
   */
  int
  ftp_pasv(ftpbuf_t *ftp, int pasv)
  {
  	char			*ptr;
  	union ipbox		ipbox;
  	unsigned long		b[6];
  	socklen_t			n;
  	struct sockaddr *sa;
  	struct sockaddr_in *sin;
  
  	if (ftp == NULL) {
  		return 0;
  	}
  	if (pasv && ftp->pasv == 2) {
  		return 1;
  	}
  	ftp->pasv = 0;
  	if (!pasv) {
  		return 1;
  	}
  	n = sizeof(ftp->pasvaddr);
  	memset(&ftp->pasvaddr, 0, n);
  	sa = (struct sockaddr *) &ftp->pasvaddr;
  
  	if (getpeername(ftp->fd, sa, &n) < 0) {
  		return 0;
  	}
  
  #if HAVE_IPV6
  	if (sa->sa_family == AF_INET6) {
  		struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;
  		char *endptr, delimiter;
  
  		/* try EPSV first */
  		if (!ftp_putcmd(ftp, "EPSV", sizeof("EPSV")-1, NULL, (size_t) 0)) {
  			return 0;
  		}
  		if (!ftp_getresp(ftp)) {
  			return 0;
  		}
  		if (ftp->resp == 229) {
  			/* parse out the port */
  			for (ptr = ftp->inbuf; *ptr && *ptr != '('; ptr++);
  			if (!*ptr) {
  				return 0;
  			}
  			delimiter = *++ptr;
  			for (n = 0; *ptr && n < 3; ptr++) {
  				if (*ptr == delimiter) {
  					n++;
  				}
  			}
  
  			sin6->sin6_port = htons((unsigned short) strtoul(ptr, &endptr, 10));
  			if (ptr == endptr || *endptr != delimiter) {
  				return 0;
  			}
  			ftp->pasv = 2;
  			return 1;
  		}
  	}
  
  	/* fall back to PASV */
  #endif
  
  	if (!ftp_putcmd(ftp, "PASV",  sizeof("PASV")-1, NULL, (size_t) 0)) {
  		return 0;
  	}
  	if (!ftp_getresp(ftp) || ftp->resp != 227) {
  		return 0;
  	}
  	/* parse out the IP and port */
  	for (ptr = ftp->inbuf; *ptr && !isdigit(*ptr); ptr++);
  	n = sscanf(ptr, "%lu,%lu,%lu,%lu,%lu,%lu", &b[0], &b[1], &b[2], &b[3], &b[4], &b[5]);
  	if (n != 6) {
  		return 0;
  	}
  	for (n = 0; n < 6; n++) {
  		ipbox.c[n] = (unsigned char) b[n];
  	}
  	sin = (struct sockaddr_in *) sa;
  	if (ftp->usepasvaddress) {
  		sin->sin_addr = ipbox.ia[0];
  	}
  	sin->sin_port = ipbox.s[2];
  
  	ftp->pasv = 2;
  
  	return 1;
  }
  /* }}} */
  
  /* {{{ ftp_get
   */
  int
  ftp_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_t path_len, ftptype_t type, zend_long resumepos)
  {
  	databuf_t		*data = NULL;
  	size_t			rcvd;
  	char			arg[11];
  
  	if (ftp == NULL) {
  		return 0;
  	}
  	if (!ftp_type(ftp, type)) {
  		goto bail;
  	}
  
  	if ((data = ftp_getdata(ftp)) == NULL) {
  		goto bail;
  	}
  
  	ftp->data = data;
  
  	if (resumepos > 0) {
  		int arg_len = snprintf(arg, sizeof(arg), ZEND_LONG_FMT, resumepos);
  
  		if (arg_len < 0) {
  			goto bail;
  		}
  		if (!ftp_putcmd(ftp, "REST", sizeof("REST")-1, arg, arg_len)) {
  			goto bail;
  		}
  		if (!ftp_getresp(ftp) || (ftp->resp != 350)) {
  			goto bail;
  		}
  	}
  
  	if (!ftp_putcmd(ftp, "RETR", sizeof("RETR")-1, path, path_len)) {
  		goto bail;
  	}
  	if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) {
  		goto bail;
  	}
  
  	if ((data = data_accept(data, ftp)) == NULL) {
  		goto bail;
  	}
  
  	while ((rcvd = my_recv(ftp, data->fd, data->buf, FTP_BUFSIZE))) {
  		if (rcvd == (size_t)-1) {
  			goto bail;
  		}
  
  		if (type == FTPTYPE_ASCII) {
  #ifndef PHP_WIN32
  			char *s;
  #endif
  			char *ptr = data->buf;
  			char *e = ptr + rcvd;
  			/* logic depends on the OS EOL
  			 * Win32 -> \r\n
  			 * Everything Else \n
  			 */
  #ifdef PHP_WIN32
  			php_stream_write(outstream, ptr, (e - ptr));
  			ptr = e;
  #else
  			while (e > ptr && (s = memchr(ptr, '\r', (e - ptr)))) {
  				php_stream_write(outstream, ptr, (s - ptr));
  				if (*(s + 1) == '\n') {
  					s++;
  					php_stream_putc(outstream, '\n');
  				}
  				ptr = s + 1;
  			}
  #endif
  			if (ptr < e) {
  				php_stream_write(outstream, ptr, (e - ptr));
  			}
  		} else if (rcvd != php_stream_write(outstream, data->buf, rcvd)) {
  			goto bail;
  		}
  	}
  
  	ftp->data = data = data_close(ftp, data);
  
  	if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) {
  		goto bail;
  	}
  
  	return 1;
  bail:
  	ftp->data = data_close(ftp, data);
  	return 0;
  }
  /* }}} */
  
  /* {{{ ftp_put
   */
  int
  ftp_put(ftpbuf_t *ftp, const char *path, const size_t path_len, php_stream *instream, ftptype_t type, zend_long startpos)
  {
  	databuf_t		*data = NULL;
  	zend_long			size;
  	char			*ptr;
  	int			ch;
  	char			arg[11];
  
  	if (ftp == NULL) {
  		return 0;
  	}
  	if (!ftp_type(ftp, type)) {
  		goto bail;
  	}
  	if ((data = ftp_getdata(ftp)) == NULL) {
  		goto bail;
  	}
  	ftp->data = data;
  
  	if (startpos > 0) {
  		int arg_len = snprintf(arg, sizeof(arg), ZEND_LONG_FMT, startpos);
  
  		if (arg_len < 0) {
  			goto bail;
  		}
  		if (!ftp_putcmd(ftp, "REST", sizeof("REST")-1, arg, arg_len)) {
  			goto bail;
  		}
  		if (!ftp_getresp(ftp) || (ftp->resp != 350)) {
  			goto bail;
  		}
  	}
  
  	if (!ftp_putcmd(ftp, "STOR", sizeof("STOR")-1, path, path_len)) {
  		goto bail;
  	}
  	if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) {
  		goto bail;
  	}
  	if ((data = data_accept(data, ftp)) == NULL) {
  		goto bail;
  	}
  
  	size = 0;
  	ptr = data->buf;
  	while (!php_stream_eof(instream) && (ch = php_stream_getc(instream))!=EOF) {
  		/* flush if necessary */
  		if (FTP_BUFSIZE - size < 2) {
  			if (my_send(ftp, data->fd, data->buf, size) != size) {
  				goto bail;
  			}
  			ptr = data->buf;
  			size = 0;
  		}
  
  		if (ch == '\n' && type == FTPTYPE_ASCII) {
  			*ptr++ = '\r';
  			size++;
  		}
  
  		*ptr++ = ch;
  		size++;
  	}
  
  	if (size && my_send(ftp, data->fd, data->buf, size) != size) {
  		goto bail;
  	}
  	ftp->data = data = data_close(ftp, data);
  
  	if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250 && ftp->resp != 200)) {
  		goto bail;
  	}
  	return 1;
  bail:
  	ftp->data = data_close(ftp, data);
  	return 0;
  }
  /* }}} */
  
  
  /* {{{ ftp_append
   */
  int
  ftp_append(ftpbuf_t *ftp, const char *path, const size_t path_len, php_stream *instream, ftptype_t type)
  {
  	databuf_t		*data = NULL;
  	zend_long			size;
  	char			*ptr;
  	int			ch;
  
  	if (ftp == NULL) {
  		return 0;
  	}
  	if (!ftp_type(ftp, type)) {
  		goto bail;
  	}
  	if ((data = ftp_getdata(ftp)) == NULL) {
  		goto bail;
  	}
  	ftp->data = data;
  
  	if (!ftp_putcmd(ftp, "APPE", sizeof("APPE")-1, path, path_len)) {
  		goto bail;
  	}
  	if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) {
  		goto bail;
  	}
  	if ((data = data_accept(data, ftp)) == NULL) {
  		goto bail;
  	}
  
  	size = 0;
  	ptr = data->buf;
  	while (!php_stream_eof(instream) && (ch = php_stream_getc(instream))!=EOF) {
  		/* flush if necessary */
  		if (FTP_BUFSIZE - size < 2) {
  			if (my_send(ftp, data->fd, data->buf, size) != size) {
  				goto bail;
  			}
  			ptr = data->buf;
  			size = 0;
  		}
  
  		if (ch == '\n' && type == FTPTYPE_ASCII) {
  			*ptr++ = '\r';
  			size++;
  		}
  
  		*ptr++ = ch;
  		size++;
  	}
  
  	if (size && my_send(ftp, data->fd, data->buf, size) != size) {
  		goto bail;
  	}
  	ftp->data = data = data_close(ftp, data);
  
  	if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250 && ftp->resp != 200)) {
  		goto bail;
  	}
  	return 1;
  bail:
  	ftp->data = data_close(ftp, data);
  	return 0;
  }
  /* }}} */
  
  /* {{{ ftp_size
   */
  zend_long
  ftp_size(ftpbuf_t *ftp, const char *path, const size_t path_len)
  {
  	if (ftp == NULL) {
  		return -1;
  	}
  	if (!ftp_type(ftp, FTPTYPE_IMAGE)) {
  		return -1;
  	}
  	if (!ftp_putcmd(ftp, "SIZE", sizeof("SIZE")-1, path, path_len)) {
  		return -1;
  	}
  	if (!ftp_getresp(ftp) || ftp->resp != 213) {
  		return -1;
  	}
  	return atol(ftp->inbuf);
  }
  /* }}} */
  
  /* {{{ ftp_mdtm
   */
  time_t
  ftp_mdtm(ftpbuf_t *ftp, const char *path, const size_t path_len)
  {
  	time_t		stamp;
  	struct tm	*gmt, tmbuf;
  	struct tm	tm;
  	char		*ptr;
  	int		n;
  
  	if (ftp == NULL) {
  		return -1;
  	}
  	if (!ftp_putcmd(ftp, "MDTM", sizeof("MDTM")-1, path, path_len)) {
  		return -1;
  	}
  	if (!ftp_getresp(ftp) || ftp->resp != 213) {
  		return -1;
  	}
  	/* parse out the timestamp */
  	for (ptr = ftp->inbuf; *ptr && !isdigit(*ptr); ptr++);
  	n = sscanf(ptr, "%4u%2u%2u%2u%2u%2u", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
  	if (n != 6) {
  		return -1;
  	}
  	tm.tm_year -= 1900;
  	tm.tm_mon--;
  	tm.tm_isdst = -1;
  
  	/* figure out the GMT offset */
  	stamp = time(NULL);
  	gmt = php_gmtime_r(&stamp, &tmbuf);
  	if (!gmt) {
  		return -1;
  	}
  	gmt->tm_isdst = -1;
  
  	/* apply the GMT offset */
  	tm.tm_sec += stamp - mktime(gmt);
  	tm.tm_isdst = gmt->tm_isdst;
  
  	stamp = mktime(&tm);
  
  	return stamp;
  }
  /* }}} */
  
  /* {{{ ftp_delete
   */
  int
  ftp_delete(ftpbuf_t *ftp, const char *path, const size_t path_len)
  {
  	if (ftp == NULL) {
  		return 0;
  	}
  	if (!ftp_putcmd(ftp, "DELE", sizeof("DELE")-1, path, path_len)) {
  		return 0;
  	}
  	if (!ftp_getresp(ftp) || ftp->resp != 250) {
  		return 0;
  	}
  
  	return 1;
  }
  /* }}} */
  
  /* {{{ ftp_rename
   */
  int
  ftp_rename(ftpbuf_t *ftp, const char *src, const size_t src_len, const char *dest, const size_t dest_len)
  {
  	if (ftp == NULL) {
  		return 0;
  	}
  	if (!ftp_putcmd(ftp, "RNFR", sizeof("RNFR")-1, src, src_len)) {
  		return 0;
  	}
  	if (!ftp_getresp(ftp) || ftp->resp != 350) {
  		return 0;
  	}
  	if (!ftp_putcmd(ftp, "RNTO", sizeof("RNTO")-1, dest, dest_len)) {
  		return 0;
  	}
  	if (!ftp_getresp(ftp) || ftp->resp != 250) {
  		return 0;
  	}
  	return 1;
  }
  /* }}} */
  
  /* {{{ ftp_site
   */
  int
  ftp_site(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len)
  {
  	if (ftp == NULL) {
  		return 0;
  	}
  	if (!ftp_putcmd(ftp, "SITE", sizeof("SITE")-1, cmd, cmd_len)) {
  		return 0;
  	}
  	if (!ftp_getresp(ftp) || ftp->resp < 200 || ftp->resp >= 300) {
  		return 0;
  	}
  
  	return 1;
  }
  /* }}} */
  
  /* static functions */
  
  /* {{{ ftp_putcmd
   */
  int
  ftp_putcmd(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len, const char *args, const size_t args_len)
  {
  	int		size;
  	char		*data;
  
  	if (strpbrk(cmd, "\r\n")) {
  		return 0;
  	}
  	/* build the output buffer */
  	if (args && args[0]) {
  		/* "cmd args\r\n\0" */
  		if (cmd_len + args_len + 4 > FTP_BUFSIZE) {
  			return 0;
  		}
  		if (strpbrk(args, "\r\n")) {
  			return 0;
  		}
  		size = slprintf(ftp->outbuf, sizeof(ftp->outbuf), "%s %s\r\n", cmd, args);
  	} else {
  		/* "cmd\r\n\0" */
  		if (cmd_len + 3 > FTP_BUFSIZE) {
  			return 0;
  		}
  		size = slprintf(ftp->outbuf, sizeof(ftp->outbuf), "%s\r\n", cmd);
  	}
  
  	data = ftp->outbuf;
  
  	/* Clear the extra-lines buffer */
  	ftp->extra = NULL;
  
  	if (my_send(ftp, ftp->fd, data, size) != size) {
  		return 0;
  	}
  	return 1;
  }
  /* }}} */
  
  /* {{{ ftp_readline
   */
  int
  ftp_readline(ftpbuf_t *ftp)
  {
  	long		size, rcvd;
  	char		*data, *eol;
  
  	/* shift the extra to the front */
  	size = FTP_BUFSIZE;
  	rcvd = 0;
  	if (ftp->extra) {
  		memmove(ftp->inbuf, ftp->extra, ftp->extralen);
  		rcvd = ftp->extralen;
  	}
  
  	data = ftp->inbuf;
  
  	do {
  		size -= rcvd;
  		for (eol = data; rcvd; rcvd--, eol++) {
  			if (*eol == '\r') {
  				*eol = 0;
  				ftp->extra = eol + 1;
  				if (rcvd > 1 && *(eol + 1) == '\n') {
  					ftp->extra++;
  					rcvd--;
  				}
  				if ((ftp->extralen = --rcvd) == 0) {
  					ftp->extra = NULL;
  				}
  				return 1;
  			} else if (*eol == '\n') {
  				*eol = 0;
  				ftp->extra = eol + 1;
  				if ((ftp->extralen = --rcvd) == 0) {
  					ftp->extra = NULL;
  				}
  				return 1;
  			}
  		}
  
  		data = eol;
  		if ((rcvd = my_recv(ftp, ftp->fd, data, size)) < 1) {
  			return 0;
  		}
  	} while (size);
  
  	return 0;
  }
  /* }}} */
  
  /* {{{ ftp_getresp
   */
  int
  ftp_getresp(ftpbuf_t *ftp)
  {
  	if (ftp == NULL) {
  		return 0;
  	}
  	ftp->resp = 0;
  
  	while (1) {
  
  		if (!ftp_readline(ftp)) {
  			return 0;
  		}
  
  		/* Break out when the end-tag is found */
  		if (isdigit(ftp->inbuf[0]) && isdigit(ftp->inbuf[1]) && isdigit(ftp->inbuf[2]) && ftp->inbuf[3] == ' ') {
  			break;
  		}
  	}
  
  	/* translate the tag */
  	if (!isdigit(ftp->inbuf[0]) || !isdigit(ftp->inbuf[1]) || !isdigit(ftp->inbuf[2])) {
  		return 0;
  	}
  
  	ftp->resp = 100 * (ftp->inbuf[0] - '0') + 10 * (ftp->inbuf[1] - '0') + (ftp->inbuf[2] - '0');
  
  	memmove(ftp->inbuf, ftp->inbuf + 4, FTP_BUFSIZE - 4);
  
  	if (ftp->extra) {
  		ftp->extra -= 4;
  	}
  	return 1;
  }
  /* }}} */
  
  /* {{{ my_send
   */
  int
  my_send(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t len)
  {
  	zend_long		size, sent;
      int         n;
  #ifdef HAVE_FTP_SSL
  	int err;
  	zend_bool retry = 0;
  	SSL *handle = NULL;
  	php_socket_t fd;
  #endif
  
  
  	size = len;
  	while (size) {
  		n = php_pollfd_for_ms(s, POLLOUT, ftp->timeout_sec * 1000);
  
  		if (n < 1) {
  #ifdef PHP_WIN32
  			if (n == 0) {
  				_set_errno(ETIMEDOUT);
  			}
  #else
  			if (n == 0) {
  				errno = ETIMEDOUT;
  			}
  #endif
  			return -1;
  		}
  
  #ifdef HAVE_FTP_SSL
  		if (ftp->use_ssl && ftp->fd == s && ftp->ssl_active) {
  			handle = ftp->ssl_handle;
  			fd = ftp->fd;
  		} else if (ftp->use_ssl && ftp->fd != s && ftp->use_ssl_for_data && ftp->data->ssl_active) {
  			handle = ftp->data->ssl_handle;
  			fd = ftp->data->fd;
  		}
  
  		if (handle) {
  			do {
  				sent = SSL_write(handle, buf, size);
  				err = SSL_get_error(handle, sent);
  
  				switch (err) {
  					case SSL_ERROR_NONE:
  						retry = 0;
  						break;
  
  					case SSL_ERROR_ZERO_RETURN:
  						retry = 0;
  						SSL_shutdown(handle);
  						break;
  
  					case SSL_ERROR_WANT_READ:
  					case SSL_ERROR_WANT_CONNECT: {
  							php_pollfd p;
  							int i;
  
  							p.fd = fd;
  							p.events = POLLOUT;
  							p.revents = 0;
  
  							i = php_poll2(&p, 1, 300);
  
  							retry = i > 0;
  						}
  						break;
  
  					default:
  						php_error_docref(NULL, E_WARNING, "SSL write failed");
  						return -1;
  				}
  			} while (retry);
  		} else {
  #endif
  			sent = send(s, buf, size, 0);
  #ifdef HAVE_FTP_SSL
  		}
  #endif
  		if (sent == -1) {
  			return -1;
  		}
  
  		buf = (char*) buf + sent;
  		size -= sent;
  	}
  
  	return len;
  }
  /* }}} */
  
  /* {{{ my_recv
   */
  int
  my_recv(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t len)
  {
  	int		n, nr_bytes;
  #ifdef HAVE_FTP_SSL
  	int err;
  	zend_bool retry = 0;
  	SSL *handle = NULL;
  	php_socket_t fd;
  #endif
  
  	n = php_pollfd_for_ms(s, PHP_POLLREADABLE, ftp->timeout_sec * 1000);
  	if (n < 1) {
  #ifdef PHP_WIN32
  		if (n == 0) {
  			_set_errno(ETIMEDOUT);
  		}
  #else
  		if (n == 0) {
  			errno = ETIMEDOUT;
  		}
  #endif
  		return -1;
  	}
  
  #ifdef HAVE_FTP_SSL
  	if (ftp->use_ssl && ftp->fd == s && ftp->ssl_active) {
  		handle = ftp->ssl_handle;
  		fd = ftp->fd;
  	} else if (ftp->use_ssl && ftp->fd != s && ftp->use_ssl_for_data && ftp->data->ssl_active) {
  		handle = ftp->data->ssl_handle;
  		fd = ftp->data->fd;
  	}
  
  	if (handle) {
  		do {
  			nr_bytes = SSL_read(handle, buf, len);
  			err = SSL_get_error(handle, nr_bytes);
  
  			switch (err) {
  				case SSL_ERROR_NONE:
  					retry = 0;
  					break;
  
  				case SSL_ERROR_ZERO_RETURN:
  					retry = 0;
  					SSL_shutdown(handle);
  					break;
  
  				case SSL_ERROR_WANT_READ:
  				case SSL_ERROR_WANT_CONNECT: {
  						php_pollfd p;
  						int i;
  
  						p.fd = fd;
  						p.events = POLLIN|POLLPRI;
  						p.revents = 0;
  
  						i = php_poll2(&p, 1, 300);
  
  						retry = i > 0;
  					}
  					break;
  
  				default:
  					php_error_docref(NULL, E_WARNING, "SSL read failed");
  					return -1;
  			}
  		} while (retry);
  	} else {
  #endif
  		nr_bytes = recv(s, buf, len, 0);
  #ifdef HAVE_FTP_SSL
  	}
  #endif
  	return (nr_bytes);
  }
  /* }}} */
  
  /* {{{ data_available
   */
  int
  data_available(ftpbuf_t *ftp, php_socket_t s)
  {
  	int		n;
  
  	n = php_pollfd_for_ms(s, PHP_POLLREADABLE, 1000);
  	if (n < 1) {
  #ifdef PHP_WIN32
  		if (n == 0) {
  			_set_errno(ETIMEDOUT);
  		}
  #else
  		if (n == 0) {
  			errno = ETIMEDOUT;
  		}
  #endif
  		return 0;
  	}
  
  	return 1;
  }
  /* }}} */
  /* {{{ data_writeable
   */
  int
  data_writeable(ftpbuf_t *ftp, php_socket_t s)
  {
  	int		n;
  
  	n = php_pollfd_for_ms(s, POLLOUT, 1000);
  	if (n < 1) {
  #ifdef PHP_WIN32
  		if (n == 0) {
  			_set_errno(ETIMEDOUT);
  		}
  #else
  		if (n == 0) {
  			errno = ETIMEDOUT;
  		}
  #endif
  		return 0;
  	}
  
  	return 1;
  }
  /* }}} */
  
  /* {{{ my_accept
   */
  int
  my_accept(ftpbuf_t *ftp, php_socket_t s, struct sockaddr *addr, socklen_t *addrlen)
  {
  	int		n;
  
  	n = php_pollfd_for_ms(s, PHP_POLLREADABLE, ftp->timeout_sec * 1000);
  	if (n < 1) {
  #ifdef PHP_WIN32
  		if (n == 0) {
  			_set_errno(ETIMEDOUT);
  		}
  #else
  		if (n == 0) {
  			errno = ETIMEDOUT;
  		}
  #endif
  		return -1;
  	}
  
  	return accept(s, addr, addrlen);
  }
  /* }}} */
  
  /* {{{ ftp_getdata
   */
  databuf_t*
  ftp_getdata(ftpbuf_t *ftp)
  {
  	int				fd = -1;
  	databuf_t		*data;
  	php_sockaddr_storage addr;
  	struct sockaddr *sa;
  	socklen_t		size;
  	union ipbox		ipbox;
  	char			arg[sizeof("255, 255, 255, 255, 255, 255")];
  	struct timeval	tv;
  	int				arg_len;
  
  
  	/* ask for a passive connection if we need one */
  	if (ftp->pasv && !ftp_pasv(ftp, 1)) {
  		return NULL;
  	}
  	/* alloc the data structure */
  	data = ecalloc(1, sizeof(*data));
  	data->listener = -1;
  	data->fd = -1;
  	data->type = ftp->type;
  
  	sa = (struct sockaddr *) &ftp->localaddr;
  	/* bind/listen */
  	if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) == SOCK_ERR) {
  		php_error_docref(NULL, E_WARNING, "socket() failed: %s (%d)", strerror(errno), errno);
  		goto bail;
  	}
  
  	/* passive connection handler */
  	if (ftp->pasv) {
  		/* clear the ready status */
  		ftp->pasv = 1;
  
  		/* connect */
  		/* Win 95/98 seems not to like size > sizeof(sockaddr_in) */
  		size = php_sockaddr_size(&ftp->pasvaddr);
  		tv.tv_sec = ftp->timeout_sec;
  		tv.tv_usec = 0;
  		if (php_connect_nonb(fd, (struct sockaddr*) &ftp->pasvaddr, size, &tv) == -1) {
  			php_error_docref(NULL, E_WARNING, "php_connect_nonb() failed: %s (%d)", strerror(errno), errno);
  			goto bail;
  		}
  
  		data->fd = fd;
  
  		ftp->data = data;
  		return data;
  	}
  
  
  	/* active (normal) connection */
  
  	/* bind to a local address */
  	php_any_addr(sa->sa_family, &addr, 0);
  	size = php_sockaddr_size(&addr);
  
  	if (bind(fd, (struct sockaddr*) &addr, size) != 0) {
  		php_error_docref(NULL, E_WARNING, "bind() failed: %s (%d)", strerror(errno), errno);
  		goto bail;
  	}
  
  	if (getsockname(fd, (struct sockaddr*) &addr, &size) != 0) {
  		php_error_docref(NULL, E_WARNING, "getsockname() failed: %s (%d)", strerror(errno), errno);
  		goto bail;
  	}
  
  	if (listen(fd, 5) != 0) {
  		php_error_docref(NULL, E_WARNING, "listen() failed: %s (%d)", strerror(errno), errno);
  		goto bail;
  	}
  
  	data->listener = fd;
  
  #if HAVE_IPV6 && HAVE_INET_NTOP
  	if (sa->sa_family == AF_INET6) {
  		/* need to use EPRT */
  		char eprtarg[INET6_ADDRSTRLEN + sizeof("|x||xxxxx|")];
  		char out[INET6_ADDRSTRLEN];
  		int eprtarg_len;
  		inet_ntop(AF_INET6, &((struct sockaddr_in6*) sa)->sin6_addr, out, sizeof(out));
  		eprtarg_len = snprintf(eprtarg, sizeof(eprtarg), "|2|%s|%hu|", out, ntohs(((struct sockaddr_in6 *) &addr)->sin6_port));
  
  		if (eprtarg_len < 0) {
  			goto bail;
  		}
  
  		if (!ftp_putcmd(ftp, "EPRT", sizeof("EPRT")-1, eprtarg, eprtarg_len)) {
  			goto bail;
  		}
  
  		if (!ftp_getresp(ftp) || ftp->resp != 200) {
  			goto bail;
  		}
  
  		ftp->data = data;
  		return data;
  	}
  #endif
  
  	/* send the PORT */
  	ipbox.ia[0] = ((struct sockaddr_in*) sa)->sin_addr;
  	ipbox.s[2] = ((struct sockaddr_in*) &addr)->sin_port;
  	arg_len = snprintf(arg, sizeof(arg), "%u,%u,%u,%u,%u,%u", ipbox.c[0], ipbox.c[1], ipbox.c[2], ipbox.c[3], ipbox.c[4], ipbox.c[5]);
  
  	if (arg_len < 0) {
  		goto bail;
  	}
  	if (!ftp_putcmd(ftp, "PORT", sizeof("PORT")-1, arg, arg_len)) {
  		goto bail;
  	}
  	if (!ftp_getresp(ftp) || ftp->resp != 200) {
  		goto bail;
  	}
  
  	ftp->data = data;
  	return data;
  
  bail:
  	if (fd != -1) {
  		closesocket(fd);
  	}
  	efree(data);
  	return NULL;
  }
  /* }}} */
  
  /* {{{ data_accept
   */
  databuf_t*
  data_accept(databuf_t *data, ftpbuf_t *ftp)
  {
  	php_sockaddr_storage addr;
  	socklen_t			size;
  
  #ifdef HAVE_FTP_SSL
  	SSL_CTX		*ctx;
  	SSL_SESSION *session;
  	int err, res;
  	zend_bool retry;
  #endif
  
  	if (data->fd != -1) {
  		goto data_accepted;
  	}
  	size = sizeof(addr);
  	data->fd = my_accept(ftp, data->listener, (struct sockaddr*) &addr, &size);
  	closesocket(data->listener);
  	data->listener = -1;
  
  	if (data->fd == -1) {
  		efree(data);
  		return NULL;
  	}
  
  data_accepted:
  #ifdef HAVE_FTP_SSL
  
  	/* now enable ssl if we need to */
  	if (ftp->use_ssl && ftp->use_ssl_for_data) {
  		ctx = SSL_get_SSL_CTX(ftp->ssl_handle);
  		if (ctx == NULL) {
  			php_error_docref(NULL, E_WARNING, "data_accept: failed to retreive the existing SSL context");
  			return 0;
  		}
  
  		data->ssl_handle = SSL_new(ctx);
  		if (data->ssl_handle == NULL) {
  			php_error_docref(NULL, E_WARNING, "data_accept: failed to create the SSL handle");
  			return 0;
  		}
  
  		SSL_set_fd(data->ssl_handle, data->fd);
  
  		if (ftp->old_ssl) {
  			SSL_copy_session_id(data->ssl_handle, ftp->ssl_handle);
  		}
  
  		/* get the session from the control connection so we can re-use it */
  		session = SSL_get_session(ftp->ssl_handle);
  		if (session == NULL) {
  			php_error_docref(NULL, E_WARNING, "data_accept: failed to retreive the existing SSL session");
  			SSL_free(data->ssl_handle);
  			return 0;
  		}
  
  		/* and set it on the data connection */
  		res = SSL_set_session(data->ssl_handle, session);
  		if (res == 0) {
  			php_error_docref(NULL, E_WARNING, "data_accept: failed to set the existing SSL session");
  			SSL_free(data->ssl_handle);
  			return 0;
  		}
  
  		do {
  			res = SSL_connect(data->ssl_handle);
  			err = SSL_get_error(data->ssl_handle, res);
  
  			switch (err) {
  				case SSL_ERROR_NONE:
  					retry = 0;
  					break;
  
  				case SSL_ERROR_ZERO_RETURN:
  					retry = 0;
  					SSL_shutdown(data->ssl_handle);
  					break;
  
  				case SSL_ERROR_WANT_READ:
  				case SSL_ERROR_WANT_WRITE: {
  						php_pollfd p;
  						int i;
  
  						p.fd = ftp->fd;
  						p.events = (err == SSL_ERROR_WANT_READ) ? (POLLIN|POLLPRI) : POLLOUT;
  						p.revents = 0;
  
  						i = php_poll2(&p, 1, 300);
  
  						retry = i > 0;
  					}
  					break;
  
  				default:
  					php_error_docref(NULL, E_WARNING, "data_accept: SSL/TLS handshake failed");
  					SSL_shutdown(data->ssl_handle);
  					SSL_free(data->ssl_handle);
  					return 0;
  			}
  		} while (retry);
  
  		data->ssl_active = 1;
  	}
  
  #endif
  
  	return data;
  }
  /* }}} */
  
  /* {{{ data_close
   */
  databuf_t*
  data_close(ftpbuf_t *ftp, databuf_t *data)
  {
  	if (data == NULL) {
  		return NULL;
  	}
  	if (data->listener != -1) {
  #ifdef HAVE_FTP_SSL
  		if (data->ssl_active) {
  			/* don't free the data context, it's the same as the control */
  			SSL_shutdown(data->ssl_handle);
  			SSL_free(data->ssl_handle);
  			data->ssl_active = 0;
  		}
  #endif
  		closesocket(data->listener);
  	}
  	if (data->fd != -1) {
  #ifdef HAVE_FTP_SSL
  		if (data->ssl_active) {
  			/* don't free the data context, it's the same as the control */
  			SSL_shutdown(data->ssl_handle);
  			SSL_free(data->ssl_handle);
  			data->ssl_active = 0;
  		}
  #endif
  		closesocket(data->fd);
  	}
  	if (ftp) {
  		ftp->data = NULL;
  	}
  	efree(data);
  	return NULL;
  }
  /* }}} */
  
  /* {{{ ftp_genlist
   */
  char**
  ftp_genlist(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len, const char *path, const size_t path_len)
  {
  	php_stream	*tmpstream = NULL;
  	databuf_t	*data = NULL;
  	char		*ptr;
  	int		ch, lastch;
  	size_t		size, rcvd;
  	size_t		lines;
  	char		**ret = NULL;
  	char		**entry;
  	char		*text;
  
  
  	if ((tmpstream = php_stream_fopen_tmpfile()) == NULL) {
  		php_error_docref(NULL, E_WARNING, "Unable to create temporary file.  Check permissions in temporary files directory.");
  		return NULL;
  	}
  
  	if (!ftp_type(ftp, FTPTYPE_ASCII)) {
  		goto bail;
  	}
  
  	if ((data = ftp_getdata(ftp)) == NULL) {
  		goto bail;
  	}
  	ftp->data = data;
  
  	if (!ftp_putcmd(ftp, cmd, cmd_len, path, path_len)) {
  		goto bail;
  	}
  	if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125 && ftp->resp != 226)) {
  		goto bail;
  	}
  
  	/* some servers don't open a ftp-data connection if the directory is empty */
  	if (ftp->resp == 226) {
  		ftp->data = data_close(ftp, data);
  		php_stream_close(tmpstream);
  		return ecalloc(1, sizeof(char*));
  	}
  
  	/* pull data buffer into tmpfile */
  	if ((data = data_accept(data, ftp)) == NULL) {
  		goto bail;
  	}
  	size = 0;
  	lines = 0;
  	lastch = 0;
  	while ((rcvd = my_recv(ftp, data->fd, data->buf, FTP_BUFSIZE))) {
  		if (rcvd == (size_t)-1 || rcvd > ((size_t)(-1))-size) {
  			goto bail;
  		}
  
  		php_stream_write(tmpstream, data->buf, rcvd);
  
  		size += rcvd;
  		for (ptr = data->buf; rcvd; rcvd--, ptr++) {
  			if (*ptr == '\n' && lastch == '\r') {
  				lines++;
  			}
  			lastch = *ptr;
  		}
  	}
  
  	ftp->data = data_close(ftp, data);
  
  	php_stream_rewind(tmpstream);
  
  	ret = safe_emalloc((lines + 1), sizeof(char*), size);
  
  	entry = ret;
  	text = (char*) (ret + lines + 1);
  	*entry = text;
  	lastch = 0;
  	while ((ch = php_stream_getc(tmpstream)) != EOF) {
  		if (ch == '\n' && lastch == '\r') {
  			*(text - 1) = 0;
  			*++entry = text;
  		} else {
  			*text++ = ch;
  		}
  		lastch = ch;
  	}
  	*entry = NULL;
  
  	php_stream_close(tmpstream);
  
  	if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) {
  		efree(ret);
  		return NULL;
  	}
  
  	return ret;
  bail:
  	ftp->data = data_close(ftp, data);
  	php_stream_close(tmpstream);
  	if (ret)
  		efree(ret);
  	return NULL;
  }
  /* }}} */
  
  /* {{{ ftp_nb_get
   */
  int
  ftp_nb_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_t path_len, ftptype_t type, zend_long resumepos)
  {
  	databuf_t		*data = NULL;
  	char			arg[11];
  
  	if (ftp == NULL) {
  		return PHP_FTP_FAILED;
  	}
  
  	if (!ftp_type(ftp, type)) {
  		goto bail;
  	}
  
  	if ((data = ftp_getdata(ftp)) == NULL) {
  		goto bail;
  	}
  
  	if (resumepos>0) {
  		int arg_len = snprintf(arg, sizeof(arg), ZEND_LONG_FMT, resumepos);
  
  		if (arg_len < 0) {
  			goto bail;
  		}
  		if (!ftp_putcmd(ftp, "REST", sizeof("REST")-1, arg, arg_len)) {
  			goto bail;
  		}
  		if (!ftp_getresp(ftp) || (ftp->resp != 350)) {
  			goto bail;
  		}
  	}
  
  	if (!ftp_putcmd(ftp, "RETR", sizeof("RETR")-1, path, path_len)) {
  		goto bail;
  	}
  	if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) {
  		goto bail;
  	}
  
  	if ((data = data_accept(data, ftp)) == NULL) {
  		goto bail;
  	}
  
  	ftp->data = data;
  	ftp->stream = outstream;
  	ftp->lastch = 0;
  	ftp->nb = 1;
  
  	return (ftp_nb_continue_read(ftp));
  
  bail:
  	ftp->data = data_close(ftp, data);
  	return PHP_FTP_FAILED;
  }
  /* }}} */
  
  /* {{{ ftp_nb_continue_read
   */
  int
  ftp_nb_continue_read(ftpbuf_t *ftp)
  {
  	databuf_t	*data = NULL;
  	char		*ptr;
  	int		lastch;
  	size_t		rcvd;
  	ftptype_t	type;
  
  	data = ftp->data;
  
  	/* check if there is already more data */
  	if (!data_available(ftp, data->fd)) {
  		return PHP_FTP_MOREDATA;
  	}
  
  	type = ftp->type;
  
  	lastch = ftp->lastch;
  	if ((rcvd = my_recv(ftp, data->fd, data->buf, FTP_BUFSIZE))) {
  		if (rcvd == (size_t)-1) {
  			goto bail;
  		}
  
  		if (type == FTPTYPE_ASCII) {
  			for (ptr = data->buf; rcvd; rcvd--, ptr++) {
  				if (lastch == '\r' && *ptr != '\n') {
  					php_stream_putc(ftp->stream, '\r');
  				}
  				if (*ptr != '\r') {
  					php_stream_putc(ftp->stream, *ptr);
  				}
  				lastch = *ptr;
  			}
  		} else if (rcvd != php_stream_write(ftp->stream, data->buf, rcvd)) {
  			goto bail;
  		}
  
  		ftp->lastch = lastch;
  		return PHP_FTP_MOREDATA;
  	}
  
  	if (type == FTPTYPE_ASCII && lastch == '\r') {
  		php_stream_putc(ftp->stream, '\r');
  	}
  
  	ftp->data = data = data_close(ftp, data);
  
  	if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) {
  		goto bail;
  	}
  
  	ftp->nb = 0;
  	return PHP_FTP_FINISHED;
  bail:
  	ftp->nb = 0;
  	ftp->data = data_close(ftp, data);
  	return PHP_FTP_FAILED;
  }
  /* }}} */
  
  /* {{{ ftp_nb_put
   */
  int
  ftp_nb_put(ftpbuf_t *ftp, const char *path, const size_t path_len, php_stream *instream, ftptype_t type, zend_long startpos)
  {
  	databuf_t		*data = NULL;
  	char			arg[11];
  
  	if (ftp == NULL) {
  		return 0;
  	}
  	if (!ftp_type(ftp, type)) {
  		goto bail;
  	}
  	if ((data = ftp_getdata(ftp)) == NULL) {
  		goto bail;
  	}
  	if (startpos > 0) {
  		int arg_len = snprintf(arg, sizeof(arg), ZEND_LONG_FMT, startpos);
  
  		if (arg_len < 0) {
  			goto bail;
  		}
  		if (!ftp_putcmd(ftp, "REST", sizeof("REST")-1, arg, arg_len)) {
  			goto bail;
  		}
  		if (!ftp_getresp(ftp) || (ftp->resp != 350)) {
  			goto bail;
  		}
  	}
  
  	if (!ftp_putcmd(ftp, "STOR", sizeof("STOR")-1, path, path_len)) {
  		goto bail;
  	}
  	if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) {
  		goto bail;
  	}
  	if ((data = data_accept(data, ftp)) == NULL) {
  		goto bail;
  	}
  	ftp->data = data;
  	ftp->stream = instream;
  	ftp->lastch = 0;
  	ftp->nb = 1;
  
  	return (ftp_nb_continue_write(ftp));
  
  bail:
  	ftp->data = data_close(ftp, data);
  	return PHP_FTP_FAILED;
  }
  /* }}} */
  
  
  /* {{{ ftp_nb_continue_write
   */
  int
  ftp_nb_continue_write(ftpbuf_t *ftp)
  {
  	long			size;
  	char			*ptr;
  	int 			ch;
  
  	/* check if we can write more data */
  	if (!data_writeable(ftp, ftp->data->fd)) {
  		return PHP_FTP_MOREDATA;
  	}
  
  	size = 0;
  	ptr = ftp->data->buf;
  	while (!php_stream_eof(ftp->stream) && (ch = php_stream_getc(ftp->stream)) != EOF) {
  
  		if (ch == '\n' && ftp->type == FTPTYPE_ASCII) {
  			*ptr++ = '\r';
  			size++;
  		}
  
  		*ptr++ = ch;
  		size++;
  
  		/* flush if necessary */
  		if (FTP_BUFSIZE - size < 2) {
  			if (my_send(ftp, ftp->data->fd, ftp->data->buf, size) != size) {
  				goto bail;
  			}
  			return PHP_FTP_MOREDATA;
  		}
  	}
  
  	if (size && my_send(ftp, ftp->data->fd, ftp->data->buf, size) != size) {
  		goto bail;
  	}
  	ftp->data = data_close(ftp, ftp->data);
  
  	if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) {
  		goto bail;
  	}
  	ftp->nb = 0;
  	return PHP_FTP_FINISHED;
  bail:
  	ftp->data = data_close(ftp, ftp->data);
  	ftp->nb = 0;
  	return PHP_FTP_FAILED;
  }
  /* }}} */
  
  #endif /* HAVE_FTP */
  
  /*
   * Local variables:
   * tab-width: 4
   * c-basic-offset: 4
   * End:
   * vim600: sw=4 ts=4 fdm=marker
   * vim<600: sw=4 ts=4
   */
  