1/* 2 +----------------------------------------------------------------------+ 3 | PHP Version 5 | 4 +----------------------------------------------------------------------+ 5 | Copyright (c) 1997-2013 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: Rasmus Lerdorf <rasmus@php.net> | 16 | Jim Winstead <jimw@php.net> | 17 | Hartmut Holzgraefe <hholzgra@php.net> | 18 | Sara Golemon <pollita@php.net> | 19 +----------------------------------------------------------------------+ 20 */ 21/* $Id$ */ 22 23#include "php.h" 24#include "php_globals.h" 25#include "php_network.h" 26#include "php_ini.h" 27 28#include <stdio.h> 29#include <stdlib.h> 30#include <errno.h> 31#include <sys/types.h> 32#include <sys/stat.h> 33#include <fcntl.h> 34 35#ifdef PHP_WIN32 36#include <winsock2.h> 37#define O_RDONLY _O_RDONLY 38#include "win32/param.h" 39#else 40#include <sys/param.h> 41#endif 42 43#include "php_standard.h" 44 45#include <sys/types.h> 46#if HAVE_SYS_SOCKET_H 47#include <sys/socket.h> 48#endif 49 50#ifdef PHP_WIN32 51#include <winsock2.h> 52#elif defined(NETWARE) && defined(USE_WINSOCK) 53#include <novsock2.h> 54#else 55#include <netinet/in.h> 56#include <netdb.h> 57#if HAVE_ARPA_INET_H 58#include <arpa/inet.h> 59#endif 60#endif 61 62#if defined(PHP_WIN32) || defined(__riscos__) || defined(NETWARE) 63#undef AF_UNIX 64#endif 65 66#if defined(AF_UNIX) 67#include <sys/un.h> 68#endif 69 70#include "php_fopen_wrappers.h" 71 72#define FTPS_ENCRYPT_DATA 1 73#define GET_FTP_RESULT(stream) get_ftp_result((stream), tmp_line, sizeof(tmp_line) TSRMLS_CC) 74 75typedef struct _php_ftp_dirstream_data { 76 php_stream *datastream; 77 php_stream *controlstream; 78 php_stream *dirstream; 79} php_ftp_dirstream_data; 80 81/* {{{ get_ftp_result 82 */ 83static inline int get_ftp_result(php_stream *stream, char *buffer, size_t buffer_size TSRMLS_DC) 84{ 85 while (php_stream_gets(stream, buffer, buffer_size-1) && 86 !(isdigit((int) buffer[0]) && isdigit((int) buffer[1]) && 87 isdigit((int) buffer[2]) && buffer[3] == ' ')); 88 return strtol(buffer, NULL, 10); 89} 90/* }}} */ 91 92/* {{{ php_stream_ftp_stream_stat 93 */ 94static int php_stream_ftp_stream_stat(php_stream_wrapper *wrapper, php_stream *stream, php_stream_statbuf *ssb TSRMLS_DC) 95{ 96 /* For now, we return with a failure code to prevent the underlying 97 * file's details from being used instead. */ 98 return -1; 99} 100/* }}} */ 101 102/* {{{ php_stream_ftp_stream_close 103 */ 104static int php_stream_ftp_stream_close(php_stream_wrapper *wrapper, php_stream *stream TSRMLS_DC) 105{ 106 php_stream *controlstream = stream->wrapperthis; 107 int ret = 0; 108 109 if (controlstream) { 110 if (strpbrk(stream->mode, "wa+")) { 111 char tmp_line[512]; 112 int result; 113 114 /* For write modes close data stream first to signal EOF to server */ 115 result = GET_FTP_RESULT(controlstream); 116 if (result != 226 && result != 250) { 117 php_error_docref(NULL TSRMLS_CC, E_WARNING, "FTP server error %d:%s", result, tmp_line); 118 ret = EOF; 119 } 120 } 121 122 php_stream_write_string(controlstream, "QUIT\r\n"); 123 php_stream_close(controlstream); 124 stream->wrapperthis = NULL; 125 } 126 127 return ret; 128} 129/* }}} */ 130 131/* {{{ php_ftp_fopen_connect 132 */ 133static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, char *path, char *mode, int options, char **opened_path, php_stream_context *context, 134 php_stream **preuseid, php_url **presource, int *puse_ssl, int *puse_ssl_on_data TSRMLS_DC) 135{ 136 php_stream *stream = NULL, *reuseid = NULL; 137 php_url *resource = NULL; 138 int result, use_ssl, use_ssl_on_data = 0, tmp_len; 139 char tmp_line[512]; 140 char *transport; 141 int transport_len; 142 143 resource = php_url_parse(path); 144 if (resource == NULL || resource->path == NULL) { 145 if (resource && presource) { 146 *presource = resource; 147 } 148 return NULL; 149 } 150 151 use_ssl = resource->scheme && (strlen(resource->scheme) > 3) && resource->scheme[3] == 's'; 152 153 /* use port 21 if one wasn't specified */ 154 if (resource->port == 0) 155 resource->port = 21; 156 157 transport_len = spprintf(&transport, 0, "tcp://%s:%d", resource->host, resource->port); 158 stream = php_stream_xport_create(transport, transport_len, REPORT_ERRORS, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, NULL, NULL, context, NULL, NULL); 159 efree(transport); 160 if (stream == NULL) { 161 result = 0; /* silence */ 162 goto connect_errexit; 163 } 164 165 php_stream_context_set(stream, context); 166 php_stream_notify_info(context, PHP_STREAM_NOTIFY_CONNECT, NULL, 0); 167 168 /* Start talking to ftp server */ 169 result = GET_FTP_RESULT(stream); 170 if (result > 299 || result < 200) { 171 php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result); 172 goto connect_errexit; 173 } 174 175 if (use_ssl) { 176 177 /* send the AUTH TLS request name */ 178 php_stream_write_string(stream, "AUTH TLS\r\n"); 179 180 /* get the response */ 181 result = GET_FTP_RESULT(stream); 182 if (result != 234) { 183 /* AUTH TLS not supported try AUTH SSL */ 184 php_stream_write_string(stream, "AUTH SSL\r\n"); 185 186 /* get the response */ 187 result = GET_FTP_RESULT(stream); 188 if (result != 334) { 189 use_ssl = 0; 190 } else { 191 /* we must reuse the old SSL session id */ 192 /* if we talk to an old ftpd-ssl */ 193 reuseid = stream; 194 } 195 } else { 196 /* encrypt data etc */ 197 198 199 } 200 201 } 202 203 if (use_ssl) { 204 if (php_stream_xport_crypto_setup(stream, 205 STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 206 || php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0) { 207 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode"); 208 php_stream_close(stream); 209 stream = NULL; 210 goto connect_errexit; 211 } 212 213 /* set PBSZ to 0 */ 214 php_stream_write_string(stream, "PBSZ 0\r\n"); 215 216 /* ignore the response */ 217 result = GET_FTP_RESULT(stream); 218 219 /* set data connection protection level */ 220#if FTPS_ENCRYPT_DATA 221 php_stream_write_string(stream, "PROT P\r\n"); 222 223 /* get the response */ 224 result = GET_FTP_RESULT(stream); 225 use_ssl_on_data = (result >= 200 && result<=299) || reuseid; 226#else 227 php_stream_write_string(stream, "PROT C\r\n"); 228 229 /* get the response */ 230 result = GET_FTP_RESULT(stream); 231#endif 232 } 233 234#define PHP_FTP_CNTRL_CHK(val, val_len, err_msg) { \ 235 unsigned char *s = val, *e = s + val_len; \ 236 while (s < e) { \ 237 if (iscntrl(*s)) { \ 238 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, err_msg, val); \ 239 goto connect_errexit; \ 240 } \ 241 s++; \ 242 } \ 243} 244 245 /* send the user name */ 246 if (resource->user != NULL) { 247 tmp_len = php_raw_url_decode(resource->user, strlen(resource->user)); 248 249 PHP_FTP_CNTRL_CHK(resource->user, tmp_len, "Invalid login %s") 250 251 php_stream_printf(stream TSRMLS_CC, "USER %s\r\n", resource->user); 252 } else { 253 php_stream_write_string(stream, "USER anonymous\r\n"); 254 } 255 256 /* get the response */ 257 result = GET_FTP_RESULT(stream); 258 259 /* if a password is required, send it */ 260 if (result >= 300 && result <= 399) { 261 php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_REQUIRED, tmp_line, 0); 262 263 if (resource->pass != NULL) { 264 tmp_len = php_raw_url_decode(resource->pass, strlen(resource->pass)); 265 266 PHP_FTP_CNTRL_CHK(resource->pass, tmp_len, "Invalid password %s") 267 268 php_stream_printf(stream TSRMLS_CC, "PASS %s\r\n", resource->pass); 269 } else { 270 /* if the user has configured who they are, 271 send that as the password */ 272 char *from_address = php_ini_string("from", sizeof("from"), 0); 273 if (from_address[0] != '\0') { 274 php_stream_printf(stream TSRMLS_CC, "PASS %s\r\n", from_address); 275 } else { 276 php_stream_write_string(stream, "PASS anonymous\r\n"); 277 } 278 } 279 280 /* read the response */ 281 result = GET_FTP_RESULT(stream); 282 283 if (result > 299 || result < 200) { 284 php_stream_notify_error(context, PHP_STREAM_NOTIFY_AUTH_RESULT, tmp_line, result); 285 } else { 286 php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_RESULT, tmp_line, result); 287 } 288 } 289 if (result > 299 || result < 200) { 290 goto connect_errexit; 291 } 292 293 if (puse_ssl) { 294 *puse_ssl = use_ssl; 295 } 296 if (puse_ssl_on_data) { 297 *puse_ssl_on_data = use_ssl_on_data; 298 } 299 if (preuseid) { 300 *preuseid = reuseid; 301 } 302 if (presource) { 303 *presource = resource; 304 } 305 306 return stream; 307 308connect_errexit: 309 if (resource) { 310 php_url_free(resource); 311 } 312 313 if (stream) { 314 php_stream_close(stream); 315 } 316 317 return NULL; 318} 319/* }}} */ 320 321/* {{{ php_fopen_do_pasv 322 */ 323static unsigned short php_fopen_do_pasv(php_stream *stream, char *ip, size_t ip_size, char **phoststart TSRMLS_DC) 324{ 325 char tmp_line[512]; 326 int result, i; 327 unsigned short portno; 328 char *tpath, *ttpath, *hoststart=NULL; 329 330#ifdef HAVE_IPV6 331 /* We try EPSV first, needed for IPv6 and works on some IPv4 servers */ 332 php_stream_write_string(stream, "EPSV\r\n"); 333 result = GET_FTP_RESULT(stream); 334 335 /* check if we got a 229 response */ 336 if (result != 229) { 337#endif 338 /* EPSV failed, let's try PASV */ 339 php_stream_write_string(stream, "PASV\r\n"); 340 result = GET_FTP_RESULT(stream); 341 342 /* make sure we got a 227 response */ 343 if (result != 227) { 344 return 0; 345 } 346 347 /* parse pasv command (129, 80, 95, 25, 13, 221) */ 348 tpath = tmp_line; 349 /* skip over the "227 Some message " part */ 350 for (tpath += 4; *tpath && !isdigit((int) *tpath); tpath++); 351 if (!*tpath) { 352 return 0; 353 } 354 /* skip over the host ip, to get the port */ 355 hoststart = tpath; 356 for (i = 0; i < 4; i++) { 357 for (; isdigit((int) *tpath); tpath++); 358 if (*tpath != ',') { 359 return 0; 360 } 361 *tpath='.'; 362 tpath++; 363 } 364 tpath[-1] = '\0'; 365 memcpy(ip, hoststart, ip_size); 366 ip[ip_size-1] = '\0'; 367 hoststart = ip; 368 369 /* pull out the MSB of the port */ 370 portno = (unsigned short) strtoul(tpath, &ttpath, 10) * 256; 371 if (ttpath == NULL) { 372 /* didn't get correct response from PASV */ 373 return 0; 374 } 375 tpath = ttpath; 376 if (*tpath != ',') { 377 return 0; 378 } 379 tpath++; 380 /* pull out the LSB of the port */ 381 portno += (unsigned short) strtoul(tpath, &ttpath, 10); 382#ifdef HAVE_IPV6 383 } else { 384 /* parse epsv command (|||6446|) */ 385 for (i = 0, tpath = tmp_line + 4; *tpath; tpath++) { 386 if (*tpath == '|') { 387 i++; 388 if (i == 3) 389 break; 390 } 391 } 392 if (i < 3) { 393 return 0; 394 } 395 /* pull out the port */ 396 portno = (unsigned short) strtoul(tpath + 1, &ttpath, 10); 397 } 398#endif 399 if (ttpath == NULL) { 400 /* didn't get correct response from EPSV/PASV */ 401 return 0; 402 } 403 404 if (phoststart) { 405 *phoststart = hoststart; 406 } 407 408 return portno; 409} 410/* }}} */ 411 412/* {{{ php_fopen_url_wrap_ftp 413 */ 414php_stream * php_stream_url_wrap_ftp(php_stream_wrapper *wrapper, char *path, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC) 415{ 416 php_stream *stream = NULL, *datastream = NULL; 417 php_url *resource = NULL; 418 char tmp_line[512]; 419 char ip[sizeof("123.123.123.123")]; 420 unsigned short portno; 421 char *hoststart = NULL; 422 int result = 0, use_ssl, use_ssl_on_data=0; 423 php_stream *reuseid=NULL; 424 size_t file_size = 0; 425 zval **tmpzval; 426 int allow_overwrite = 0; 427 int read_write = 0; 428 char *transport; 429 int transport_len; 430 431 tmp_line[0] = '\0'; 432 433 if (strpbrk(mode, "r+")) { 434 read_write = 1; /* Open for reading */ 435 } 436 if (strpbrk(mode, "wa+")) { 437 if (read_write) { 438 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP does not support simultaneous read/write connections"); 439 return NULL; 440 } 441 if (strchr(mode, 'a')) { 442 read_write = 3; /* Open for Appending */ 443 } else { 444 read_write = 2; /* Open for writting */ 445 } 446 } 447 if (!read_write) { 448 /* No mode specified? */ 449 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unknown file open mode"); 450 return NULL; 451 } 452 453 if (context && 454 php_stream_context_get_option(context, "ftp", "proxy", &tmpzval) == SUCCESS) { 455 if (read_write == 1) { 456 /* Use http wrapper to proxy ftp request */ 457 return php_stream_url_wrap_http(wrapper, path, mode, options, opened_path, context STREAMS_CC TSRMLS_CC); 458 } else { 459 /* ftp proxy is read-only */ 460 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP proxy may only be used in read mode"); 461 return NULL; 462 } 463 } 464 465 stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data TSRMLS_CC); 466 if (!stream) { 467 goto errexit; 468 } 469 470 /* set the connection to be binary */ 471 php_stream_write_string(stream, "TYPE I\r\n"); 472 result = GET_FTP_RESULT(stream); 473 if (result > 299 || result < 200) 474 goto errexit; 475 476 /* find out the size of the file (verifying it exists) */ 477 php_stream_printf(stream TSRMLS_CC, "SIZE %s\r\n", resource->path); 478 479 /* read the response */ 480 result = GET_FTP_RESULT(stream); 481 if (read_write == 1) { 482 /* Read Mode */ 483 char *sizestr; 484 485 /* when reading file, it must exist */ 486 if (result > 299 || result < 200) { 487 errno = ENOENT; 488 goto errexit; 489 } 490 491 sizestr = strchr(tmp_line, ' '); 492 if (sizestr) { 493 sizestr++; 494 file_size = atoi(sizestr); 495 php_stream_notify_file_size(context, file_size, tmp_line, result); 496 } 497 } else if (read_write == 2) { 498 /* when writing file (but not appending), it must NOT exist, unless a context option exists which allows it */ 499 if (context && php_stream_context_get_option(context, "ftp", "overwrite", &tmpzval) == SUCCESS) { 500 allow_overwrite = Z_LVAL_PP(tmpzval); 501 } 502 if (result <= 299 && result >= 200) { 503 if (allow_overwrite) { 504 /* Context permits overwritting file, 505 so we just delete whatever's there in preparation */ 506 php_stream_printf(stream TSRMLS_CC, "DELE %s\r\n", resource->path); 507 result = GET_FTP_RESULT(stream); 508 if (result >= 300 || result <= 199) { 509 goto errexit; 510 } 511 } else { 512 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Remote file already exists and overwrite context option not specified"); 513 errno = EEXIST; 514 goto errexit; 515 } 516 } 517 } 518 519 /* set up the passive connection */ 520 portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart TSRMLS_CC); 521 522 if (!portno) { 523 goto errexit; 524 } 525 526 /* Send RETR/STOR command */ 527 if (read_write == 1) { 528 /* set resume position if applicable */ 529 if (context && 530 php_stream_context_get_option(context, "ftp", "resume_pos", &tmpzval) == SUCCESS && 531 Z_TYPE_PP(tmpzval) == IS_LONG && 532 Z_LVAL_PP(tmpzval) > 0) { 533 php_stream_printf(stream TSRMLS_CC, "REST %ld\r\n", Z_LVAL_PP(tmpzval)); 534 result = GET_FTP_RESULT(stream); 535 if (result < 300 || result > 399) { 536 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to resume from offset %ld", Z_LVAL_PP(tmpzval)); 537 goto errexit; 538 } 539 } 540 541 /* retrieve file */ 542 memcpy(tmp_line, "RETR", sizeof("RETR")); 543 } else if (read_write == 2) { 544 /* Write new file */ 545 memcpy(tmp_line, "STOR", sizeof("STOR")); 546 } else { 547 /* Append */ 548 memcpy(tmp_line, "APPE", sizeof("APPE")); 549 } 550 php_stream_printf(stream TSRMLS_CC, "%s %s\r\n", tmp_line, (resource->path != NULL ? resource->path : "/")); 551 552 /* open the data channel */ 553 if (hoststart == NULL) { 554 hoststart = resource->host; 555 } 556 transport_len = spprintf(&transport, 0, "tcp://%s:%d", hoststart, portno); 557 datastream = php_stream_xport_create(transport, transport_len, REPORT_ERRORS, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, NULL, NULL, context, NULL, NULL); 558 efree(transport); 559 if (datastream == NULL) { 560 goto errexit; 561 } 562 563 result = GET_FTP_RESULT(stream); 564 if (result != 150 && result != 125) { 565 /* Could not retrieve or send the file 566 * this data will only be sent to us after connection on the data port was initiated. 567 */ 568 php_stream_close(datastream); 569 datastream = NULL; 570 goto errexit; 571 } 572 573 php_stream_context_set(datastream, context); 574 php_stream_notify_progress_init(context, 0, file_size); 575 576 if (use_ssl_on_data && (php_stream_xport_crypto_setup(datastream, 577 STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 || 578 php_stream_xport_crypto_enable(datastream, 1 TSRMLS_CC) < 0)) { 579 580 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode"); 581 php_stream_close(datastream); 582 datastream = NULL; 583 goto errexit; 584 } 585 586 /* remember control stream */ 587 datastream->wrapperthis = stream; 588 589 php_url_free(resource); 590 return datastream; 591 592errexit: 593 if (resource) { 594 php_url_free(resource); 595 } 596 if (stream) { 597 php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result); 598 php_stream_close(stream); 599 } 600 if (tmp_line[0] != '\0') 601 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP server reports %s", tmp_line); 602 return NULL; 603} 604/* }}} */ 605 606/* {{{ php_ftp_dirsteam_read 607 */ 608static size_t php_ftp_dirstream_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) 609{ 610 php_stream_dirent *ent = (php_stream_dirent *)buf; 611 php_stream *innerstream; 612 size_t tmp_len; 613 char *basename; 614 size_t basename_len; 615 616 innerstream = ((php_ftp_dirstream_data *)stream->abstract)->datastream; 617 618 if (count != sizeof(php_stream_dirent)) { 619 return 0; 620 } 621 622 if (php_stream_eof(innerstream)) { 623 return 0; 624 } 625 626 if (!php_stream_get_line(innerstream, ent->d_name, sizeof(ent->d_name), &tmp_len)) { 627 return 0; 628 } 629 630 php_basename(ent->d_name, tmp_len, NULL, 0, &basename, &basename_len TSRMLS_CC); 631 if (!basename) { 632 return 0; 633 } 634 635 if (!basename_len) { 636 efree(basename); 637 return 0; 638 } 639 640 tmp_len = MIN(sizeof(ent->d_name), basename_len - 1); 641 memcpy(ent->d_name, basename, tmp_len); 642 ent->d_name[tmp_len - 1] = '\0'; 643 efree(basename); 644 645 /* Trim off trailing whitespace characters */ 646 tmp_len--; 647 while (tmp_len >= 0 && 648 (ent->d_name[tmp_len] == '\n' || ent->d_name[tmp_len] == '\r' || 649 ent->d_name[tmp_len] == '\t' || ent->d_name[tmp_len] == ' ')) { 650 ent->d_name[tmp_len--] = '\0'; 651 } 652 653 return sizeof(php_stream_dirent); 654} 655/* }}} */ 656 657/* {{{ php_ftp_dirstream_close 658 */ 659static int php_ftp_dirstream_close(php_stream *stream, int close_handle TSRMLS_DC) 660{ 661 php_ftp_dirstream_data *data = stream->abstract; 662 663 /* close control connection */ 664 if (data->controlstream) { 665 php_stream_close(data->controlstream); 666 data->controlstream = NULL; 667 } 668 /* close data connection */ 669 php_stream_close(data->datastream); 670 data->datastream = NULL; 671 672 efree(data); 673 stream->abstract = NULL; 674 675 return 0; 676} 677/* }}} */ 678 679/* ftp dirstreams only need to support read and close operations, 680 They can't be rewound because the underlying ftp stream can't be rewound. */ 681static php_stream_ops php_ftp_dirstream_ops = { 682 NULL, /* write */ 683 php_ftp_dirstream_read, /* read */ 684 php_ftp_dirstream_close, /* close */ 685 NULL, /* flush */ 686 "ftpdir", 687 NULL, /* rewind */ 688 NULL, /* cast */ 689 NULL, /* stat */ 690 NULL /* set option */ 691}; 692 693/* {{{ php_stream_ftp_opendir 694 */ 695php_stream * php_stream_ftp_opendir(php_stream_wrapper *wrapper, char *path, char *mode, int options, char **opened_path, php_stream_context *context STREAMS_DC TSRMLS_DC) 696{ 697 php_stream *stream, *reuseid, *datastream = NULL; 698 php_ftp_dirstream_data *dirsdata; 699 php_url *resource = NULL; 700 int result = 0, use_ssl, use_ssl_on_data = 0; 701 char *hoststart = NULL, tmp_line[512]; 702 char ip[sizeof("123.123.123.123")]; 703 unsigned short portno; 704 705 tmp_line[0] = '\0'; 706 707 stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data TSRMLS_CC); 708 if (!stream) { 709 goto opendir_errexit; 710 } 711 712 /* set the connection to be ascii */ 713 php_stream_write_string(stream, "TYPE A\r\n"); 714 result = GET_FTP_RESULT(stream); 715 if (result > 299 || result < 200) 716 goto opendir_errexit; 717 718 /* set up the passive connection */ 719 portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart TSRMLS_CC); 720 721 if (!portno) { 722 goto opendir_errexit; 723 } 724 725 php_stream_printf(stream TSRMLS_CC, "NLST %s\r\n", (resource->path != NULL ? resource->path : "/")); 726 727 /* open the data channel */ 728 if (hoststart == NULL) { 729 hoststart = resource->host; 730 } 731 datastream = php_stream_sock_open_host(hoststart, portno, SOCK_STREAM, 0, 0); 732 if (datastream == NULL) { 733 goto opendir_errexit; 734 } 735 736 result = GET_FTP_RESULT(stream); 737 if (result != 150 && result != 125) { 738 /* Could not retrieve or send the file 739 * this data will only be sent to us after connection on the data port was initiated. 740 */ 741 php_stream_close(datastream); 742 datastream = NULL; 743 goto opendir_errexit; 744 } 745 746 php_stream_context_set(datastream, context); 747 748 if (use_ssl_on_data && (php_stream_xport_crypto_setup(stream, 749 STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 || 750 php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0)) { 751 752 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode"); 753 php_stream_close(datastream); 754 datastream = NULL; 755 goto opendir_errexit; 756 } 757 758 php_url_free(resource); 759 760 dirsdata = emalloc(sizeof *dirsdata); 761 dirsdata->datastream = datastream; 762 dirsdata->controlstream = stream; 763 dirsdata->dirstream = php_stream_alloc(&php_ftp_dirstream_ops, dirsdata, 0, mode); 764 765 return dirsdata->dirstream; 766 767opendir_errexit: 768 if (resource) { 769 php_url_free(resource); 770 } 771 if (stream) { 772 php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result); 773 php_stream_close(stream); 774 } 775 if (tmp_line[0] != '\0') { 776 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP server reports %s", tmp_line); 777 } 778 return NULL; 779} 780/* }}} */ 781 782/* {{{ php_stream_ftp_url_stat 783 */ 784static int php_stream_ftp_url_stat(php_stream_wrapper *wrapper, char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC) 785{ 786 php_stream *stream = NULL; 787 php_url *resource = NULL; 788 int result; 789 char tmp_line[512]; 790 791 /* If ssb is NULL then someone is misbehaving */ 792 if (!ssb) return -1; 793 794 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL TSRMLS_CC); 795 if (!stream) { 796 goto stat_errexit; 797 } 798 799 ssb->sb.st_mode = 0644; /* FTP won't give us a valid mode, so aproximate one based on being readable */ 800 php_stream_printf(stream TSRMLS_CC, "CWD %s\r\n", (resource->path != NULL ? resource->path : "/")); /* If we can CWD to it, it's a directory (maybe a link, but we can't tell) */ 801 result = GET_FTP_RESULT(stream); 802 if (result < 200 || result > 299) { 803 ssb->sb.st_mode |= S_IFREG; 804 } else { 805 ssb->sb.st_mode |= S_IFDIR; 806 } 807 808 php_stream_write_string(stream, "TYPE I\r\n"); /* we need this since some servers refuse to accept SIZE command in ASCII mode */ 809 810 result = GET_FTP_RESULT(stream); 811 812 if(result < 200 || result > 299) { 813 goto stat_errexit; 814 } 815 816 php_stream_printf(stream TSRMLS_CC, "SIZE %s\r\n", (resource->path != NULL ? resource->path : "/")); 817 result = GET_FTP_RESULT(stream); 818 if (result < 200 || result > 299) { 819 /* Failure either means it doesn't exist 820 or it's a directory and this server 821 fails on listing directory sizes */ 822 if (ssb->sb.st_mode & S_IFDIR) { 823 ssb->sb.st_size = 0; 824 } else { 825 goto stat_errexit; 826 } 827 } else { 828 ssb->sb.st_size = atoi(tmp_line + 4); 829 } 830 831 php_stream_printf(stream TSRMLS_CC, "MDTM %s\r\n", (resource->path != NULL ? resource->path : "/")); 832 result = GET_FTP_RESULT(stream); 833 if (result == 213) { 834 char *p = tmp_line + 4; 835 int n; 836 struct tm tm, tmbuf, *gmt; 837 time_t stamp; 838 839 while (p - tmp_line < sizeof(tmp_line) && !isdigit(*p)) { 840 p++; 841 } 842 843 if (p - tmp_line > sizeof(tmp_line)) { 844 goto mdtm_error; 845 } 846 847 n = sscanf(p, "%4u%2u%2u%2u%2u%2u", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec); 848 if (n != 6) { 849 goto mdtm_error; 850 } 851 852 tm.tm_year -= 1900; 853 tm.tm_mon--; 854 tm.tm_isdst = -1; 855 856 /* figure out the GMT offset */ 857 stamp = time(NULL); 858 gmt = php_gmtime_r(&stamp, &tmbuf); 859 if (!gmt) { 860 goto mdtm_error; 861 } 862 gmt->tm_isdst = -1; 863 864 /* apply the GMT offset */ 865 tm.tm_sec += stamp - mktime(gmt); 866 tm.tm_isdst = gmt->tm_isdst; 867 868 ssb->sb.st_mtime = mktime(&tm); 869 } else { 870 /* error or unsupported command */ 871mdtm_error: 872 ssb->sb.st_mtime = -1; 873 } 874 875 ssb->sb.st_ino = 0; /* Unknown values */ 876 ssb->sb.st_dev = 0; 877 ssb->sb.st_uid = 0; 878 ssb->sb.st_gid = 0; 879 ssb->sb.st_atime = -1; 880 ssb->sb.st_ctime = -1; 881 882 ssb->sb.st_nlink = 1; 883 ssb->sb.st_rdev = -1; 884#ifdef HAVE_ST_BLKSIZE 885 ssb->sb.st_blksize = 4096; /* Guess since FTP won't expose this information */ 886#ifdef HAVE_ST_BLOCKS 887 ssb->sb.st_blocks = (int)((4095 + ssb->sb.st_size) / ssb->sb.st_blksize); /* emulate ceil */ 888#endif 889#endif 890 php_stream_close(stream); 891 php_url_free(resource); 892 return 0; 893 894stat_errexit: 895 if (resource) { 896 php_url_free(resource); 897 } 898 if (stream) { 899 php_stream_close(stream); 900 } 901 return -1; 902} 903/* }}} */ 904 905/* {{{ php_stream_ftp_unlink 906 */ 907static int php_stream_ftp_unlink(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC) 908{ 909 php_stream *stream = NULL; 910 php_url *resource = NULL; 911 int result; 912 char tmp_line[512]; 913 914 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC); 915 if (!stream) { 916 if (options & REPORT_ERRORS) { 917 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url); 918 } 919 goto unlink_errexit; 920 } 921 922 if (resource->path == NULL) { 923 if (options & REPORT_ERRORS) { 924 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url); 925 } 926 goto unlink_errexit; 927 } 928 929 /* Attempt to delete the file */ 930 php_stream_printf(stream TSRMLS_CC, "DELE %s\r\n", (resource->path != NULL ? resource->path : "/")); 931 932 result = GET_FTP_RESULT(stream); 933 if (result < 200 || result > 299) { 934 if (options & REPORT_ERRORS) { 935 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Deleting file: %s", tmp_line); 936 } 937 goto unlink_errexit; 938 } 939 940 php_url_free(resource); 941 php_stream_close(stream); 942 return 1; 943 944unlink_errexit: 945 if (resource) { 946 php_url_free(resource); 947 } 948 if (stream) { 949 php_stream_close(stream); 950 } 951 return 0; 952} 953/* }}} */ 954 955/* {{{ php_stream_ftp_rename 956 */ 957static int php_stream_ftp_rename(php_stream_wrapper *wrapper, char *url_from, char *url_to, int options, php_stream_context *context TSRMLS_DC) 958{ 959 php_stream *stream = NULL; 960 php_url *resource_from = NULL, *resource_to = NULL; 961 int result; 962 char tmp_line[512]; 963 964 resource_from = php_url_parse(url_from); 965 resource_to = php_url_parse(url_to); 966 /* Must be same scheme (ftp/ftp or ftps/ftps), same host, and same port 967 (or a 21/0 0/21 combination which is also "same") 968 Also require paths to/from */ 969 if (!resource_from || 970 !resource_to || 971 !resource_from->scheme || 972 !resource_to->scheme || 973 strcmp(resource_from->scheme, resource_to->scheme) || 974 !resource_from->host || 975 !resource_to->host || 976 strcmp(resource_from->host, resource_to->host) || 977 (resource_from->port != resource_to->port && 978 resource_from->port * resource_to->port != 0 && 979 resource_from->port + resource_to->port != 21) || 980 !resource_from->path || 981 !resource_to->path) { 982 goto rename_errexit; 983 } 984 985 stream = php_ftp_fopen_connect(wrapper, url_from, "r", 0, NULL, NULL, NULL, NULL, NULL, NULL TSRMLS_CC); 986 if (!stream) { 987 if (options & REPORT_ERRORS) { 988 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", resource_from->host); 989 } 990 goto rename_errexit; 991 } 992 993 /* Rename FROM */ 994 php_stream_printf(stream TSRMLS_CC, "RNFR %s\r\n", (resource_from->path != NULL ? resource_from->path : "/")); 995 996 result = GET_FTP_RESULT(stream); 997 if (result < 300 || result > 399) { 998 if (options & REPORT_ERRORS) { 999 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Renaming file: %s", tmp_line); 1000 } 1001 goto rename_errexit; 1002 } 1003 1004 /* Rename TO */ 1005 php_stream_printf(stream TSRMLS_CC, "RNTO %s\r\n", (resource_to->path != NULL ? resource_to->path : "/")); 1006 1007 result = GET_FTP_RESULT(stream); 1008 if (result < 200 || result > 299) { 1009 if (options & REPORT_ERRORS) { 1010 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Renaming file: %s", tmp_line); 1011 } 1012 goto rename_errexit; 1013 } 1014 1015 php_url_free(resource_from); 1016 php_url_free(resource_to); 1017 php_stream_close(stream); 1018 return 1; 1019 1020rename_errexit: 1021 if (resource_from) { 1022 php_url_free(resource_from); 1023 } 1024 if (resource_to) { 1025 php_url_free(resource_to); 1026 } 1027 if (stream) { 1028 php_stream_close(stream); 1029 } 1030 return 0; 1031} 1032/* }}} */ 1033 1034/* {{{ php_stream_ftp_mkdir 1035 */ 1036static int php_stream_ftp_mkdir(php_stream_wrapper *wrapper, char *url, int mode, int options, php_stream_context *context TSRMLS_DC) 1037{ 1038 php_stream *stream = NULL; 1039 php_url *resource = NULL; 1040 int result, recursive = options & PHP_STREAM_MKDIR_RECURSIVE; 1041 char tmp_line[512]; 1042 1043 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC); 1044 if (!stream) { 1045 if (options & REPORT_ERRORS) { 1046 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url); 1047 } 1048 goto mkdir_errexit; 1049 } 1050 1051 if (resource->path == NULL) { 1052 if (options & REPORT_ERRORS) { 1053 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url); 1054 } 1055 goto mkdir_errexit; 1056 } 1057 1058 if (!recursive) { 1059 php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", resource->path); 1060 result = GET_FTP_RESULT(stream); 1061 } else { 1062 /* we look for directory separator from the end of string, thus hopefuly reducing our work load */ 1063 char *p, *e, *buf; 1064 1065 buf = estrdup(resource->path); 1066 e = buf + strlen(buf); 1067 1068 /* find a top level directory we need to create */ 1069 while ((p = strrchr(buf, '/'))) { 1070 *p = '\0'; 1071 php_stream_printf(stream TSRMLS_CC, "CWD %s\r\n", buf); 1072 result = GET_FTP_RESULT(stream); 1073 if (result >= 200 && result <= 299) { 1074 *p = '/'; 1075 break; 1076 } 1077 } 1078 if (p == buf) { 1079 php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", resource->path); 1080 result = GET_FTP_RESULT(stream); 1081 } else { 1082 php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", buf); 1083 result = GET_FTP_RESULT(stream); 1084 if (result >= 200 && result <= 299) { 1085 if (!p) { 1086 p = buf; 1087 } 1088 /* create any needed directories if the creation of the 1st directory worked */ 1089 while (++p != e) { 1090 if (*p == '\0' && *(p + 1) != '\0') { 1091 *p = '/'; 1092 php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", buf); 1093 result = GET_FTP_RESULT(stream); 1094 if (result < 200 || result > 299) { 1095 if (options & REPORT_ERRORS) { 1096 php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tmp_line); 1097 } 1098 break; 1099 } 1100 } 1101 } 1102 } 1103 } 1104 efree(buf); 1105 } 1106 1107 php_url_free(resource); 1108 php_stream_close(stream); 1109 1110 if (result < 200 || result > 299) { 1111 /* Failure */ 1112 return 0; 1113 } 1114 1115 return 1; 1116 1117mkdir_errexit: 1118 if (resource) { 1119 php_url_free(resource); 1120 } 1121 if (stream) { 1122 php_stream_close(stream); 1123 } 1124 return 0; 1125} 1126/* }}} */ 1127 1128/* {{{ php_stream_ftp_rmdir 1129 */ 1130static int php_stream_ftp_rmdir(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC) 1131{ 1132 php_stream *stream = NULL; 1133 php_url *resource = NULL; 1134 int result; 1135 char tmp_line[512]; 1136 1137 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC); 1138 if (!stream) { 1139 if (options & REPORT_ERRORS) { 1140 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url); 1141 } 1142 goto rmdir_errexit; 1143 } 1144 1145 if (resource->path == NULL) { 1146 if (options & REPORT_ERRORS) { 1147 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url); 1148 } 1149 goto rmdir_errexit; 1150 } 1151 1152 php_stream_printf(stream TSRMLS_CC, "RMD %s\r\n", resource->path); 1153 result = GET_FTP_RESULT(stream); 1154 1155 if (result < 200 || result > 299) { 1156 if (options & REPORT_ERRORS) { 1157 php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tmp_line); 1158 } 1159 goto rmdir_errexit; 1160 } 1161 1162 php_url_free(resource); 1163 php_stream_close(stream); 1164 1165 return 1; 1166 1167rmdir_errexit: 1168 if (resource) { 1169 php_url_free(resource); 1170 } 1171 if (stream) { 1172 php_stream_close(stream); 1173 } 1174 return 0; 1175} 1176/* }}} */ 1177 1178static php_stream_wrapper_ops ftp_stream_wops = { 1179 php_stream_url_wrap_ftp, 1180 php_stream_ftp_stream_close, /* stream_close */ 1181 php_stream_ftp_stream_stat, 1182 php_stream_ftp_url_stat, /* stat_url */ 1183 php_stream_ftp_opendir, /* opendir */ 1184 "ftp", 1185 php_stream_ftp_unlink, /* unlink */ 1186 php_stream_ftp_rename, /* rename */ 1187 php_stream_ftp_mkdir, /* mkdir */ 1188 php_stream_ftp_rmdir /* rmdir */ 1189}; 1190 1191PHPAPI php_stream_wrapper php_stream_ftp_wrapper = { 1192 &ftp_stream_wops, 1193 NULL, 1194 1 /* is_url */ 1195}; 1196 1197 1198/* 1199 * Local variables: 1200 * tab-width: 4 1201 * c-basic-offset: 4 1202 * End: 1203 * vim600: sw=4 ts=4 fdm=marker 1204 * vim<600: sw=4 ts=4 1205 */ 1206