xref: /PHP-8.4/ext/ftp/ftp.c (revision 11accb5c)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Andrew Skalski <askalski@chek.com>                          |
14    |          Stefan Esser <sesser@php.net> (resume functions)            |
15    +----------------------------------------------------------------------+
16  */
17 
18 #ifdef HAVE_CONFIG_H
19 #include <config.h>
20 #endif
21 
22 #include "php.h"
23 
24 #include <stdio.h>
25 #include <ctype.h>
26 #include <stdlib.h>
27 #ifdef HAVE_UNISTD_H
28 #include <unistd.h>
29 #endif
30 #include <fcntl.h>
31 #include <string.h>
32 #include <time.h>
33 #ifdef PHP_WIN32
34 #include <winsock2.h>
35 #else
36 #ifdef HAVE_SYS_TYPES_H
37 #include <sys/types.h>
38 #endif
39 #include <sys/socket.h>
40 #include <netinet/in.h>
41 #include <arpa/inet.h>
42 #include <netdb.h>
43 #endif
44 #include <errno.h>
45 
46 #ifdef HAVE_SYS_TIME_H
47 #include <sys/time.h>
48 #endif
49 
50 #ifdef HAVE_SYS_SELECT_H
51 #include <sys/select.h>
52 #endif
53 
54 #ifdef HAVE_FTP_SSL
55 #include <openssl/ssl.h>
56 #include <openssl/err.h>
57 #endif
58 
59 #include "ftp.h"
60 #include "ext/standard/fsock.h"
61 
62 #ifdef PHP_WIN32
63 # undef ETIMEDOUT
64 # define ETIMEDOUT WSAETIMEDOUT
65 #endif
66 
67 /* sends an ftp command, returns true on success, false on error.
68  * it sends the string "cmd args\r\n" if args is non-null, or
69  * "cmd\r\n" if args is null
70  */
71 static int		ftp_putcmd(	ftpbuf_t *ftp,
72 					const char *cmd,
73 					const size_t cmd_len,
74 					const char *args,
75 					const size_t args_len);
76 
77 /* wrapper around send/recv to handle timeouts */
78 static int		my_send(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t len);
79 static int		my_recv(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t len);
80 static int		my_accept(ftpbuf_t *ftp, php_socket_t s, struct sockaddr *addr, socklen_t *addrlen);
81 
82 /* reads a line the socket , returns true on success, false on error */
83 static int		ftp_readline(ftpbuf_t *ftp);
84 
85 /* reads an ftp response, returns true on success, false on error */
86 static int		ftp_getresp(ftpbuf_t *ftp);
87 
88 /* sets the ftp transfer type */
89 static int		ftp_type(ftpbuf_t *ftp, ftptype_t type);
90 
91 /* opens up a data stream */
92 static databuf_t*	ftp_getdata(ftpbuf_t *ftp);
93 
94 /* accepts the data connection, returns updated data buffer */
95 static databuf_t*	data_accept(databuf_t *data, ftpbuf_t *ftp);
96 
97 /* closes the data connection, no-op if already closed */
98 static void		data_close(ftpbuf_t *ftp);
99 
100 /* generic file lister */
101 static char**		ftp_genlist(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len, const char *path, const size_t path_len);
102 
103 #ifdef HAVE_FTP_SSL
104 /* shuts down a TLS/SSL connection */
105 static void		ftp_ssl_shutdown(ftpbuf_t *ftp, php_socket_t fd, SSL *ssl_handle);
106 #endif
107 
108 /* IP and port conversion box */
109 union ipbox {
110 	struct in_addr	ia[2];
111 	unsigned short	s[4];
112 	unsigned char	c[8];
113 };
114 
115 /* {{{ ftp_open */
116 ftpbuf_t*
ftp_open(const char * host,short port,zend_long timeout_sec)117 ftp_open(const char *host, short port, zend_long timeout_sec)
118 {
119 	ftpbuf_t		*ftp;
120 	socklen_t		 size;
121 	struct timeval tv;
122 
123 
124 	/* alloc the ftp structure */
125 	ftp = ecalloc(1, sizeof(*ftp));
126 
127 	tv.tv_sec = timeout_sec;
128 	tv.tv_usec = 0;
129 
130 	ftp->fd = php_network_connect_socket_to_host(host,
131 			(unsigned short) (port ? port : 21), SOCK_STREAM,
132 			0, &tv, NULL, NULL, NULL, 0, STREAM_SOCKOP_NONE);
133 	if (ftp->fd == -1) {
134 		goto bail;
135 	}
136 
137 	/* Default Settings */
138 	ftp->timeout_sec = timeout_sec;
139 	ftp->nb = 0;
140 
141 	size = sizeof(ftp->localaddr);
142 	memset(&ftp->localaddr, 0, size);
143 	if (getsockname(ftp->fd, (struct sockaddr*) &ftp->localaddr, &size) != 0) {
144 		php_error_docref(NULL, E_WARNING, "getsockname failed: %s (%d)", strerror(errno), errno);
145 		goto bail;
146 	}
147 
148 	if (!ftp_getresp(ftp) || ftp->resp != 220) {
149 		goto bail;
150 	}
151 
152 	return ftp;
153 
154 bail:
155 	if (ftp->fd != -1) {
156 		closesocket(ftp->fd);
157 	}
158 	efree(ftp);
159 	return NULL;
160 }
161 /* }}} */
162 
163 /* {{{ ftp_close */
164 ftpbuf_t*
ftp_close(ftpbuf_t * ftp)165 ftp_close(ftpbuf_t *ftp)
166 {
167 	if (ftp == NULL) {
168 		return NULL;
169 	}
170 #ifdef HAVE_FTP_SSL
171 	if (ftp->last_ssl_session) {
172 		SSL_SESSION_free(ftp->last_ssl_session);
173 	}
174 #endif
175 	data_close(ftp);
176 	if (ftp->stream && ftp->closestream) {
177 			php_stream_close(ftp->stream);
178 	}
179 	if (ftp->fd != -1) {
180 #ifdef HAVE_FTP_SSL
181 		if (ftp->ssl_active) {
182 			ftp_ssl_shutdown(ftp, ftp->fd, ftp->ssl_handle);
183 		}
184 #endif
185 		closesocket(ftp->fd);
186 	}
187 	ftp_gc(ftp);
188 	efree(ftp);
189 	return NULL;
190 }
191 /* }}} */
192 
193 /* {{{ ftp_gc */
194 void
ftp_gc(ftpbuf_t * ftp)195 ftp_gc(ftpbuf_t *ftp)
196 {
197 	if (ftp == NULL) {
198 		return;
199 	}
200 	if (ftp->pwd) {
201 		efree(ftp->pwd);
202 		ftp->pwd = NULL;
203 	}
204 	if (ftp->syst) {
205 		efree(ftp->syst);
206 		ftp->syst = NULL;
207 	}
208 }
209 /* }}} */
210 
211 /* {{{ ftp_quit */
212 int
ftp_quit(ftpbuf_t * ftp)213 ftp_quit(ftpbuf_t *ftp)
214 {
215 	if (ftp == NULL) {
216 		return 0;
217 	}
218 
219 	if (!ftp_putcmd(ftp, "QUIT", sizeof("QUIT")-1, NULL, (size_t) 0)) {
220 		return 0;
221 	}
222 	if (!ftp_getresp(ftp) || ftp->resp != 221) {
223 		return 0;
224 	}
225 
226 	if (ftp->pwd) {
227 		efree(ftp->pwd);
228 		ftp->pwd = NULL;
229 	}
230 
231 	return 1;
232 }
233 /* }}} */
234 
235 #ifdef HAVE_FTP_SSL
ftp_ssl_new_session_cb(SSL * ssl,SSL_SESSION * sess)236 static int ftp_ssl_new_session_cb(SSL *ssl, SSL_SESSION *sess)
237 {
238 	ftpbuf_t *ftp = SSL_get_app_data(ssl);
239 
240 	/* Technically there can be multiple sessions per connection, but we only care about the most recent one. */
241 	if (ftp->last_ssl_session) {
242 		SSL_SESSION_free(ftp->last_ssl_session);
243 	}
244 	ftp->last_ssl_session = SSL_get1_session(ssl);
245 
246 	/* Return 0 as we are not using OpenSSL's session cache. */
247 	return 0;
248 }
249 #endif
250 
251 /* {{{ ftp_login */
252 int
ftp_login(ftpbuf_t * ftp,const char * user,const size_t user_len,const char * pass,const size_t pass_len)253 ftp_login(ftpbuf_t *ftp, const char *user, const size_t user_len, const char *pass, const size_t pass_len)
254 {
255 #ifdef HAVE_FTP_SSL
256 	SSL_CTX	*ctx = NULL;
257 	long ssl_ctx_options = SSL_OP_ALL;
258 	int err, res;
259 	bool retry;
260 #endif
261 	if (ftp == NULL) {
262 		return 0;
263 	}
264 
265 #ifdef HAVE_FTP_SSL
266 	if (ftp->use_ssl && !ftp->ssl_active) {
267 		if (!ftp_putcmd(ftp, "AUTH", sizeof("AUTH")-1, "TLS", sizeof("TLS")-1)) {
268 			return 0;
269 		}
270 		if (!ftp_getresp(ftp)) {
271 			return 0;
272 		}
273 
274 		if (ftp->resp != 234) {
275 			if (!ftp_putcmd(ftp, "AUTH", sizeof("AUTH")-1, "SSL", sizeof("SSL")-1)) {
276 				return 0;
277 			}
278 			if (!ftp_getresp(ftp)) {
279 				return 0;
280 			}
281 
282 			if (ftp->resp != 334) {
283 				return 0;
284 			} else {
285 				ftp->old_ssl = 1;
286 				ftp->use_ssl_for_data = 1;
287 			}
288 		}
289 
290 		ctx = SSL_CTX_new(SSLv23_client_method());
291 		if (ctx == NULL) {
292 			php_error_docref(NULL, E_WARNING, "Failed to create the SSL context");
293 			return 0;
294 		}
295 
296 		ssl_ctx_options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
297 		SSL_CTX_set_options(ctx, ssl_ctx_options);
298 
299 		/* Allow SSL to re-use sessions.
300 		 * We're relying on our own session storage as only at most one session will ever be active per FTP connection. */
301 		SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_BOTH | SSL_SESS_CACHE_NO_INTERNAL);
302 		SSL_CTX_sess_set_new_cb(ctx, ftp_ssl_new_session_cb);
303 
304 		ftp->ssl_handle = SSL_new(ctx);
305 		SSL_set_app_data(ftp->ssl_handle, ftp); /* Needed for ftp_ssl_new_session_cb */
306 		SSL_CTX_free(ctx);
307 
308 		if (ftp->ssl_handle == NULL) {
309 			php_error_docref(NULL, E_WARNING, "Failed to create the SSL handle");
310 			return 0;
311 		}
312 
313 		SSL_set_fd(ftp->ssl_handle, ftp->fd);
314 
315 		do {
316 			res = SSL_connect(ftp->ssl_handle);
317 			err = SSL_get_error(ftp->ssl_handle, res);
318 
319 			/* TODO check if handling other error codes would make sense */
320 			switch (err) {
321 				case SSL_ERROR_NONE:
322 					retry = 0;
323 					break;
324 
325 				case SSL_ERROR_ZERO_RETURN:
326 					retry = 0;
327 					SSL_shutdown(ftp->ssl_handle);
328 					break;
329 
330 				case SSL_ERROR_WANT_READ:
331 				case SSL_ERROR_WANT_WRITE: {
332 						php_pollfd p;
333 						int i;
334 
335 						p.fd = ftp->fd;
336 						p.events = (err == SSL_ERROR_WANT_READ) ? (POLLIN|POLLPRI) : POLLOUT;
337 						p.revents = 0;
338 
339 						i = php_poll2(&p, 1, 300);
340 
341 						retry = i > 0;
342 					}
343 					break;
344 
345 				default:
346 					php_error_docref(NULL, E_WARNING, "SSL/TLS handshake failed");
347 					SSL_shutdown(ftp->ssl_handle);
348 					SSL_free(ftp->ssl_handle);
349 					return 0;
350 			}
351 		} while (retry);
352 
353 		ftp->ssl_active = 1;
354 
355 		if (!ftp->old_ssl) {
356 
357 			/* set protection buffersize to zero */
358 			if (!ftp_putcmd(ftp, "PBSZ", sizeof("PBSZ")-1, "0", sizeof("0")-1)) {
359 				return 0;
360 			}
361 			if (!ftp_getresp(ftp)) {
362 				return 0;
363 			}
364 
365 			/* enable data conn encryption */
366 			if (!ftp_putcmd(ftp, "PROT", sizeof("PROT")-1, "P", sizeof("P")-1)) {
367 				return 0;
368 			}
369 			if (!ftp_getresp(ftp)) {
370 				return 0;
371 			}
372 
373 			ftp->use_ssl_for_data = (ftp->resp >= 200 && ftp->resp <=299);
374 		}
375 	}
376 #endif
377 
378 	if (!ftp_putcmd(ftp, "USER", sizeof("USER")-1, user, user_len)) {
379 		return 0;
380 	}
381 	if (!ftp_getresp(ftp)) {
382 		return 0;
383 	}
384 	if (ftp->resp == 230) {
385 		return 1;
386 	}
387 	if (ftp->resp != 331) {
388 		return 0;
389 	}
390 	if (!ftp_putcmd(ftp, "PASS", sizeof("PASS")-1, pass, pass_len)) {
391 		return 0;
392 	}
393 	if (!ftp_getresp(ftp)) {
394 		return 0;
395 	}
396 	return (ftp->resp == 230);
397 }
398 /* }}} */
399 
400 /* {{{ ftp_reinit */
401 int
ftp_reinit(ftpbuf_t * ftp)402 ftp_reinit(ftpbuf_t *ftp)
403 {
404 	if (ftp == NULL) {
405 		return 0;
406 	}
407 
408 	ftp_gc(ftp);
409 
410 	ftp->nb = 0;
411 
412 	if (!ftp_putcmd(ftp, "REIN", sizeof("REIN")-1, NULL, (size_t) 0)) {
413 		return 0;
414 	}
415 	if (!ftp_getresp(ftp) || ftp->resp != 220) {
416 		return 0;
417 	}
418 
419 	return 1;
420 }
421 /* }}} */
422 
423 /* {{{ ftp_syst */
424 const char*
ftp_syst(ftpbuf_t * ftp)425 ftp_syst(ftpbuf_t *ftp)
426 {
427 	char *syst, *end;
428 
429 	if (ftp == NULL) {
430 		return NULL;
431 	}
432 
433 	/* default to cached value */
434 	if (ftp->syst) {
435 		return ftp->syst;
436 	}
437 	if (!ftp_putcmd(ftp, "SYST", sizeof("SYST")-1, NULL, (size_t) 0)) {
438 		return NULL;
439 	}
440 	if (!ftp_getresp(ftp) || ftp->resp != 215) {
441 		return NULL;
442 	}
443 	syst = ftp->inbuf;
444 	while (*syst == ' ') {
445 		syst++;
446 	}
447 	if ((end = strchr(syst, ' '))) {
448 		*end = 0;
449 	}
450 	ftp->syst = estrdup(syst);
451 	if (end) {
452 		*end = ' ';
453 	}
454 	return ftp->syst;
455 }
456 /* }}} */
457 
458 /* {{{ ftp_pwd */
459 const char*
ftp_pwd(ftpbuf_t * ftp)460 ftp_pwd(ftpbuf_t *ftp)
461 {
462 	char *pwd, *end;
463 
464 	if (ftp == NULL) {
465 		return NULL;
466 	}
467 
468 	/* default to cached value */
469 	if (ftp->pwd) {
470 		return ftp->pwd;
471 	}
472 	if (!ftp_putcmd(ftp, "PWD", sizeof("PWD")-1, NULL, (size_t) 0)) {
473 		return NULL;
474 	}
475 	if (!ftp_getresp(ftp) || ftp->resp != 257) {
476 		return NULL;
477 	}
478 	/* copy out the pwd from response */
479 	if ((pwd = strchr(ftp->inbuf, '"')) == NULL) {
480 		return NULL;
481 	}
482 	if ((end = strrchr(++pwd, '"')) == NULL) {
483 		return NULL;
484 	}
485 	ftp->pwd = estrndup(pwd, end - pwd);
486 
487 	return ftp->pwd;
488 }
489 /* }}} */
490 
491 /* {{{ ftp_exec */
492 int
ftp_exec(ftpbuf_t * ftp,const char * cmd,const size_t cmd_len)493 ftp_exec(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len)
494 {
495 	if (ftp == NULL) {
496 		return 0;
497 	}
498 	if (!ftp_putcmd(ftp, "SITE EXEC", sizeof("SITE EXEC")-1, cmd, cmd_len)) {
499 		return 0;
500 	}
501 	if (!ftp_getresp(ftp) || ftp->resp != 200) {
502 		return 0;
503 	}
504 
505 	return 1;
506 }
507 /* }}} */
508 
509 /* {{{ ftp_raw */
510 void
ftp_raw(ftpbuf_t * ftp,const char * cmd,const size_t cmd_len,zval * return_value)511 ftp_raw(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len, zval *return_value)
512 {
513 	if (ftp == NULL || cmd == NULL) {
514 		RETURN_NULL();
515 	}
516 	if (!ftp_putcmd(ftp, cmd, cmd_len, NULL, (size_t) 0)) {
517 		RETURN_NULL();
518 	}
519 	array_init(return_value);
520 	while (ftp_readline(ftp)) {
521 		add_next_index_string(return_value, ftp->inbuf);
522 		if (isdigit(ftp->inbuf[0]) && isdigit(ftp->inbuf[1]) && isdigit(ftp->inbuf[2]) && ftp->inbuf[3] == ' ') {
523 			return;
524 		}
525 	}
526 }
527 /* }}} */
528 
529 /* {{{ ftp_chdir */
530 int
ftp_chdir(ftpbuf_t * ftp,const char * dir,const size_t dir_len)531 ftp_chdir(ftpbuf_t *ftp, const char *dir, const size_t dir_len)
532 {
533 	if (ftp == NULL) {
534 		return 0;
535 	}
536 
537 	if (ftp->pwd) {
538 		efree(ftp->pwd);
539 		ftp->pwd = NULL;
540 	}
541 
542 	if (!ftp_putcmd(ftp, "CWD", sizeof("CWD")-1, dir, dir_len)) {
543 		return 0;
544 	}
545 	if (!ftp_getresp(ftp) || ftp->resp != 250) {
546 		return 0;
547 	}
548 	return 1;
549 }
550 /* }}} */
551 
552 /* {{{ ftp_cdup */
553 int
ftp_cdup(ftpbuf_t * ftp)554 ftp_cdup(ftpbuf_t *ftp)
555 {
556 	if (ftp == NULL) {
557 		return 0;
558 	}
559 
560 	if (ftp->pwd) {
561 		efree(ftp->pwd);
562 		ftp->pwd = NULL;
563 	}
564 
565 	if (!ftp_putcmd(ftp, "CDUP", sizeof("CDUP")-1, NULL, (size_t) 0)) {
566 		return 0;
567 	}
568 	if (!ftp_getresp(ftp) || ftp->resp != 250) {
569 		return 0;
570 	}
571 	return 1;
572 }
573 /* }}} */
574 
575 /* {{{ ftp_mkdir */
576 zend_string*
ftp_mkdir(ftpbuf_t * ftp,const char * dir,const size_t dir_len)577 ftp_mkdir(ftpbuf_t *ftp, const char *dir, const size_t dir_len)
578 {
579 	char *mkd, *end;
580 	zend_string *ret;
581 
582 	if (ftp == NULL) {
583 		return NULL;
584 	}
585 	if (!ftp_putcmd(ftp, "MKD", sizeof("MKD")-1, dir, dir_len)) {
586 		return NULL;
587 	}
588 	if (!ftp_getresp(ftp) || ftp->resp != 257) {
589 		return NULL;
590 	}
591 	/* copy out the dir from response */
592 	if ((mkd = strchr(ftp->inbuf, '"')) == NULL) {
593 		return zend_string_init(dir, dir_len, 0);
594 	}
595 	if ((end = strrchr(++mkd, '"')) == NULL) {
596 		return NULL;
597 	}
598 	*end = 0;
599 	ret = zend_string_init(mkd, end - mkd, 0);
600 	*end = '"';
601 
602 	return ret;
603 }
604 /* }}} */
605 
606 /* {{{ ftp_rmdir */
607 int
ftp_rmdir(ftpbuf_t * ftp,const char * dir,const size_t dir_len)608 ftp_rmdir(ftpbuf_t *ftp, const char *dir, const size_t dir_len)
609 {
610 	if (ftp == NULL) {
611 		return 0;
612 	}
613 	if (!ftp_putcmd(ftp, "RMD", sizeof("RMD")-1, dir, dir_len)) {
614 		return 0;
615 	}
616 	if (!ftp_getresp(ftp) || ftp->resp != 250) {
617 		return 0;
618 	}
619 	return 1;
620 }
621 /* }}} */
622 
623 /* {{{ ftp_chmod */
624 int
ftp_chmod(ftpbuf_t * ftp,const int mode,const char * filename,const int filename_len)625 ftp_chmod(ftpbuf_t *ftp, const int mode, const char *filename, const int filename_len)
626 {
627 	char *buffer;
628 	size_t buffer_len;
629 
630 	if (ftp == NULL || filename_len <= 0) {
631 		return 0;
632 	}
633 
634 	buffer_len = spprintf(&buffer, 0, "CHMOD %o %s", mode, filename);
635 
636 	if (!buffer) {
637 		return 0;
638 	}
639 
640 	if (!ftp_putcmd(ftp, "SITE", sizeof("SITE")-1, buffer, buffer_len)) {
641 		efree(buffer);
642 		return 0;
643 	}
644 
645 	efree(buffer);
646 
647 	if (!ftp_getresp(ftp) || ftp->resp != 200) {
648 		return 0;
649 	}
650 
651 	return 1;
652 }
653 /* }}} */
654 
655 /* {{{ ftp_alloc */
656 int
ftp_alloc(ftpbuf_t * ftp,const zend_long size,zend_string ** response)657 ftp_alloc(ftpbuf_t *ftp, const zend_long size, zend_string **response)
658 {
659 	char buffer[64];
660 	int buffer_len;
661 
662 	if (ftp == NULL || size <= 0) {
663 		return 0;
664 	}
665 
666 	buffer_len = snprintf(buffer, sizeof(buffer) - 1, ZEND_LONG_FMT, size);
667 
668 	if (buffer_len < 0) {
669 		return 0;
670 	}
671 
672 	if (!ftp_putcmd(ftp, "ALLO", sizeof("ALLO")-1, buffer, buffer_len)) {
673 		return 0;
674 	}
675 
676 	if (!ftp_getresp(ftp)) {
677 		return 0;
678 	}
679 
680 	if (response) {
681 		*response = zend_string_init(ftp->inbuf, strlen(ftp->inbuf), 0);
682 	}
683 
684 	if (ftp->resp < 200 || ftp->resp >= 300) {
685 		return 0;
686 	}
687 
688 	return 1;
689 }
690 /* }}} */
691 
692 /* {{{ ftp_nlist */
693 char**
ftp_nlist(ftpbuf_t * ftp,const char * path,const size_t path_len)694 ftp_nlist(ftpbuf_t *ftp, const char *path, const size_t path_len)
695 {
696 	return ftp_genlist(ftp, "NLST", sizeof("NLST")-1, path, path_len);
697 }
698 /* }}} */
699 
700 /* {{{ ftp_list */
701 char**
ftp_list(ftpbuf_t * ftp,const char * path,const size_t path_len,int recursive)702 ftp_list(ftpbuf_t *ftp, const char *path, const size_t path_len, int recursive)
703 {
704 	return ftp_genlist(ftp, ((recursive) ? "LIST -R" : "LIST"), ((recursive) ? sizeof("LIST -R")-1 : sizeof("LIST")-1), path, path_len);
705 }
706 /* }}} */
707 
708 /* {{{ ftp_mlsd */
709 char**
ftp_mlsd(ftpbuf_t * ftp,const char * path,const size_t path_len)710 ftp_mlsd(ftpbuf_t *ftp, const char *path, const size_t path_len)
711 {
712 	return ftp_genlist(ftp, "MLSD", sizeof("MLSD")-1, path, path_len);
713 }
714 /* }}} */
715 
716 /* {{{ ftp_mlsd_parse_line */
717 int
ftp_mlsd_parse_line(HashTable * ht,const char * input)718 ftp_mlsd_parse_line(HashTable *ht, const char *input) {
719 
720 	zval zstr;
721 	const char *end = input + strlen(input);
722 
723 	const char *sp = memchr(input, ' ', end - input);
724 	if (!sp) {
725 		php_error_docref(NULL, E_WARNING, "Missing pathname in MLSD response");
726 		return FAILURE;
727 	}
728 
729 	/* Extract pathname */
730 	ZVAL_STRINGL(&zstr, sp + 1, end - sp - 1);
731 	zend_hash_update(ht, ZSTR_KNOWN(ZEND_STR_NAME), &zstr);
732 	end = sp;
733 
734 	while (input < end) {
735 		const char *semi, *eq;
736 
737 		/* Find end of fact */
738 		semi = memchr(input, ';', end - input);
739 		if (!semi) {
740 			php_error_docref(NULL, E_WARNING, "Malformed fact in MLSD response");
741 			return FAILURE;
742 		}
743 
744 		/* Separate fact key and value */
745 		eq = memchr(input, '=', semi - input);
746 		if (!eq) {
747 			php_error_docref(NULL, E_WARNING, "Malformed fact in MLSD response");
748 			return FAILURE;
749 		}
750 
751 		ZVAL_STRINGL(&zstr, eq + 1, semi - eq - 1);
752 		zend_hash_str_update(ht, input, eq - input, &zstr);
753 		input = semi + 1;
754 	}
755 
756 	return SUCCESS;
757 }
758 /* }}} */
759 
760 /* {{{ ftp_type */
761 int
ftp_type(ftpbuf_t * ftp,ftptype_t type)762 ftp_type(ftpbuf_t *ftp, ftptype_t type)
763 {
764 	const char *typechar;
765 
766 	if (ftp == NULL) {
767 		return 0;
768 	}
769 	if (type == ftp->type) {
770 		return 1;
771 	}
772 	if (type == FTPTYPE_ASCII) {
773 		typechar = "A";
774 	} else if (type == FTPTYPE_IMAGE) {
775 		typechar = "I";
776 	} else {
777 		return 0;
778 	}
779 	if (!ftp_putcmd(ftp, "TYPE", sizeof("TYPE")-1, typechar, 1)) {
780 		return 0;
781 	}
782 	if (!ftp_getresp(ftp) || ftp->resp != 200) {
783 		return 0;
784 	}
785 	ftp->type = type;
786 
787 	return 1;
788 }
789 /* }}} */
790 
791 /* {{{ ftp_pasv */
792 int
ftp_pasv(ftpbuf_t * ftp,int pasv)793 ftp_pasv(ftpbuf_t *ftp, int pasv)
794 {
795 	char			*ptr;
796 	union ipbox		ipbox;
797 	unsigned long		b[6];
798 	socklen_t			n;
799 	struct sockaddr *sa;
800 	struct sockaddr_in *sin;
801 
802 	if (ftp == NULL) {
803 		return 0;
804 	}
805 	if (pasv && ftp->pasv == 2) {
806 		return 1;
807 	}
808 	ftp->pasv = 0;
809 	if (!pasv) {
810 		return 1;
811 	}
812 	n = sizeof(ftp->pasvaddr);
813 	memset(&ftp->pasvaddr, 0, n);
814 	sa = (struct sockaddr *) &ftp->pasvaddr;
815 
816 	if (getpeername(ftp->fd, sa, &n) < 0) {
817 		return 0;
818 	}
819 
820 #ifdef HAVE_IPV6
821 	if (sa->sa_family == AF_INET6) {
822 		struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa;
823 		char *endptr, delimiter;
824 
825 		/* try EPSV first */
826 		if (!ftp_putcmd(ftp, "EPSV", sizeof("EPSV")-1, NULL, (size_t) 0)) {
827 			return 0;
828 		}
829 		if (!ftp_getresp(ftp)) {
830 			return 0;
831 		}
832 		if (ftp->resp == 229) {
833 			/* parse out the port */
834 			for (ptr = ftp->inbuf; *ptr && *ptr != '('; ptr++);
835 			if (!*ptr) {
836 				return 0;
837 			}
838 			delimiter = *++ptr;
839 			for (n = 0; *ptr && n < 3; ptr++) {
840 				if (*ptr == delimiter) {
841 					n++;
842 				}
843 			}
844 
845 			sin6->sin6_port = htons((unsigned short) strtoul(ptr, &endptr, 10));
846 			if (ptr == endptr || *endptr != delimiter) {
847 				return 0;
848 			}
849 			ftp->pasv = 2;
850 			return 1;
851 		}
852 	}
853 
854 	/* fall back to PASV */
855 #endif
856 
857 	if (!ftp_putcmd(ftp, "PASV",  sizeof("PASV")-1, NULL, (size_t) 0)) {
858 		return 0;
859 	}
860 	if (!ftp_getresp(ftp) || ftp->resp != 227) {
861 		return 0;
862 	}
863 	/* parse out the IP and port */
864 	for (ptr = ftp->inbuf; *ptr && !isdigit(*ptr); ptr++);
865 	n = sscanf(ptr, "%lu,%lu,%lu,%lu,%lu,%lu", &b[0], &b[1], &b[2], &b[3], &b[4], &b[5]);
866 	if (n != 6) {
867 		return 0;
868 	}
869 	for (n = 0; n < 6; n++) {
870 		ipbox.c[n] = (unsigned char) b[n];
871 	}
872 	sin = (struct sockaddr_in *) sa;
873 	if (ftp->usepasvaddress) {
874 		sin->sin_addr = ipbox.ia[0];
875 	}
876 	sin->sin_port = ipbox.s[2];
877 
878 	ftp->pasv = 2;
879 
880 	return 1;
881 }
882 /* }}} */
883 
884 /* {{{ ftp_get */
885 int
ftp_get(ftpbuf_t * ftp,php_stream * outstream,const char * path,const size_t path_len,ftptype_t type,zend_long resumepos)886 ftp_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_t path_len, ftptype_t type, zend_long resumepos)
887 {
888 	databuf_t		*data = NULL;
889 	size_t			rcvd;
890 	char			arg[MAX_LENGTH_OF_LONG];
891 
892 	if (ftp == NULL) {
893 		return 0;
894 	}
895 	if (!ftp_type(ftp, type)) {
896 		goto bail;
897 	}
898 
899 	if ((data = ftp_getdata(ftp)) == NULL) {
900 		goto bail;
901 	}
902 
903 	if (resumepos > 0) {
904 		int arg_len = snprintf(arg, sizeof(arg), ZEND_LONG_FMT, resumepos);
905 
906 		if (arg_len < 0) {
907 			goto bail;
908 		}
909 		if (!ftp_putcmd(ftp, "REST", sizeof("REST")-1, arg, arg_len)) {
910 			goto bail;
911 		}
912 		if (!ftp_getresp(ftp) || (ftp->resp != 350)) {
913 			goto bail;
914 		}
915 	}
916 
917 	if (!ftp_putcmd(ftp, "RETR", sizeof("RETR")-1, path, path_len)) {
918 		goto bail;
919 	}
920 	if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) {
921 		goto bail;
922 	}
923 
924 	if ((data = data_accept(data, ftp)) == NULL) {
925 		goto bail;
926 	}
927 
928 	while ((rcvd = my_recv(ftp, data->fd, data->buf, FTP_BUFSIZE))) {
929 		if (rcvd == (size_t)-1) {
930 			goto bail;
931 		}
932 
933 		if (type == FTPTYPE_ASCII) {
934 #ifndef PHP_WIN32
935 			char *s;
936 #endif
937 			char *ptr = data->buf;
938 			char *e = ptr + rcvd;
939 			/* logic depends on the OS EOL
940 			 * Win32 -> \r\n
941 			 * Everything Else \n
942 			 */
943 #ifdef PHP_WIN32
944 			php_stream_write(outstream, ptr, (e - ptr));
945 			ptr = e;
946 #else
947 			while (e > ptr && (s = memchr(ptr, '\r', (e - ptr)))) {
948 				php_stream_write(outstream, ptr, (s - ptr));
949 				if (*(s + 1) == '\n') {
950 					s++;
951 					php_stream_putc(outstream, '\n');
952 				}
953 				ptr = s + 1;
954 			}
955 #endif
956 			if (ptr < e) {
957 				php_stream_write(outstream, ptr, (e - ptr));
958 			}
959 		} else if (rcvd != php_stream_write(outstream, data->buf, rcvd)) {
960 			goto bail;
961 		}
962 	}
963 
964 	data_close(ftp);
965 
966 	if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) {
967 		goto bail;
968 	}
969 
970 	return 1;
971 bail:
972 	data_close(ftp);
973 	return 0;
974 }
975 /* }}} */
976 
ftp_send_stream_to_data_socket(ftpbuf_t * ftp,databuf_t * data,php_stream * instream,ftptype_t type,bool send_once_and_return)977 static zend_result ftp_send_stream_to_data_socket(ftpbuf_t *ftp, databuf_t *data, php_stream *instream, ftptype_t type, bool send_once_and_return)
978 {
979 	if (type == FTPTYPE_ASCII) {
980 		/* Change (and later restore) flags to make sure php_stream_get_line() searches '\n'. */
981 		const uint32_t flags_mask = PHP_STREAM_FLAG_EOL_UNIX | PHP_STREAM_FLAG_DETECT_EOL | PHP_STREAM_FLAG_EOL_MAC;
982 		uint32_t old_flags = instream->flags & flags_mask;
983 		instream->flags = (instream->flags & ~flags_mask) | PHP_STREAM_FLAG_EOL_UNIX;
984 
985 		char *ptr = data->buf;
986 		const char *end = data->buf + FTP_BUFSIZE;
987 		while (!php_stream_eof(instream)) {
988 			size_t line_length;
989 			if (!php_stream_get_line(instream, ptr, end - ptr, &line_length)) {
990 				break;
991 			}
992 
993 			ZEND_ASSERT(line_length != 0);
994 
995 			ptr += line_length - 1;
996 			/* Replace \n with \r\n */
997 			if (*ptr == '\n') {
998 				*ptr = '\r';
999 				/* The streams layer always puts a \0 byte at the end of a line,
1000 				 * so there is always place to add an extra byte. */
1001 				*++ptr = '\n';
1002 			}
1003 
1004 			ptr++;
1005 
1006 			/* If less than 2 bytes remain, either the buffer is completely full or there is a single byte left to put a '\0'
1007 			 * which isn't really useful, in this case send and reset the buffer. */
1008 			if (end - ptr < 2) {
1009 				size_t send_size = FTP_BUFSIZE - (end - ptr);
1010 				if (UNEXPECTED(my_send(ftp, data->fd, data->buf, send_size) != send_size)) {
1011 					instream->flags = (instream->flags & ~flags_mask) | old_flags;
1012 					return FAILURE;
1013 				}
1014 				ptr = data->buf;
1015 				if (send_once_and_return) {
1016 					break;
1017 				}
1018 			}
1019 		}
1020 
1021 		instream->flags = (instream->flags & ~flags_mask) | old_flags;
1022 
1023 		if (end - ptr < FTP_BUFSIZE) {
1024 			size_t send_size = FTP_BUFSIZE - (end - ptr);
1025 			if (UNEXPECTED(my_send(ftp, data->fd, data->buf, send_size) != send_size)) {
1026 				return FAILURE;
1027 			}
1028 		}
1029 	} else {
1030 		while (!php_stream_eof(instream)) {
1031 			ssize_t size = php_stream_read(instream, data->buf, FTP_BUFSIZE);
1032 			if (size == 0) {
1033 				break;
1034 			}
1035 			if (UNEXPECTED(size < 0)) {
1036 				return FAILURE;
1037 			}
1038 			if (UNEXPECTED(my_send(ftp, data->fd, data->buf, size) != size)) {
1039 				return FAILURE;
1040 			}
1041 			if (send_once_and_return) {
1042 				break;
1043 			}
1044 		}
1045 	}
1046 
1047 	return SUCCESS;
1048 }
1049 
1050 /* {{{ ftp_put */
1051 int
ftp_put(ftpbuf_t * ftp,const char * path,const size_t path_len,php_stream * instream,ftptype_t type,zend_long startpos)1052 ftp_put(ftpbuf_t *ftp, const char *path, const size_t path_len, php_stream *instream, ftptype_t type, zend_long startpos)
1053 {
1054 	databuf_t		*data = NULL;
1055 	char			arg[MAX_LENGTH_OF_LONG];
1056 
1057 	if (ftp == NULL) {
1058 		return 0;
1059 	}
1060 	if (!ftp_type(ftp, type)) {
1061 		goto bail;
1062 	}
1063 	if ((data = ftp_getdata(ftp)) == NULL) {
1064 		goto bail;
1065 	}
1066 
1067 	if (startpos > 0) {
1068 		int arg_len = snprintf(arg, sizeof(arg), ZEND_LONG_FMT, startpos);
1069 
1070 		if (arg_len < 0) {
1071 			goto bail;
1072 		}
1073 		if (!ftp_putcmd(ftp, "REST", sizeof("REST")-1, arg, arg_len)) {
1074 			goto bail;
1075 		}
1076 		if (!ftp_getresp(ftp) || (ftp->resp != 350)) {
1077 			goto bail;
1078 		}
1079 	}
1080 
1081 	if (!ftp_putcmd(ftp, "STOR", sizeof("STOR")-1, path, path_len)) {
1082 		goto bail;
1083 	}
1084 	if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) {
1085 		goto bail;
1086 	}
1087 	if ((data = data_accept(data, ftp)) == NULL) {
1088 		goto bail;
1089 	}
1090 
1091 	if (ftp_send_stream_to_data_socket(ftp, data, instream, type, false) != SUCCESS) {
1092 		goto bail;
1093 	}
1094 
1095 	data_close(ftp);
1096 
1097 	if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250 && ftp->resp != 200)) {
1098 		goto bail;
1099 	}
1100 	return 1;
1101 bail:
1102 	data_close(ftp);
1103 	return 0;
1104 }
1105 /* }}} */
1106 
1107 
1108 /* {{{ ftp_append */
1109 int
ftp_append(ftpbuf_t * ftp,const char * path,const size_t path_len,php_stream * instream,ftptype_t type)1110 ftp_append(ftpbuf_t *ftp, const char *path, const size_t path_len, php_stream *instream, ftptype_t type)
1111 {
1112 	databuf_t		*data = NULL;
1113 
1114 	if (ftp == NULL) {
1115 		return 0;
1116 	}
1117 	if (!ftp_type(ftp, type)) {
1118 		goto bail;
1119 	}
1120 	if ((data = ftp_getdata(ftp)) == NULL) {
1121 		goto bail;
1122 	}
1123 	ftp->data = data;
1124 
1125 	if (!ftp_putcmd(ftp, "APPE", sizeof("APPE")-1, path, path_len)) {
1126 		goto bail;
1127 	}
1128 	if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) {
1129 		goto bail;
1130 	}
1131 	if ((data = data_accept(data, ftp)) == NULL) {
1132 		goto bail;
1133 	}
1134 
1135 	if (ftp_send_stream_to_data_socket(ftp, data, instream, type, false) != SUCCESS) {
1136 		goto bail;
1137 	}
1138 
1139 	data_close(ftp);
1140 
1141 	if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250 && ftp->resp != 200)) {
1142 		goto bail;
1143 	}
1144 	return 1;
1145 bail:
1146 	data_close(ftp);
1147 	return 0;
1148 }
1149 /* }}} */
1150 
1151 /* {{{ ftp_size */
1152 zend_long
ftp_size(ftpbuf_t * ftp,const char * path,const size_t path_len)1153 ftp_size(ftpbuf_t *ftp, const char *path, const size_t path_len)
1154 {
1155 	if (ftp == NULL) {
1156 		return -1;
1157 	}
1158 	if (!ftp_type(ftp, FTPTYPE_IMAGE)) {
1159 		return -1;
1160 	}
1161 	if (!ftp_putcmd(ftp, "SIZE", sizeof("SIZE")-1, path, path_len)) {
1162 		return -1;
1163 	}
1164 	if (!ftp_getresp(ftp) || ftp->resp != 213) {
1165 		return -1;
1166 	}
1167 	return ZEND_ATOL(ftp->inbuf);
1168 }
1169 /* }}} */
1170 
1171 /* {{{ ftp_mdtm */
1172 time_t
ftp_mdtm(ftpbuf_t * ftp,const char * path,const size_t path_len)1173 ftp_mdtm(ftpbuf_t *ftp, const char *path, const size_t path_len)
1174 {
1175 	time_t		stamp;
1176 	struct tm	*gmt, tmbuf;
1177 	struct tm	tm;
1178 	char		*ptr;
1179 	int		n;
1180 
1181 	if (ftp == NULL) {
1182 		return -1;
1183 	}
1184 	if (!ftp_putcmd(ftp, "MDTM", sizeof("MDTM")-1, path, path_len)) {
1185 		return -1;
1186 	}
1187 	if (!ftp_getresp(ftp) || ftp->resp != 213) {
1188 		return -1;
1189 	}
1190 	/* parse out the timestamp */
1191 	for (ptr = ftp->inbuf; *ptr && !isdigit(*ptr); ptr++);
1192 	n = sscanf(ptr, "%4d%2d%2d%2d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
1193 	if (n != 6) {
1194 		return -1;
1195 	}
1196 	tm.tm_year -= 1900;
1197 	tm.tm_mon--;
1198 	tm.tm_isdst = -1;
1199 
1200 	/* figure out the GMT offset */
1201 	stamp = time(NULL);
1202 	gmt = php_gmtime_r(&stamp, &tmbuf);
1203 	if (!gmt) {
1204 		return -1;
1205 	}
1206 	gmt->tm_isdst = -1;
1207 
1208 	/* apply the GMT offset */
1209 	tm.tm_sec += stamp - mktime(gmt);
1210 	tm.tm_isdst = gmt->tm_isdst;
1211 
1212 	stamp = mktime(&tm);
1213 
1214 	return stamp;
1215 }
1216 /* }}} */
1217 
1218 /* {{{ ftp_delete */
1219 int
ftp_delete(ftpbuf_t * ftp,const char * path,const size_t path_len)1220 ftp_delete(ftpbuf_t *ftp, const char *path, const size_t path_len)
1221 {
1222 	if (ftp == NULL) {
1223 		return 0;
1224 	}
1225 	if (!ftp_putcmd(ftp, "DELE", sizeof("DELE")-1, path, path_len)) {
1226 		return 0;
1227 	}
1228 	if (!ftp_getresp(ftp) || ftp->resp != 250) {
1229 		return 0;
1230 	}
1231 
1232 	return 1;
1233 }
1234 /* }}} */
1235 
1236 /* {{{ ftp_rename */
1237 int
ftp_rename(ftpbuf_t * ftp,const char * src,const size_t src_len,const char * dest,const size_t dest_len)1238 ftp_rename(ftpbuf_t *ftp, const char *src, const size_t src_len, const char *dest, const size_t dest_len)
1239 {
1240 	if (ftp == NULL) {
1241 		return 0;
1242 	}
1243 	if (!ftp_putcmd(ftp, "RNFR", sizeof("RNFR")-1, src, src_len)) {
1244 		return 0;
1245 	}
1246 	if (!ftp_getresp(ftp) || ftp->resp != 350) {
1247 		return 0;
1248 	}
1249 	if (!ftp_putcmd(ftp, "RNTO", sizeof("RNTO")-1, dest, dest_len)) {
1250 		return 0;
1251 	}
1252 	if (!ftp_getresp(ftp) || ftp->resp != 250) {
1253 		return 0;
1254 	}
1255 	return 1;
1256 }
1257 /* }}} */
1258 
1259 /* {{{ ftp_site */
1260 int
ftp_site(ftpbuf_t * ftp,const char * cmd,const size_t cmd_len)1261 ftp_site(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len)
1262 {
1263 	if (ftp == NULL) {
1264 		return 0;
1265 	}
1266 	if (!ftp_putcmd(ftp, "SITE", sizeof("SITE")-1, cmd, cmd_len)) {
1267 		return 0;
1268 	}
1269 	if (!ftp_getresp(ftp) || ftp->resp < 200 || ftp->resp >= 300) {
1270 		return 0;
1271 	}
1272 
1273 	return 1;
1274 }
1275 /* }}} */
1276 
1277 /* static functions */
1278 
1279 /* {{{ ftp_putcmd */
1280 int
ftp_putcmd(ftpbuf_t * ftp,const char * cmd,const size_t cmd_len,const char * args,const size_t args_len)1281 ftp_putcmd(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len, const char *args, const size_t args_len)
1282 {
1283 	int		size;
1284 	char		*data;
1285 
1286 	if (strpbrk(cmd, "\r\n")) {
1287 		return 0;
1288 	}
1289 	/* build the output buffer */
1290 	if (args && args[0]) {
1291 		/* "cmd args\r\n\0" */
1292 		if (cmd_len + args_len + 4 > FTP_BUFSIZE) {
1293 			return 0;
1294 		}
1295 		if (strpbrk(args, "\r\n")) {
1296 			return 0;
1297 		}
1298 		size = slprintf(ftp->outbuf, sizeof(ftp->outbuf), "%s %s\r\n", cmd, args);
1299 	} else {
1300 		/* "cmd\r\n\0" */
1301 		if (cmd_len + 3 > FTP_BUFSIZE) {
1302 			return 0;
1303 		}
1304 		size = slprintf(ftp->outbuf, sizeof(ftp->outbuf), "%s\r\n", cmd);
1305 	}
1306 
1307 	data = ftp->outbuf;
1308 
1309 	/* Clear the inbuf and extra-lines buffer */
1310 	ftp->inbuf[0] = '\0';
1311 	ftp->extra = NULL;
1312 
1313 	if (my_send(ftp, ftp->fd, data, size) != size) {
1314 		return 0;
1315 	}
1316 	return 1;
1317 }
1318 /* }}} */
1319 
1320 /* {{{ ftp_readline */
1321 int
ftp_readline(ftpbuf_t * ftp)1322 ftp_readline(ftpbuf_t *ftp)
1323 {
1324 	long		size, rcvd;
1325 	char		*data, *eol;
1326 
1327 	/* shift the extra to the front */
1328 	size = FTP_BUFSIZE;
1329 	rcvd = 0;
1330 	if (ftp->extra) {
1331 		memmove(ftp->inbuf, ftp->extra, ftp->extralen);
1332 		rcvd = ftp->extralen;
1333 	}
1334 
1335 	data = ftp->inbuf;
1336 
1337 	do {
1338 		size -= rcvd;
1339 		for (eol = data; rcvd; rcvd--, eol++) {
1340 			if (*eol == '\r') {
1341 				*eol = 0;
1342 				ftp->extra = eol + 1;
1343 				if (rcvd > 1 && *(eol + 1) == '\n') {
1344 					ftp->extra++;
1345 					rcvd--;
1346 				}
1347 				if ((ftp->extralen = --rcvd) == 0) {
1348 					ftp->extra = NULL;
1349 				}
1350 				return 1;
1351 			} else if (*eol == '\n') {
1352 				*eol = 0;
1353 				ftp->extra = eol + 1;
1354 				if ((ftp->extralen = --rcvd) == 0) {
1355 					ftp->extra = NULL;
1356 				}
1357 				return 1;
1358 			}
1359 		}
1360 
1361 		data = eol;
1362 		if ((rcvd = my_recv(ftp, ftp->fd, data, size)) < 1) {
1363 			*data = 0;
1364 			return 0;
1365 		}
1366 	} while (size);
1367 
1368 	*data = 0;
1369 	return 0;
1370 }
1371 /* }}} */
1372 
1373 /* {{{ ftp_getresp */
1374 int
ftp_getresp(ftpbuf_t * ftp)1375 ftp_getresp(ftpbuf_t *ftp)
1376 {
1377 	if (ftp == NULL) {
1378 		return 0;
1379 	}
1380 	ftp->resp = 0;
1381 
1382 	while (1) {
1383 
1384 		if (!ftp_readline(ftp)) {
1385 			return 0;
1386 		}
1387 
1388 		/* Break out when the end-tag is found */
1389 		if (isdigit(ftp->inbuf[0]) && isdigit(ftp->inbuf[1]) && isdigit(ftp->inbuf[2]) && ftp->inbuf[3] == ' ') {
1390 			break;
1391 		}
1392 	}
1393 
1394 	/* translate the tag */
1395 	if (!isdigit(ftp->inbuf[0]) || !isdigit(ftp->inbuf[1]) || !isdigit(ftp->inbuf[2])) {
1396 		return 0;
1397 	}
1398 
1399 	ftp->resp = 100 * (ftp->inbuf[0] - '0') + 10 * (ftp->inbuf[1] - '0') + (ftp->inbuf[2] - '0');
1400 
1401 	memmove(ftp->inbuf, ftp->inbuf + 4, FTP_BUFSIZE - 4);
1402 
1403 	if (ftp->extra) {
1404 		ftp->extra -= 4;
1405 	}
1406 	return 1;
1407 }
1408 /* }}} */
1409 
single_send(ftpbuf_t * ftp,php_socket_t s,void * buf,size_t size)1410 int single_send(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t size) {
1411 #ifdef HAVE_FTP_SSL
1412 	int err;
1413 	bool retry = 0;
1414 	SSL *handle = NULL;
1415 	php_socket_t fd;
1416 	size_t sent;
1417 
1418 	if (ftp->use_ssl && ftp->fd == s && ftp->ssl_active) {
1419 		handle = ftp->ssl_handle;
1420 		fd = ftp->fd;
1421 	} else if (ftp->use_ssl && ftp->fd != s && ftp->use_ssl_for_data && ftp->data->ssl_active) {
1422 		handle = ftp->data->ssl_handle;
1423 		fd = ftp->data->fd;
1424 	} else {
1425 		return send(s, buf, size, 0);
1426 	}
1427 
1428 	do {
1429 		sent = SSL_write(handle, buf, size);
1430 		err = SSL_get_error(handle, sent);
1431 
1432 		switch (err) {
1433 			case SSL_ERROR_NONE:
1434 				retry = 0;
1435 				break;
1436 
1437 			case SSL_ERROR_ZERO_RETURN:
1438 				retry = 0;
1439 				SSL_shutdown(handle);
1440 				break;
1441 
1442 			case SSL_ERROR_WANT_READ:
1443 			case SSL_ERROR_WANT_CONNECT: {
1444 					php_pollfd p;
1445 					int i;
1446 
1447 					p.fd = fd;
1448 					p.events = POLLOUT;
1449 					p.revents = 0;
1450 
1451 					i = php_poll2(&p, 1, 300);
1452 
1453 					retry = i > 0;
1454 				}
1455 				break;
1456 
1457 			default:
1458 				php_error_docref(NULL, E_WARNING, "SSL write failed");
1459 				return -1;
1460 		}
1461 	} while (retry);
1462 	return sent;
1463 #else
1464 	return send(s, buf, size, 0);
1465 #endif
1466 }
1467 
1468 /* {{{ my_send */
1469 int
my_send(ftpbuf_t * ftp,php_socket_t s,void * buf,size_t len)1470 my_send(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t len)
1471 {
1472 	zend_long size, sent;
1473 	int       n;
1474 
1475 	size = len;
1476 	while (size) {
1477 		n = php_pollfd_for_ms(s, POLLOUT, ftp->timeout_sec * 1000);
1478 
1479 		if (n < 1) {
1480 			char buf[256];
1481 			if (n == 0) {
1482 #ifdef PHP_WIN32
1483 				_set_errno(ETIMEDOUT);
1484 #else
1485 				errno = ETIMEDOUT;
1486 #endif
1487 			}
1488 			php_error_docref(NULL, E_WARNING, "%s", php_socket_strerror(errno, buf, sizeof buf));
1489 			return -1;
1490 		}
1491 
1492 		sent = single_send(ftp, s, buf, size);
1493 		if (sent == -1) {
1494 			return -1;
1495 		}
1496 
1497 		buf = (char*) buf + sent;
1498 		size -= sent;
1499 	}
1500 
1501 	return len;
1502 }
1503 /* }}} */
1504 
1505 /* {{{ my_recv */
1506 int
my_recv(ftpbuf_t * ftp,php_socket_t s,void * buf,size_t len)1507 my_recv(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t len)
1508 {
1509 	int		n, nr_bytes;
1510 #ifdef HAVE_FTP_SSL
1511 	int err;
1512 	bool retry = 0;
1513 	SSL *handle = NULL;
1514 	php_socket_t fd;
1515 #endif
1516 	n = php_pollfd_for_ms(s, PHP_POLLREADABLE, ftp->timeout_sec * 1000);
1517 	if (n < 1) {
1518 		char buf[256];
1519 		if (n == 0) {
1520 #ifdef PHP_WIN32
1521 			_set_errno(ETIMEDOUT);
1522 #else
1523 			errno = ETIMEDOUT;
1524 #endif
1525 		}
1526 		php_error_docref(NULL, E_WARNING, "%s", php_socket_strerror(errno, buf, sizeof buf));
1527 		return -1;
1528 	}
1529 
1530 #ifdef HAVE_FTP_SSL
1531 	if (ftp->use_ssl && ftp->fd == s && ftp->ssl_active) {
1532 		handle = ftp->ssl_handle;
1533 		fd = ftp->fd;
1534 	} else if (ftp->use_ssl && ftp->fd != s && ftp->use_ssl_for_data && ftp->data->ssl_active) {
1535 		handle = ftp->data->ssl_handle;
1536 		fd = ftp->data->fd;
1537 	}
1538 
1539 	if (handle) {
1540 		do {
1541 			nr_bytes = SSL_read(handle, buf, len);
1542 			err = SSL_get_error(handle, nr_bytes);
1543 
1544 			switch (err) {
1545 				case SSL_ERROR_NONE:
1546 					retry = 0;
1547 					break;
1548 
1549 				case SSL_ERROR_ZERO_RETURN:
1550 					retry = 0;
1551 					SSL_shutdown(handle);
1552 					break;
1553 
1554 				case SSL_ERROR_WANT_READ:
1555 				case SSL_ERROR_WANT_CONNECT: {
1556 						php_pollfd p;
1557 						int i;
1558 
1559 						p.fd = fd;
1560 						p.events = POLLIN|POLLPRI;
1561 						p.revents = 0;
1562 
1563 						i = php_poll2(&p, 1, 300);
1564 
1565 						retry = i > 0;
1566 					}
1567 					break;
1568 
1569 				default:
1570 					php_error_docref(NULL, E_WARNING, "SSL read failed");
1571 					return -1;
1572 			}
1573 		} while (retry);
1574 	} else {
1575 #endif
1576 		nr_bytes = recv(s, buf, len, 0);
1577 #ifdef HAVE_FTP_SSL
1578 	}
1579 #endif
1580 	return (nr_bytes);
1581 }
1582 /* }}} */
1583 
1584 /* {{{ data_available */
1585 int
data_available(ftpbuf_t * ftp,php_socket_t s)1586 data_available(ftpbuf_t *ftp, php_socket_t s)
1587 {
1588 	int		n;
1589 
1590 	n = php_pollfd_for_ms(s, PHP_POLLREADABLE, 1000);
1591 	if (n < 1) {
1592 		char buf[256];
1593 		if (n == 0) {
1594 #ifdef PHP_WIN32
1595 			_set_errno(ETIMEDOUT);
1596 #else
1597 			errno = ETIMEDOUT;
1598 #endif
1599 		}
1600 		php_error_docref(NULL, E_WARNING, "%s", php_socket_strerror(errno, buf, sizeof buf));
1601 		return 0;
1602 	}
1603 
1604 	return 1;
1605 }
1606 /* }}} */
1607 /* {{{ data_writeable */
1608 int
data_writeable(ftpbuf_t * ftp,php_socket_t s)1609 data_writeable(ftpbuf_t *ftp, php_socket_t s)
1610 {
1611 	int		n;
1612 
1613 	n = php_pollfd_for_ms(s, POLLOUT, 1000);
1614 	if (n < 1) {
1615 		char buf[256];
1616 		if (n == 0) {
1617 #ifdef PHP_WIN32
1618 			_set_errno(ETIMEDOUT);
1619 #else
1620 			errno = ETIMEDOUT;
1621 #endif
1622 		}
1623 		php_error_docref(NULL, E_WARNING, "%s", php_socket_strerror(errno, buf, sizeof buf));
1624 		return 0;
1625 	}
1626 
1627 	return 1;
1628 }
1629 /* }}} */
1630 
1631 /* {{{ my_accept */
1632 int
my_accept(ftpbuf_t * ftp,php_socket_t s,struct sockaddr * addr,socklen_t * addrlen)1633 my_accept(ftpbuf_t *ftp, php_socket_t s, struct sockaddr *addr, socklen_t *addrlen)
1634 {
1635 	int		n;
1636 
1637 	n = php_pollfd_for_ms(s, PHP_POLLREADABLE, ftp->timeout_sec * 1000);
1638 	if (n < 1) {
1639 		char buf[256];
1640 		if (n == 0) {
1641 #ifdef PHP_WIN32
1642 			_set_errno(ETIMEDOUT);
1643 #else
1644 			errno = ETIMEDOUT;
1645 #endif
1646 		}
1647 		php_error_docref(NULL, E_WARNING, "%s", php_socket_strerror(errno, buf, sizeof buf));
1648 		return -1;
1649 	}
1650 
1651 	return accept(s, addr, addrlen);
1652 }
1653 /* }}} */
1654 
1655 /* {{{ ftp_getdata */
1656 databuf_t*
ftp_getdata(ftpbuf_t * ftp)1657 ftp_getdata(ftpbuf_t *ftp)
1658 {
1659 	int				fd = -1;
1660 	databuf_t		*data;
1661 	php_sockaddr_storage addr;
1662 	struct sockaddr *sa;
1663 	socklen_t		size;
1664 	union ipbox		ipbox;
1665 	char			arg[sizeof("255, 255, 255, 255, 255, 255")];
1666 	struct timeval	tv;
1667 	int				arg_len;
1668 
1669 
1670 	/* ask for a passive connection if we need one */
1671 	if (ftp->pasv && !ftp_pasv(ftp, 1)) {
1672 		return NULL;
1673 	}
1674 	/* alloc the data structure */
1675 	data = ecalloc(1, sizeof(*data));
1676 	data->listener = -1;
1677 	data->fd = -1;
1678 	data->type = ftp->type;
1679 
1680 	sa = (struct sockaddr *) &ftp->localaddr;
1681 	/* bind/listen */
1682 	if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) == SOCK_ERR) {
1683 		php_error_docref(NULL, E_WARNING, "socket() failed: %s (%d)", strerror(errno), errno);
1684 		goto bail;
1685 	}
1686 
1687 	/* passive connection handler */
1688 	if (ftp->pasv) {
1689 		/* clear the ready status */
1690 		ftp->pasv = 1;
1691 
1692 		/* connect */
1693 		/* Win 95/98 seems not to like size > sizeof(sockaddr_in) */
1694 		size = php_sockaddr_size(&ftp->pasvaddr);
1695 		tv.tv_sec = ftp->timeout_sec;
1696 		tv.tv_usec = 0;
1697 		if (php_connect_nonb(fd, (struct sockaddr*) &ftp->pasvaddr, size, &tv) == -1) {
1698 			php_error_docref(NULL, E_WARNING, "php_connect_nonb() failed: %s (%d)", strerror(errno), errno);
1699 			goto bail;
1700 		}
1701 
1702 		data->fd = fd;
1703 
1704 		ftp->data = data;
1705 		return data;
1706 	}
1707 
1708 
1709 	/* active (normal) connection */
1710 
1711 	/* bind to a local address */
1712 	php_any_addr(sa->sa_family, &addr, 0);
1713 	size = php_sockaddr_size(&addr);
1714 
1715 	if (bind(fd, (struct sockaddr*) &addr, size) != 0) {
1716 		php_error_docref(NULL, E_WARNING, "bind() failed: %s (%d)", strerror(errno), errno);
1717 		goto bail;
1718 	}
1719 
1720 	if (getsockname(fd, (struct sockaddr*) &addr, &size) != 0) {
1721 		php_error_docref(NULL, E_WARNING, "getsockname() failed: %s (%d)", strerror(errno), errno);
1722 		goto bail;
1723 	}
1724 
1725 	if (listen(fd, 5) != 0) {
1726 		php_error_docref(NULL, E_WARNING, "listen() failed: %s (%d)", strerror(errno), errno);
1727 		goto bail;
1728 	}
1729 
1730 	data->listener = fd;
1731 
1732 #ifdef HAVE_IPV6
1733 	if (sa->sa_family == AF_INET6) {
1734 		/* need to use EPRT */
1735 		char eprtarg[INET6_ADDRSTRLEN + sizeof("|x||xxxxx|")];
1736 		char out[INET6_ADDRSTRLEN];
1737 		int eprtarg_len;
1738 		const char *r;
1739 		r = inet_ntop(AF_INET6, &((struct sockaddr_in6*) sa)->sin6_addr, out, sizeof(out));
1740 		ZEND_ASSERT(r != NULL);
1741 
1742 		eprtarg_len = snprintf(eprtarg, sizeof(eprtarg), "|2|%s|%hu|", out, ntohs(((struct sockaddr_in6 *) &addr)->sin6_port));
1743 
1744 		if (eprtarg_len < 0) {
1745 			goto bail;
1746 		}
1747 
1748 		if (!ftp_putcmd(ftp, "EPRT", sizeof("EPRT")-1, eprtarg, eprtarg_len)) {
1749 			goto bail;
1750 		}
1751 
1752 		if (!ftp_getresp(ftp) || ftp->resp != 200) {
1753 			goto bail;
1754 		}
1755 
1756 		ftp->data = data;
1757 		return data;
1758 	}
1759 #endif
1760 
1761 	/* send the PORT */
1762 	ipbox.ia[0] = ((struct sockaddr_in*) sa)->sin_addr;
1763 	ipbox.s[2] = ((struct sockaddr_in*) &addr)->sin_port;
1764 	arg_len = snprintf(arg, sizeof(arg), "%u,%u,%u,%u,%u,%u", ipbox.c[0], ipbox.c[1], ipbox.c[2], ipbox.c[3], ipbox.c[4], ipbox.c[5]);
1765 
1766 	if (arg_len < 0) {
1767 		goto bail;
1768 	}
1769 	if (!ftp_putcmd(ftp, "PORT", sizeof("PORT")-1, arg, arg_len)) {
1770 		goto bail;
1771 	}
1772 	if (!ftp_getresp(ftp) || ftp->resp != 200) {
1773 		goto bail;
1774 	}
1775 
1776 	ftp->data = data;
1777 	return data;
1778 
1779 bail:
1780 	if (fd != -1) {
1781 		closesocket(fd);
1782 	}
1783 	efree(data);
1784 	return NULL;
1785 }
1786 /* }}} */
1787 
1788 /* {{{ data_accept */
1789 databuf_t*
data_accept(databuf_t * data,ftpbuf_t * ftp)1790 data_accept(databuf_t *data, ftpbuf_t *ftp)
1791 {
1792 	php_sockaddr_storage addr;
1793 	socklen_t			size;
1794 
1795 #ifdef HAVE_FTP_SSL
1796 	SSL_CTX		*ctx;
1797 	SSL_SESSION *session;
1798 	int err, res;
1799 	bool retry;
1800 #endif
1801 
1802 	if (data->fd != -1) {
1803 		goto data_accepted;
1804 	}
1805 	size = sizeof(addr);
1806 	data->fd = my_accept(ftp, data->listener, (struct sockaddr*) &addr, &size);
1807 	closesocket(data->listener);
1808 	data->listener = -1;
1809 
1810 	if (data->fd == -1) {
1811 		efree(data);
1812 		return NULL;
1813 	}
1814 
1815 data_accepted:
1816 #ifdef HAVE_FTP_SSL
1817 
1818 	/* now enable ssl if we need to */
1819 	if (ftp->use_ssl && ftp->use_ssl_for_data) {
1820 		ctx = SSL_get_SSL_CTX(ftp->ssl_handle);
1821 		if (ctx == NULL) {
1822 			php_error_docref(NULL, E_WARNING, "data_accept: failed to retrieve the existing SSL context");
1823 			return 0;
1824 		}
1825 
1826 		data->ssl_handle = SSL_new(ctx);
1827 		if (data->ssl_handle == NULL) {
1828 			php_error_docref(NULL, E_WARNING, "data_accept: failed to create the SSL handle");
1829 			return 0;
1830 		}
1831 
1832 		SSL_set_fd(data->ssl_handle, data->fd);
1833 
1834 		if (ftp->old_ssl) {
1835 			SSL_copy_session_id(data->ssl_handle, ftp->ssl_handle);
1836 		}
1837 
1838 		/* get the session from the control connection so we can re-use it */
1839 		session = ftp->last_ssl_session;
1840 		if (session == NULL) {
1841 			php_error_docref(NULL, E_WARNING, "data_accept: failed to retrieve the existing SSL session");
1842 			SSL_free(data->ssl_handle);
1843 			return 0;
1844 		}
1845 
1846 		/* and set it on the data connection */
1847 		SSL_set_app_data(data->ssl_handle, ftp); /* Needed for ftp_ssl_new_session_cb */
1848 		res = SSL_set_session(data->ssl_handle, session);
1849 		if (res == 0) {
1850 			php_error_docref(NULL, E_WARNING, "data_accept: failed to set the existing SSL session");
1851 			SSL_free(data->ssl_handle);
1852 			return 0;
1853 		}
1854 
1855 		do {
1856 			res = SSL_connect(data->ssl_handle);
1857 			err = SSL_get_error(data->ssl_handle, res);
1858 
1859 			switch (err) {
1860 				case SSL_ERROR_NONE:
1861 					retry = 0;
1862 					break;
1863 
1864 				case SSL_ERROR_ZERO_RETURN:
1865 					retry = 0;
1866 					SSL_shutdown(data->ssl_handle);
1867 					break;
1868 
1869 				case SSL_ERROR_WANT_READ:
1870 				case SSL_ERROR_WANT_WRITE: {
1871 						php_pollfd p;
1872 						int i;
1873 
1874 						p.fd = data->fd;
1875 						p.events = (err == SSL_ERROR_WANT_READ) ? (POLLIN|POLLPRI) : POLLOUT;
1876 						p.revents = 0;
1877 
1878 						i = php_poll2(&p, 1, 300);
1879 
1880 						retry = i > 0;
1881 					}
1882 					break;
1883 
1884 				default:
1885 					php_error_docref(NULL, E_WARNING, "data_accept: SSL/TLS handshake failed");
1886 					SSL_shutdown(data->ssl_handle);
1887 					SSL_free(data->ssl_handle);
1888 					return 0;
1889 			}
1890 		} while (retry);
1891 
1892 		data->ssl_active = 1;
1893 	}
1894 
1895 #endif
1896 
1897 	return data;
1898 }
1899 /* }}} */
1900 
1901 /* {{{ ftp_ssl_shutdown */
1902 #ifdef HAVE_FTP_SSL
ftp_ssl_shutdown(ftpbuf_t * ftp,php_socket_t fd,SSL * ssl_handle)1903 static void ftp_ssl_shutdown(ftpbuf_t *ftp, php_socket_t fd, SSL *ssl_handle) {
1904 	/* In TLS 1.3 it's common to receive session tickets after the handshake has completed. We need to train
1905 	   the socket (read the tickets until EOF/close_notify alert) before closing the socket. Otherwise the
1906 	   server might get an ECONNRESET which might lead to data truncation on server side.
1907 	*/
1908 	char buf[256]; /* We will use this for the OpenSSL error buffer, so it has
1909 			  to be at least 256 bytes long.*/
1910 	int done = 1, err, nread;
1911 	unsigned long sslerror;
1912 
1913 	err = SSL_shutdown(ssl_handle);
1914 	if (err < 0) {
1915 		php_error_docref(NULL, E_WARNING, "SSL_shutdown failed");
1916 	}
1917 	else if (err == 0) {
1918 		/* The shutdown is not yet finished. Call SSL_read() to do a bidirectional shutdown. */
1919 		done = 0;
1920 	}
1921 
1922 	while (!done && data_available(ftp, fd)) {
1923 		ERR_clear_error();
1924 		nread = SSL_read(ssl_handle, buf, sizeof(buf));
1925 		if (nread <= 0) {
1926 			err = SSL_get_error(ssl_handle, nread);
1927 			switch (err) {
1928 				case SSL_ERROR_NONE: /* this is not an error */
1929 				case SSL_ERROR_ZERO_RETURN: /* no more data */
1930 					/* This is the expected response. There was no data but only
1931 					   the close notify alert */
1932 					done = 1;
1933 					break;
1934 				case SSL_ERROR_WANT_READ:
1935 					/* there's data pending, re-invoke SSL_read() */
1936 					break;
1937 				case SSL_ERROR_WANT_WRITE:
1938 					/* SSL wants a write. Really odd. Let's bail out. */
1939 					done = 1;
1940 					break;
1941 				case SSL_ERROR_SYSCALL:
1942 					/* most likely the peer closed the connection without
1943 					   sending a close_notify shutdown alert;
1944 					   bail out to avoid raising a spurious warning */
1945 					done = 1;
1946 					break;
1947 				default:
1948 					if ((sslerror = ERR_get_error())) {
1949 						ERR_error_string_n(sslerror, buf, sizeof(buf));
1950 						php_error_docref(NULL, E_WARNING, "SSL_read on shutdown: %s", buf);
1951 					} else if (errno) {
1952 						php_error_docref(NULL, E_WARNING, "SSL_read on shutdown: %s (%d)", strerror(errno), errno);
1953 					}
1954 					done = 1;
1955 					break;
1956 			}
1957 		}
1958 	}
1959 	(void)SSL_free(ssl_handle);
1960 }
1961 #endif
1962 /* }}} */
1963 
1964 /* {{{ data_close */
data_close(ftpbuf_t * ftp)1965 void data_close(ftpbuf_t *ftp)
1966 {
1967 	ZEND_ASSERT(ftp != NULL);
1968 	databuf_t *data = ftp->data;
1969 	if (data == NULL) {
1970 		return;
1971 	}
1972 	if (data->listener != -1) {
1973 #ifdef HAVE_FTP_SSL
1974 		if (data->ssl_active) {
1975 			/* don't free the data context, it's the same as the control */
1976 			ftp_ssl_shutdown(ftp, data->listener, data->ssl_handle);
1977 			data->ssl_active = 0;
1978 		}
1979 #endif
1980 		closesocket(data->listener);
1981 	}
1982 	if (data->fd != -1) {
1983 #ifdef HAVE_FTP_SSL
1984 		if (data->ssl_active) {
1985 			/* don't free the data context, it's the same as the control */
1986 			ftp_ssl_shutdown(ftp, data->fd, data->ssl_handle);
1987 			data->ssl_active = 0;
1988 		}
1989 #endif
1990 		closesocket(data->fd);
1991 	}
1992 	ftp->data = NULL;
1993 	efree(data);
1994 }
1995 /* }}} */
1996 
1997 /* {{{ ftp_genlist */
1998 char**
ftp_genlist(ftpbuf_t * ftp,const char * cmd,const size_t cmd_len,const char * path,const size_t path_len)1999 ftp_genlist(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len, const char *path, const size_t path_len)
2000 {
2001 	php_stream	*tmpstream = NULL;
2002 	databuf_t	*data = NULL;
2003 	char		*ptr;
2004 	int		ch, lastch;
2005 	size_t		size, rcvd;
2006 	size_t		lines;
2007 	char		**ret = NULL;
2008 	char		**entry;
2009 	char		*text;
2010 
2011 
2012 	if ((tmpstream = php_stream_fopen_tmpfile()) == NULL) {
2013 		php_error_docref(NULL, E_WARNING, "Unable to create temporary file.  Check permissions in temporary files directory.");
2014 		return NULL;
2015 	}
2016 
2017 	if (!ftp_type(ftp, FTPTYPE_ASCII)) {
2018 		goto bail;
2019 	}
2020 
2021 	if ((data = ftp_getdata(ftp)) == NULL) {
2022 		goto bail;
2023 	}
2024 	ftp->data = data;
2025 
2026 	if (!ftp_putcmd(ftp, cmd, cmd_len, path, path_len)) {
2027 		goto bail;
2028 	}
2029 	if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125 && ftp->resp != 226)) {
2030 		goto bail;
2031 	}
2032 
2033 	/* some servers don't open a ftp-data connection if the directory is empty */
2034 	if (ftp->resp == 226) {
2035 		data_close(ftp);
2036 		php_stream_close(tmpstream);
2037 		return ecalloc(1, sizeof(char*));
2038 	}
2039 
2040 	/* pull data buffer into tmpfile */
2041 	if ((data = data_accept(data, ftp)) == NULL) {
2042 		goto bail;
2043 	}
2044 	size = 0;
2045 	lines = 0;
2046 	lastch = 0;
2047 	while ((rcvd = my_recv(ftp, data->fd, data->buf, FTP_BUFSIZE))) {
2048 		if (rcvd == (size_t)-1 || rcvd > ((size_t)(-1))-size) {
2049 			goto bail;
2050 		}
2051 
2052 		php_stream_write(tmpstream, data->buf, rcvd);
2053 
2054 		size += rcvd;
2055 		for (ptr = data->buf; rcvd; rcvd--, ptr++) {
2056 			if (*ptr == '\n' && lastch == '\r') {
2057 				lines++;
2058 			}
2059 			lastch = *ptr;
2060 		}
2061 	}
2062 
2063 	data_close(ftp);
2064 
2065 	php_stream_rewind(tmpstream);
2066 
2067 	ret = safe_emalloc((lines + 1), sizeof(char*), size);
2068 
2069 	entry = ret;
2070 	text = (char*) (ret + lines + 1);
2071 	*entry = text;
2072 	lastch = 0;
2073 	while ((ch = php_stream_getc(tmpstream)) != EOF) {
2074 		if (ch == '\n' && lastch == '\r') {
2075 			*(text - 1) = 0;
2076 			*++entry = text;
2077 		} else {
2078 			*text++ = ch;
2079 		}
2080 		lastch = ch;
2081 	}
2082 	*entry = NULL;
2083 
2084 	php_stream_close(tmpstream);
2085 
2086 	if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) {
2087 		efree(ret);
2088 		return NULL;
2089 	}
2090 
2091 	return ret;
2092 bail:
2093 	data_close(ftp);
2094 	php_stream_close(tmpstream);
2095 	if (ret)
2096 		efree(ret);
2097 	return NULL;
2098 }
2099 /* }}} */
2100 
2101 /* {{{ ftp_nb_get */
2102 int
ftp_nb_get(ftpbuf_t * ftp,php_stream * outstream,const char * path,const size_t path_len,ftptype_t type,zend_long resumepos)2103 ftp_nb_get(ftpbuf_t *ftp, php_stream *outstream, const char *path, const size_t path_len, ftptype_t type, zend_long resumepos)
2104 {
2105 	databuf_t		*data = NULL;
2106 	char			arg[MAX_LENGTH_OF_LONG];
2107 
2108 	if (ftp == NULL) {
2109 		return PHP_FTP_FAILED;
2110 	}
2111 
2112 	if (ftp->data != NULL) {
2113 		/* If there is a transfer in action, abort it.
2114 		 * If we don't, we get an invalid state and memory leaks when the new connection gets opened. */
2115 		data_close(ftp);
2116 		if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) {
2117 			goto bail;
2118 		}
2119 	}
2120 
2121 	if (!ftp_type(ftp, type)) {
2122 		goto bail;
2123 	}
2124 
2125 	if ((data = ftp_getdata(ftp)) == NULL) {
2126 		goto bail;
2127 	}
2128 
2129 	if (resumepos>0) {
2130 		int arg_len = snprintf(arg, sizeof(arg), ZEND_LONG_FMT, resumepos);
2131 
2132 		if (arg_len < 0) {
2133 			goto bail;
2134 		}
2135 		if (!ftp_putcmd(ftp, "REST", sizeof("REST")-1, arg, arg_len)) {
2136 			goto bail;
2137 		}
2138 		if (!ftp_getresp(ftp) || (ftp->resp != 350)) {
2139 			goto bail;
2140 		}
2141 	}
2142 
2143 	if (!ftp_putcmd(ftp, "RETR", sizeof("RETR")-1, path, path_len)) {
2144 		goto bail;
2145 	}
2146 	if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) {
2147 		goto bail;
2148 	}
2149 
2150 	if ((data = data_accept(data, ftp)) == NULL) {
2151 		goto bail;
2152 	}
2153 
2154 	ftp->data = data;
2155 	ftp->stream = outstream;
2156 	ftp->lastch = 0;
2157 	ftp->nb = 1;
2158 
2159 	return (ftp_nb_continue_read(ftp));
2160 
2161 bail:
2162 	data_close(ftp);
2163 	return PHP_FTP_FAILED;
2164 }
2165 /* }}} */
2166 
2167 /* {{{ ftp_nb_continue_read */
2168 int
ftp_nb_continue_read(ftpbuf_t * ftp)2169 ftp_nb_continue_read(ftpbuf_t *ftp)
2170 {
2171 	databuf_t	*data = NULL;
2172 	char		*ptr;
2173 	char		lastch;
2174 	size_t		rcvd;
2175 	ftptype_t	type;
2176 
2177 	data = ftp->data;
2178 
2179 	/* check if there is already more data */
2180 	if (!data_available(ftp, data->fd)) {
2181 		return PHP_FTP_MOREDATA;
2182 	}
2183 
2184 	type = ftp->type;
2185 
2186 	lastch = ftp->lastch;
2187 	if ((rcvd = my_recv(ftp, data->fd, data->buf, FTP_BUFSIZE))) {
2188 		if (rcvd == (size_t)-1) {
2189 			goto bail;
2190 		}
2191 
2192 		if (type == FTPTYPE_ASCII) {
2193 			for (ptr = data->buf; rcvd; rcvd--, ptr++) {
2194 				if (lastch == '\r' && *ptr != '\n') {
2195 					php_stream_putc(ftp->stream, '\r');
2196 				}
2197 				if (*ptr != '\r') {
2198 					php_stream_putc(ftp->stream, *ptr);
2199 				}
2200 				lastch = *ptr;
2201 			}
2202 		} else if (rcvd != php_stream_write(ftp->stream, data->buf, rcvd)) {
2203 			goto bail;
2204 		}
2205 
2206 		ftp->lastch = lastch;
2207 		return PHP_FTP_MOREDATA;
2208 	}
2209 
2210 	if (type == FTPTYPE_ASCII && lastch == '\r') {
2211 		php_stream_putc(ftp->stream, '\r');
2212 	}
2213 
2214 	data_close(ftp);
2215 
2216 	if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) {
2217 		goto bail;
2218 	}
2219 
2220 	ftp->nb = 0;
2221 	return PHP_FTP_FINISHED;
2222 bail:
2223 	ftp->nb = 0;
2224 	data_close(ftp);
2225 	return PHP_FTP_FAILED;
2226 }
2227 /* }}} */
2228 
2229 /* {{{ ftp_nb_put */
2230 int
ftp_nb_put(ftpbuf_t * ftp,const char * path,const size_t path_len,php_stream * instream,ftptype_t type,zend_long startpos)2231 ftp_nb_put(ftpbuf_t *ftp, const char *path, const size_t path_len, php_stream *instream, ftptype_t type, zend_long startpos)
2232 {
2233 	databuf_t		*data = NULL;
2234 	char			arg[MAX_LENGTH_OF_LONG];
2235 
2236 	if (ftp == NULL) {
2237 		return 0;
2238 	}
2239 	if (!ftp_type(ftp, type)) {
2240 		goto bail;
2241 	}
2242 	if ((data = ftp_getdata(ftp)) == NULL) {
2243 		goto bail;
2244 	}
2245 	if (startpos > 0) {
2246 		int arg_len = snprintf(arg, sizeof(arg), ZEND_LONG_FMT, startpos);
2247 
2248 		if (arg_len < 0) {
2249 			goto bail;
2250 		}
2251 		if (!ftp_putcmd(ftp, "REST", sizeof("REST")-1, arg, arg_len)) {
2252 			goto bail;
2253 		}
2254 		if (!ftp_getresp(ftp) || (ftp->resp != 350)) {
2255 			goto bail;
2256 		}
2257 	}
2258 
2259 	if (!ftp_putcmd(ftp, "STOR", sizeof("STOR")-1, path, path_len)) {
2260 		goto bail;
2261 	}
2262 	if (!ftp_getresp(ftp) || (ftp->resp != 150 && ftp->resp != 125)) {
2263 		goto bail;
2264 	}
2265 	if ((data = data_accept(data, ftp)) == NULL) {
2266 		goto bail;
2267 	}
2268 	ftp->data = data;
2269 	ftp->stream = instream;
2270 	ftp->lastch = 0;
2271 	ftp->nb = 1;
2272 
2273 	return (ftp_nb_continue_write(ftp));
2274 
2275 bail:
2276 	data_close(ftp);
2277 	return PHP_FTP_FAILED;
2278 }
2279 /* }}} */
2280 
2281 
2282 /* {{{ ftp_nb_continue_write */
2283 int
ftp_nb_continue_write(ftpbuf_t * ftp)2284 ftp_nb_continue_write(ftpbuf_t *ftp)
2285 {
2286 	/* check if we can write more data */
2287 	if (!data_writeable(ftp, ftp->data->fd)) {
2288 		return PHP_FTP_MOREDATA;
2289 	}
2290 
2291 	if (ftp_send_stream_to_data_socket(ftp, ftp->data, ftp->stream, ftp->type, true) != SUCCESS) {
2292 		goto bail;
2293 	}
2294 
2295 	if (!php_stream_eof(ftp->stream)) {
2296 		return PHP_FTP_MOREDATA;
2297 	}
2298 
2299 	data_close(ftp);
2300 
2301 	if (!ftp_getresp(ftp) || (ftp->resp != 226 && ftp->resp != 250)) {
2302 		goto bail;
2303 	}
2304 	ftp->nb = 0;
2305 	return PHP_FTP_FINISHED;
2306 bail:
2307 	data_close(ftp);
2308 	ftp->nb = 0;
2309 	return PHP_FTP_FAILED;
2310 }
2311 /* }}} */
2312