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