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