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