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 if (FG(from_address)) { 273 php_stream_printf(stream TSRMLS_CC, "PASS %s\r\n", FG(from_address)); 274 } else { 275 php_stream_write_string(stream, "PASS anonymous\r\n"); 276 } 277 } 278 279 /* read the response */ 280 result = GET_FTP_RESULT(stream); 281 282 if (result > 299 || result < 200) { 283 php_stream_notify_error(context, PHP_STREAM_NOTIFY_AUTH_RESULT, tmp_line, result); 284 } else { 285 php_stream_notify_info(context, PHP_STREAM_NOTIFY_AUTH_RESULT, tmp_line, result); 286 } 287 } 288 if (result > 299 || result < 200) { 289 goto connect_errexit; 290 } 291 292 if (puse_ssl) { 293 *puse_ssl = use_ssl; 294 } 295 if (puse_ssl_on_data) { 296 *puse_ssl_on_data = use_ssl_on_data; 297 } 298 if (preuseid) { 299 *preuseid = reuseid; 300 } 301 if (presource) { 302 *presource = resource; 303 } 304 305 return stream; 306 307connect_errexit: 308 if (resource) { 309 php_url_free(resource); 310 } 311 312 if (stream) { 313 php_stream_close(stream); 314 } 315 316 return NULL; 317} 318/* }}} */ 319 320/* {{{ php_fopen_do_pasv 321 */ 322static unsigned short php_fopen_do_pasv(php_stream *stream, char *ip, size_t ip_size, char **phoststart TSRMLS_DC) 323{ 324 char tmp_line[512]; 325 int result, i; 326 unsigned short portno; 327 char *tpath, *ttpath, *hoststart=NULL; 328 329#ifdef HAVE_IPV6 330 /* We try EPSV first, needed for IPv6 and works on some IPv4 servers */ 331 php_stream_write_string(stream, "EPSV\r\n"); 332 result = GET_FTP_RESULT(stream); 333 334 /* check if we got a 229 response */ 335 if (result != 229) { 336#endif 337 /* EPSV failed, let's try PASV */ 338 php_stream_write_string(stream, "PASV\r\n"); 339 result = GET_FTP_RESULT(stream); 340 341 /* make sure we got a 227 response */ 342 if (result != 227) { 343 return 0; 344 } 345 346 /* parse pasv command (129, 80, 95, 25, 13, 221) */ 347 tpath = tmp_line; 348 /* skip over the "227 Some message " part */ 349 for (tpath += 4; *tpath && !isdigit((int) *tpath); tpath++); 350 if (!*tpath) { 351 return 0; 352 } 353 /* skip over the host ip, to get the port */ 354 hoststart = tpath; 355 for (i = 0; i < 4; i++) { 356 for (; isdigit((int) *tpath); tpath++); 357 if (*tpath != ',') { 358 return 0; 359 } 360 *tpath='.'; 361 tpath++; 362 } 363 tpath[-1] = '\0'; 364 memcpy(ip, hoststart, ip_size); 365 ip[ip_size-1] = '\0'; 366 hoststart = ip; 367 368 /* pull out the MSB of the port */ 369 portno = (unsigned short) strtoul(tpath, &ttpath, 10) * 256; 370 if (ttpath == NULL) { 371 /* didn't get correct response from PASV */ 372 return 0; 373 } 374 tpath = ttpath; 375 if (*tpath != ',') { 376 return 0; 377 } 378 tpath++; 379 /* pull out the LSB of the port */ 380 portno += (unsigned short) strtoul(tpath, &ttpath, 10); 381#ifdef HAVE_IPV6 382 } else { 383 /* parse epsv command (|||6446|) */ 384 for (i = 0, tpath = tmp_line + 4; *tpath; tpath++) { 385 if (*tpath == '|') { 386 i++; 387 if (i == 3) 388 break; 389 } 390 } 391 if (i < 3) { 392 return 0; 393 } 394 /* pull out the port */ 395 portno = (unsigned short) strtoul(tpath + 1, &ttpath, 10); 396 } 397#endif 398 if (ttpath == NULL) { 399 /* didn't get correct response from EPSV/PASV */ 400 return 0; 401 } 402 403 if (phoststart) { 404 *phoststart = hoststart; 405 } 406 407 return portno; 408} 409/* }}} */ 410 411/* {{{ php_fopen_url_wrap_ftp 412 */ 413php_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) 414{ 415 php_stream *stream = NULL, *datastream = NULL; 416 php_url *resource = NULL; 417 char tmp_line[512]; 418 char ip[sizeof("123.123.123.123")]; 419 unsigned short portno; 420 char *hoststart = NULL; 421 int result = 0, use_ssl, use_ssl_on_data=0; 422 php_stream *reuseid=NULL; 423 size_t file_size = 0; 424 zval **tmpzval; 425 int allow_overwrite = 0; 426 int read_write = 0; 427 char *transport; 428 int transport_len; 429 430 tmp_line[0] = '\0'; 431 432 if (strpbrk(mode, "r+")) { 433 read_write = 1; /* Open for reading */ 434 } 435 if (strpbrk(mode, "wa+")) { 436 if (read_write) { 437 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP does not support simultaneous read/write connections"); 438 return NULL; 439 } 440 if (strchr(mode, 'a')) { 441 read_write = 3; /* Open for Appending */ 442 } else { 443 read_write = 2; /* Open for writing */ 444 } 445 } 446 if (!read_write) { 447 /* No mode specified? */ 448 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unknown file open mode"); 449 return NULL; 450 } 451 452 if (context && 453 php_stream_context_get_option(context, "ftp", "proxy", &tmpzval) == SUCCESS) { 454 if (read_write == 1) { 455 /* Use http wrapper to proxy ftp request */ 456 return php_stream_url_wrap_http(wrapper, path, mode, options, opened_path, context STREAMS_CC TSRMLS_CC); 457 } else { 458 /* ftp proxy is read-only */ 459 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP proxy may only be used in read mode"); 460 return NULL; 461 } 462 } 463 464 stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data TSRMLS_CC); 465 if (!stream) { 466 goto errexit; 467 } 468 469 /* set the connection to be binary */ 470 php_stream_write_string(stream, "TYPE I\r\n"); 471 result = GET_FTP_RESULT(stream); 472 if (result > 299 || result < 200) 473 goto errexit; 474 475 /* find out the size of the file (verifying it exists) */ 476 php_stream_printf(stream TSRMLS_CC, "SIZE %s\r\n", resource->path); 477 478 /* read the response */ 479 result = GET_FTP_RESULT(stream); 480 if (read_write == 1) { 481 /* Read Mode */ 482 char *sizestr; 483 484 /* when reading file, it must exist */ 485 if (result > 299 || result < 200) { 486 errno = ENOENT; 487 goto errexit; 488 } 489 490 sizestr = strchr(tmp_line, ' '); 491 if (sizestr) { 492 sizestr++; 493 file_size = atoi(sizestr); 494 php_stream_notify_file_size(context, file_size, tmp_line, result); 495 } 496 } else if (read_write == 2) { 497 /* when writing file (but not appending), it must NOT exist, unless a context option exists which allows it */ 498 if (context && php_stream_context_get_option(context, "ftp", "overwrite", &tmpzval) == SUCCESS) { 499 allow_overwrite = Z_LVAL_PP(tmpzval); 500 } 501 if (result <= 299 && result >= 200) { 502 if (allow_overwrite) { 503 /* Context permits overwriting file, 504 so we just delete whatever's there in preparation */ 505 php_stream_printf(stream TSRMLS_CC, "DELE %s\r\n", resource->path); 506 result = GET_FTP_RESULT(stream); 507 if (result >= 300 || result <= 199) { 508 goto errexit; 509 } 510 } else { 511 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Remote file already exists and overwrite context option not specified"); 512 errno = EEXIST; 513 goto errexit; 514 } 515 } 516 } 517 518 /* set up the passive connection */ 519 portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart TSRMLS_CC); 520 521 if (!portno) { 522 goto errexit; 523 } 524 525 /* Send RETR/STOR command */ 526 if (read_write == 1) { 527 /* set resume position if applicable */ 528 if (context && 529 php_stream_context_get_option(context, "ftp", "resume_pos", &tmpzval) == SUCCESS && 530 Z_TYPE_PP(tmpzval) == IS_LONG && 531 Z_LVAL_PP(tmpzval) > 0) { 532 php_stream_printf(stream TSRMLS_CC, "REST %ld\r\n", Z_LVAL_PP(tmpzval)); 533 result = GET_FTP_RESULT(stream); 534 if (result < 300 || result > 399) { 535 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to resume from offset %ld", Z_LVAL_PP(tmpzval)); 536 goto errexit; 537 } 538 } 539 540 /* retrieve file */ 541 memcpy(tmp_line, "RETR", sizeof("RETR")); 542 } else if (read_write == 2) { 543 /* Write new file */ 544 memcpy(tmp_line, "STOR", sizeof("STOR")); 545 } else { 546 /* Append */ 547 memcpy(tmp_line, "APPE", sizeof("APPE")); 548 } 549 php_stream_printf(stream TSRMLS_CC, "%s %s\r\n", tmp_line, (resource->path != NULL ? resource->path : "/")); 550 551 /* open the data channel */ 552 if (hoststart == NULL) { 553 hoststart = resource->host; 554 } 555 transport_len = spprintf(&transport, 0, "tcp://%s:%d", hoststart, portno); 556 datastream = php_stream_xport_create(transport, transport_len, REPORT_ERRORS, STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, NULL, NULL, context, NULL, NULL); 557 efree(transport); 558 if (datastream == NULL) { 559 goto errexit; 560 } 561 562 result = GET_FTP_RESULT(stream); 563 if (result != 150 && result != 125) { 564 /* Could not retrieve or send the file 565 * this data will only be sent to us after connection on the data port was initiated. 566 */ 567 php_stream_close(datastream); 568 datastream = NULL; 569 goto errexit; 570 } 571 572 php_stream_context_set(datastream, context); 573 php_stream_notify_progress_init(context, 0, file_size); 574 575 if (use_ssl_on_data && (php_stream_xport_crypto_setup(datastream, 576 STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 || 577 php_stream_xport_crypto_enable(datastream, 1 TSRMLS_CC) < 0)) { 578 579 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode"); 580 php_stream_close(datastream); 581 datastream = NULL; 582 goto errexit; 583 } 584 585 /* remember control stream */ 586 datastream->wrapperthis = stream; 587 588 php_url_free(resource); 589 return datastream; 590 591errexit: 592 if (resource) { 593 php_url_free(resource); 594 } 595 if (stream) { 596 php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result); 597 php_stream_close(stream); 598 } 599 if (tmp_line[0] != '\0') 600 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP server reports %s", tmp_line); 601 return NULL; 602} 603/* }}} */ 604 605/* {{{ php_ftp_dirsteam_read 606 */ 607static size_t php_ftp_dirstream_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) 608{ 609 php_stream_dirent *ent = (php_stream_dirent *)buf; 610 php_stream *innerstream; 611 size_t tmp_len; 612 char *basename; 613 size_t basename_len; 614 615 innerstream = ((php_ftp_dirstream_data *)stream->abstract)->datastream; 616 617 if (count != sizeof(php_stream_dirent)) { 618 return 0; 619 } 620 621 if (php_stream_eof(innerstream)) { 622 return 0; 623 } 624 625 if (!php_stream_get_line(innerstream, ent->d_name, sizeof(ent->d_name), &tmp_len)) { 626 return 0; 627 } 628 629 php_basename(ent->d_name, tmp_len, NULL, 0, &basename, &basename_len TSRMLS_CC); 630 if (!basename) { 631 return 0; 632 } 633 634 if (!basename_len) { 635 efree(basename); 636 return 0; 637 } 638 639 tmp_len = MIN(sizeof(ent->d_name), basename_len - 1); 640 memcpy(ent->d_name, basename, tmp_len); 641 ent->d_name[tmp_len - 1] = '\0'; 642 efree(basename); 643 644 /* Trim off trailing whitespace characters */ 645 tmp_len--; 646 while (tmp_len >= 0 && 647 (ent->d_name[tmp_len] == '\n' || ent->d_name[tmp_len] == '\r' || 648 ent->d_name[tmp_len] == '\t' || ent->d_name[tmp_len] == ' ')) { 649 ent->d_name[tmp_len--] = '\0'; 650 } 651 652 return sizeof(php_stream_dirent); 653} 654/* }}} */ 655 656/* {{{ php_ftp_dirstream_close 657 */ 658static int php_ftp_dirstream_close(php_stream *stream, int close_handle TSRMLS_DC) 659{ 660 php_ftp_dirstream_data *data = stream->abstract; 661 662 /* close control connection */ 663 if (data->controlstream) { 664 php_stream_close(data->controlstream); 665 data->controlstream = NULL; 666 } 667 /* close data connection */ 668 php_stream_close(data->datastream); 669 data->datastream = NULL; 670 671 efree(data); 672 stream->abstract = NULL; 673 674 return 0; 675} 676/* }}} */ 677 678/* ftp dirstreams only need to support read and close operations, 679 They can't be rewound because the underlying ftp stream can't be rewound. */ 680static php_stream_ops php_ftp_dirstream_ops = { 681 NULL, /* write */ 682 php_ftp_dirstream_read, /* read */ 683 php_ftp_dirstream_close, /* close */ 684 NULL, /* flush */ 685 "ftpdir", 686 NULL, /* rewind */ 687 NULL, /* cast */ 688 NULL, /* stat */ 689 NULL /* set option */ 690}; 691 692/* {{{ php_stream_ftp_opendir 693 */ 694php_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) 695{ 696 php_stream *stream, *reuseid, *datastream = NULL; 697 php_ftp_dirstream_data *dirsdata; 698 php_url *resource = NULL; 699 int result = 0, use_ssl, use_ssl_on_data = 0; 700 char *hoststart = NULL, tmp_line[512]; 701 char ip[sizeof("123.123.123.123")]; 702 unsigned short portno; 703 704 tmp_line[0] = '\0'; 705 706 stream = php_ftp_fopen_connect(wrapper, path, mode, options, opened_path, context, &reuseid, &resource, &use_ssl, &use_ssl_on_data TSRMLS_CC); 707 if (!stream) { 708 goto opendir_errexit; 709 } 710 711 /* set the connection to be ascii */ 712 php_stream_write_string(stream, "TYPE A\r\n"); 713 result = GET_FTP_RESULT(stream); 714 if (result > 299 || result < 200) 715 goto opendir_errexit; 716 717 /* set up the passive connection */ 718 portno = php_fopen_do_pasv(stream, ip, sizeof(ip), &hoststart TSRMLS_CC); 719 720 if (!portno) { 721 goto opendir_errexit; 722 } 723 724 php_stream_printf(stream TSRMLS_CC, "NLST %s\r\n", (resource->path != NULL ? resource->path : "/")); 725 726 /* open the data channel */ 727 if (hoststart == NULL) { 728 hoststart = resource->host; 729 } 730 datastream = php_stream_sock_open_host(hoststart, portno, SOCK_STREAM, 0, 0); 731 if (datastream == NULL) { 732 goto opendir_errexit; 733 } 734 735 result = GET_FTP_RESULT(stream); 736 if (result != 150 && result != 125) { 737 /* Could not retrieve or send the file 738 * this data will only be sent to us after connection on the data port was initiated. 739 */ 740 php_stream_close(datastream); 741 datastream = NULL; 742 goto opendir_errexit; 743 } 744 745 php_stream_context_set(datastream, context); 746 747 if (use_ssl_on_data && (php_stream_xport_crypto_setup(stream, 748 STREAM_CRYPTO_METHOD_SSLv23_CLIENT, NULL TSRMLS_CC) < 0 || 749 php_stream_xport_crypto_enable(stream, 1 TSRMLS_CC) < 0)) { 750 751 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to activate SSL mode"); 752 php_stream_close(datastream); 753 datastream = NULL; 754 goto opendir_errexit; 755 } 756 757 php_url_free(resource); 758 759 dirsdata = emalloc(sizeof *dirsdata); 760 dirsdata->datastream = datastream; 761 dirsdata->controlstream = stream; 762 dirsdata->dirstream = php_stream_alloc(&php_ftp_dirstream_ops, dirsdata, 0, mode); 763 764 return dirsdata->dirstream; 765 766opendir_errexit: 767 if (resource) { 768 php_url_free(resource); 769 } 770 if (stream) { 771 php_stream_notify_error(context, PHP_STREAM_NOTIFY_FAILURE, tmp_line, result); 772 php_stream_close(stream); 773 } 774 if (tmp_line[0] != '\0') { 775 php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "FTP server reports %s", tmp_line); 776 } 777 return NULL; 778} 779/* }}} */ 780 781/* {{{ php_stream_ftp_url_stat 782 */ 783static int php_stream_ftp_url_stat(php_stream_wrapper *wrapper, char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context TSRMLS_DC) 784{ 785 php_stream *stream = NULL; 786 php_url *resource = NULL; 787 int result; 788 char tmp_line[512]; 789 790 /* If ssb is NULL then someone is misbehaving */ 791 if (!ssb) return -1; 792 793 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, context, NULL, &resource, NULL, NULL TSRMLS_CC); 794 if (!stream) { 795 goto stat_errexit; 796 } 797 798 ssb->sb.st_mode = 0644; /* FTP won't give us a valid mode, so aproximate one based on being readable */ 799 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) */ 800 result = GET_FTP_RESULT(stream); 801 if (result < 200 || result > 299) { 802 ssb->sb.st_mode |= S_IFREG; 803 } else { 804 ssb->sb.st_mode |= S_IFDIR; 805 } 806 807 php_stream_write_string(stream, "TYPE I\r\n"); /* we need this since some servers refuse to accept SIZE command in ASCII mode */ 808 809 result = GET_FTP_RESULT(stream); 810 811 if(result < 200 || result > 299) { 812 goto stat_errexit; 813 } 814 815 php_stream_printf(stream TSRMLS_CC, "SIZE %s\r\n", (resource->path != NULL ? resource->path : "/")); 816 result = GET_FTP_RESULT(stream); 817 if (result < 200 || result > 299) { 818 /* Failure either means it doesn't exist 819 or it's a directory and this server 820 fails on listing directory sizes */ 821 if (ssb->sb.st_mode & S_IFDIR) { 822 ssb->sb.st_size = 0; 823 } else { 824 goto stat_errexit; 825 } 826 } else { 827 ssb->sb.st_size = atoi(tmp_line + 4); 828 } 829 830 php_stream_printf(stream TSRMLS_CC, "MDTM %s\r\n", (resource->path != NULL ? resource->path : "/")); 831 result = GET_FTP_RESULT(stream); 832 if (result == 213) { 833 char *p = tmp_line + 4; 834 int n; 835 struct tm tm, tmbuf, *gmt; 836 time_t stamp; 837 838 while (p - tmp_line < sizeof(tmp_line) && !isdigit(*p)) { 839 p++; 840 } 841 842 if (p - tmp_line > sizeof(tmp_line)) { 843 goto mdtm_error; 844 } 845 846 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); 847 if (n != 6) { 848 goto mdtm_error; 849 } 850 851 tm.tm_year -= 1900; 852 tm.tm_mon--; 853 tm.tm_isdst = -1; 854 855 /* figure out the GMT offset */ 856 stamp = time(NULL); 857 gmt = php_gmtime_r(&stamp, &tmbuf); 858 if (!gmt) { 859 goto mdtm_error; 860 } 861 gmt->tm_isdst = -1; 862 863 /* apply the GMT offset */ 864 tm.tm_sec += stamp - mktime(gmt); 865 tm.tm_isdst = gmt->tm_isdst; 866 867 ssb->sb.st_mtime = mktime(&tm); 868 } else { 869 /* error or unsupported command */ 870mdtm_error: 871 ssb->sb.st_mtime = -1; 872 } 873 874 ssb->sb.st_ino = 0; /* Unknown values */ 875 ssb->sb.st_dev = 0; 876 ssb->sb.st_uid = 0; 877 ssb->sb.st_gid = 0; 878 ssb->sb.st_atime = -1; 879 ssb->sb.st_ctime = -1; 880 881 ssb->sb.st_nlink = 1; 882 ssb->sb.st_rdev = -1; 883#ifdef HAVE_ST_BLKSIZE 884 ssb->sb.st_blksize = 4096; /* Guess since FTP won't expose this information */ 885#ifdef HAVE_ST_BLOCKS 886 ssb->sb.st_blocks = (int)((4095 + ssb->sb.st_size) / ssb->sb.st_blksize); /* emulate ceil */ 887#endif 888#endif 889 php_stream_close(stream); 890 php_url_free(resource); 891 return 0; 892 893stat_errexit: 894 if (resource) { 895 php_url_free(resource); 896 } 897 if (stream) { 898 php_stream_close(stream); 899 } 900 return -1; 901} 902/* }}} */ 903 904/* {{{ php_stream_ftp_unlink 905 */ 906static int php_stream_ftp_unlink(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC) 907{ 908 php_stream *stream = NULL; 909 php_url *resource = NULL; 910 int result; 911 char tmp_line[512]; 912 913 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC); 914 if (!stream) { 915 if (options & REPORT_ERRORS) { 916 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url); 917 } 918 goto unlink_errexit; 919 } 920 921 if (resource->path == NULL) { 922 if (options & REPORT_ERRORS) { 923 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url); 924 } 925 goto unlink_errexit; 926 } 927 928 /* Attempt to delete the file */ 929 php_stream_printf(stream TSRMLS_CC, "DELE %s\r\n", (resource->path != NULL ? resource->path : "/")); 930 931 result = GET_FTP_RESULT(stream); 932 if (result < 200 || result > 299) { 933 if (options & REPORT_ERRORS) { 934 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Deleting file: %s", tmp_line); 935 } 936 goto unlink_errexit; 937 } 938 939 php_url_free(resource); 940 php_stream_close(stream); 941 return 1; 942 943unlink_errexit: 944 if (resource) { 945 php_url_free(resource); 946 } 947 if (stream) { 948 php_stream_close(stream); 949 } 950 return 0; 951} 952/* }}} */ 953 954/* {{{ php_stream_ftp_rename 955 */ 956static int php_stream_ftp_rename(php_stream_wrapper *wrapper, char *url_from, char *url_to, int options, php_stream_context *context TSRMLS_DC) 957{ 958 php_stream *stream = NULL; 959 php_url *resource_from = NULL, *resource_to = NULL; 960 int result; 961 char tmp_line[512]; 962 963 resource_from = php_url_parse(url_from); 964 resource_to = php_url_parse(url_to); 965 /* Must be same scheme (ftp/ftp or ftps/ftps), same host, and same port 966 (or a 21/0 0/21 combination which is also "same") 967 Also require paths to/from */ 968 if (!resource_from || 969 !resource_to || 970 !resource_from->scheme || 971 !resource_to->scheme || 972 strcmp(resource_from->scheme, resource_to->scheme) || 973 !resource_from->host || 974 !resource_to->host || 975 strcmp(resource_from->host, resource_to->host) || 976 (resource_from->port != resource_to->port && 977 resource_from->port * resource_to->port != 0 && 978 resource_from->port + resource_to->port != 21) || 979 !resource_from->path || 980 !resource_to->path) { 981 goto rename_errexit; 982 } 983 984 stream = php_ftp_fopen_connect(wrapper, url_from, "r", 0, NULL, NULL, NULL, NULL, NULL, NULL TSRMLS_CC); 985 if (!stream) { 986 if (options & REPORT_ERRORS) { 987 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", resource_from->host); 988 } 989 goto rename_errexit; 990 } 991 992 /* Rename FROM */ 993 php_stream_printf(stream TSRMLS_CC, "RNFR %s\r\n", (resource_from->path != NULL ? resource_from->path : "/")); 994 995 result = GET_FTP_RESULT(stream); 996 if (result < 300 || result > 399) { 997 if (options & REPORT_ERRORS) { 998 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Renaming file: %s", tmp_line); 999 } 1000 goto rename_errexit; 1001 } 1002 1003 /* Rename TO */ 1004 php_stream_printf(stream TSRMLS_CC, "RNTO %s\r\n", (resource_to->path != NULL ? resource_to->path : "/")); 1005 1006 result = GET_FTP_RESULT(stream); 1007 if (result < 200 || result > 299) { 1008 if (options & REPORT_ERRORS) { 1009 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error Renaming file: %s", tmp_line); 1010 } 1011 goto rename_errexit; 1012 } 1013 1014 php_url_free(resource_from); 1015 php_url_free(resource_to); 1016 php_stream_close(stream); 1017 return 1; 1018 1019rename_errexit: 1020 if (resource_from) { 1021 php_url_free(resource_from); 1022 } 1023 if (resource_to) { 1024 php_url_free(resource_to); 1025 } 1026 if (stream) { 1027 php_stream_close(stream); 1028 } 1029 return 0; 1030} 1031/* }}} */ 1032 1033/* {{{ php_stream_ftp_mkdir 1034 */ 1035static int php_stream_ftp_mkdir(php_stream_wrapper *wrapper, char *url, int mode, int options, php_stream_context *context TSRMLS_DC) 1036{ 1037 php_stream *stream = NULL; 1038 php_url *resource = NULL; 1039 int result, recursive = options & PHP_STREAM_MKDIR_RECURSIVE; 1040 char tmp_line[512]; 1041 1042 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC); 1043 if (!stream) { 1044 if (options & REPORT_ERRORS) { 1045 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url); 1046 } 1047 goto mkdir_errexit; 1048 } 1049 1050 if (resource->path == NULL) { 1051 if (options & REPORT_ERRORS) { 1052 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url); 1053 } 1054 goto mkdir_errexit; 1055 } 1056 1057 if (!recursive) { 1058 php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", resource->path); 1059 result = GET_FTP_RESULT(stream); 1060 } else { 1061 /* we look for directory separator from the end of string, thus hopefuly reducing our work load */ 1062 char *p, *e, *buf; 1063 1064 buf = estrdup(resource->path); 1065 e = buf + strlen(buf); 1066 1067 /* find a top level directory we need to create */ 1068 while ((p = strrchr(buf, '/'))) { 1069 *p = '\0'; 1070 php_stream_printf(stream TSRMLS_CC, "CWD %s\r\n", buf); 1071 result = GET_FTP_RESULT(stream); 1072 if (result >= 200 && result <= 299) { 1073 *p = '/'; 1074 break; 1075 } 1076 } 1077 if (p == buf) { 1078 php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", resource->path); 1079 result = GET_FTP_RESULT(stream); 1080 } else { 1081 php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", buf); 1082 result = GET_FTP_RESULT(stream); 1083 if (result >= 200 && result <= 299) { 1084 if (!p) { 1085 p = buf; 1086 } 1087 /* create any needed directories if the creation of the 1st directory worked */ 1088 while (++p != e) { 1089 if (*p == '\0' && *(p + 1) != '\0') { 1090 *p = '/'; 1091 php_stream_printf(stream TSRMLS_CC, "MKD %s\r\n", buf); 1092 result = GET_FTP_RESULT(stream); 1093 if (result < 200 || result > 299) { 1094 if (options & REPORT_ERRORS) { 1095 php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tmp_line); 1096 } 1097 break; 1098 } 1099 } 1100 } 1101 } 1102 } 1103 efree(buf); 1104 } 1105 1106 php_url_free(resource); 1107 php_stream_close(stream); 1108 1109 if (result < 200 || result > 299) { 1110 /* Failure */ 1111 return 0; 1112 } 1113 1114 return 1; 1115 1116mkdir_errexit: 1117 if (resource) { 1118 php_url_free(resource); 1119 } 1120 if (stream) { 1121 php_stream_close(stream); 1122 } 1123 return 0; 1124} 1125/* }}} */ 1126 1127/* {{{ php_stream_ftp_rmdir 1128 */ 1129static int php_stream_ftp_rmdir(php_stream_wrapper *wrapper, char *url, int options, php_stream_context *context TSRMLS_DC) 1130{ 1131 php_stream *stream = NULL; 1132 php_url *resource = NULL; 1133 int result; 1134 char tmp_line[512]; 1135 1136 stream = php_ftp_fopen_connect(wrapper, url, "r", 0, NULL, NULL, NULL, &resource, NULL, NULL TSRMLS_CC); 1137 if (!stream) { 1138 if (options & REPORT_ERRORS) { 1139 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to connect to %s", url); 1140 } 1141 goto rmdir_errexit; 1142 } 1143 1144 if (resource->path == NULL) { 1145 if (options & REPORT_ERRORS) { 1146 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid path provided in %s", url); 1147 } 1148 goto rmdir_errexit; 1149 } 1150 1151 php_stream_printf(stream TSRMLS_CC, "RMD %s\r\n", resource->path); 1152 result = GET_FTP_RESULT(stream); 1153 1154 if (result < 200 || result > 299) { 1155 if (options & REPORT_ERRORS) { 1156 php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tmp_line); 1157 } 1158 goto rmdir_errexit; 1159 } 1160 1161 php_url_free(resource); 1162 php_stream_close(stream); 1163 1164 return 1; 1165 1166rmdir_errexit: 1167 if (resource) { 1168 php_url_free(resource); 1169 } 1170 if (stream) { 1171 php_stream_close(stream); 1172 } 1173 return 0; 1174} 1175/* }}} */ 1176 1177static php_stream_wrapper_ops ftp_stream_wops = { 1178 php_stream_url_wrap_ftp, 1179 php_stream_ftp_stream_close, /* stream_close */ 1180 php_stream_ftp_stream_stat, 1181 php_stream_ftp_url_stat, /* stat_url */ 1182 php_stream_ftp_opendir, /* opendir */ 1183 "ftp", 1184 php_stream_ftp_unlink, /* unlink */ 1185 php_stream_ftp_rename, /* rename */ 1186 php_stream_ftp_mkdir, /* mkdir */ 1187 php_stream_ftp_rmdir /* rmdir */ 1188}; 1189 1190PHPAPI php_stream_wrapper php_stream_ftp_wrapper = { 1191 &ftp_stream_wops, 1192 NULL, 1193 1 /* is_url */ 1194}; 1195 1196 1197/* 1198 * Local variables: 1199 * tab-width: 4 1200 * c-basic-offset: 4 1201 * End: 1202 * vim600: sw=4 ts=4 fdm=marker 1203 * vim<600: sw=4 ts=4 1204 */ 1205