ftp_fopen_wrapper.c 32 KB

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