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