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