mysqlnd_loaddata.c 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  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: Andrey Hristov <andrey@php.net> |
  14. | Ulf Wendel <uw@php.net> |
  15. | Georg Richter <georg@php.net> |
  16. +----------------------------------------------------------------------+
  17. */
  18. #include "php.h"
  19. #include "mysqlnd.h"
  20. #include "mysqlnd_wireprotocol.h"
  21. #include "mysqlnd_priv.h"
  22. #include "mysqlnd_debug.h"
  23. /* {{{ mysqlnd_local_infile_init */
  24. static
  25. int mysqlnd_local_infile_init(void ** ptr, const char * const filename)
  26. {
  27. MYSQLND_INFILE_INFO *info;
  28. php_stream_context *context = NULL;
  29. DBG_ENTER("mysqlnd_local_infile_init");
  30. info = ((MYSQLND_INFILE_INFO *)mnd_ecalloc(1, sizeof(MYSQLND_INFILE_INFO)));
  31. if (!info) {
  32. DBG_RETURN(1);
  33. }
  34. *ptr = info;
  35. /* check open_basedir */
  36. if (PG(open_basedir)) {
  37. if (php_check_open_basedir_ex(filename, 0) == -1) {
  38. strcpy(info->error_msg, "open_basedir restriction in effect. Unable to open file");
  39. info->error_no = CR_UNKNOWN_ERROR;
  40. DBG_RETURN(1);
  41. }
  42. }
  43. info->filename = filename;
  44. info->fd = php_stream_open_wrapper_ex((char *)filename, "r", 0, NULL, context);
  45. if (info->fd == NULL) {
  46. snprintf((char *)info->error_msg, sizeof(info->error_msg), "Can't find file '%-.64s'.", filename);
  47. info->error_no = MYSQLND_EE_FILENOTFOUND;
  48. DBG_RETURN(1);
  49. }
  50. DBG_RETURN(0);
  51. }
  52. /* }}} */
  53. /* {{{ mysqlnd_local_infile_read */
  54. static
  55. int mysqlnd_local_infile_read(void * ptr, zend_uchar * buf, unsigned int buf_len)
  56. {
  57. MYSQLND_INFILE_INFO *info = (MYSQLND_INFILE_INFO *)ptr;
  58. int count;
  59. DBG_ENTER("mysqlnd_local_infile_read");
  60. count = (int) php_stream_read(info->fd, (char *) buf, buf_len);
  61. if (count < 0) {
  62. strcpy(info->error_msg, "Error reading file");
  63. info->error_no = CR_UNKNOWN_ERROR;
  64. }
  65. DBG_RETURN(count);
  66. }
  67. /* }}} */
  68. /* {{{ mysqlnd_local_infile_error */
  69. static
  70. int mysqlnd_local_infile_error(void * ptr, char *error_buf, unsigned int error_buf_len)
  71. {
  72. MYSQLND_INFILE_INFO *info = (MYSQLND_INFILE_INFO *)ptr;
  73. DBG_ENTER("mysqlnd_local_infile_error");
  74. if (info) {
  75. strlcpy(error_buf, info->error_msg, error_buf_len);
  76. DBG_INF_FMT("have info, %d", info->error_no);
  77. DBG_RETURN(info->error_no);
  78. }
  79. strlcpy(error_buf, "Unknown error", error_buf_len);
  80. DBG_INF_FMT("no info, %d", CR_UNKNOWN_ERROR);
  81. DBG_RETURN(CR_UNKNOWN_ERROR);
  82. }
  83. /* }}} */
  84. /* {{{ mysqlnd_local_infile_end */
  85. static
  86. void mysqlnd_local_infile_end(void * ptr)
  87. {
  88. MYSQLND_INFILE_INFO *info = (MYSQLND_INFILE_INFO *)ptr;
  89. if (info) {
  90. /* php_stream_close segfaults on NULL */
  91. if (info->fd) {
  92. php_stream_close(info->fd);
  93. info->fd = NULL;
  94. }
  95. mnd_efree(info);
  96. }
  97. }
  98. /* }}} */
  99. /* {{{ mysqlnd_local_infile_default */
  100. PHPAPI void
  101. mysqlnd_local_infile_default(MYSQLND_CONN_DATA * conn)
  102. {
  103. conn->infile.local_infile_init = mysqlnd_local_infile_init;
  104. conn->infile.local_infile_read = mysqlnd_local_infile_read;
  105. conn->infile.local_infile_error = mysqlnd_local_infile_error;
  106. conn->infile.local_infile_end = mysqlnd_local_infile_end;
  107. }
  108. /* }}} */
  109. static const char *lost_conn = "Lost connection to MySQL server during LOAD DATA of a local file";
  110. /* {{{ mysqlnd_handle_local_infile */
  111. enum_func_status
  112. mysqlnd_handle_local_infile(MYSQLND_CONN_DATA * conn, const char * const filename, bool * is_warning)
  113. {
  114. zend_uchar *buf = NULL;
  115. zend_uchar empty_packet[MYSQLND_HEADER_SIZE];
  116. enum_func_status result = FAIL;
  117. unsigned int buflen = 4096;
  118. void *info = NULL;
  119. int bufsize;
  120. size_t ret;
  121. MYSQLND_INFILE infile;
  122. MYSQLND_PFC * net = conn->protocol_frame_codec;
  123. MYSQLND_VIO * vio = conn->vio;
  124. bool is_local_infile_enabled = (conn->options->flags & CLIENT_LOCAL_FILES) == CLIENT_LOCAL_FILES;
  125. const char* local_infile_directory = conn->options->local_infile_directory;
  126. bool is_local_infile_dir_set = local_infile_directory != NULL;
  127. bool prerequisities_ok = TRUE;
  128. DBG_ENTER("mysqlnd_handle_local_infile");
  129. /*
  130. if local_infile is disabled, and local_infile_dir is not set, then operation is forbidden
  131. */
  132. if (!is_local_infile_enabled && !is_local_infile_dir_set) {
  133. SET_CLIENT_ERROR(conn->error_info, CR_LOAD_DATA_LOCAL_INFILE_REJECTED, UNKNOWN_SQLSTATE,
  134. "LOAD DATA LOCAL INFILE is forbidden, check related settings like "
  135. "mysqli.allow_local_infile|mysqli.local_infile_directory or "
  136. "PDO::MYSQL_ATTR_LOCAL_INFILE|PDO::MYSQL_ATTR_LOCAL_INFILE_DIRECTORY");
  137. prerequisities_ok = FALSE;
  138. }
  139. /*
  140. if local_infile_dir is set, then check whether it actually exists, and is accessible
  141. */
  142. if (is_local_infile_dir_set) {
  143. php_stream *stream = php_stream_opendir(local_infile_directory, REPORT_ERRORS, NULL);
  144. if (stream) {
  145. php_stream_closedir(stream);
  146. } else {
  147. SET_CLIENT_ERROR(conn->error_info, CR_LOAD_DATA_LOCAL_INFILE_REJECTED, UNKNOWN_SQLSTATE, "cannot open local_infile_directory");
  148. prerequisities_ok = FALSE;
  149. }
  150. }
  151. /*
  152. if local_infile is disabled and local_infile_dir is set, then we have to check whether
  153. filename is located inside its subtree
  154. but only in such a case, because when local_infile is enabled, then local_infile_dir is ignored
  155. */
  156. if (prerequisities_ok && !is_local_infile_enabled && is_local_infile_dir_set) {
  157. if (php_check_specific_open_basedir(local_infile_directory, filename) == -1) {
  158. SET_CLIENT_ERROR(conn->error_info, CR_LOAD_DATA_LOCAL_INFILE_REJECTED, UNKNOWN_SQLSTATE,
  159. "LOAD DATA LOCAL INFILE DIRECTORY restriction in effect. Unable to open file");
  160. prerequisities_ok = FALSE;
  161. }
  162. }
  163. if (!prerequisities_ok) {
  164. /* write empty packet to server */
  165. ret = net->data->m.send(net, vio, empty_packet, 0, conn->stats, conn->error_info);
  166. *is_warning = TRUE;
  167. goto infile_error;
  168. }
  169. infile = conn->infile;
  170. /* allocate buffer for reading data */
  171. buf = (zend_uchar *) mnd_ecalloc(1, buflen);
  172. *is_warning = FALSE;
  173. /* init handler: allocate read buffer and open file */
  174. if (infile.local_infile_init(&info, (char *)filename)) {
  175. char tmp_buf[sizeof(conn->error_info->error)];
  176. int tmp_error_no;
  177. *is_warning = TRUE;
  178. /* error occurred */
  179. tmp_error_no = infile.local_infile_error(info, tmp_buf, sizeof(tmp_buf));
  180. SET_CLIENT_ERROR(conn->error_info, tmp_error_no, UNKNOWN_SQLSTATE, tmp_buf);
  181. /* write empty packet to server */
  182. ret = net->data->m.send(net, vio, empty_packet, 0, conn->stats, conn->error_info);
  183. goto infile_error;
  184. }
  185. /* read data */
  186. while ((bufsize = infile.local_infile_read (info, buf + MYSQLND_HEADER_SIZE, buflen - MYSQLND_HEADER_SIZE)) > 0) {
  187. if ((ret = net->data->m.send(net, vio, buf, bufsize, conn->stats, conn->error_info)) == 0) {
  188. DBG_ERR_FMT("Error during read : %d %s %s", CR_SERVER_LOST, UNKNOWN_SQLSTATE, lost_conn);
  189. SET_CLIENT_ERROR(conn->error_info, CR_SERVER_LOST, UNKNOWN_SQLSTATE, lost_conn);
  190. goto infile_error;
  191. }
  192. }
  193. /* send empty packet for eof */
  194. if ((ret = net->data->m.send(net, vio, empty_packet, 0, conn->stats, conn->error_info)) == 0) {
  195. SET_CLIENT_ERROR(conn->error_info, CR_SERVER_LOST, UNKNOWN_SQLSTATE, lost_conn);
  196. goto infile_error;
  197. }
  198. /* error during read occurred */
  199. if (bufsize < 0) {
  200. char tmp_buf[sizeof(conn->error_info->error)];
  201. int tmp_error_no;
  202. *is_warning = TRUE;
  203. DBG_ERR_FMT("Bufsize < 0, warning, %d %s %s", CR_SERVER_LOST, UNKNOWN_SQLSTATE, lost_conn);
  204. tmp_error_no = infile.local_infile_error(info, tmp_buf, sizeof(tmp_buf));
  205. SET_CLIENT_ERROR(conn->error_info, tmp_error_no, UNKNOWN_SQLSTATE, tmp_buf);
  206. goto infile_error;
  207. }
  208. result = PASS;
  209. infile_error:
  210. /* get response from server and update upsert values */
  211. if (FAIL == conn->payload_decoder_factory->m.send_command_handle_response(
  212. conn->payload_decoder_factory,
  213. PROT_OK_PACKET, FALSE, COM_QUERY, FALSE,
  214. conn->error_info,
  215. conn->upsert_status,
  216. &conn->last_message)) {
  217. result = FAIL;
  218. }
  219. (*conn->infile.local_infile_end)(info);
  220. if (buf) {
  221. mnd_efree(buf);
  222. }
  223. DBG_INF_FMT("%s", result == PASS? "PASS":"FAIL");
  224. DBG_RETURN(result);
  225. }
  226. /* }}} */