1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2017 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
434 tmp_line[0] = '\0';
435
436 if (strpbrk(mode, "r+")) {
437 read_write = 1; /* Open for reading */
438 }
439 if (strpbrk(mode, "wa+")) {
440 if (read_write) {
441 php_stream_wrapper_log_error(wrapper, options, "FTP does not support simultaneous read/write connections");
442 return NULL;
443 }
444 if (strchr(mode, 'a')) {
445 read_write = 3; /* Open for Appending */
446 } else {
447 read_write = 2; /* Open for writing */
448 }
449 }
450 if (!read_write) {
451 /* No mode specified? */
452 php_stream_wrapper_log_error(wrapper, options, "Unknown file open mode");
453 return NULL;
454 }
455
456 if (context &&
457 (tmpzval = php_stream_context_get_option(context, "ftp", "proxy")) != NULL) {
458 if (read_write == 1) {
459 /* Use http wrapper to proxy ftp request */
460 return php_stream_url_wrap_http(wrapper, path, mode, options, opened_path, context STREAMS_CC);
461 } else {
462 /* ftp proxy is read-only */
463 php_stream_wrapper_log_error(wrapper, options, "FTP proxy may only be used in read mode");
464 return NULL;
465 }
466 }
467
468 stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data);
469 if (!stream) {
470 goto errexit;
471 }
472
473 /* set the connection to be binary */
474 php_stream_write_string(stream, "TYPE I\r\n");
475 result = GET_FTP_RESULT(stream);
476 if (result > 299 || result < 200)
477 goto errexit;
478
479 /* find out the size of the file (verifying it exists) */
480 php_stream_printf(stream, "SIZE %s\r\n", resource->path);
481
482 /* read the response */
483 result = GET_FTP_RESULT(stream);
484 if (read_write == 1) {
485 /* Read Mode */
486 char *sizestr;
487
488 /* when reading file, it must exist */
489 if (result > 299 || result < 200) {
490 errno = ENOENT;
491 goto errexit;
492 }
493
494 sizestr = strchr(tmp_line, ' ');
495 if (sizestr) {
496 sizestr++;
497 file_size = atoi(sizestr);
498 php_stream_notify_file_size(context, file_size, tmp_line, result);
499 }
500 } else if (read_write == 2) {
501 /* when writing file (but not appending), it must NOT exist, unless a context option exists which allows it */
502 if (context && (tmpzval = php_stream_context_get_option(context, "ftp", "overwrite")) != NULL) {
503 allow_overwrite = Z_LVAL_P(tmpzval) ? 1 : 0;
504 }
505 if (result <= 299 && result >= 200) {
506 if (allow_overwrite) {
507 /* Context permits overwriting file,
508 so we just delete whatever's there in preparation */
509 php_stream_printf(stream, "DELE %s\r\n", resource->path);
510 result = GET_FTP_RESULT(stream);
511 if (result >= 300 || result <= 199) {
512 goto errexit;
513 }
514 } else {
515 php_stream_wrapper_log_error(wrapper, options, "Remote file already exists and overwrite context option not specified");
516 errno = EEXIST;
517 goto errexit;
518 }
519 }
520 }
521
522 /* set up the passive connection */
523 portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart);
524
525 if (!portno) {
526 goto errexit;
527 }
528
529 /* Send RETR/STOR command */
530 if (read_write == 1) {
531 /* set resume position if applicable */
532 if (context &&
533 (tmpzval = php_stream_context_get_option(context, "ftp", "resume_pos")) != NULL &&
534 Z_TYPE_P(tmpzval) == IS_LONG &&
535 Z_LVAL_P(tmpzval) > 0) {
536 php_stream_printf(stream, "REST %pd\r\n", Z_LVAL_P(tmpzval));
537 result = GET_FTP_RESULT(stream);
538 if (result < 300 || result > 399) {
539 php_stream_wrapper_log_error(wrapper, options, "Unable to resume from offset %pd", Z_LVAL_P(tmpzval));
540 goto errexit;
541 }
542 }
543
544 /* retrieve file */
545 memcpy(tmp_line, "RETR", sizeof("RETR"));
546 } else if (read_write == 2) {
547 /* Write new file */
548 memcpy(tmp_line, "STOR", sizeof("STOR"));
549 } else {
550 /* Append */
551 memcpy(tmp_line, "APPE", sizeof("APPE"));
552 }
553 php_stream_printf(stream, "%s %s\r\n", tmp_line, (resource->path != NULL ? resource->path : "/"));
554
555 /* open the data channel */
556 if (hoststart == NULL) {
557 hoststart = resource->host;
558 }
559 transport_len = (int)spprintf(&transport, 0, "tcp://%s:%d", hoststart, portno);
560 datastream = php_stream_xport_create(transport, transport_len, REPORT_ERRORS, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, NULL, NULL, context, NULL, NULL);
561 efree(transport);
562 if (datastream == NULL) {
563 goto errexit;
564 }
565
566 result = GET_FTP_RESULT(stream);
567 if (result != 150 && result != 125) {
568 /* Could not retrieve or send the file
569 * this data will only be sent to us after connection on the data port was initiated.
570 */
571 php_stream_close(datastream);
572 datastream = NULL;
573 goto errexit;
574 }
575
576 php_stream_context_set(datastream, context);
577 php_stream_notify_progress_init(context, 0, file_size);
578
579 if (use_ssl_on_data && (php_stream_xport_crypto_setup(datastream,
580 STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 ||
581 php_stream_xport_crypto_enable(datastream, 1) < 0)) {
582
583 php_stream_wrapper_log_error(wrapper, options, "Unable to activate SSL mode");
584 php_stream_close(datastream);
585 datastream = NULL;
586 goto errexit;
587 }
588
589 /* remember control stream */
590 datastream->wrapperthis = stream;
591
592 php_url_free(resource);
593 return datastream;
594
595 errexit:
596 if (resource) {
597 php_url_free(resource);
598 }
599 if (stream) {
600 php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result);
601 php_stream_close(stream);
602 }
603 if (tmp_line[0] != '\0')
604 php_stream_wrapper_log_error(wrapper, options, "FTP server reports %s", tmp_line);
605 return NULL;
606 }
607 /* }}} */
608
609 /* {{{ php_ftp_dirsteam_read
610 */
php_ftp_dirstream_read(php_stream * stream,char * buf,size_t count)611 static size_t php_ftp_dirstream_read(php_stream *stream, char *buf, size_t count)
612 {
613 php_stream_dirent *ent = (php_stream_dirent *)buf;
614 php_stream *innerstream;
615 size_t tmp_len;
616 zend_string *basename;
617
618 innerstream = ((php_ftp_dirstream_data *)stream->abstract)->datastream;
619
620 if (count != sizeof(php_stream_dirent)) {
621 return 0;
622 }
623
624 if (php_stream_eof(innerstream)) {
625 return 0;
626 }
627
628 if (!php_stream_get_line(innerstream, ent->d_name, sizeof(ent->d_name), &tmp_len)) {
629 return 0;
630 }
631
632 basename = php_basename(ent->d_name, tmp_len, NULL, 0);
633
634 tmp_len = MIN(sizeof(ent->d_name), ZSTR_LEN(basename) - 1);
635 memcpy(ent->d_name, ZSTR_VAL(basename), tmp_len);
636 ent->d_name[tmp_len - 1] = '\0';
637 zend_string_release(basename);
638
639 /* Trim off trailing whitespace characters */
640 while (tmp_len > 0 &&
641 (ent->d_name[tmp_len - 1] == '\n' || ent->d_name[tmp_len - 1] == '\r' ||
642 ent->d_name[tmp_len - 1] == '\t' || ent->d_name[tmp_len - 1] == ' ')) {
643 ent->d_name[--tmp_len] = '\0';
644 }
645
646 return sizeof(php_stream_dirent);
647 }
648 /* }}} */
649
650 /* {{{ php_ftp_dirstream_close
651 */
php_ftp_dirstream_close(php_stream * stream,int close_handle)652 static int php_ftp_dirstream_close(php_stream *stream, int close_handle)
653 {
654 php_ftp_dirstream_data *data = stream->abstract;
655
656 /* close control connection */
657 if (data->controlstream) {
658 php_stream_close(data->controlstream);
659 data->controlstream = NULL;
660 }
661 /* close data connection */
662 php_stream_close(data->datastream);
663 data->datastream = NULL;
664
665 efree(data);
666 stream->abstract = NULL;
667
668 return 0;
669 }
670 /* }}} */
671
672 /* ftp dirstreams only need to support read and close operations,
673 They can't be rewound because the underlying ftp stream can't be rewound. */
674 static php_stream_ops php_ftp_dirstream_ops = {
675 NULL, /* write */
676 php_ftp_dirstream_read, /* read */
677 php_ftp_dirstream_close, /* close */
678 NULL, /* flush */
679 "ftpdir",
680 NULL, /* rewind */
681 NULL, /* cast */
682 NULL, /* stat */
683 NULL /* set option */
684 };
685
686 /* {{{ php_stream_ftp_opendir
687 */
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)688 php_stream * php_stream_ftp_opendir(php_stream_wrapper *wrapper, const char *path, const char *mode, int options,
689 zend_string **opened_path, php_stream_context *context STREAMS_DC)
690 {
691 php_stream *stream, *reuseid, *datastream = NULL;
692 php_ftp_dirstream_data *dirsdata;
693 php_url *resource = NULL;
694 int result = 0, use_ssl, use_ssl_on_data = 0;
695 char *hoststart = NULL, tmp_line[512];
696 char ip[sizeof("123.123.123.123")];
697 unsigned short portno;
698
699 tmp_line[0] = '\0';
700
701 stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data);
702 if (!stream) {
703 goto opendir_errexit;
704 }
705
706 /* set the connection to be ascii */
707 php_stream_write_string(stream, "TYPE A\r\n");
708 result = GET_FTP_RESULT(stream);
709 if (result > 299 || result < 200)
710 goto opendir_errexit;
711
712 // tmp_line isn't relevant after the php_fopen_do_pasv().
713 tmp_line[0] = '\0';
714
715 /* set up the passive connection */
716 portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart);
717
718 if (!portno) {
719 goto opendir_errexit;
720 }
721
722 /* open the data channel */
723 if (hoststart == NULL) {
724 hoststart = resource->host;
725 }
726
727 datastream = php_stream_sock_open_host(hoststart, portno, SOCK_STREAM, 0, 0);
728 if (datastream == NULL) {
729 goto opendir_errexit;
730 }
731
732 php_stream_printf(stream, "NLST %s\r\n", (resource->path != NULL ? resource->path : "/"));
733
734 result = GET_FTP_RESULT(stream);
735 if (result != 150 && result != 125) {
736 /* Could not retrieve or send the file
737 * this data will only be sent to us after connection on the data port was initiated.
738 */
739 php_stream_close(datastream);
740 datastream = NULL;
741 goto opendir_errexit;
742 }
743
744 php_stream_context_set(datastream, context);
745 if (use_ssl_on_data && (php_stream_xport_crypto_setup(datastream,
746 STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL) < 0 ||
747 php_stream_xport_crypto_enable(datastream, 1) < 0)) {
748
749 php_stream_wrapper_log_error(wrapper, options, "Unable to activate SSL mode");
750 php_stream_close(datastream);
751 datastream = NULL;
752 goto opendir_errexit;
753 }
754
755 php_url_free(resource);
756
757 dirsdata = emalloc(sizeof *dirsdata);
758 dirsdata->datastream = datastream;
759 dirsdata->controlstream = stream;
760 dirsdata->dirstream = php_stream_alloc(&php_ftp_dirstream_ops, dirsdata, 0, mode);
761
762 return dirsdata->dirstream;
763
764 opendir_errexit:
765 if (resource) {
766 php_url_free(resource);
767 }
768 if (stream) {
769 php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result);
770 php_stream_close(stream);
771 }
772 if (tmp_line[0] != '\0') {
773 php_stream_wrapper_log_error(wrapper, options, "FTP server reports %s", tmp_line);
774 }
775 return NULL;
776 }
777 /* }}} */
778
779 /* {{{ php_stream_ftp_url_stat
780 */
php_stream_ftp_url_stat(php_stream_wrapper * wrapper,const char * url,int flags,php_stream_statbuf * ssb,php_stream_context * context)781 static int php_stream_ftp_url_stat(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context)
782 {
783 php_stream *stream = NULL;
784 php_url *resource = NULL;
785 int result;
786 char tmp_line[512];
787
788 /* If ssb is NULL then someone is misbehaving */
789 if (!ssb) return -1;
790
791 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL);
792 if (!stream) {
793 goto stat_errexit;
794 }
795
796 ssb->sb.st_mode = 0644; /* FTP won't give us a valid mode, so approximate one based on being readable */
797 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) */
798 result = GET_FTP_RESULT(stream);
799 if (result < 200 || result > 299) {
800 ssb->sb.st_mode |= S_IFREG;
801 } else {
802 ssb->sb.st_mode |= S_IFDIR;
803 }
804
805 php_stream_write_string(stream, "TYPE I\r\n"); /* we need this since some servers refuse to accept SIZE command in ASCII mode */
806
807 result = GET_FTP_RESULT(stream);
808
809 if(result < 200 || result > 299) {
810 goto stat_errexit;
811 }
812
813 php_stream_printf(stream, "SIZE %s\r\n", (resource->path != NULL ? resource->path : "/"));
814 result = GET_FTP_RESULT(stream);
815 if (result < 200 || result > 299) {
816 /* Failure either means it doesn't exist
817 or it's a directory and this server
818 fails on listing directory sizes */
819 if (ssb->sb.st_mode & S_IFDIR) {
820 ssb->sb.st_size = 0;
821 } else {
822 goto stat_errexit;
823 }
824 } else {
825 ssb->sb.st_size = atoi(tmp_line + 4);
826 }
827
828 php_stream_printf(stream, "MDTM %s\r\n", (resource->path != NULL ? resource->path : "/"));
829 result = GET_FTP_RESULT(stream);
830 if (result == 213) {
831 char *p = tmp_line + 4;
832 int n;
833 struct tm tm, tmbuf, *gmt;
834 time_t stamp;
835
836 while (p - tmp_line < sizeof(tmp_line) && !isdigit(*p)) {
837 p++;
838 }
839
840 if (p - tmp_line > sizeof(tmp_line)) {
841 goto mdtm_error;
842 }
843
844 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);
845 if (n != 6) {
846 goto mdtm_error;
847 }
848
849 tm.tm_year -= 1900;
850 tm.tm_mon--;
851 tm.tm_isdst = -1;
852
853 /* figure out the GMT offset */
854 stamp = time(NULL);
855 gmt = php_gmtime_r(&stamp, &tmbuf);
856 if (!gmt) {
857 goto mdtm_error;
858 }
859 gmt->tm_isdst = -1;
860
861 /* apply the GMT offset */
862 tm.tm_sec += (long)(stamp - mktime(gmt));
863 tm.tm_isdst = gmt->tm_isdst;
864
865 ssb->sb.st_mtime = mktime(&tm);
866 } else {
867 /* error or unsupported command */
868 mdtm_error:
869 ssb->sb.st_mtime = -1;
870 }
871
872 ssb->sb.st_ino = 0; /* Unknown values */
873 ssb->sb.st_dev = 0;
874 ssb->sb.st_uid = 0;
875 ssb->sb.st_gid = 0;
876 ssb->sb.st_atime = -1;
877 ssb->sb.st_ctime = -1;
878
879 ssb->sb.st_nlink = 1;
880 ssb->sb.st_rdev = -1;
881 #ifdef HAVE_ST_BLKSIZE
882 ssb->sb.st_blksize = 4096; /* Guess since FTP won't expose this information */
883 #ifdef HAVE_ST_BLOCKS
884 ssb->sb.st_blocks = (int)((4095 + ssb->sb.st_size) / ssb->sb.st_blksize); /* emulate ceil */
885 #endif
886 #endif
887 php_stream_close(stream);
888 php_url_free(resource);
889 return 0;
890
891 stat_errexit:
892 if (resource) {
893 php_url_free(resource);
894 }
895 if (stream) {
896 php_stream_close(stream);
897 }
898 return -1;
899 }
900 /* }}} */
901
902 /* {{{ php_stream_ftp_unlink
903 */
php_stream_ftp_unlink(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context)904 static int php_stream_ftp_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context)
905 {
906 php_stream *stream = NULL;
907 php_url *resource = NULL;
908 int result;
909 char tmp_line[512];
910
911 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL);
912 if (!stream) {
913 if (options & REPORT_ERRORS) {
914 php_error_docref(NULL, E_WARNING, "Unable to connect to %s", url);
915 }
916 goto unlink_errexit;
917 }
918
919 if (resource->path == NULL) {
920 if (options & REPORT_ERRORS) {
921 php_error_docref(NULL, E_WARNING, "Invalid path provided in %s", url);
922 }
923 goto unlink_errexit;
924 }
925
926 /* Attempt to delete the file */
927 php_stream_printf(stream, "DELE %s\r\n", (resource->path != NULL ? resource->path : "/"));
928
929 result = GET_FTP_RESULT(stream);
930 if (result < 200 || result > 299) {
931 if (options & REPORT_ERRORS) {
932 php_error_docref(NULL, E_WARNING, "Error Deleting file: %s", tmp_line);
933 }
934 goto unlink_errexit;
935 }
936
937 php_url_free(resource);
938 php_stream_close(stream);
939 return 1;
940
941 unlink_errexit:
942 if (resource) {
943 php_url_free(resource);
944 }
945 if (stream) {
946 php_stream_close(stream);
947 }
948 return 0;
949 }
950 /* }}} */
951
952 /* {{{ php_stream_ftp_rename
953 */
php_stream_ftp_rename(php_stream_wrapper * wrapper,const char * url_from,const char * url_to,int options,php_stream_context * context)954 static int php_stream_ftp_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context)
955 {
956 php_stream *stream = NULL;
957 php_url *resource_from = NULL, *resource_to = NULL;
958 int result;
959 char tmp_line[512];
960
961 resource_from = php_url_parse(url_from);
962 resource_to = php_url_parse(url_to);
963 /* Must be same scheme (ftp/ftp or ftps/ftps), same host, and same port
964 (or a 21/0 0/21 combination which is also "same")
965 Also require paths to/from */
966 if (!resource_from ||
967 !resource_to ||
968 !resource_from->scheme ||
969 !resource_to->scheme ||
970 strcmp(resource_from->scheme, resource_to->scheme) ||
971 !resource_from->host ||
972 !resource_to->host ||
973 strcmp(resource_from->host, resource_to->host) ||
974 (resource_from->port != resource_to->port &&
975 resource_from->port * resource_to->port != 0 &&
976 resource_from->port + resource_to->port != 21) ||
977 !resource_from->path ||
978 !resource_to->path) {
979 goto rename_errexit;
980 }
981
982 stream = php_ftp_fopen_connect(wrapper, url_from, "r", 0, NULL, context, NULL, NULL, NULL, NULL);
983 if (!stream) {
984 if (options & REPORT_ERRORS) {
985 php_error_docref(NULL, E_WARNING, "Unable to connect to %s", resource_from->host);
986 }
987 goto rename_errexit;
988 }
989
990 /* Rename FROM */
991 php_stream_printf(stream, "RNFR %s\r\n", (resource_from->path != NULL ? resource_from->path : "/"));
992
993 result = GET_FTP_RESULT(stream);
994 if (result < 300 || result > 399) {
995 if (options & REPORT_ERRORS) {
996 php_error_docref(NULL, E_WARNING, "Error Renaming file: %s", tmp_line);
997 }
998 goto rename_errexit;
999 }
1000
1001 /* Rename TO */
1002 php_stream_printf(stream, "RNTO %s\r\n", (resource_to->path != NULL ? resource_to->path : "/"));
1003
1004 result = GET_FTP_RESULT(stream);
1005 if (result < 200 || result > 299) {
1006 if (options & REPORT_ERRORS) {
1007 php_error_docref(NULL, E_WARNING, "Error Renaming file: %s", tmp_line);
1008 }
1009 goto rename_errexit;
1010 }
1011
1012 php_url_free(resource_from);
1013 php_url_free(resource_to);
1014 php_stream_close(stream);
1015 return 1;
1016
1017 rename_errexit:
1018 if (resource_from) {
1019 php_url_free(resource_from);
1020 }
1021 if (resource_to) {
1022 php_url_free(resource_to);
1023 }
1024 if (stream) {
1025 php_stream_close(stream);
1026 }
1027 return 0;
1028 }
1029 /* }}} */
1030
1031 /* {{{ php_stream_ftp_mkdir
1032 */
php_stream_ftp_mkdir(php_stream_wrapper * wrapper,const char * url,int mode,int options,php_stream_context * context)1033 static int php_stream_ftp_mkdir(php_stream_wrapper *wrapper, const char *url, int mode, int options, php_stream_context *context)
1034 {
1035 php_stream *stream = NULL;
1036 php_url *resource = NULL;
1037 int result, recursive = options & PHP_STREAM_MKDIR_RECURSIVE;
1038 char tmp_line[512];
1039
1040 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL);
1041 if (!stream) {
1042 if (options & REPORT_ERRORS) {
1043 php_error_docref(NULL, E_WARNING, "Unable to connect to %s", url);
1044 }
1045 goto mkdir_errexit;
1046 }
1047
1048 if (resource->path == NULL) {
1049 if (options & REPORT_ERRORS) {
1050 php_error_docref(NULL, E_WARNING, "Invalid path provided in %s", url);
1051 }
1052 goto mkdir_errexit;
1053 }
1054
1055 if (!recursive) {
1056 php_stream_printf(stream, "MKD %s\r\n", resource->path);
1057 result = GET_FTP_RESULT(stream);
1058 } else {
1059 /* we look for directory separator from the end of string, thus hopefuly reducing our work load */
1060 char *p, *e, *buf;
1061
1062 buf = estrdup(resource->path);
1063 e = buf + strlen(buf);
1064
1065 /* find a top level directory we need to create */
1066 while ((p = strrchr(buf, '/'))) {
1067 *p = '\0';
1068 php_stream_printf(stream, "CWD %s\r\n", buf);
1069 result = GET_FTP_RESULT(stream);
1070 if (result >= 200 && result <= 299) {
1071 *p = '/';
1072 break;
1073 }
1074 }
1075 if (p == buf) {
1076 php_stream_printf(stream, "MKD %s\r\n", resource->path);
1077 result = GET_FTP_RESULT(stream);
1078 } else {
1079 php_stream_printf(stream, "MKD %s\r\n", buf);
1080 result = GET_FTP_RESULT(stream);
1081 if (result >= 200 && result <= 299) {
1082 if (!p) {
1083 p = buf;
1084 }
1085 /* create any needed directories if the creation of the 1st directory worked */
1086 while (++p != e) {
1087 if (*p == '\0' && *(p + 1) != '\0') {
1088 *p = '/';
1089 php_stream_printf(stream, "MKD %s\r\n", buf);
1090 result = GET_FTP_RESULT(stream);
1091 if (result < 200 || result > 299) {
1092 if (options & REPORT_ERRORS) {
1093 php_error_docref(NULL, E_WARNING, "%s", tmp_line);
1094 }
1095 break;
1096 }
1097 }
1098 }
1099 }
1100 }
1101 efree(buf);
1102 }
1103
1104 php_url_free(resource);
1105 php_stream_close(stream);
1106
1107 if (result < 200 || result > 299) {
1108 /* Failure */
1109 return 0;
1110 }
1111
1112 return 1;
1113
1114 mkdir_errexit:
1115 if (resource) {
1116 php_url_free(resource);
1117 }
1118 if (stream) {
1119 php_stream_close(stream);
1120 }
1121 return 0;
1122 }
1123 /* }}} */
1124
1125 /* {{{ php_stream_ftp_rmdir
1126 */
php_stream_ftp_rmdir(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context)1127 static int php_stream_ftp_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context)
1128 {
1129 php_stream *stream = NULL;
1130 php_url *resource = NULL;
1131 int result;
1132 char tmp_line[512];
1133
1134 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL);
1135 if (!stream) {
1136 if (options & REPORT_ERRORS) {
1137 php_error_docref(NULL, E_WARNING, "Unable to connect to %s", url);
1138 }
1139 goto rmdir_errexit;
1140 }
1141
1142 if (resource->path == NULL) {
1143 if (options & REPORT_ERRORS) {
1144 php_error_docref(NULL, E_WARNING, "Invalid path provided in %s", url);
1145 }
1146 goto rmdir_errexit;
1147 }
1148
1149 php_stream_printf(stream, "RMD %s\r\n", resource->path);
1150 result = GET_FTP_RESULT(stream);
1151
1152 if (result < 200 || result > 299) {
1153 if (options & REPORT_ERRORS) {
1154 php_error_docref(NULL, E_WARNING, "%s", tmp_line);
1155 }
1156 goto rmdir_errexit;
1157 }
1158
1159 php_url_free(resource);
1160 php_stream_close(stream);
1161
1162 return 1;
1163
1164 rmdir_errexit:
1165 if (resource) {
1166 php_url_free(resource);
1167 }
1168 if (stream) {
1169 php_stream_close(stream);
1170 }
1171 return 0;
1172 }
1173 /* }}} */
1174
1175 static php_stream_wrapper_ops ftp_stream_wops = {
1176 php_stream_url_wrap_ftp,
1177 php_stream_ftp_stream_close, /* stream_close */
1178 php_stream_ftp_stream_stat,
1179 php_stream_ftp_url_stat, /* stat_url */
1180 php_stream_ftp_opendir, /* opendir */
1181 "ftp",
1182 php_stream_ftp_unlink, /* unlink */
1183 php_stream_ftp_rename, /* rename */
1184 php_stream_ftp_mkdir, /* mkdir */
1185 php_stream_ftp_rmdir /* rmdir */
1186 };
1187
1188 PHPAPI php_stream_wrapper php_stream_ftp_wrapper = {
1189 &ftp_stream_wops,
1190 NULL,
1191 1 /* is_url */
1192 };
1193
1194
1195 /*
1196 * Local variables:
1197 * tab-width: 4
1198 * c-basic-offset: 4
1199 * End:
1200 * vim600: sw=4 ts=4 fdm=marker
1201 * vim<600: sw=4 ts=4
1202 */
1203