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