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