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