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