1/*
2 +----------------------------------------------------------------------+
3 | PHP Version 5 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2015 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 while (tmp_len > 0 &&
648 (ent->d_name[tmp_len - 1] == '\n' || ent->d_name[tmp_len - 1] == '\r' ||
649 ent->d_name[tmp_len - 1] == '\t' || ent->d_name[tmp_len - 1] == ' ')) {
650 ent->d_name[--tmp_len] = '\0';
651 }
652
653 return sizeof(php_stream_dirent);
654}
655/* }}} */
656
657/* {{{ php_ftp_dirstream_close
658 */
659static int php_ftp_dirstream_close(php_stream *stream, int close_handle TSRMLS_DC)
660{
661 php_ftp_dirstream_data *data = stream->abstract;
662
663 /* close control connection */
664 if (data->controlstream) {
665 php_stream_close(data->controlstream);
666 data->controlstream = NULL;
667 }
668 /* close data connection */
669 php_stream_close(data->datastream);
670 data->datastream = NULL;
671
672 efree(data);
673 stream->abstract = NULL;
674
675 return 0;
676}
677/* }}} */
678
679/* ftp dirstreams only need to support read and close operations,
680 They can't be rewound because the underlying ftp stream can't be rewound. */
681static php_stream_ops php_ftp_dirstream_ops = {
682 NULL, /* write */
683 php_ftp_dirstream_read, /* read */
684 php_ftp_dirstream_close, /* close */
685 NULL, /* flush */
686 "ftpdir",
687 NULL, /* rewind */
688 NULL, /* cast */
689 NULL, /* stat */
690 NULL /* set option */
691};
692
693/* {{{ php_stream_ftp_opendir
694 */
695php_stream * php_stream_ftp_opendir(php_stream_wrapper *wrapper, const char *path, const char *mode, int options,
696 char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
697{
698 php_stream *stream, *reuseid, *datastream = NULL;
699 php_ftp_dirstream_data *dirsdata;
700 php_url *resource = NULL;
701 int result = 0, use_ssl, use_ssl_on_data = 0;
702 char *hoststart = NULL, tmp_line[512];
703 char ip[sizeof("123.123.123.123")];
704 unsigned short portno;
705
706 tmp_line[0] = '\0';
707
708 stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data TSRMLS_CC);
709 if (!stream) {
710 goto opendir_errexit;
711 }
712
713 /* set the connection to be ascii */
714 php_stream_write_string(stream, "TYPE A\r\n");
715 result = GET_FTP_RESULT(stream);
716 if (result > 299 || result < 200)
717 goto opendir_errexit;
718
719 /* set up the passive connection */
720 portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart TSRMLS_CC);
721
722 if (!portno) {
723 goto opendir_errexit;
724 }
725
726 php_stream_printf(stream TSRMLS_CC, "NLST %s\r\n", (resource->path != NULL ? resource->path : "/"));
727
728 /* open the data channel */
729 if (hoststart == NULL) {
730 hoststart = resource->host;
731 }
732 datastream = php_stream_sock_open_host(hoststart, portno, SOCK_STREAM, 0, 0);
733 if (datastream == NULL) {
734 goto opendir_errexit;
735 }
736
737 result = GET_FTP_RESULT(stream);
738 if (result != 150 && result != 125) {
739 /* Could not retrieve or send the file
740 * this data will only be sent to us after connection on the data port was initiated.
741 */
742 php_stream_close(datastream);
743 datastream = NULL;
744 goto opendir_errexit;
745 }
746
747 php_stream_context_set(datastream, context);
748
749 if (use_ssl_on_data && (php_stream_xport_crypto_setup(stream,
750 STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 ||
751 php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0)) {
752
753 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode");
754 php_stream_close(datastream);
755 datastream = NULL;
756 goto opendir_errexit;
757 }
758
759 php_url_free(resource);
760
761 dirsdata = emalloc(sizeof *dirsdata);
762 dirsdata->datastream = datastream;
763 dirsdata->controlstream = stream;
764 dirsdata->dirstream = php_stream_alloc(&php_ftp_dirstream_ops, dirsdata, 0, mode);
765
766 return dirsdata->dirstream;
767
768opendir_errexit:
769 if (resource) {
770 php_url_free(resource);
771 }
772 if (stream) {
773 php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result);
774 php_stream_close(stream);
775 }
776 if (tmp_line[0] != '\0') {
777 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP server reports %s", tmp_line);
778 }
779 return NULL;
780}
781/* }}} */
782
783/* {{{ php_stream_ftp_url_stat
784 */
785static 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)
786{
787 php_stream *stream = NULL;
788 php_url *resource = NULL;
789 int result;
790 char tmp_line[512];
791
792 /* If ssb is NULL then someone is misbehaving */
793 if (!ssb) return -1;
794
795 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL TSRMLS_CC);
796 if (!stream) {
797 goto stat_errexit;
798 }
799
800 ssb->sb.st_mode = 0644; /* FTP won't give us a valid mode, so aproximate one based on being readable */
801 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) */
802 result = GET_FTP_RESULT(stream);
803 if (result < 200 || result > 299) {
804 ssb->sb.st_mode |= S_IFREG;
805 } else {
806 ssb->sb.st_mode |= S_IFDIR;
807 }
808
809 php_stream_write_string(stream, "TYPE I\r\n"); /* we need this since some servers refuse to accept SIZE command in ASCII mode */
810
811 result = GET_FTP_RESULT(stream);
812
813 if(result < 200 || result > 299) {
814 goto stat_errexit;
815 }
816
817 php_stream_printf(stream TSRMLS_CC, "SIZE %s\r\n", (resource->path != NULL ? resource->path : "/"));
818 result = GET_FTP_RESULT(stream);
819 if (result < 200 || result > 299) {
820 /* Failure either means it doesn't exist
821 or it's a directory and this server
822 fails on listing directory sizes */
823 if (ssb->sb.st_mode & S_IFDIR) {
824 ssb->sb.st_size = 0;
825 } else {
826 goto stat_errexit;
827 }
828 } else {
829 ssb->sb.st_size = atoi(tmp_line + 4);
830 }
831
832 php_stream_printf(stream TSRMLS_CC, "MDTM %s\r\n", (resource->path != NULL ? resource->path : "/"));
833 result = GET_FTP_RESULT(stream);
834 if (result == 213) {
835 char *p = tmp_line + 4;
836 int n;
837 struct tm tm, tmbuf, *gmt;
838 time_t stamp;
839
840 while (p - tmp_line < sizeof(tmp_line) && !isdigit(*p)) {
841 p++;
842 }
843
844 if (p - tmp_line > sizeof(tmp_line)) {
845 goto mdtm_error;
846 }
847
848 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);
849 if (n != 6) {
850 goto mdtm_error;
851 }
852
853 tm.tm_year -= 1900;
854 tm.tm_mon--;
855 tm.tm_isdst = -1;
856
857 /* figure out the GMT offset */
858 stamp = time(NULL);
859 gmt = php_gmtime_r(&stamp, &tmbuf);
860 if (!gmt) {
861 goto mdtm_error;
862 }
863 gmt->tm_isdst = -1;
864
865 /* apply the GMT offset */
866 tm.tm_sec += stamp - mktime(gmt);
867 tm.tm_isdst = gmt->tm_isdst;
868
869 ssb->sb.st_mtime = mktime(&tm);
870 } else {
871 /* error or unsupported command */
872mdtm_error:
873 ssb->sb.st_mtime = -1;
874 }
875
876 ssb->sb.st_ino = 0; /* Unknown values */
877 ssb->sb.st_dev = 0;
878 ssb->sb.st_uid = 0;
879 ssb->sb.st_gid = 0;
880 ssb->sb.st_atime = -1;
881 ssb->sb.st_ctime = -1;
882
883 ssb->sb.st_nlink = 1;
884 ssb->sb.st_rdev = -1;
885#ifdef HAVE_ST_BLKSIZE
886 ssb->sb.st_blksize = 4096; /* Guess since FTP won't expose this information */
887#ifdef HAVE_ST_BLOCKS
888 ssb->sb.st_blocks = (int)((4095 + ssb->sb.st_size) / ssb->sb.st_blksize); /* emulate ceil */
889#endif
890#endif
891 php_stream_close(stream);
892 php_url_free(resource);
893 return 0;
894
895stat_errexit:
896 if (resource) {
897 php_url_free(resource);
898 }
899 if (stream) {
900 php_stream_close(stream);
901 }
902 return -1;
903}
904/* }}} */
905
906/* {{{ php_stream_ftp_unlink
907 */
908static int php_stream_ftp_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context TSRMLS_DC)
909{
910 php_stream *stream = NULL;
911 php_url *resource = NULL;
912 int result;
913 char tmp_line[512];
914
915 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
916 if (!stream) {
917 if (options & REPORT_ERRORS) {
918 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
919 }
920 goto unlink_errexit;
921 }
922
923 if (resource->path == NULL) {
924 if (options & REPORT_ERRORS) {
925 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
926 }
927 goto unlink_errexit;
928 }
929
930 /* Attempt to delete the file */
931 php_stream_printf(stream TSRMLS_CC, "DELE %s\r\n", (resource->path != NULL ? resource->path : "/"));
932
933 result = GET_FTP_RESULT(stream);
934 if (result < 200 || result > 299) {
935 if (options & REPORT_ERRORS) {
936 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Deleting file: %s", tmp_line);
937 }
938 goto unlink_errexit;
939 }
940
941 php_url_free(resource);
942 php_stream_close(stream);
943 return 1;
944
945unlink_errexit:
946 if (resource) {
947 php_url_free(resource);
948 }
949 if (stream) {
950 php_stream_close(stream);
951 }
952 return 0;
953}
954/* }}} */
955
956/* {{{ php_stream_ftp_rename
957 */
958static 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)
959{
960 php_stream *stream = NULL;
961 php_url *resource_from = NULL, *resource_to = NULL;
962 int result;
963 char tmp_line[512];
964
965 resource_from = php_url_parse(url_from);
966 resource_to = php_url_parse(url_to);
967 /* Must be same scheme (ftp/ftp or ftps/ftps), same host, and same port
968 (or a 21/0 0/21 combination which is also "same")
969 Also require paths to/from */
970 if (!resource_from ||
971 !resource_to ||
972 !resource_from->scheme ||
973 !resource_to->scheme ||
974 strcmp(resource_from->scheme, resource_to->scheme) ||
975 !resource_from->host ||
976 !resource_to->host ||
977 strcmp(resource_from->host, resource_to->host) ||
978 (resource_from->port != resource_to->port &&
979 resource_from->port * resource_to->port != 0 &&
980 resource_from->port + resource_to->port != 21) ||
981 !resource_from->path ||
982 !resource_to->path) {
983 goto rename_errexit;
984 }
985
986 stream = php_ftp_fopen_connect(wrapper, url_from, "r", 0, NULL, NULL, NULL, NULL, NULL, NULL TSRMLS_CC);
987 if (!stream) {
988 if (options & REPORT_ERRORS) {
989 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", resource_from->host);
990 }
991 goto rename_errexit;
992 }
993
994 /* Rename FROM */
995 php_stream_printf(stream TSRMLS_CC, "RNFR %s\r\n", (resource_from->path != NULL ? resource_from->path : "/"));
996
997 result = GET_FTP_RESULT(stream);
998 if (result < 300 || result > 399) {
999 if (options & REPORT_ERRORS) {
1000 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Renaming file: %s", tmp_line);
1001 }
1002 goto rename_errexit;
1003 }
1004
1005 /* Rename TO */
1006 php_stream_printf(stream TSRMLS_CC, "RNTO %s\r\n", (resource_to->path != NULL ? resource_to->path : "/"));
1007
1008 result = GET_FTP_RESULT(stream);
1009 if (result < 200 || result > 299) {
1010 if (options & REPORT_ERRORS) {
1011 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Renaming file: %s", tmp_line);
1012 }
1013 goto rename_errexit;
1014 }
1015
1016 php_url_free(resource_from);
1017 php_url_free(resource_to);
1018 php_stream_close(stream);
1019 return 1;
1020
1021rename_errexit:
1022 if (resource_from) {
1023 php_url_free(resource_from);
1024 }
1025 if (resource_to) {
1026 php_url_free(resource_to);
1027 }
1028 if (stream) {
1029 php_stream_close(stream);
1030 }
1031 return 0;
1032}
1033/* }}} */
1034
1035/* {{{ php_stream_ftp_mkdir
1036 */
1037static int php_stream_ftp_mkdir(php_stream_wrapper *wrapper, const char *url, int mode, int options, php_stream_context *context TSRMLS_DC)
1038{
1039 php_stream *stream = NULL;
1040 php_url *resource = NULL;
1041 int result, recursive = options & PHP_STREAM_MKDIR_RECURSIVE;
1042 char tmp_line[512];
1043
1044 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
1045 if (!stream) {
1046 if (options & REPORT_ERRORS) {
1047 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
1048 }
1049 goto mkdir_errexit;
1050 }
1051
1052 if (resource->path == NULL) {
1053 if (options & REPORT_ERRORS) {
1054 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
1055 }
1056 goto mkdir_errexit;
1057 }
1058
1059 if (!recursive) {
1060 php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", resource->path);
1061 result = GET_FTP_RESULT(stream);
1062 } else {
1063 /* we look for directory separator from the end of string, thus hopefuly reducing our work load */
1064 char *p, *e, *buf;
1065
1066 buf = estrdup(resource->path);
1067 e = buf + strlen(buf);
1068
1069 /* find a top level directory we need to create */
1070 while ((p = strrchr(buf, '/'))) {
1071 *p = '\0';
1072 php_stream_printf(stream TSRMLS_CC, "CWD %s\r\n", buf);
1073 result = GET_FTP_RESULT(stream);
1074 if (result >= 200 && result <= 299) {
1075 *p = '/';
1076 break;
1077 }
1078 }
1079 if (p == buf) {
1080 php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", resource->path);
1081 result = GET_FTP_RESULT(stream);
1082 } else {
1083 php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", buf);
1084 result = GET_FTP_RESULT(stream);
1085 if (result >= 200 && result <= 299) {
1086 if (!p) {
1087 p = buf;
1088 }
1089 /* create any needed directories if the creation of the 1st directory worked */
1090 while (++p != e) {
1091 if (*p == '\0' && *(p + 1) != '\0') {
1092 *p = '/';
1093 php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", buf);
1094 result = GET_FTP_RESULT(stream);
1095 if (result < 200 || result > 299) {
1096 if (options & REPORT_ERRORS) {
1097 php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tmp_line);
1098 }
1099 break;
1100 }
1101 }
1102 }
1103 }
1104 }
1105 efree(buf);
1106 }
1107
1108 php_url_free(resource);
1109 php_stream_close(stream);
1110
1111 if (result < 200 || result > 299) {
1112 /* Failure */
1113 return 0;
1114 }
1115
1116 return 1;
1117
1118mkdir_errexit:
1119 if (resource) {
1120 php_url_free(resource);
1121 }
1122 if (stream) {
1123 php_stream_close(stream);
1124 }
1125 return 0;
1126}
1127/* }}} */
1128
1129/* {{{ php_stream_ftp_rmdir
1130 */
1131static int php_stream_ftp_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context TSRMLS_DC)
1132{
1133 php_stream *stream = NULL;
1134 php_url *resource = NULL;
1135 int result;
1136 char tmp_line[512];
1137
1138 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
1139 if (!stream) {
1140 if (options & REPORT_ERRORS) {
1141 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
1142 }
1143 goto rmdir_errexit;
1144 }
1145
1146 if (resource->path == NULL) {
1147 if (options & REPORT_ERRORS) {
1148 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
1149 }
1150 goto rmdir_errexit;
1151 }
1152
1153 php_stream_printf(stream TSRMLS_CC, "RMD %s\r\n", resource->path);
1154 result = GET_FTP_RESULT(stream);
1155
1156 if (result < 200 || result > 299) {
1157 if (options & REPORT_ERRORS) {
1158 php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tmp_line);
1159 }
1160 goto rmdir_errexit;
1161 }
1162
1163 php_url_free(resource);
1164 php_stream_close(stream);
1165
1166 return 1;
1167
1168rmdir_errexit:
1169 if (resource) {
1170 php_url_free(resource);
1171 }
1172 if (stream) {
1173 php_stream_close(stream);
1174 }
1175 return 0;
1176}
1177/* }}} */
1178
1179static php_stream_wrapper_ops ftp_stream_wops = {
1180 php_stream_url_wrap_ftp,
1181 php_stream_ftp_stream_close, /* stream_close */
1182 php_stream_ftp_stream_stat,
1183 php_stream_ftp_url_stat, /* stat_url */
1184 php_stream_ftp_opendir, /* opendir */
1185 "ftp",
1186 php_stream_ftp_unlink, /* unlink */
1187 php_stream_ftp_rename, /* rename */
1188 php_stream_ftp_mkdir, /* mkdir */
1189 php_stream_ftp_rmdir /* rmdir */
1190};
1191
1192PHPAPI php_stream_wrapper php_stream_ftp_wrapper = {
1193 &ftp_stream_wops,
1194 NULL,
1195 1 /* is_url */
1196};
1197
1198
1199/*
1200 * Local variables:
1201 * tab-width: 4
1202 * c-basic-offset: 4
1203 * End:
1204 * vim600: sw=4 ts=4 fdm=marker
1205 * vim<600: sw=4 ts=4
1206 */
1207