1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2014 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Authors: Rasmus Lerdorf <rasmus@php.net>                             |
16   |          Jim Winstead <jimw@php.net>                                 |
17   |          Hartmut Holzgraefe <hholzgra@php.net>                       |
18   |          Sara Golemon <pollita@php.net>                              |
19   +----------------------------------------------------------------------+
20 */
21/* $Id$ */
22
23#include "php.h"
24#include "php_globals.h"
25#include "php_network.h"
26#include "php_ini.h"
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <errno.h>
31#include <sys/types.h>
32#include <sys/stat.h>
33#include <fcntl.h>
34
35#ifdef PHP_WIN32
36#include <winsock2.h>
37#define O_RDONLY _O_RDONLY
38#include "win32/param.h"
39#else
40#include <sys/param.h>
41#endif
42
43#include "php_standard.h"
44
45#include <sys/types.h>
46#if HAVE_SYS_SOCKET_H
47#include <sys/socket.h>
48#endif
49
50#ifdef PHP_WIN32
51#include <winsock2.h>
52#elif defined(NETWARE) && defined(USE_WINSOCK)
53#include <novsock2.h>
54#else
55#include <netinet/in.h>
56#include <netdb.h>
57#if HAVE_ARPA_INET_H
58#include <arpa/inet.h>
59#endif
60#endif
61
62#if defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE)
63#undef AF_UNIX
64#endif
65
66#if defined(AF_UNIX)
67#include <sys/un.h>
68#endif
69
70#include "php_fopen_wrappers.h"
71
72#define FTPS_ENCRYPT_DATA 1
73#define GET_FTP_RESULT(stream)  get_ftp_result((stream), tmp_line, sizeof(tmp_line) TSRMLS_CC)
74
75typedef struct _php_ftp_dirstream_data {
76    php_stream *datastream;
77    php_stream *controlstream;
78    php_stream *dirstream;
79} php_ftp_dirstream_data;
80
81/* {{{ get_ftp_result
82 */
83static inline int get_ftp_result(php_stream *stream, char *buffer, size_t buffer_size TSRMLS_DC)
84{
85    while (php_stream_gets(stream, buffer, buffer_size-1) &&
86           !(isdigit((int) buffer[0]) && isdigit((int) buffer[1]) &&
87             isdigit((int) buffer[2]) && buffer[3] == ' '));
88    return strtol(buffer, NULL, 10);
89}
90/* }}} */
91
92/* {{{ php_stream_ftp_stream_stat
93 */
94static int php_stream_ftp_stream_stat(php_stream_wrapper *wrapper, php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC)
95{
96    /* For now, we return with a failure code to prevent the underlying
97     * file's details from being used instead. */
98    return -1;
99}
100/* }}} */
101
102/* {{{ php_stream_ftp_stream_close
103 */
104static int php_stream_ftp_stream_close(php_stream_wrapper *wrapper, php_stream *stream TSRMLS_DC)
105{
106    php_stream *controlstream = stream->wrapperthis;
107    int ret = 0;
108
109    if (controlstream) {
110        if (strpbrk(stream->mode, "wa+")) {
111            char tmp_line[512];
112            int result;
113
114            /* For write modes close data stream first to signal EOF to server */
115            result = GET_FTP_RESULT(controlstream);
116            if (result != 226 && result != 250) {
117                php_error_docref(NULL TSRMLS_CC, E_WARNING, "FTP server error %d:%s", result, tmp_line);
118                ret = EOF;
119            }
120        }
121
122        php_stream_write_string(controlstream, "QUIT\r\n");
123        php_stream_close(controlstream);
124        stream->wrapperthis = NULL;
125    }
126
127    return ret;
128}
129/* }}} */
130
131/* {{{ php_ftp_fopen_connect
132 */
133static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, const char *path, const char *mode, int options,
134                                         char **opened_path, php_stream_context *context, php_stream **preuseid,
135                                         php_url **presource, int *puse_ssl, int *puse_ssl_on_data TSRMLS_DC)
136{
137    php_stream *stream = NULL, *reuseid = NULL;
138    php_url *resource = NULL;
139    int result, use_ssl, use_ssl_on_data = 0, tmp_len;
140    char tmp_line[512];
141    char *transport;
142    int transport_len;
143
144    resource = php_url_parse(path);
145    if (resource == NULL || resource->path == NULL) {
146        if (resource && presource) {
147            *presource = resource;
148        }
149        return NULL;
150    }
151
152    use_ssl = resource->scheme && (strlen(resource->scheme) > 3) && resource->scheme[3] == 's';
153
154    /* use port 21 if one wasn't specified */
155    if (resource->port == 0)
156        resource->port = 21;
157
158    transport_len = spprintf(&transport, 0, "tcp://%s:%d", resource->host, resource->port);
159    stream = php_stream_xport_create(transport, transport_len, REPORT_ERRORS, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, NULL, NULL, context, NULL, NULL);
160    efree(transport);
161    if (stream == NULL) {
162        result = 0; /* silence */
163        goto connect_errexit;
164    }
165
166    php_stream_context_set(stream, context);
167    php_stream_notify_info(context, PHP_STREAM_NOTIFY_CONNECT, NULL, 0);
168
169    /* Start talking to ftp server */
170    result = GET_FTP_RESULT(stream);
171    if (result > 299 || result < 200) {
172        php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result);
173        goto connect_errexit;
174    }
175
176    if (use_ssl)    {
177
178        /* send the AUTH TLS request name */
179        php_stream_write_string(stream, "AUTH TLS\r\n");
180
181        /* get the response */
182        result = GET_FTP_RESULT(stream);
183        if (result != 234) {
184            /* AUTH TLS not supported try AUTH SSL */
185            php_stream_write_string(stream, "AUTH SSL\r\n");
186
187            /* get the response */
188            result = GET_FTP_RESULT(stream);
189            if (result != 334) {
190                use_ssl = 0;
191            } else {
192                /* we must reuse the old SSL session id */
193                /* if we talk to an old ftpd-ssl */
194                reuseid = stream;
195            }
196        } else {
197            /* encrypt data etc */
198
199
200        }
201
202    }
203
204    if (use_ssl) {
205        if (php_stream_xport_crypto_setup(stream,
206                STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0
207                || php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0) {
208            php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode");
209            php_stream_close(stream);
210            stream = NULL;
211            goto connect_errexit;
212        }
213
214        /* set PBSZ to 0 */
215        php_stream_write_string(stream, "PBSZ 0\r\n");
216
217        /* ignore the response */
218        result = GET_FTP_RESULT(stream);
219
220        /* set data connection protection level */
221#if FTPS_ENCRYPT_DATA
222        php_stream_write_string(stream, "PROT P\r\n");
223
224        /* get the response */
225        result = GET_FTP_RESULT(stream);
226        use_ssl_on_data = (result >= 200 && result<=299) || reuseid;
227#else
228        php_stream_write_string(stream, "PROT C\r\n");
229
230        /* get the response */
231        result = GET_FTP_RESULT(stream);
232#endif
233    }
234
235#define PHP_FTP_CNTRL_CHK(val, val_len, err_msg) {  \
236    unsigned char *s = val, *e = s + val_len;   \
237    while (s < e) { \
238        if (iscntrl(*s)) {  \
239            php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, err_msg, val); \
240            goto connect_errexit;   \
241        }   \
242        s++;    \
243    }   \
244}
245
246    /* send the user name */
247    if (resource->user != NULL) {
248        tmp_len = php_raw_url_decode(resource->user, strlen(resource->user));
249
250        PHP_FTP_CNTRL_CHK(resource->user, tmp_len, "Invalid login %s")
251
252        php_stream_printf(stream TSRMLS_CC, "USER %s\r\n", resource->user);
253    } else {
254        php_stream_write_string(stream, "USER anonymous\r\n");
255    }
256
257    /* get the response */
258    result = GET_FTP_RESULT(stream);
259
260    /* if a password is required, send it */
261    if (result >= 300 && result <= 399) {
262        php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_REQUIRED, tmp_line, 0);
263
264        if (resource->pass != NULL) {
265            tmp_len = php_raw_url_decode(resource->pass, strlen(resource->pass));
266
267            PHP_FTP_CNTRL_CHK(resource->pass, tmp_len, "Invalid password %s")
268
269            php_stream_printf(stream TSRMLS_CC, "PASS %s\r\n", resource->pass);
270        } else {
271            /* if the user has configured who they are,
272               send that as the password */
273            if (FG(from_address)) {
274                php_stream_printf(stream TSRMLS_CC, "PASS %s\r\n", FG(from_address));
275            } else {
276                php_stream_write_string(stream, "PASS anonymous\r\n");
277            }
278        }
279
280        /* read the response */
281        result = GET_FTP_RESULT(stream);
282
283        if (result > 299 || result < 200) {
284            php_stream_notify_error(context, PHP_STREAM_NOTIFY_AUTH_RESULT, tmp_line, result);
285        } else {
286            php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_RESULT, tmp_line, result);
287        }
288    }
289    if (result > 299 || result < 200) {
290        goto connect_errexit;
291    }
292
293    if (puse_ssl) {
294        *puse_ssl = use_ssl;
295    }
296    if (puse_ssl_on_data) {
297        *puse_ssl_on_data = use_ssl_on_data;
298    }
299    if (preuseid) {
300        *preuseid = reuseid;
301    }
302    if (presource) {
303        *presource = resource;
304    }
305
306    return stream;
307
308connect_errexit:
309    if (resource) {
310        php_url_free(resource);
311    }
312
313    if (stream) {
314        php_stream_close(stream);
315    }
316
317    return NULL;
318}
319/* }}} */
320
321/* {{{ php_fopen_do_pasv
322 */
323static unsigned short php_fopen_do_pasv(php_stream *stream, char *ip, size_t ip_size, char **phoststart TSRMLS_DC)
324{
325    char tmp_line[512];
326    int result, i;
327    unsigned short portno;
328    char *tpath, *ttpath, *hoststart=NULL;
329
330#ifdef HAVE_IPV6
331    /* We try EPSV first, needed for IPv6 and works on some IPv4 servers */
332    php_stream_write_string(stream, "EPSV\r\n");
333    result = GET_FTP_RESULT(stream);
334
335    /* check if we got a 229 response */
336    if (result != 229) {
337#endif
338        /* EPSV failed, let's try PASV */
339        php_stream_write_string(stream, "PASV\r\n");
340        result = GET_FTP_RESULT(stream);
341
342        /* make sure we got a 227 response */
343        if (result != 227) {
344            return 0;
345        }
346
347        /* parse pasv command (129, 80, 95, 25, 13, 221) */
348        tpath = tmp_line;
349        /* skip over the "227 Some message " part */
350        for (tpath += 4; *tpath && !isdigit((int) *tpath); tpath++);
351        if (!*tpath) {
352            return 0;
353        }
354        /* skip over the host ip, to get the port */
355        hoststart = tpath;
356        for (i = 0; i < 4; i++) {
357            for (; isdigit((int) *tpath); tpath++);
358            if (*tpath != ',') {
359                return 0;
360            }
361            *tpath='.';
362            tpath++;
363        }
364        tpath[-1] = '\0';
365        memcpy(ip, hoststart, ip_size);
366        ip[ip_size-1] = '\0';
367        hoststart = ip;
368
369        /* pull out the MSB of the port */
370        portno = (unsigned short) strtoul(tpath, &ttpath, 10) * 256;
371        if (ttpath == NULL) {
372            /* didn't get correct response from PASV */
373            return 0;
374        }
375        tpath = ttpath;
376        if (*tpath != ',') {
377            return 0;
378        }
379        tpath++;
380        /* pull out the LSB of the port */
381        portno += (unsigned short) strtoul(tpath, &ttpath, 10);
382#ifdef HAVE_IPV6
383    } else {
384        /* parse epsv command (|||6446|) */
385        for (i = 0, tpath = tmp_line + 4; *tpath; tpath++) {
386            if (*tpath == '|') {
387                i++;
388                if (i == 3)
389                    break;
390            }
391        }
392        if (i < 3) {
393            return 0;
394        }
395        /* pull out the port */
396        portno = (unsigned short) strtoul(tpath + 1, &ttpath, 10);
397    }
398#endif
399    if (ttpath == NULL) {
400        /* didn't get correct response from EPSV/PASV */
401        return 0;
402    }
403
404    if (phoststart) {
405        *phoststart = hoststart;
406    }
407
408    return portno;
409}
410/* }}} */
411
412/* {{{ php_fopen_url_wrap_ftp
413 */
414php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *path, const char *mode,
415                                     int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
416{
417    php_stream *stream = NULL, *datastream = NULL;
418    php_url *resource = NULL;
419    char tmp_line[512];
420    char ip[sizeof("123.123.123.123")];
421    unsigned short portno;
422    char *hoststart = NULL;
423    int result = 0, use_ssl, use_ssl_on_data=0;
424    php_stream *reuseid=NULL;
425    size_t file_size = 0;
426    zval **tmpzval;
427    int allow_overwrite = 0;
428    int read_write = 0;
429    char *transport;
430    int transport_len;
431
432    tmp_line[0] = '\0';
433
434    if (strpbrk(mode, "r+")) {
435        read_write = 1; /* Open for reading */
436    }
437    if (strpbrk(mode, "wa+")) {
438        if (read_write) {
439            php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP does not support simultaneous read/write connections");
440            return NULL;
441        }
442        if (strchr(mode, 'a')) {
443            read_write = 3; /* Open for Appending */
444        } else {
445            read_write = 2; /* Open for writing */
446        }
447    }
448    if (!read_write) {
449        /* No mode specified? */
450        php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unknown file open mode");
451        return NULL;
452    }
453
454    if (context &&
455        php_stream_context_get_option(context, "ftp", "proxy", &tmpzval) == SUCCESS) {
456        if (read_write == 1) {
457            /* Use http wrapper to proxy ftp request */
458            return php_stream_url_wrap_http(wrapper, path, mode, options, opened_path, context STREAMS_CC TSRMLS_CC);
459        } else {
460            /* ftp proxy is read-only */
461            php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP proxy may only be used in read mode");
462            return NULL;
463        }
464    }
465
466    stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data TSRMLS_CC);
467    if (!stream) {
468        goto errexit;
469    }
470
471    /* set the connection to be binary */
472    php_stream_write_string(stream, "TYPE I\r\n");
473    result = GET_FTP_RESULT(stream);
474    if (result > 299 || result < 200)
475        goto errexit;
476
477    /* find out the size of the file (verifying it exists) */
478    php_stream_printf(stream TSRMLS_CC, "SIZE %s\r\n", resource->path);
479
480    /* read the response */
481    result = GET_FTP_RESULT(stream);
482    if (read_write == 1) {
483        /* Read Mode */
484        char *sizestr;
485
486        /* when reading file, it must exist */
487        if (result > 299 || result < 200) {
488            errno = ENOENT;
489            goto errexit;
490        }
491
492        sizestr = strchr(tmp_line, ' ');
493        if (sizestr) {
494            sizestr++;
495            file_size = atoi(sizestr);
496            php_stream_notify_file_size(context, file_size, tmp_line, result);
497        }
498    } else if (read_write == 2) {
499        /* when writing file (but not appending), it must NOT exist, unless a context option exists which allows it */
500        if (context && php_stream_context_get_option(context, "ftp", "overwrite", &tmpzval) == SUCCESS) {
501            allow_overwrite = Z_LVAL_PP(tmpzval);
502        }
503        if (result <= 299 && result >= 200) {
504            if (allow_overwrite) {
505                /* Context permits overwriting file,
506                   so we just delete whatever's there in preparation */
507                php_stream_printf(stream TSRMLS_CC, "DELE %s\r\n", resource->path);
508                result = GET_FTP_RESULT(stream);
509                if (result >= 300 || result <= 199) {
510                    goto errexit;
511                }
512            } else {
513                php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Remote file already exists and overwrite context option not specified");
514                errno = EEXIST;
515                goto errexit;
516            }
517        }
518    }
519
520    /* set up the passive connection */
521    portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart TSRMLS_CC);
522
523    if (!portno) {
524        goto errexit;
525    }
526
527    /* Send RETR/STOR command */
528    if (read_write == 1) {
529        /* set resume position if applicable */
530        if (context &&
531            php_stream_context_get_option(context, "ftp", "resume_pos", &tmpzval) == SUCCESS &&
532            Z_TYPE_PP(tmpzval) == IS_LONG &&
533            Z_LVAL_PP(tmpzval) > 0) {
534            php_stream_printf(stream TSRMLS_CC, "REST %ld\r\n", Z_LVAL_PP(tmpzval));
535            result = GET_FTP_RESULT(stream);
536            if (result < 300 || result > 399) {
537                php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to resume from offset %ld", Z_LVAL_PP(tmpzval));
538                goto errexit;
539            }
540        }
541
542        /* retrieve file */
543        memcpy(tmp_line, "RETR", sizeof("RETR"));
544    } else if (read_write == 2) {
545        /* Write new file */
546        memcpy(tmp_line, "STOR", sizeof("STOR"));
547    } else {
548        /* Append */
549        memcpy(tmp_line, "APPE", sizeof("APPE"));
550    }
551    php_stream_printf(stream TSRMLS_CC, "%s %s\r\n", tmp_line, (resource->path != NULL ? resource->path : "/"));
552
553    /* open the data channel */
554    if (hoststart == NULL) {
555        hoststart = resource->host;
556    }
557    transport_len = spprintf(&transport, 0, "tcp://%s:%d", hoststart, portno);
558    datastream = php_stream_xport_create(transport, transport_len, REPORT_ERRORS, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, NULL, NULL, context, NULL, NULL);
559    efree(transport);
560    if (datastream == NULL) {
561        goto errexit;
562    }
563
564    result = GET_FTP_RESULT(stream);
565    if (result != 150 && result != 125) {
566        /* Could not retrieve or send the file
567         * this data will only be sent to us after connection on the data port was initiated.
568         */
569        php_stream_close(datastream);
570        datastream = NULL;
571        goto errexit;
572    }
573
574    php_stream_context_set(datastream, context);
575    php_stream_notify_progress_init(context, 0, file_size);
576
577    if (use_ssl_on_data && (php_stream_xport_crypto_setup(datastream,
578            STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 ||
579            php_stream_xport_crypto_enable(datastream, 1 TSRMLS_CC) < 0)) {
580
581        php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode");
582        php_stream_close(datastream);
583        datastream = NULL;
584        goto errexit;
585    }
586
587    /* remember control stream */
588    datastream->wrapperthis = stream;
589
590    php_url_free(resource);
591    return datastream;
592
593errexit:
594    if (resource) {
595        php_url_free(resource);
596    }
597    if (stream) {
598        php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result);
599        php_stream_close(stream);
600    }
601    if (tmp_line[0] != '\0')
602        php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP server reports %s", tmp_line);
603    return NULL;
604}
605/* }}} */
606
607/* {{{ php_ftp_dirsteam_read
608 */
609static size_t php_ftp_dirstream_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
610{
611    php_stream_dirent *ent = (php_stream_dirent *)buf;
612    php_stream *innerstream;
613    size_t tmp_len;
614    char *basename;
615    size_t basename_len;
616
617    innerstream =  ((php_ftp_dirstream_data *)stream->abstract)->datastream;
618
619    if (count != sizeof(php_stream_dirent)) {
620        return 0;
621    }
622
623    if (php_stream_eof(innerstream)) {
624        return 0;
625    }
626
627    if (!php_stream_get_line(innerstream, ent->d_name, sizeof(ent->d_name), &tmp_len)) {
628        return 0;
629    }
630
631    php_basename(ent->d_name, tmp_len, NULL, 0, &basename, &basename_len TSRMLS_CC);
632    if (!basename) {
633        return 0;
634    }
635
636    if (!basename_len) {
637        efree(basename);
638        return 0;
639    }
640
641    tmp_len = MIN(sizeof(ent->d_name), basename_len - 1);
642    memcpy(ent->d_name, basename, tmp_len);
643    ent->d_name[tmp_len - 1] = '\0';
644    efree(basename);
645
646    /* Trim off trailing whitespace characters */
647    tmp_len--;
648    while (tmp_len >= 0 &&
649            (ent->d_name[tmp_len] == '\n' || ent->d_name[tmp_len] == '\r' ||
650             ent->d_name[tmp_len] == '\t' || ent->d_name[tmp_len] == ' ')) {
651        ent->d_name[tmp_len--] = '\0';
652    }
653
654    return sizeof(php_stream_dirent);
655}
656/* }}} */
657
658/* {{{ php_ftp_dirstream_close
659 */
660static int php_ftp_dirstream_close(php_stream *stream, int close_handle TSRMLS_DC)
661{
662    php_ftp_dirstream_data *data = stream->abstract;
663
664    /* close control connection */
665    if (data->controlstream) {
666        php_stream_close(data->controlstream);
667        data->controlstream = NULL;
668    }
669    /* close data connection */
670    php_stream_close(data->datastream);
671    data->datastream = NULL;
672
673    efree(data);
674    stream->abstract = NULL;
675
676    return 0;
677}
678/* }}} */
679
680/* ftp dirstreams only need to support read and close operations,
681   They can't be rewound because the underlying ftp stream can't be rewound. */
682static php_stream_ops php_ftp_dirstream_ops = {
683    NULL, /* write */
684    php_ftp_dirstream_read, /* read */
685    php_ftp_dirstream_close, /* close */
686    NULL, /* flush */
687    "ftpdir",
688    NULL, /* rewind */
689    NULL, /* cast */
690    NULL, /* stat */
691    NULL  /* set option */
692};
693
694/* {{{ php_stream_ftp_opendir
695 */
696php_stream * php_stream_ftp_opendir(php_stream_wrapper *wrapper, const char *path, const char *mode, int options,
697                                    char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
698{
699    php_stream *stream, *reuseid, *datastream = NULL;
700    php_ftp_dirstream_data *dirsdata;
701    php_url *resource = NULL;
702    int result = 0, use_ssl, use_ssl_on_data = 0;
703    char *hoststart = NULL, tmp_line[512];
704    char ip[sizeof("123.123.123.123")];
705    unsigned short portno;
706
707    tmp_line[0] = '\0';
708
709    stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data TSRMLS_CC);
710    if (!stream) {
711        goto opendir_errexit;
712    }
713
714    /* set the connection to be ascii */
715    php_stream_write_string(stream, "TYPE A\r\n");
716    result = GET_FTP_RESULT(stream);
717    if (result > 299 || result < 200)
718        goto opendir_errexit;
719
720    /* set up the passive connection */
721    portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart TSRMLS_CC);
722
723    if (!portno) {
724        goto opendir_errexit;
725    }
726
727    php_stream_printf(stream TSRMLS_CC, "NLST %s\r\n", (resource->path != NULL ? resource->path : "/"));
728
729    /* open the data channel */
730    if (hoststart == NULL) {
731        hoststart = resource->host;
732    }
733    datastream = php_stream_sock_open_host(hoststart, portno, SOCK_STREAM, 0, 0);
734    if (datastream == NULL) {
735        goto opendir_errexit;
736    }
737
738    result = GET_FTP_RESULT(stream);
739    if (result != 150 && result != 125) {
740        /* Could not retrieve or send the file
741         * this data will only be sent to us after connection on the data port was initiated.
742         */
743        php_stream_close(datastream);
744        datastream = NULL;
745        goto opendir_errexit;
746    }
747
748    php_stream_context_set(datastream, context);
749
750    if (use_ssl_on_data && (php_stream_xport_crypto_setup(stream,
751            STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 ||
752            php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0)) {
753
754        php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode");
755        php_stream_close(datastream);
756        datastream = NULL;
757        goto opendir_errexit;
758    }
759
760    php_url_free(resource);
761
762    dirsdata = emalloc(sizeof *dirsdata);
763    dirsdata->datastream = datastream;
764    dirsdata->controlstream = stream;
765    dirsdata->dirstream = php_stream_alloc(&php_ftp_dirstream_ops, dirsdata, 0, mode);
766
767    return dirsdata->dirstream;
768
769opendir_errexit:
770    if (resource) {
771        php_url_free(resource);
772    }
773    if (stream) {
774        php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result);
775        php_stream_close(stream);
776    }
777    if (tmp_line[0] != '\0') {
778        php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP server reports %s", tmp_line);
779    }
780    return NULL;
781}
782/* }}} */
783
784/* {{{ php_stream_ftp_url_stat
785 */
786static int php_stream_ftp_url_stat(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC)
787{
788    php_stream *stream = NULL;
789    php_url *resource = NULL;
790    int result;
791    char tmp_line[512];
792
793    /* If ssb is NULL then someone is misbehaving */
794    if (!ssb) return -1;
795
796    stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL TSRMLS_CC);
797    if (!stream) {
798        goto stat_errexit;
799    }
800
801    ssb->sb.st_mode = 0644;                                 /* FTP won't give us a valid mode, so aproximate one based on being readable */
802    php_stream_printf(stream TSRMLS_CC, "CWD %s\r\n", (resource->path != NULL ? resource->path : "/")); /* If we can CWD to it, it's a directory (maybe a link, but we can't tell) */
803    result = GET_FTP_RESULT(stream);
804    if (result < 200 || result > 299) {
805        ssb->sb.st_mode |= S_IFREG;
806    } else {
807        ssb->sb.st_mode |= S_IFDIR;
808    }
809
810    php_stream_write_string(stream, "TYPE I\r\n"); /* we need this since some servers refuse to accept SIZE command in ASCII mode */
811
812    result = GET_FTP_RESULT(stream);
813
814    if(result < 200 || result > 299) {
815        goto stat_errexit;
816    }
817
818    php_stream_printf(stream TSRMLS_CC, "SIZE %s\r\n", (resource->path != NULL ? resource->path : "/"));
819    result = GET_FTP_RESULT(stream);
820    if (result < 200 || result > 299) {
821        /* Failure either means it doesn't exist
822           or it's a directory and this server
823           fails on listing directory sizes */
824        if (ssb->sb.st_mode & S_IFDIR) {
825            ssb->sb.st_size = 0;
826        } else {
827            goto stat_errexit;
828        }
829    } else {
830        ssb->sb.st_size = atoi(tmp_line + 4);
831    }
832
833    php_stream_printf(stream TSRMLS_CC, "MDTM %s\r\n", (resource->path != NULL ? resource->path : "/"));
834    result = GET_FTP_RESULT(stream);
835    if (result == 213) {
836        char *p = tmp_line + 4;
837        int n;
838        struct tm tm, tmbuf, *gmt;
839        time_t stamp;
840
841        while (p - tmp_line < sizeof(tmp_line) && !isdigit(*p)) {
842            p++;
843        }
844
845        if (p - tmp_line > sizeof(tmp_line)) {
846            goto mdtm_error;
847        }
848
849        n = sscanf(p, "%4u%2u%2u%2u%2u%2u", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
850        if (n != 6) {
851            goto mdtm_error;
852        }
853
854        tm.tm_year -= 1900;
855        tm.tm_mon--;
856        tm.tm_isdst = -1;
857
858        /* figure out the GMT offset */
859        stamp = time(NULL);
860        gmt = php_gmtime_r(&stamp, &tmbuf);
861        if (!gmt) {
862            goto mdtm_error;
863        }
864        gmt->tm_isdst = -1;
865
866        /* apply the GMT offset */
867        tm.tm_sec += stamp - mktime(gmt);
868        tm.tm_isdst = gmt->tm_isdst;
869
870        ssb->sb.st_mtime = mktime(&tm);
871    } else {
872        /* error or unsupported command */
873mdtm_error:
874        ssb->sb.st_mtime = -1;
875    }
876
877    ssb->sb.st_ino = 0;                     /* Unknown values */
878    ssb->sb.st_dev = 0;
879    ssb->sb.st_uid = 0;
880    ssb->sb.st_gid = 0;
881    ssb->sb.st_atime = -1;
882    ssb->sb.st_ctime = -1;
883
884    ssb->sb.st_nlink = 1;
885    ssb->sb.st_rdev = -1;
886#ifdef HAVE_ST_BLKSIZE
887    ssb->sb.st_blksize = 4096;              /* Guess since FTP won't expose this information */
888#ifdef HAVE_ST_BLOCKS
889    ssb->sb.st_blocks = (int)((4095 + ssb->sb.st_size) / ssb->sb.st_blksize); /* emulate ceil */
890#endif
891#endif
892    php_stream_close(stream);
893    php_url_free(resource);
894    return 0;
895
896stat_errexit:
897    if (resource) {
898        php_url_free(resource);
899    }
900    if (stream) {
901        php_stream_close(stream);
902    }
903    return -1;
904}
905/* }}} */
906
907/* {{{ php_stream_ftp_unlink
908 */
909static int php_stream_ftp_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context TSRMLS_DC)
910{
911    php_stream *stream = NULL;
912    php_url *resource = NULL;
913    int result;
914    char tmp_line[512];
915
916    stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
917    if (!stream) {
918        if (options & REPORT_ERRORS) {
919            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
920        }
921        goto unlink_errexit;
922    }
923
924    if (resource->path == NULL) {
925        if (options & REPORT_ERRORS) {
926            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
927        }
928        goto unlink_errexit;
929    }
930
931    /* Attempt to delete the file */
932    php_stream_printf(stream TSRMLS_CC, "DELE %s\r\n", (resource->path != NULL ? resource->path : "/"));
933
934    result = GET_FTP_RESULT(stream);
935    if (result < 200 || result > 299) {
936        if (options & REPORT_ERRORS) {
937            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Deleting file: %s", tmp_line);
938        }
939        goto unlink_errexit;
940    }
941
942    php_url_free(resource);
943    php_stream_close(stream);
944    return 1;
945
946unlink_errexit:
947    if (resource) {
948        php_url_free(resource);
949    }
950    if (stream) {
951        php_stream_close(stream);
952    }
953    return 0;
954}
955/* }}} */
956
957/* {{{ php_stream_ftp_rename
958 */
959static int php_stream_ftp_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context TSRMLS_DC)
960{
961    php_stream *stream = NULL;
962    php_url *resource_from = NULL, *resource_to = NULL;
963    int result;
964    char tmp_line[512];
965
966    resource_from = php_url_parse(url_from);
967    resource_to = php_url_parse(url_to);
968    /* Must be same scheme (ftp/ftp or ftps/ftps), same host, and same port
969        (or a 21/0 0/21 combination which is also "same")
970       Also require paths to/from */
971    if (!resource_from ||
972        !resource_to ||
973        !resource_from->scheme ||
974        !resource_to->scheme ||
975        strcmp(resource_from->scheme, resource_to->scheme) ||
976        !resource_from->host ||
977        !resource_to->host ||
978        strcmp(resource_from->host, resource_to->host) ||
979        (resource_from->port != resource_to->port &&
980         resource_from->port * resource_to->port != 0 &&
981         resource_from->port + resource_to->port != 21) ||
982        !resource_from->path ||
983        !resource_to->path) {
984        goto rename_errexit;
985    }
986
987    stream = php_ftp_fopen_connect(wrapper, url_from, "r", 0, NULL, NULL, NULL, NULL, NULL, NULL TSRMLS_CC);
988    if (!stream) {
989        if (options & REPORT_ERRORS) {
990            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", resource_from->host);
991        }
992        goto rename_errexit;
993    }
994
995    /* Rename FROM */
996    php_stream_printf(stream TSRMLS_CC, "RNFR %s\r\n", (resource_from->path != NULL ? resource_from->path : "/"));
997
998    result = GET_FTP_RESULT(stream);
999    if (result < 300 || result > 399) {
1000        if (options & REPORT_ERRORS) {
1001            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Renaming file: %s", tmp_line);
1002        }
1003        goto rename_errexit;
1004    }
1005
1006    /* Rename TO */
1007    php_stream_printf(stream TSRMLS_CC, "RNTO %s\r\n", (resource_to->path != NULL ? resource_to->path : "/"));
1008
1009    result = GET_FTP_RESULT(stream);
1010    if (result < 200 || result > 299) {
1011        if (options & REPORT_ERRORS) {
1012            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Renaming file: %s", tmp_line);
1013        }
1014        goto rename_errexit;
1015    }
1016
1017    php_url_free(resource_from);
1018    php_url_free(resource_to);
1019    php_stream_close(stream);
1020    return 1;
1021
1022rename_errexit:
1023    if (resource_from) {
1024        php_url_free(resource_from);
1025    }
1026    if (resource_to) {
1027        php_url_free(resource_to);
1028    }
1029    if (stream) {
1030        php_stream_close(stream);
1031    }
1032    return 0;
1033}
1034/* }}} */
1035
1036/* {{{ php_stream_ftp_mkdir
1037 */
1038static int php_stream_ftp_mkdir(php_stream_wrapper *wrapper, const char *url, int mode, int options, php_stream_context *context TSRMLS_DC)
1039{
1040    php_stream *stream = NULL;
1041    php_url *resource = NULL;
1042    int result, recursive = options & PHP_STREAM_MKDIR_RECURSIVE;
1043    char tmp_line[512];
1044
1045    stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
1046    if (!stream) {
1047        if (options & REPORT_ERRORS) {
1048            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
1049        }
1050        goto mkdir_errexit;
1051    }
1052
1053    if (resource->path == NULL) {
1054        if (options & REPORT_ERRORS) {
1055            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
1056        }
1057        goto mkdir_errexit;
1058    }
1059
1060    if (!recursive) {
1061        php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", resource->path);
1062        result = GET_FTP_RESULT(stream);
1063    } else {
1064        /* we look for directory separator from the end of string, thus hopefuly reducing our work load */
1065        char *p, *e, *buf;
1066
1067        buf = estrdup(resource->path);
1068        e = buf + strlen(buf);
1069
1070        /* find a top level directory we need to create */
1071        while ((p = strrchr(buf, '/'))) {
1072            *p = '\0';
1073            php_stream_printf(stream TSRMLS_CC, "CWD %s\r\n", buf);
1074            result = GET_FTP_RESULT(stream);
1075            if (result >= 200 && result <= 299) {
1076                *p = '/';
1077                break;
1078            }
1079        }
1080        if (p == buf) {
1081            php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", resource->path);
1082            result = GET_FTP_RESULT(stream);
1083        } else {
1084            php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", buf);
1085            result = GET_FTP_RESULT(stream);
1086            if (result >= 200 && result <= 299) {
1087                if (!p) {
1088                    p = buf;
1089                }
1090                /* create any needed directories if the creation of the 1st directory worked */
1091                while (++p != e) {
1092                    if (*p == '\0' && *(p + 1) != '\0') {
1093                        *p = '/';
1094                        php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", buf);
1095                        result = GET_FTP_RESULT(stream);
1096                        if (result < 200 || result > 299) {
1097                            if (options & REPORT_ERRORS) {
1098                                php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tmp_line);
1099                            }
1100                            break;
1101                        }
1102                    }
1103                }
1104            }
1105        }
1106        efree(buf);
1107    }
1108
1109    php_url_free(resource);
1110    php_stream_close(stream);
1111
1112    if (result < 200 || result > 299) {
1113        /* Failure */
1114        return 0;
1115    }
1116
1117    return 1;
1118
1119mkdir_errexit:
1120    if (resource) {
1121        php_url_free(resource);
1122    }
1123    if (stream) {
1124        php_stream_close(stream);
1125    }
1126    return 0;
1127}
1128/* }}} */
1129
1130/* {{{ php_stream_ftp_rmdir
1131 */
1132static int php_stream_ftp_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context TSRMLS_DC)
1133{
1134    php_stream *stream = NULL;
1135    php_url *resource = NULL;
1136    int result;
1137    char tmp_line[512];
1138
1139    stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
1140    if (!stream) {
1141        if (options & REPORT_ERRORS) {
1142            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
1143        }
1144        goto rmdir_errexit;
1145    }
1146
1147    if (resource->path == NULL) {
1148        if (options & REPORT_ERRORS) {
1149            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
1150        }
1151        goto rmdir_errexit;
1152    }
1153
1154    php_stream_printf(stream TSRMLS_CC, "RMD %s\r\n", resource->path);
1155    result = GET_FTP_RESULT(stream);
1156
1157    if (result < 200 || result > 299) {
1158        if (options & REPORT_ERRORS) {
1159            php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tmp_line);
1160        }
1161        goto rmdir_errexit;
1162    }
1163
1164    php_url_free(resource);
1165    php_stream_close(stream);
1166
1167    return 1;
1168
1169rmdir_errexit:
1170    if (resource) {
1171        php_url_free(resource);
1172    }
1173    if (stream) {
1174        php_stream_close(stream);
1175    }
1176    return 0;
1177}
1178/* }}} */
1179
1180static php_stream_wrapper_ops ftp_stream_wops = {
1181    php_stream_url_wrap_ftp,
1182    php_stream_ftp_stream_close, /* stream_close */
1183    php_stream_ftp_stream_stat,
1184    php_stream_ftp_url_stat, /* stat_url */
1185    php_stream_ftp_opendir, /* opendir */
1186    "ftp",
1187    php_stream_ftp_unlink, /* unlink */
1188    php_stream_ftp_rename, /* rename */
1189    php_stream_ftp_mkdir,  /* mkdir */
1190    php_stream_ftp_rmdir   /* rmdir */
1191};
1192
1193PHPAPI php_stream_wrapper php_stream_ftp_wrapper =  {
1194    &ftp_stream_wops,
1195    NULL,
1196    1 /* is_url */
1197};
1198
1199
1200/*
1201 * Local variables:
1202 * tab-width: 4
1203 * c-basic-offset: 4
1204 * End:
1205 * vim600: sw=4 ts=4 fdm=marker
1206 * vim<600: sw=4 ts=4
1207 */
1208