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