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