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