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