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