php-src/ext/ftp/ftp.c
Wez Furlong 0a18a9d744 A add much more useful select(2) implementation than is provided by
windows sockets.  The winsock implementation will only work with sockets;
our implementation works with sockets and file descriptors.
By association, stream_select() will now operate correctly with files, pipes and sockets.

This change required linking against the winsock2 library.  In terms of
compatibility, only older versions of windows 95 do not have winsock2
installed by default.  It is available as a redistributable file, and is most likely installed by any OS patches (eg: Internet Explorer) applied by the user.

Also, add a win32 compatible pipe test when opening a stream from a pipe.  This test will only work on NT, win2k and XP platforms.  Without this test, interleaved fread() and select() calls would cause the read buffer to be clobbered.  I will be working on a fix for this issue for win9x.
2003-02-16 03:48:49 +00:00

1911 lines
35 KiB
C

/*
+----------------------------------------------------------------------+
| PHP Version 4 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2003 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 2.02 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available at through the world-wide-web at |
| http://www.php.net/license/2_02.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$ */
#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>
#elif defined(NETWARE)
#ifdef USE_WINSOCK /* Modified to use Winsock (NOVSOCK2.H), atleast for now */
#include <novsock2.h>
#else
#ifdef NEW_LIBC
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#else
#include <sys/socket.h>
#endif
#endif
#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
#include "ftp.h"
#include "ext/standard/fsock.h"
/* Additional headers for NetWare */
#if defined(NETWARE) && defined(NEW_LIBC) && !defined(USE_WINSOCK)
#include <sys/select.h>
#endif
/* 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 char *args);
/* wrapper around send/recv to handle timeouts */
static int my_send(ftpbuf_t *ftp, int s, void *buf, size_t len);
static int my_recv(ftpbuf_t *ftp, int s, void *buf, size_t len);
static int my_accept(ftpbuf_t *ftp, int s, struct sockaddr *addr,
int *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 TSRMLS_DC);
/* 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 char *path TSRMLS_DC);
/* IP and port conversion box */
union ipbox {
unsigned long l[2];
unsigned short s[4];
unsigned char c[8];
};
/* {{{ ftp_open
*/
ftpbuf_t*
ftp_open(const char *host, short port, long timeout_sec TSRMLS_DC)
{
ftpbuf_t *ftp;
int 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_hostconnect(host, (unsigned short) (port ? port : 21), SOCK_STREAM, &tv TSRMLS_CC);
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, (unsigned int*)&size) == -1) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "getsockname failed: %s (%d)\n", 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->fd != -1) {
#if HAVE_OPENSSL_EXT
if (ftp->ssl_active) {
SSL_shutdown(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);
}
if (ftp->syst) {
efree(ftp->syst);
}
}
/* }}} */
/* {{{ ftp_quit
*/
int
ftp_quit(ftpbuf_t *ftp)
{
if (ftp == NULL) {
return 0;
}
if (!ftp_putcmd(ftp, "QUIT", NULL)) {
return 0;
}
if (!ftp_getresp(ftp) || ftp->resp != 221) {
return 0;
}
if (ftp->pwd) {
efree(ftp->pwd);
}
return 1;
}
/* }}} */
/* {{{ ftp_login
*/
int
ftp_login(ftpbuf_t *ftp, const char *user, const char *pass TSRMLS_DC)
{
#if HAVE_OPENSSL_EXT
SSL_CTX *ctx = NULL;
#endif
if (ftp == NULL) {
return 0;
}
#if HAVE_OPENSSL_EXT
if (ftp->use_ssl && !ftp->ssl_active) {
if (!ftp_putcmd(ftp, "AUTH", "TLS")) {
return 0;
}
if (!ftp_getresp(ftp)) {
return 0;
}
if (ftp->resp != 234) {
if (!ftp_putcmd(ftp, "AUTH", "SSL")) {
return 0;
}
if (!ftp_getresp(ftp)) {
return 0;
}
if (ftp->resp != 334) {
ftp->use_ssl = 0;
} else {
ftp->old_ssl = 1;
ftp->use_ssl_for_data = 1;
}
}
/* now enable ssl if we still need to */
if (ftp->use_ssl) {
ctx = SSL_CTX_new(SSLv23_client_method());
if (ctx == NULL) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create the SSL context");
return 0;
}
ftp->ssl_handle = SSL_new(ctx);
if (ftp->ssl_handle == NULL) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "failed to create the SSL handle");
SSL_CTX_free(ctx);
return 0;
}
SSL_set_fd(ftp->ssl_handle, ftp->fd);
if (SSL_connect(ftp->ssl_handle) <= 0) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "SSL/TLS handshake failed");
SSL_shutdown(ftp->ssl_handle);
return 0;
}
ftp->ssl_active = 1;
if (!ftp->old_ssl) {
/* set protection buffersize to zero */
if (!ftp_putcmd(ftp, "PBSZ", "0")) {
return 0;
}
if (!ftp_getresp(ftp)) {
return 0;
}
/* enable data conn encryption */
if (!ftp_putcmd(ftp, "PROT", "P")) {
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", user)) {
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", pass)) {
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", NULL)) {
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", NULL)) {
return NULL;
}
if (!ftp_getresp(ftp) || ftp->resp != 215) {
return NULL;
}
syst = ftp->inbuf;
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", NULL)) {
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;
}
*end = 0;
ftp->pwd = estrdup(pwd);
*end = '"';
return ftp->pwd;
}
/* }}} */
/* {{{ ftp_exec
*/
int
ftp_exec(ftpbuf_t *ftp, const char *cmd)
{
if (ftp == NULL) {
return 0;
}
if (!ftp_putcmd(ftp, "SITE EXEC", cmd)) {
return 0;
}
if (!ftp_getresp(ftp) || ftp->resp != 200) {
return 0;
}
return 1;
}
/* }}} */
/* {{{ ftp_raw
*/
void
ftp_raw(ftpbuf_t *ftp, const char *cmd, zval *return_value)
{
if (ftp == NULL || cmd == NULL) {
RETURN_NULL();
}
if (!ftp_putcmd(ftp, cmd, NULL)) {
RETURN_NULL();
}
array_init(return_value);
while (ftp_readline(ftp)) {
add_next_index_string(return_value, ftp->inbuf, 1);
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)
{
if (ftp == NULL) {
return 0;
}
if (ftp->pwd) {
efree(ftp->pwd);
}
if (!ftp_putcmd(ftp, "CWD", dir)) {
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);
}
if (!ftp_putcmd(ftp, "CDUP", NULL)) {
return 0;
}
if (!ftp_getresp(ftp) || ftp->resp != 250) {
return 0;
}
return 1;
}
/* }}} */
/* {{{ ftp_mkdir
*/
char*
ftp_mkdir(ftpbuf_t *ftp, const char *dir)
{
char *mkd, *end;
if (ftp == NULL) {
return NULL;
}
if (!ftp_putcmd(ftp, "MKD", dir)) {
return NULL;
}
if (!ftp_getresp(ftp) || ftp->resp != 257) {
return NULL;
}
/* copy out the dir from response */
if ((mkd = strchr(ftp->inbuf, '"')) == NULL) {
mkd = estrdup(dir);
return mkd;
}
if ((end = strrchr(++mkd, '"')) == NULL) {
return NULL;
}
*end = 0;
mkd = estrdup(mkd);
*end = '"';
return mkd;
}
/* }}} */
/* {{{ ftp_rmdir
*/
int
ftp_rmdir(ftpbuf_t *ftp, const char *dir)
{
if (ftp == NULL) {
return 0;
}
if (!ftp_putcmd(ftp, "RMD", dir)) {
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;
if (ftp == NULL || filename_len <= 0) {
return 0;
}
if (!(buffer = emalloc(32 + filename_len + 1))) {
return 0;
}
sprintf(buffer, "CHMOD %o %s", mode, filename);
if (!ftp_putcmd(ftp, "SITE", buffer)) {
efree(buffer);
return 0;
}
efree(buffer);
if (!ftp_getresp(ftp) || ftp->resp != 200) {
return 0;
}
return 1;
}
/* }}} */
/* {{{ ftp_nlist
*/
char**
ftp_nlist(ftpbuf_t *ftp, const char *path TSRMLS_DC)
{
return ftp_genlist(ftp, "NLST", path TSRMLS_CC);
}
/* }}} */
/* {{{ ftp_list
*/
char**
ftp_list(ftpbuf_t *ftp, const char *path, int recursive TSRMLS_DC)
{
return ftp_genlist(ftp, ((recursive) ? "LIST -R" : "LIST"), path TSRMLS_CC);
}
/* }}} */
/* {{{ ftp_type
*/
int
ftp_type(ftpbuf_t *ftp, ftptype_t type)
{
char typechar[2] = "?";
if (ftp == NULL) {
return 0;
}
if (type == ftp->type) {
return 1;
}
if (type == FTPTYPE_ASCII) {
typechar[0] = 'A';
} else if (type == FTPTYPE_IMAGE) {
typechar[0] = 'I';
} else {
return 0;
}
if (!ftp_putcmd(ftp, "TYPE", typechar)) {
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];
int 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;
#ifdef HAVE_IPV6
if (getpeername(ftp->fd, sa, &n) < 0) {
return 0;
}
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", NULL)) {
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", NULL)) {
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;
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = ipbox.l[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, ftptype_t type, int resumepos TSRMLS_DC)
{
databuf_t *data = NULL;
char *ptr;
int lastch;
size_t rcvd;
char arg[11];
if (ftp == NULL) {
return 0;
}
if (!ftp_type(ftp, type)) {
goto bail;
}
if ((data = ftp_getdata(ftp TSRMLS_CC)) == NULL) {
goto bail;
}
ftp->data = data;
if (resumepos > 0) {
if (resumepos > 2147483647) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "PHP cannot handle files greater then 2147483647 bytes.\n");
goto bail;
}
sprintf(arg, "%u", resumepos);
if (!ftp_putcmd(ftp, "REST", arg)) {
goto bail;
}
if (!ftp_getresp(ftp) || (ftp->resp != 350)) {
goto bail;
}
}
if (!ftp_putcmd(ftp, "RETR", path)) {
goto bail;
}
if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) {
goto bail;
}
if ((data = data_accept(data, ftp)) == NULL) {
goto bail;
}
lastch = 0;
while ((rcvd = my_recv(ftp, data->fd, data->buf, FTP_BUFSIZE))) {
if (rcvd == -1) {
goto bail;
}
if (type == FTPTYPE_ASCII) {
for (ptr = data->buf; rcvd; rcvd--, ptr++) {
if (lastch == '\r' && *ptr != '\n')
php_stream_putc(outstream, '\r');
if (*ptr != '\r')
php_stream_putc(outstream, *ptr);
lastch = *ptr;
}
} else if (rcvd != php_stream_write(outstream, data->buf, rcvd)) {
goto bail;
}
}
if (type == FTPTYPE_ASCII && lastch == '\r') {
php_stream_putc(outstream, '\r');
}
data = data_close(ftp, data);
ftp->data = NULL;
if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) {
goto bail;
}
return 1;
bail:
data_close(ftp, data);
ftp->data = NULL;
return 0;
}
/* }}} */
/* {{{ ftp_put
*/
int
ftp_put(ftpbuf_t *ftp, const char *path, php_stream *instream, ftptype_t type, int startpos TSRMLS_DC)
{
databuf_t *data = NULL;
int 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 TSRMLS_CC)) == NULL) {
goto bail;
}
ftp->data = data;
if (startpos > 0) {
if (startpos > 2147483647) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "PHP cannot handle files with a size greater then 2147483647 bytes.\n");
goto bail;
}
sprintf(arg, "%u", startpos);
if (!ftp_putcmd(ftp, "REST", arg)) {
goto bail;
}
if (!ftp_getresp(ftp) || (ftp->resp != 350)) {
goto bail;
}
}
if (!ftp_putcmd(ftp, "STOR", path)) {
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;
}
data = data_close(ftp, data);
if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) {
goto bail;
}
return 1;
bail:
data_close(ftp, data);
return 0;
}
/* }}} */
/* {{{ ftp_size
*/
int
ftp_size(ftpbuf_t *ftp, const char *path)
{
if (ftp == NULL) {
return -1;
}
if (!ftp_type(ftp, FTPTYPE_IMAGE)) {
return -1;
}
if (!ftp_putcmd(ftp, "SIZE", path)) {
return -1;
}
if (!ftp_getresp(ftp) || ftp->resp != 213) {
return -1;
}
return atoi(ftp->inbuf);
}
/* }}} */
/* {{{ ftp_mdtm
*/
time_t
ftp_mdtm(ftpbuf_t *ftp, const char *path)
{
time_t stamp;
struct tm *gmt, tmbuf;
struct tm tm;
char *ptr;
int n;
if (ftp == NULL) {
return -1;
}
if (!ftp_putcmd(ftp, "MDTM", path)) {
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);
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)
{
if (ftp == NULL) {
return 0;
}
if (!ftp_putcmd(ftp, "DELE", path)) {
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 char *dest)
{
if (ftp == NULL) {
return 0;
}
if (!ftp_putcmd(ftp, "RNFR", src)) {
return 0;
}
if (!ftp_getresp(ftp) || ftp->resp != 350) {
return 0;
}
if (!ftp_putcmd(ftp, "RNTO", dest)) {
return 0;
}
if (!ftp_getresp(ftp) || ftp->resp != 250) {
return 0;
}
return 1;
}
/* }}} */
/* {{{ ftp_site
*/
int
ftp_site(ftpbuf_t *ftp, const char *cmd)
{
if (ftp == NULL) {
return 0;
}
if (!ftp_putcmd(ftp, "SITE", cmd)) {
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 char *args)
{
int size;
char *data;
/* build the output buffer */
if (args && args[0]) {
/* "cmd args\r\n\0" */
if (strlen(cmd) + strlen(args) + 4 > FTP_BUFSIZE) {
return 0;
}
size = sprintf(ftp->outbuf, "%s %s\r\n", cmd, args);
} else {
/* "cmd\r\n\0" */
if (strlen(cmd) + 3 > FTP_BUFSIZE) {
return 0;
}
size = sprintf(ftp->outbuf, "%s\r\n", cmd);
}
data = ftp->outbuf;
if (my_send(ftp, ftp->fd, data, size) != size) {
return 0;
}
return 1;
}
/* }}} */
/* {{{ ftp_readline
*/
int
ftp_readline(ftpbuf_t *ftp)
{
int 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)
{
char *buf;
if (ftp == NULL) {
return 0;
}
buf = ftp->inbuf;
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, int s, void *buf, size_t len)
{
fd_set write_set;
struct timeval tv;
int n, size, sent;
size = len;
while (size) {
tv.tv_sec = ftp->timeout_sec;
tv.tv_usec = 0;
FD_ZERO(&write_set);
FD_SET(s, &write_set);
n = select(s + 1, NULL, &write_set, NULL, &tv);
if (n < 1) {
#if !defined(PHP_WIN32) && !(defined(NETWARE) && defined(USE_WINSOCK))
if (n == 0) {
errno = ETIMEDOUT;
}
#endif
return -1;
}
#if HAVE_OPENSSL_EXT
if (ftp->use_ssl && ftp->fd == s && ftp->ssl_active) {
sent = SSL_write(ftp->ssl_handle, buf, size);
} else if (ftp->use_ssl && ftp->fd != s && ftp->use_ssl_for_data && ftp->data->ssl_active) {
sent = SSL_write(ftp->data->ssl_handle, buf, size);
} else {
#endif
sent = send(s, buf, size, 0);
#if HAVE_OPENSSL_EXT
}
#endif
if (sent == -1) {
return -1;
}
buf = (char*) buf + sent;
size -= sent;
}
return len;
}
/* }}} */
/* {{{ my_recv
*/
int
my_recv(ftpbuf_t *ftp, int s, void *buf, size_t len)
{
fd_set read_set;
struct timeval tv;
int n, nr_bytes;
tv.tv_sec = ftp->timeout_sec;
tv.tv_usec = 0;
FD_ZERO(&read_set);
FD_SET(s, &read_set);
n = select(s + 1, &read_set, NULL, NULL, &tv);
if (n < 1) {
#if !defined(PHP_WIN32) && !(defined(NETWARE) && defined(USE_WINSOCK))
if (n == 0) {
errno = ETIMEDOUT;
}
#endif
return -1;
}
#if HAVE_OPENSSL_EXT
if (ftp->use_ssl && ftp->fd == s && ftp->ssl_active) {
nr_bytes = SSL_read(ftp->ssl_handle, buf, len);
} else if (ftp->use_ssl && ftp->fd != s && ftp->use_ssl_for_data && ftp->data->ssl_active) {
nr_bytes = SSL_read(ftp->data->ssl_handle, buf, len);
} else {
#endif
nr_bytes = recv(s, buf, len, 0);
#if HAVE_OPENSSL_EXT
}
#endif
return (nr_bytes);
}
/* }}} */
/* {{{ data_available
*/
int
data_available(ftpbuf_t *ftp, int s)
{
fd_set read_set;
struct timeval tv;
int n;
tv.tv_sec = 0;
tv.tv_usec = 1;
FD_ZERO(&read_set);
FD_SET(s, &read_set);
n = select(s + 1, &read_set, NULL, NULL, &tv);
if (n < 1) {
#if !defined(PHP_WIN32) && !(defined(NETWARE) && defined(USE_WINSOCK))
if (n == 0) {
errno = ETIMEDOUT;
}
#endif
return 0;
}
return 1;
}
/* }}} */
/* {{{ data_writeable
*/
int
data_writeable(ftpbuf_t *ftp, int s)
{
fd_set write_set;
struct timeval tv;
int n;
tv.tv_sec = 0;
tv.tv_usec = 1;
FD_ZERO(&write_set);
FD_SET(s, &write_set);
n = select(s + 1, NULL, &write_set, NULL, &tv);
if (n < 1) {
#ifndef PHP_WIN32
if (n == 0) {
errno = ETIMEDOUT;
}
#endif
return 0;
}
return 1;
}
/* }}} */
/* {{{ my_accept
*/
int
my_accept(ftpbuf_t *ftp, int s, struct sockaddr *addr, int *addrlen)
{
fd_set accept_set;
struct timeval tv;
int n;
tv.tv_sec = ftp->timeout_sec;
tv.tv_usec = 0;
FD_ZERO(&accept_set);
FD_SET(s, &accept_set);
n = select(s + 1, &accept_set, NULL, NULL, &tv);
if (n < 1) {
#if !defined(PHP_WIN32) && !(defined(NETWARE) && defined(USE_WINSOCK))
if (n == 0) {
errno = ETIMEDOUT;
}
#endif
return -1;
}
return accept(s, addr, (unsigned int*)addrlen);
}
/* }}} */
/* {{{ ftp_getdata
*/
databuf_t*
ftp_getdata(ftpbuf_t *ftp TSRMLS_DC)
{
int fd = -1;
databuf_t *data;
php_sockaddr_storage addr;
struct sockaddr *sa;
int size;
union ipbox ipbox;
char arg[sizeof("255, 255, 255, 255, 255, 255")];
struct timeval tv;
/* 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)) == -1) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "socket() failed: %s (%d)\n", 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 TSRMLS_CC, E_WARNING, "php_connect_nonb() failed: %s (%d)\n", 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) == -1) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "bind() failed: %s (%d)\n", strerror(errno), errno);
goto bail;
}
if (getsockname(fd, (struct sockaddr*) &addr, (unsigned int*)&size) == -1) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "getsockname() failed: %s (%d)\n", strerror(errno), errno);
goto bail;
}
if (listen(fd, 5) == -1) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "listen() failed: %s (%d)\n", strerror(errno), errno);
goto bail;
}
data->listener = fd;
#ifdef HAVE_IPV6
if (sa->sa_family == AF_INET6) {
/* need to use EPRT */
char eprtarg[INET6_ADDRSTRLEN + sizeof("|x||xxxxx|")];
char out[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &((struct sockaddr_in6*) sa)->sin6_addr, out, sizeof(out));
sprintf(eprtarg, "|2|%s|%hu|", out, ntohs(((struct sockaddr_in6 *) &addr)->sin6_port));
if (!ftp_putcmd(ftp, "EPRT", eprtarg)) {
goto bail;
}
if (!ftp_getresp(ftp) || ftp->resp != 200) {
goto bail;
}
ftp->data = data;
return data;
}
#endif
/* send the PORT */
ipbox.l[0] = ((struct sockaddr_in*) sa)->sin_addr.s_addr;
ipbox.s[2] = ((struct sockaddr_in*) &addr)->sin_port;
sprintf(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 (!ftp_putcmd(ftp, "PORT", arg)) {
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;
int size;
#if HAVE_OPENSSL_EXT
SSL_CTX *ctx;
TSRMLS_FETCH();
#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:
#if HAVE_OPENSSL_EXT
/* now enable ssl if we need to */
if (ftp->use_ssl && ftp->use_ssl_for_data) {
ctx = SSL_CTX_new(SSLv23_client_method());
if (ctx == NULL) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "data_accept: failed to create the SSL context");
return 0;
}
data->ssl_handle = SSL_new(ctx);
if (data->ssl_handle == NULL) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "data_accept: failed to create the SSL handle");
SSL_CTX_free(ctx);
return 0;
}
SSL_set_fd(data->ssl_handle, data->fd);
if (ftp->old_ssl) {
SSL_copy_session_id(data->ssl_handle, ftp->ssl_handle);
}
if (SSL_connect(data->ssl_handle) <= 0) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "data_accept: SSL/TLS handshake failed");
SSL_shutdown(data->ssl_handle);
return 0;
}
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) {
#if HAVE_OPENSSL_EXT
if (data->ssl_active) {
SSL_shutdown(data->ssl_handle);
data->ssl_active = 0;
}
#endif
closesocket(data->listener);
}
if (data->fd != -1) {
#if HAVE_OPENSSL_EXT
if (data->ssl_active) {
SSL_shutdown(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 char *path TSRMLS_DC)
{
FILE *tmpfp = NULL;
databuf_t *data = NULL;
char *ptr;
int ch, lastch;
int size, rcvd;
int lines;
char **ret = NULL;
char **entry;
char *text;
if ((tmpfp = tmpfile()) == NULL) {
return NULL;
}
if (!ftp_type(ftp, FTPTYPE_ASCII)) {
goto bail;
}
if ((data = ftp_getdata(ftp TSRMLS_CC)) == NULL) {
goto bail;
}
ftp->data = data;
if (!ftp_putcmd(ftp, cmd, path)) {
goto bail;
}
if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) {
goto bail;
}
/* 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 == -1) {
goto bail;
}
fwrite(data->buf, rcvd, 1, tmpfp);
size += rcvd;
for (ptr = data->buf; rcvd; rcvd--, ptr++) {
if (*ptr == '\n' && lastch == '\r') {
lines++;
} else {
size++;
}
lastch = *ptr;
}
}
data = data_close(ftp, data);
if (ferror(tmpfp)) {
goto bail;
}
rewind(tmpfp);
ret = emalloc((lines + 1) * sizeof(char**) + size * sizeof(char*));
entry = ret;
text = (char*) (ret + lines + 1);
*entry = text;
lastch = 0;
while ((ch = getc(tmpfp)) != EOF) {
if (ch == '\n' && lastch == '\r') {
*(text - 1) = 0;
*++entry = text;
} else {
*text++ = ch;
}
lastch = ch;
}
*entry = NULL;
if (ferror(tmpfp)) {
goto bail;
}
fclose(tmpfp);
if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) {
efree(ret);
return NULL;
}
return ret;
bail:
if (data)
data_close(ftp, data);
fclose(tmpfp);
if (ret)
efree(ret);
return NULL;
}
/* }}} */
/* {{{ ftp_nb_get
*/
int
ftp_nb_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, ftptype_t type, int resumepos TSRMLS_DC)
{
databuf_t *data = NULL;
char arg[11];
if (ftp == NULL) {
goto bail;
}
if (!ftp_type(ftp, type)) {
goto bail;
}
if ((data = ftp_getdata(ftp TSRMLS_CC)) == NULL) {
goto bail;
}
if (resumepos>0) {
/* We are working on an architecture that supports 64-bit integers
* since php is 32 bit by design, we bail out with warning
*/
if (resumepos > 2147483647) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "PHP cannot handle files greater then 2147483648 bytes.\n");
goto bail;
}
sprintf(arg, "%u", resumepos);
if (!ftp_putcmd(ftp, "REST", arg)) {
goto bail;
}
if (!ftp_getresp(ftp) || (ftp->resp != 350)) {
goto bail;
}
}
if (!ftp_putcmd(ftp, "RETR", path)) {
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 TSRMLS_CC));
bail:
data_close(ftp, data);
return PHP_FTP_FAILED;
}
/* }}} */
/* {{{ ftp_nb_continue_read
*/
int
ftp_nb_continue_read(ftpbuf_t *ftp TSRMLS_DC)
{
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 == -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');
}
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;
data_close(ftp, data);
return PHP_FTP_FAILED;
}
/* }}} */
/* {{{ ftp_nb_put
*/
int
ftp_nb_put(ftpbuf_t *ftp, const char *path, php_stream *instream, ftptype_t type, int startpos TSRMLS_DC)
{
databuf_t *data = NULL;
char arg[11];
if (ftp == NULL) {
return 0;
}
if (!ftp_type(ftp, type)) {
goto bail;
}
if ((data = ftp_getdata(ftp TSRMLS_CC)) == NULL) {
goto bail;
}
if (startpos > 0) {
if (startpos > 2147483647) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "PHP cannot handle files with a size greater then 2147483647 bytes.\n");
goto bail;
}
sprintf(arg, "%u", startpos);
if (!ftp_putcmd(ftp, "REST", arg)) {
goto bail;
}
if (!ftp_getresp(ftp) || (ftp->resp != 350)) {
goto bail;
}
}
if (!ftp_putcmd(ftp, "STOR", path)) {
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 TSRMLS_CC));
bail:
data_close(ftp, data);
return PHP_FTP_FAILED;
}
/* }}} */
/* {{{ ftp_nb_continue_write
*/
int
ftp_nb_continue_write(ftpbuf_t *ftp TSRMLS_DC)
{
int 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:
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
*/