diff --git a/ext/ftp/ftp.c b/ext/ftp/ftp.c index 50678ce66c6..405843fcdd9 100644 --- a/ext/ftp/ftp.c +++ b/ext/ftp/ftp.c @@ -98,7 +98,7 @@ static databuf_t* ftp_getdata(ftpbuf_t *ftp); static databuf_t* data_accept(databuf_t *data, ftpbuf_t *ftp); /* closes the data connection, returns NULL */ -static databuf_t* data_close(databuf_t *data); +static databuf_t* data_close(ftpbuf_t *ftp, databuf_t *data); /* generic file lister */ static char** ftp_genlist(ftpbuf_t *ftp, @@ -168,10 +168,16 @@ ftp_close(ftpbuf_t *ftp) { if (ftp == NULL) return NULL; - if (ftp->fd != -1) - closesocket(ftp->fd); if (ftp->data) - data_close(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); free(ftp); return NULL; @@ -218,9 +224,80 @@ ftp_quit(ftpbuf_t *ftp) int ftp_login(ftpbuf_t *ftp, const char *user, const char *pass) { +#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, "ftp_login: 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, "ftp_login: 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, "ftp_login: 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)) @@ -585,6 +662,8 @@ ftp_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, ftptype_t type, if ((data = ftp_getdata(ftp)) == NULL) { goto bail; } + + ftp->data = data; if (resumepos>0) { sprintf(arg, "%u", resumepos); @@ -631,7 +710,8 @@ ftp_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, ftptype_t type, if (type == FTPTYPE_ASCII && lastch == '\r') php_stream_putc(outstream, '\r'); - data = data_close(data); + data = data_close(ftp, data); + ftp->data = NULL; if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) { goto bail; @@ -639,7 +719,8 @@ ftp_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, ftptype_t type, return 1; bail: - data_close(data); + data_close(ftp, data); + ftp->data = NULL; return 0; } /* }}} */ @@ -664,6 +745,8 @@ ftp_put(ftpbuf_t *ftp, const char *path, php_stream *instream, ftptype_t type, i if ((data = ftp_getdata(ftp)) == NULL) goto bail; + + ftp->data = data; if (startpos>0) { sprintf(arg, "%u", startpos); @@ -706,14 +789,14 @@ ftp_put(ftpbuf_t *ftp, const char *path, php_stream *instream, ftptype_t type, i if (size && my_send(ftp, data->fd, data->buf, size) != size) goto bail; - data = data_close(data); + data = data_close(ftp, data); if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) goto bail; return 1; bail: - data_close(data); + data_close(ftp, data); return 0; } /* }}} */ @@ -997,6 +1080,14 @@ my_send(ftpbuf_t *ftp, int s, void *buf, size_t len) 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 (sent == -1) return -1; @@ -1016,7 +1107,7 @@ my_recv(ftpbuf_t *ftp, int s, void *buf, size_t len) { fd_set read_set; struct timeval tv; - int n; + int n, nr_bytes; tv.tv_sec = ftp->timeout_sec; tv.tv_usec = 0; @@ -1033,7 +1124,17 @@ my_recv(ftpbuf_t *ftp, int s, void *buf, size_t len) return -1; } - return recv(s, buf, len, 0); +#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); + + return (nr_bytes); } /* }}} */ @@ -1170,6 +1271,7 @@ ftp_getdata(ftpbuf_t *ftp) data->fd = fd; + ftp->data = data; return data; } @@ -1211,6 +1313,7 @@ ftp_getdata(ftpbuf_t *ftp) if (!ftp_getresp(ftp) || ftp->resp != 200) goto bail; + ftp->data = data; return data; } #endif @@ -1227,6 +1330,7 @@ ftp_getdata(ftpbuf_t *ftp) if (!ftp_getresp(ftp) || ftp->resp != 200) goto bail; + ftp->data = data; return data; bail: @@ -1242,11 +1346,14 @@ bail: databuf_t* data_accept(databuf_t *data, ftpbuf_t *ftp) { +#if HAVE_OPENSSL_EXT + SSL_CTX *ctx; +#endif php_sockaddr_storage addr; int size; if (data->fd != -1) - return data; + goto data_accepted; size = sizeof(addr); data->fd = my_accept(ftp, data->listener, (struct sockaddr*) &addr, &size); @@ -1258,6 +1365,42 @@ data_accept(databuf_t *data, ftpbuf_t *ftp) 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; } /* }}} */ @@ -1265,14 +1408,31 @@ data_accept(databuf_t *data, ftpbuf_t *ftp) /* {{{ data_close */ databuf_t* -data_close(databuf_t *data) +data_close(ftpbuf_t *ftp, databuf_t *data) { if (data == NULL) return NULL; - if (data->listener != -1) + 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 (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; + } free(data); return NULL; } @@ -1302,6 +1462,7 @@ ftp_genlist(ftpbuf_t *ftp, const char *cmd, const char *path) if ((data = ftp_getdata(ftp)) == NULL) goto bail; + ftp->data = data; if (!ftp_putcmd(ftp, cmd, path)) goto bail; @@ -1331,7 +1492,7 @@ ftp_genlist(ftpbuf_t *ftp, const char *cmd, const char *path) } } - data = data_close(data); + data = data_close(ftp, data); if (ferror(tmpfp)) goto bail; @@ -1374,7 +1535,7 @@ ftp_genlist(ftpbuf_t *ftp, const char *cmd, const char *path) return ret; bail: - data_close(data); + data_close(ftp, data); fclose(tmpfp); free(ret); return NULL; @@ -1430,7 +1591,7 @@ ftp_nb_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, ftptype_t typ return (ftp_nb_continue_read(ftp)); bail: - data_close(data); + data_close(ftp, data); return PHP_FTP_FAILED; } /* }}} */ @@ -1483,7 +1644,7 @@ ftp_nb_continue_read(ftpbuf_t *ftp) if (type == FTPTYPE_ASCII && lastch == '\r') php_stream_putc(ftp->stream, '\r'); - data = data_close(data); + data = data_close(ftp, data); if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) { goto bail; @@ -1493,7 +1654,7 @@ ftp_nb_continue_read(ftpbuf_t *ftp) return PHP_FTP_FINISHED; bail: ftp->nb = 0; - data_close(data); + data_close(ftp, data); return PHP_FTP_FAILED; } /* }}} */ @@ -1542,7 +1703,7 @@ ftp_nb_put(ftpbuf_t *ftp, const char *path, php_stream *instream, ftptype_t type return (ftp_nb_continue_write(ftp)); bail: - data_close(data); + data_close(ftp, data); return PHP_FTP_FAILED; } @@ -1589,7 +1750,7 @@ ftp_nb_continue_write(ftpbuf_t *ftp) if (size && my_send(ftp, ftp->data->fd, ftp->data->buf, size) != size) goto bail; - ftp->data = data_close(ftp->data); + ftp->data = data_close(ftp, ftp->data); if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) goto bail; @@ -1597,7 +1758,7 @@ ftp_nb_continue_write(ftpbuf_t *ftp) ftp->nb = 0; return PHP_FTP_FINISHED; bail: - data_close(ftp->data); + data_close(ftp, ftp->data); ftp->nb = 0; return PHP_FTP_FAILED; } diff --git a/ext/ftp/ftp.h b/ext/ftp/ftp.h index f4c816a3d3f..d93f028c28a 100644 --- a/ext/ftp/ftp.h +++ b/ext/ftp/ftp.h @@ -49,6 +49,8 @@ typedef struct databuf int fd; /* data connection */ ftptype_t type; /* transfer type */ char buf[FTP_BUFSIZE]; /* data buffer */ + SSL *ssl_handle; /* ssl handle */ + int ssl_active; /* flag if ssl is active or not */ } databuf_t; typedef struct ftpbuf @@ -74,6 +76,14 @@ typedef struct ftpbuf int lastch; /* last char of previous call */ int direction; /* recv = 0 / send = 1 */ int closestream;/* close or not close stream */ +#if HAVE_OPENSSL_EXT + int use_ssl; /* enable(1) or disable(0) ssl */ + int use_ssl_for_data; /* en/disable ssl for the dataconnection */ + int old_ssl; /* old mode = forced data encryption */ + SSL *ssl_handle; /* handle for control connection */ + int ssl_active; /* ssl active on control conn */ +#endif + } ftpbuf_t; diff --git a/ext/ftp/php_ftp.c b/ext/ftp/php_ftp.c index 55fcdb0b59e..7acff3e71a9 100644 --- a/ext/ftp/php_ftp.c +++ b/ext/ftp/php_ftp.c @@ -48,6 +48,9 @@ static int le_ftpbuf; function_entry php_ftp_functions[] = { PHP_FE(ftp_connect, NULL) +#if HAVE_OPENSSL_EXT + PHP_FE(ftp_ssl_connect, NULL) +#endif PHP_FE(ftp_login, NULL) PHP_FE(ftp_pwd, NULL) PHP_FE(ftp_cdup, NULL) @@ -162,11 +165,50 @@ PHP_FUNCTION(ftp_connect) /* autoseek for resuming */ ftp->autoseek = FTP_DEFAULT_AUTOSEEK; +#if HAVE_OPENSSL_EXT + /* disable ssl */ + ftp->use_ssl = 0; +#endif ZEND_REGISTER_RESOURCE(return_value, ftp, le_ftpbuf); } /* }}} */ +#if HAVE_OPENSSL_EXT +/* {{{ proto resource ftp_ssl_connect(string host [, int port [, int timeout)]]) + Opens a FTP-SSL stream */ +PHP_FUNCTION(ftp_ssl_connect) +{ + ftpbuf_t *ftp; + char *host; + int host_len, port = 0; + long timeout_sec = FTP_DEFAULT_TIMEOUT; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|ll", &host, &host_len, &port, &timeout_sec) == FAILURE) { + return; + } + + if (timeout_sec <= 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Timeout has to be greater than 0"); + RETURN_FALSE; + } + + /* connect */ + ftp = ftp_open(host, (short)port, timeout_sec TSRMLS_CC); + if (ftp == NULL) { + RETURN_FALSE; + } + + /* autoseek for resuming */ + ftp->autoseek = FTP_DEFAULT_AUTOSEEK; + /* enable ssl */ + ftp->use_ssl = 1; + + ZEND_REGISTER_RESOURCE(return_value, ftp, le_ftpbuf); +} +/* }}} */ +#endif + /* {{{ proto bool ftp_login(resource stream, string username, string password) Logs into the FTP server */ PHP_FUNCTION(ftp_login) diff --git a/ext/ftp/php_ftp.h b/ext/ftp/php_ftp.h index e0f7bd78711..ab10e9f269c 100644 --- a/ext/ftp/php_ftp.h +++ b/ext/ftp/php_ftp.h @@ -35,6 +35,9 @@ PHP_MINIT_FUNCTION(ftp); PHP_MINFO_FUNCTION(ftp); PHP_FUNCTION(ftp_connect); +#if HAVE_OPENSSL_EXT +PHP_FUNCTION(ftp_ssl_connect); +#endif PHP_FUNCTION(ftp_login); PHP_FUNCTION(ftp_pwd); PHP_FUNCTION(ftp_cdup);