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