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