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