1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 5 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2016 The PHP Group |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
15 | Authors: Rasmus Lerdorf <rasmus@php.net> |
16 | Jim Winstead <jimw@php.net> |
17 | Hartmut Holzgraefe <hholzgra@php.net> |
18 | Sara Golemon <pollita@php.net> |
19 +----------------------------------------------------------------------+
20 */
21 /* $Id$ */
22
23 #include "php.h"
24 #include "php_globals.h"
25 #include "php_network.h"
26 #include "php_ini.h"
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <errno.h>
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <fcntl.h>
34
35 #ifdef PHP_WIN32
36 #include <winsock2.h>
37 #define O_RDONLY _O_RDONLY
38 #include "win32/param.h"
39 #else
40 #include <sys/param.h>
41 #endif
42
43 #include "php_standard.h"
44
45 #include <sys/types.h>
46 #if HAVE_SYS_SOCKET_H
47 #include <sys/socket.h>
48 #endif
49
50 #ifdef PHP_WIN32
51 #include <winsock2.h>
52 #elif defined(NETWARE) && defined(USE_WINSOCK)
53 #include <novsock2.h>
54 #else
55 #include <netinet/in.h>
56 #include <netdb.h>
57 #if HAVE_ARPA_INET_H
58 #include <arpa/inet.h>
59 #endif
60 #endif
61
62 #if defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE)
63 #undef AF_UNIX
64 #endif
65
66 #if defined(AF_UNIX)
67 #include <sys/un.h>
68 #endif
69
70 #include "php_fopen_wrappers.h"
71
72 #define FTPS_ENCRYPT_DATA 1
73 #define GET_FTP_RESULT(stream) get_ftp_result((stream), tmp_line, sizeof(tmp_line) TSRMLS_CC)
74
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 TSRMLS_DC)83 static inline int get_ftp_result(php_stream *stream, char *buffer, size_t buffer_size TSRMLS_DC)
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 TSRMLS_DC)95 static int php_stream_ftp_stream_stat(php_stream_wrapper *wrapper, php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC)
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 TSRMLS_DC)105 static int php_stream_ftp_stream_close(php_stream_wrapper *wrapper, php_stream *stream TSRMLS_DC)
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 TSRMLS_CC, 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,char ** opened_path,php_stream_context * context,php_stream ** preuseid,php_url ** presource,int * puse_ssl,int * puse_ssl_on_data TSRMLS_DC)134 static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, const char *path, const char *mode, int options,
135 char **opened_path, php_stream_context *context, php_stream **preuseid,
136 php_url **presource, int *puse_ssl, int *puse_ssl_on_data TSRMLS_DC)
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 = 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 TSRMLS_CC, "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 TSRMLS_CC) < 0
209 || php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0) {
210 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "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 = val, *e = s + val_len; \
239 while (s < e) { \
240 if (iscntrl(*s)) { \
241 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, 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 = php_raw_url_decode(resource->user, strlen(resource->user));
251
252 PHP_FTP_CNTRL_CHK(resource->user, tmp_len, "Invalid login %s")
253
254 php_stream_printf(stream TSRMLS_CC, "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 = php_raw_url_decode(resource->pass, strlen(resource->pass));
268
269 PHP_FTP_CNTRL_CHK(resource->pass, tmp_len, "Invalid password %s")
270
271 php_stream_printf(stream TSRMLS_CC, "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 TSRMLS_CC, "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 TSRMLS_DC)325 static unsigned short php_fopen_do_pasv(php_stream *stream, char *ip, size_t ip_size, char **phoststart TSRMLS_DC)
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,char ** opened_path,php_stream_context * context STREAMS_DC TSRMLS_DC)416 php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, const char *path, const char *mode,
417 int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_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 int allow_overwrite = 0;
430 int 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 TSRMLS_CC, "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 TSRMLS_CC, "Unknown file open mode");
453 return NULL;
454 }
455
456 if (context &&
457 php_stream_context_get_option(context, "ftp", "proxy", &tmpzval) == SUCCESS) {
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 TSRMLS_CC);
461 } else {
462 /* ftp proxy is read-only */
463 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "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 TSRMLS_CC);
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 TSRMLS_CC, "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 && php_stream_context_get_option(context, "ftp", "overwrite", &tmpzval) == SUCCESS) {
503 allow_overwrite = Z_LVAL_PP(tmpzval);
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 TSRMLS_CC, "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 TSRMLS_CC, "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 TSRMLS_CC);
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 php_stream_context_get_option(context, "ftp", "resume_pos", &tmpzval) == SUCCESS &&
534 Z_TYPE_PP(tmpzval) == IS_LONG &&
535 Z_LVAL_PP(tmpzval) > 0) {
536 php_stream_printf(stream TSRMLS_CC, "REST %ld\r\n", Z_LVAL_PP(tmpzval));
537 result = GET_FTP_RESULT(stream);
538 if (result < 300 || result > 399) {
539 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to resume from offset %ld", Z_LVAL_PP(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 TSRMLS_CC, "%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 = 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 TSRMLS_CC) < 0 ||
581 php_stream_xport_crypto_enable(datastream, 1 TSRMLS_CC) < 0)) {
582
583 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "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 TSRMLS_CC, "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 TSRMLS_DC)611 static size_t php_ftp_dirstream_read(php_stream *stream, char *buf, size_t count TSRMLS_DC)
612 {
613 php_stream_dirent *ent = (php_stream_dirent *)buf;
614 php_stream *innerstream;
615 size_t tmp_len;
616 char *basename;
617 size_t basename_len;
618
619 innerstream = ((php_ftp_dirstream_data *)stream->abstract)->datastream;
620
621 if (count != sizeof(php_stream_dirent)) {
622 return 0;
623 }
624
625 if (php_stream_eof(innerstream)) {
626 return 0;
627 }
628
629 if (!php_stream_get_line(innerstream, ent->d_name, sizeof(ent->d_name), &tmp_len)) {
630 return 0;
631 }
632
633 php_basename(ent->d_name, tmp_len, NULL, 0, &basename, &basename_len TSRMLS_CC);
634 if (!basename) {
635 return 0;
636 }
637
638 if (!basename_len) {
639 efree(basename);
640 return 0;
641 }
642
643 tmp_len = MIN(sizeof(ent->d_name), basename_len - 1);
644 memcpy(ent->d_name, basename, tmp_len);
645 ent->d_name[tmp_len - 1] = '\0';
646 efree(basename);
647
648 /* Trim off trailing whitespace characters */
649 while (tmp_len > 0 &&
650 (ent->d_name[tmp_len - 1] == '\n' || ent->d_name[tmp_len - 1] == '\r' ||
651 ent->d_name[tmp_len - 1] == '\t' || ent->d_name[tmp_len - 1] == ' ')) {
652 ent->d_name[--tmp_len] = '\0';
653 }
654
655 return sizeof(php_stream_dirent);
656 }
657 /* }}} */
658
659 /* {{{ php_ftp_dirstream_close
660 */
php_ftp_dirstream_close(php_stream * stream,int close_handle TSRMLS_DC)661 static int php_ftp_dirstream_close(php_stream *stream, int close_handle TSRMLS_DC)
662 {
663 php_ftp_dirstream_data *data = stream->abstract;
664
665 /* close control connection */
666 if (data->controlstream) {
667 php_stream_close(data->controlstream);
668 data->controlstream = NULL;
669 }
670 /* close data connection */
671 php_stream_close(data->datastream);
672 data->datastream = NULL;
673
674 efree(data);
675 stream->abstract = NULL;
676
677 return 0;
678 }
679 /* }}} */
680
681 /* ftp dirstreams only need to support read and close operations,
682 They can't be rewound because the underlying ftp stream can't be rewound. */
683 static php_stream_ops php_ftp_dirstream_ops = {
684 NULL, /* write */
685 php_ftp_dirstream_read, /* read */
686 php_ftp_dirstream_close, /* close */
687 NULL, /* flush */
688 "ftpdir",
689 NULL, /* rewind */
690 NULL, /* cast */
691 NULL, /* stat */
692 NULL /* set option */
693 };
694
695 /* {{{ php_stream_ftp_opendir
696 */
php_stream_ftp_opendir(php_stream_wrapper * wrapper,const char * path,const char * mode,int options,char ** opened_path,php_stream_context * context STREAMS_DC TSRMLS_DC)697 php_stream * php_stream_ftp_opendir(php_stream_wrapper *wrapper, const char *path, const char *mode, int options,
698 char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC)
699 {
700 php_stream *stream, *reuseid, *datastream = NULL;
701 php_ftp_dirstream_data *dirsdata;
702 php_url *resource = NULL;
703 int result = 0, use_ssl, use_ssl_on_data = 0;
704 char *hoststart = NULL, tmp_line[512];
705 char ip[sizeof("123.123.123.123")];
706 unsigned short portno;
707
708 tmp_line[0] = '\0';
709
710 stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data TSRMLS_CC);
711 if (!stream) {
712 goto opendir_errexit;
713 }
714
715 /* set the connection to be ascii */
716 php_stream_write_string(stream, "TYPE A\r\n");
717 result = GET_FTP_RESULT(stream);
718 if (result > 299 || result < 200)
719 goto opendir_errexit;
720
721 // tmp_line isn't relevant after the php_fopen_do_pasv().
722 tmp_line[0] = '\0';
723
724 /* set up the passive connection */
725 portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart TSRMLS_CC);
726
727 if (!portno) {
728 goto opendir_errexit;
729 }
730
731 /* open the data channel */
732 if (hoststart == NULL) {
733 hoststart = resource->host;
734 }
735
736 datastream = php_stream_sock_open_host(hoststart, portno, SOCK_STREAM, 0, 0);
737 if (datastream == NULL) {
738 goto opendir_errexit;
739 }
740
741
742 php_stream_printf(stream TSRMLS_CC, "NLST %s\r\n", (resource->path != NULL ? resource->path : "/"));
743
744 result = GET_FTP_RESULT(stream);
745 if (result != 150 && result != 125) {
746 /* Could not retrieve or send the file
747 * this data will only be sent to us after connection on the data port was initiated.
748 */
749 php_stream_close(datastream);
750 datastream = NULL;
751 goto opendir_errexit;
752 }
753
754 php_stream_context_set(datastream, context);
755 if (use_ssl_on_data && (php_stream_xport_crypto_setup(datastream,
756 STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 ||
757 php_stream_xport_crypto_enable(datastream, 1 TSRMLS_CC) < 0)) {
758
759 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode");
760 php_stream_close(datastream);
761 datastream = NULL;
762 goto opendir_errexit;
763 }
764
765 php_url_free(resource);
766
767 dirsdata = emalloc(sizeof *dirsdata);
768 dirsdata->datastream = datastream;
769 dirsdata->controlstream = stream;
770 dirsdata->dirstream = php_stream_alloc(&php_ftp_dirstream_ops, dirsdata, 0, mode);
771
772 return dirsdata->dirstream;
773
774 opendir_errexit:
775 if (resource) {
776 php_url_free(resource);
777 }
778 if (stream) {
779 php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result);
780 php_stream_close(stream);
781 }
782 if (tmp_line[0] != '\0') {
783 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP server reports %s", tmp_line);
784 }
785 return NULL;
786 }
787 /* }}} */
788
789 /* {{{ php_stream_ftp_url_stat
790 */
php_stream_ftp_url_stat(php_stream_wrapper * wrapper,const char * url,int flags,php_stream_statbuf * ssb,php_stream_context * context TSRMLS_DC)791 static int php_stream_ftp_url_stat(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC)
792 {
793 php_stream *stream = NULL;
794 php_url *resource = NULL;
795 int result;
796 char tmp_line[512];
797
798 /* If ssb is NULL then someone is misbehaving */
799 if (!ssb) return -1;
800
801 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL TSRMLS_CC);
802 if (!stream) {
803 goto stat_errexit;
804 }
805
806 ssb->sb.st_mode = 0644; /* FTP won't give us a valid mode, so aproximate one based on being readable */
807 php_stream_printf(stream TSRMLS_CC, "CWD %s\r\n", (resource->path != NULL ? resource->path : "/")); /* If we can CWD to it, it's a directory (maybe a link, but we can't tell) */
808 result = GET_FTP_RESULT(stream);
809 if (result < 200 || result > 299) {
810 ssb->sb.st_mode |= S_IFREG;
811 } else {
812 ssb->sb.st_mode |= S_IFDIR;
813 }
814
815 php_stream_write_string(stream, "TYPE I\r\n"); /* we need this since some servers refuse to accept SIZE command in ASCII mode */
816
817 result = GET_FTP_RESULT(stream);
818
819 if(result < 200 || result > 299) {
820 goto stat_errexit;
821 }
822
823 php_stream_printf(stream TSRMLS_CC, "SIZE %s\r\n", (resource->path != NULL ? resource->path : "/"));
824 result = GET_FTP_RESULT(stream);
825 if (result < 200 || result > 299) {
826 /* Failure either means it doesn't exist
827 or it's a directory and this server
828 fails on listing directory sizes */
829 if (ssb->sb.st_mode & S_IFDIR) {
830 ssb->sb.st_size = 0;
831 } else {
832 goto stat_errexit;
833 }
834 } else {
835 ssb->sb.st_size = atoi(tmp_line + 4);
836 }
837
838 php_stream_printf(stream TSRMLS_CC, "MDTM %s\r\n", (resource->path != NULL ? resource->path : "/"));
839 result = GET_FTP_RESULT(stream);
840 if (result == 213) {
841 char *p = tmp_line + 4;
842 int n;
843 struct tm tm, tmbuf, *gmt;
844 time_t stamp;
845
846 while (p - tmp_line < sizeof(tmp_line) && !isdigit(*p)) {
847 p++;
848 }
849
850 if (p - tmp_line > sizeof(tmp_line)) {
851 goto mdtm_error;
852 }
853
854 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);
855 if (n != 6) {
856 goto mdtm_error;
857 }
858
859 tm.tm_year -= 1900;
860 tm.tm_mon--;
861 tm.tm_isdst = -1;
862
863 /* figure out the GMT offset */
864 stamp = time(NULL);
865 gmt = php_gmtime_r(&stamp, &tmbuf);
866 if (!gmt) {
867 goto mdtm_error;
868 }
869 gmt->tm_isdst = -1;
870
871 /* apply the GMT offset */
872 tm.tm_sec += stamp - mktime(gmt);
873 tm.tm_isdst = gmt->tm_isdst;
874
875 ssb->sb.st_mtime = mktime(&tm);
876 } else {
877 /* error or unsupported command */
878 mdtm_error:
879 ssb->sb.st_mtime = -1;
880 }
881
882 ssb->sb.st_ino = 0; /* Unknown values */
883 ssb->sb.st_dev = 0;
884 ssb->sb.st_uid = 0;
885 ssb->sb.st_gid = 0;
886 ssb->sb.st_atime = -1;
887 ssb->sb.st_ctime = -1;
888
889 ssb->sb.st_nlink = 1;
890 ssb->sb.st_rdev = -1;
891 #ifdef HAVE_ST_BLKSIZE
892 ssb->sb.st_blksize = 4096; /* Guess since FTP won't expose this information */
893 #ifdef HAVE_ST_BLOCKS
894 ssb->sb.st_blocks = (int)((4095 + ssb->sb.st_size) / ssb->sb.st_blksize); /* emulate ceil */
895 #endif
896 #endif
897 php_stream_close(stream);
898 php_url_free(resource);
899 return 0;
900
901 stat_errexit:
902 if (resource) {
903 php_url_free(resource);
904 }
905 if (stream) {
906 php_stream_close(stream);
907 }
908 return -1;
909 }
910 /* }}} */
911
912 /* {{{ php_stream_ftp_unlink
913 */
php_stream_ftp_unlink(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context TSRMLS_DC)914 static int php_stream_ftp_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context TSRMLS_DC)
915 {
916 php_stream *stream = NULL;
917 php_url *resource = NULL;
918 int result;
919 char tmp_line[512];
920
921 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
922 if (!stream) {
923 if (options & REPORT_ERRORS) {
924 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
925 }
926 goto unlink_errexit;
927 }
928
929 if (resource->path == NULL) {
930 if (options & REPORT_ERRORS) {
931 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
932 }
933 goto unlink_errexit;
934 }
935
936 /* Attempt to delete the file */
937 php_stream_printf(stream TSRMLS_CC, "DELE %s\r\n", (resource->path != NULL ? resource->path : "/"));
938
939 result = GET_FTP_RESULT(stream);
940 if (result < 200 || result > 299) {
941 if (options & REPORT_ERRORS) {
942 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Deleting file: %s", tmp_line);
943 }
944 goto unlink_errexit;
945 }
946
947 php_url_free(resource);
948 php_stream_close(stream);
949 return 1;
950
951 unlink_errexit:
952 if (resource) {
953 php_url_free(resource);
954 }
955 if (stream) {
956 php_stream_close(stream);
957 }
958 return 0;
959 }
960 /* }}} */
961
962 /* {{{ php_stream_ftp_rename
963 */
php_stream_ftp_rename(php_stream_wrapper * wrapper,const char * url_from,const char * url_to,int options,php_stream_context * context TSRMLS_DC)964 static int php_stream_ftp_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context TSRMLS_DC)
965 {
966 php_stream *stream = NULL;
967 php_url *resource_from = NULL, *resource_to = NULL;
968 int result;
969 char tmp_line[512];
970
971 resource_from = php_url_parse(url_from);
972 resource_to = php_url_parse(url_to);
973 /* Must be same scheme (ftp/ftp or ftps/ftps), same host, and same port
974 (or a 21/0 0/21 combination which is also "same")
975 Also require paths to/from */
976 if (!resource_from ||
977 !resource_to ||
978 !resource_from->scheme ||
979 !resource_to->scheme ||
980 strcmp(resource_from->scheme, resource_to->scheme) ||
981 !resource_from->host ||
982 !resource_to->host ||
983 strcmp(resource_from->host, resource_to->host) ||
984 (resource_from->port != resource_to->port &&
985 resource_from->port * resource_to->port != 0 &&
986 resource_from->port + resource_to->port != 21) ||
987 !resource_from->path ||
988 !resource_to->path) {
989 goto rename_errexit;
990 }
991
992 stream = php_ftp_fopen_connect(wrapper, url_from, "r", 0, NULL, NULL, NULL, NULL, NULL, NULL TSRMLS_CC);
993 if (!stream) {
994 if (options & REPORT_ERRORS) {
995 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", resource_from->host);
996 }
997 goto rename_errexit;
998 }
999
1000 /* Rename FROM */
1001 php_stream_printf(stream TSRMLS_CC, "RNFR %s\r\n", (resource_from->path != NULL ? resource_from->path : "/"));
1002
1003 result = GET_FTP_RESULT(stream);
1004 if (result < 300 || result > 399) {
1005 if (options & REPORT_ERRORS) {
1006 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Renaming file: %s", tmp_line);
1007 }
1008 goto rename_errexit;
1009 }
1010
1011 /* Rename TO */
1012 php_stream_printf(stream TSRMLS_CC, "RNTO %s\r\n", (resource_to->path != NULL ? resource_to->path : "/"));
1013
1014 result = GET_FTP_RESULT(stream);
1015 if (result < 200 || result > 299) {
1016 if (options & REPORT_ERRORS) {
1017 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Renaming file: %s", tmp_line);
1018 }
1019 goto rename_errexit;
1020 }
1021
1022 php_url_free(resource_from);
1023 php_url_free(resource_to);
1024 php_stream_close(stream);
1025 return 1;
1026
1027 rename_errexit:
1028 if (resource_from) {
1029 php_url_free(resource_from);
1030 }
1031 if (resource_to) {
1032 php_url_free(resource_to);
1033 }
1034 if (stream) {
1035 php_stream_close(stream);
1036 }
1037 return 0;
1038 }
1039 /* }}} */
1040
1041 /* {{{ php_stream_ftp_mkdir
1042 */
php_stream_ftp_mkdir(php_stream_wrapper * wrapper,const char * url,int mode,int options,php_stream_context * context TSRMLS_DC)1043 static int php_stream_ftp_mkdir(php_stream_wrapper *wrapper, const char *url, int mode, int options, php_stream_context *context TSRMLS_DC)
1044 {
1045 php_stream *stream = NULL;
1046 php_url *resource = NULL;
1047 int result, recursive = options & PHP_STREAM_MKDIR_RECURSIVE;
1048 char tmp_line[512];
1049
1050 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
1051 if (!stream) {
1052 if (options & REPORT_ERRORS) {
1053 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
1054 }
1055 goto mkdir_errexit;
1056 }
1057
1058 if (resource->path == NULL) {
1059 if (options & REPORT_ERRORS) {
1060 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
1061 }
1062 goto mkdir_errexit;
1063 }
1064
1065 if (!recursive) {
1066 php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", resource->path);
1067 result = GET_FTP_RESULT(stream);
1068 } else {
1069 /* we look for directory separator from the end of string, thus hopefuly reducing our work load */
1070 char *p, *e, *buf;
1071
1072 buf = estrdup(resource->path);
1073 e = buf + strlen(buf);
1074
1075 /* find a top level directory we need to create */
1076 while ((p = strrchr(buf, '/'))) {
1077 *p = '\0';
1078 php_stream_printf(stream TSRMLS_CC, "CWD %s\r\n", buf);
1079 result = GET_FTP_RESULT(stream);
1080 if (result >= 200 && result <= 299) {
1081 *p = '/';
1082 break;
1083 }
1084 }
1085 if (p == buf) {
1086 php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", resource->path);
1087 result = GET_FTP_RESULT(stream);
1088 } else {
1089 php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", buf);
1090 result = GET_FTP_RESULT(stream);
1091 if (result >= 200 && result <= 299) {
1092 if (!p) {
1093 p = buf;
1094 }
1095 /* create any needed directories if the creation of the 1st directory worked */
1096 while (++p != e) {
1097 if (*p == '\0' && *(p + 1) != '\0') {
1098 *p = '/';
1099 php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", buf);
1100 result = GET_FTP_RESULT(stream);
1101 if (result < 200 || result > 299) {
1102 if (options & REPORT_ERRORS) {
1103 php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tmp_line);
1104 }
1105 break;
1106 }
1107 }
1108 }
1109 }
1110 }
1111 efree(buf);
1112 }
1113
1114 php_url_free(resource);
1115 php_stream_close(stream);
1116
1117 if (result < 200 || result > 299) {
1118 /* Failure */
1119 return 0;
1120 }
1121
1122 return 1;
1123
1124 mkdir_errexit:
1125 if (resource) {
1126 php_url_free(resource);
1127 }
1128 if (stream) {
1129 php_stream_close(stream);
1130 }
1131 return 0;
1132 }
1133 /* }}} */
1134
1135 /* {{{ php_stream_ftp_rmdir
1136 */
php_stream_ftp_rmdir(php_stream_wrapper * wrapper,const char * url,int options,php_stream_context * context TSRMLS_DC)1137 static int php_stream_ftp_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context TSRMLS_DC)
1138 {
1139 php_stream *stream = NULL;
1140 php_url *resource = NULL;
1141 int result;
1142 char tmp_line[512];
1143
1144 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC);
1145 if (!stream) {
1146 if (options & REPORT_ERRORS) {
1147 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url);
1148 }
1149 goto rmdir_errexit;
1150 }
1151
1152 if (resource->path == NULL) {
1153 if (options & REPORT_ERRORS) {
1154 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url);
1155 }
1156 goto rmdir_errexit;
1157 }
1158
1159 php_stream_printf(stream TSRMLS_CC, "RMD %s\r\n", resource->path);
1160 result = GET_FTP_RESULT(stream);
1161
1162 if (result < 200 || result > 299) {
1163 if (options & REPORT_ERRORS) {
1164 php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tmp_line);
1165 }
1166 goto rmdir_errexit;
1167 }
1168
1169 php_url_free(resource);
1170 php_stream_close(stream);
1171
1172 return 1;
1173
1174 rmdir_errexit:
1175 if (resource) {
1176 php_url_free(resource);
1177 }
1178 if (stream) {
1179 php_stream_close(stream);
1180 }
1181 return 0;
1182 }
1183 /* }}} */
1184
1185 static php_stream_wrapper_ops ftp_stream_wops = {
1186 php_stream_url_wrap_ftp,
1187 php_stream_ftp_stream_close, /* stream_close */
1188 php_stream_ftp_stream_stat,
1189 php_stream_ftp_url_stat, /* stat_url */
1190 php_stream_ftp_opendir, /* opendir */
1191 "ftp",
1192 php_stream_ftp_unlink, /* unlink */
1193 php_stream_ftp_rename, /* rename */
1194 php_stream_ftp_mkdir, /* mkdir */
1195 php_stream_ftp_rmdir /* rmdir */
1196 };
1197
1198 PHPAPI php_stream_wrapper php_stream_ftp_wrapper = {
1199 &ftp_stream_wops,
1200 NULL,
1201 1 /* is_url */
1202 };
1203
1204
1205 /*
1206 * Local variables:
1207 * tab-width: 4
1208 * c-basic-offset: 4
1209 * End:
1210 * vim600: sw=4 ts=4 fdm=marker
1211 * vim<600: sw=4 ts=4
1212 */
1213