PHP  
 PHP: Test and Code Coverage Analysis
downloads | QA | documentation | faq | getting help | mailing lists | reporting bugs | php.net sites | links | my php.net 
 

LCOV - code coverage report
Current view: top level - ext/mysqlnd - mysqlnd_auth.c (source / functions) Hit Total Coverage
Test: PHP Code Coverage Lines: 197 358 55.0 %
Date: 2016-02-09 Functions: 9 13 69.2 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   +----------------------------------------------------------------------+
       3             :   | PHP Version 7                                                        |
       4             :   +----------------------------------------------------------------------+
       5             :   | Copyright (c) 2006-2016 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: Andrey Hristov <andrey@mysql.com>                           |
      16             :   |          Ulf Wendel <uwendel@mysql.com>                              |
      17             :   |          Georg Richter <georg@mysql.com>                             |
      18             :   +----------------------------------------------------------------------+
      19             : */
      20             : 
      21             : #include "php.h"
      22             : #include "mysqlnd.h"
      23             : #include "mysqlnd_structs.h"
      24             : #include "mysqlnd_auth.h"
      25             : #include "mysqlnd_wireprotocol.h"
      26             : #include "mysqlnd_connection.h"
      27             : #include "mysqlnd_priv.h"
      28             : #include "mysqlnd_charset.h"
      29             : #include "mysqlnd_debug.h"
      30             : 
      31             : static const char * const mysqlnd_old_passwd  = "mysqlnd cannot connect to MySQL 4.1+ using the old insecure authentication. "
      32             : "Please use an administration tool to reset your password with the command SET PASSWORD = PASSWORD('your_existing_password'). This will "
      33             : "store a new, and more secure, hash value in mysql.user. If this user is used in other scripts executed by PHP 5.2 or earlier you might need to remove the old-passwords "
      34             : "flag from your my.cnf file";
      35             : 
      36             : 
      37             : /* {{{ mysqlnd_run_authentication */
      38             : enum_func_status
      39        1762 : mysqlnd_run_authentication(
      40             :                         MYSQLND_CONN_DATA * conn,
      41             :                         const char * const user,
      42             :                         const char * const passwd,
      43             :                         const size_t passwd_len,
      44             :                         const char * const db,
      45             :                         const size_t db_len,
      46             :                         const MYSQLND_STRING auth_plugin_data,
      47             :                         const char * const auth_protocol,
      48             :                         unsigned int charset_no,
      49             :                         const MYSQLND_SESSION_OPTIONS * const session_options,
      50             :                         zend_ulong mysql_flags,
      51             :                         zend_bool silent,
      52             :                         zend_bool is_change_user
      53             :                         )
      54             : {
      55        1762 :         enum_func_status ret = FAIL;
      56        1762 :         zend_bool first_call = TRUE;
      57             : 
      58        1762 :         char * switch_to_auth_protocol = NULL;
      59        1762 :         size_t switch_to_auth_protocol_len = 0;
      60        1762 :         char * requested_protocol = NULL;
      61             :         zend_uchar * plugin_data;
      62             :         size_t plugin_data_len;
      63             : 
      64        1762 :         DBG_ENTER("mysqlnd_run_authentication");
      65             : 
      66        1762 :         plugin_data_len = auth_plugin_data.l;
      67        1762 :         plugin_data = mnd_emalloc(plugin_data_len + 1);
      68        1762 :         if (!plugin_data) {
      69           0 :                 goto end;
      70             :         }
      71        1762 :         memcpy(plugin_data, auth_plugin_data.s, plugin_data_len);
      72        1762 :         plugin_data[plugin_data_len] = '\0';
      73             : 
      74        1762 :         requested_protocol = mnd_pestrdup(auth_protocol? auth_protocol : MYSQLND_DEFAULT_AUTH_PROTOCOL, FALSE);
      75        1762 :         if (!requested_protocol) {
      76           0 :                 goto end;
      77             :         }
      78             : 
      79             :         do {
      80        1762 :                 struct st_mysqlnd_authentication_plugin * auth_plugin = conn->m->fetch_auth_plugin_by_name(requested_protocol);
      81             : 
      82        1762 :                 if (!auth_plugin) {
      83           0 :                         php_error_docref(NULL, E_WARNING, "The server requested authentication method unknown to the client [%s]", requested_protocol);
      84           0 :                         SET_CLIENT_ERROR(conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "The server requested authentication method unknown to the client");
      85           0 :                         goto end;
      86             :                 }
      87        1762 :                 DBG_INF("plugin found");
      88             : 
      89             :                 {
      90        1762 :                         zend_uchar * switch_to_auth_protocol_data = NULL;
      91        1762 :                         size_t switch_to_auth_protocol_data_len = 0;
      92        1762 :                         zend_uchar * scrambled_data = NULL;
      93        1762 :                         size_t scrambled_data_len = 0;
      94             : 
      95        1762 :                         switch_to_auth_protocol = NULL;
      96        1762 :                         switch_to_auth_protocol_len = 0;
      97             : 
      98        1762 :                         if (conn->authentication_plugin_data.s) {
      99          76 :                                 mnd_pefree(conn->authentication_plugin_data.s, conn->persistent);
     100          76 :                                 conn->authentication_plugin_data.s = NULL;
     101             :                         }
     102        1762 :                         conn->authentication_plugin_data.l = plugin_data_len;
     103        1762 :                         conn->authentication_plugin_data.s = mnd_pemalloc(conn->authentication_plugin_data.l, conn->persistent);
     104        1762 :                         if (!conn->authentication_plugin_data.s) {
     105           0 :                                 SET_OOM_ERROR(conn->error_info);
     106           0 :                                 goto end;
     107             :                         }
     108        1762 :                         memcpy(conn->authentication_plugin_data.s, plugin_data, plugin_data_len);
     109             : 
     110        1762 :                         DBG_INF_FMT("salt(%d)=[%.*s]", plugin_data_len, plugin_data_len, plugin_data);
     111             :                         /* The data should be allocated with malloc() */
     112        1762 :                         scrambled_data =
     113        3524 :                                 auth_plugin->methods.get_auth_data(NULL, &scrambled_data_len, conn, user, passwd, passwd_len,
     114             :                                                                                                    plugin_data, plugin_data_len, session_options,
     115        1762 :                                                                                                    conn->protocol_frame_codec->data, mysql_flags);
     116        1762 :                         if (conn->error_info->error_no) {
     117           0 :                                 goto end;
     118             :                         }
     119        1762 :                         if (FALSE == is_change_user) {
     120        1686 :                                 ret = mysqlnd_auth_handshake(conn, user, passwd, passwd_len, db, db_len, session_options, mysql_flags,
     121             :                                                                                         charset_no,
     122             :                                                                                         first_call,
     123             :                                                                                         requested_protocol,
     124             :                                                                                         scrambled_data, scrambled_data_len,
     125             :                                                                                         &switch_to_auth_protocol, &switch_to_auth_protocol_len,
     126             :                                                                                         &switch_to_auth_protocol_data, &switch_to_auth_protocol_data_len
     127             :                                                                                         );
     128             :                         } else {
     129          76 :                                 ret = mysqlnd_auth_change_user(conn, user, strlen(user), passwd, passwd_len, db, db_len, silent,
     130             :                                                                                            first_call,
     131             :                                                                                            requested_protocol,
     132             :                                                                                            scrambled_data, scrambled_data_len,
     133             :                                                                                            &switch_to_auth_protocol, &switch_to_auth_protocol_len,
     134             :                                                                                            &switch_to_auth_protocol_data, &switch_to_auth_protocol_data_len
     135             :                                                                                           );
     136             :                         }
     137        1762 :                         first_call = FALSE;
     138        1762 :                         free(scrambled_data);
     139             : 
     140        1762 :                         DBG_INF_FMT("switch_to_auth_protocol=%s", switch_to_auth_protocol? switch_to_auth_protocol:"n/a");
     141        1762 :                         if (requested_protocol && switch_to_auth_protocol) {
     142           0 :                                 mnd_efree(requested_protocol);
     143           0 :                                 requested_protocol = switch_to_auth_protocol;
     144             :                         }
     145             : 
     146        1762 :                         if (plugin_data) {
     147        1762 :                                 mnd_efree(plugin_data);
     148             :                         }
     149        1762 :                         plugin_data_len = switch_to_auth_protocol_data_len;
     150        1762 :                         plugin_data = switch_to_auth_protocol_data;
     151             :                 }
     152        1762 :                 DBG_INF_FMT("conn->error_info->error_no = %d", conn->error_info->error_no);
     153        1762 :         } while (ret == FAIL && conn->error_info->error_no == 0 && switch_to_auth_protocol != NULL);
     154             : 
     155        1762 :         if (ret == PASS) {
     156        1723 :                 DBG_INF_FMT("saving requested_protocol=%s", requested_protocol);
     157        1723 :                 conn->m->set_client_option(conn, MYSQLND_OPT_AUTH_PROTOCOL, requested_protocol);
     158             :         }
     159             : end:
     160        1762 :         if (plugin_data) {
     161           0 :                 mnd_efree(plugin_data);
     162             :         }
     163        1762 :         if (requested_protocol) {
     164        1762 :                 mnd_efree(requested_protocol);
     165             :         }
     166             : 
     167        1762 :         DBG_RETURN(ret);
     168             : }
     169             : /* }}} */
     170             : 
     171             : 
     172             : /* {{{ mysqlnd_switch_to_ssl_if_needed */
     173             : static enum_func_status
     174        1690 : mysqlnd_switch_to_ssl_if_needed(MYSQLND_CONN_DATA * conn,
     175             :                                                                 unsigned int charset_no,
     176             :                                                                 size_t server_capabilities,
     177             :                                                                 const MYSQLND_SESSION_OPTIONS * const session_options,
     178             :                                                                 zend_ulong mysql_flags)
     179             : {
     180        1690 :         enum_func_status ret = FAIL;
     181             :         const MYSQLND_CHARSET * charset;
     182        1690 :         DBG_ENTER("mysqlnd_switch_to_ssl_if_needed");
     183             : 
     184        1690 :         if (session_options->charset_name && (charset = mysqlnd_find_charset_name(session_options->charset_name))) {
     185           4 :                 charset_no      = charset->nr;
     186             :         }
     187             : 
     188             :         {
     189        1690 :                 size_t client_capabilities = mysql_flags;
     190        1690 :                 struct st_mysqlnd_protocol_command * command = conn->command_factory(COM_ENABLE_SSL, conn, client_capabilities, server_capabilities, charset_no);
     191        1690 :                 if (command) {
     192        1690 :                         ret = command->run(command);
     193        1690 :                         command->free_command(command);
     194             :                 }
     195             :         }
     196        1690 :         DBG_RETURN(ret);
     197             : }
     198             : /* }}} */
     199             : 
     200             : 
     201             : /* {{{ mysqlnd_connect_run_authentication */
     202             : enum_func_status
     203        1690 : mysqlnd_connect_run_authentication(
     204             :                         MYSQLND_CONN_DATA * conn,
     205             :                         const char * const user,
     206             :                         const char * const passwd,
     207             :                         const char * const db,
     208             :                         size_t db_len,
     209             :                         size_t passwd_len,
     210             :                         MYSQLND_STRING authentication_plugin_data,
     211             :                         const char * const authentication_protocol,
     212             :                         const unsigned int charset_no,
     213             :                         size_t server_capabilities,
     214             :                         const MYSQLND_SESSION_OPTIONS * const session_options,
     215             :                         zend_ulong mysql_flags
     216             :                         )
     217             : {
     218        1690 :         enum_func_status ret = FAIL;
     219        1690 :         DBG_ENTER("mysqlnd_connect_run_authentication");
     220             : 
     221        1690 :         ret = mysqlnd_switch_to_ssl_if_needed(conn, charset_no, server_capabilities, session_options, mysql_flags);
     222        1690 :         if (PASS == ret) {
     223        1686 :                 ret = mysqlnd_run_authentication(conn, user, passwd, passwd_len, db, db_len,
     224             :                                                                                  authentication_plugin_data, authentication_protocol,
     225             :                                                                                  charset_no, session_options, mysql_flags, FALSE /*silent*/, FALSE/*is_change*/);
     226             :         }
     227        1690 :         DBG_RETURN(ret);
     228             : }
     229             : /* }}} */
     230             : 
     231             : 
     232             : /* {{{ mysqlnd_auth_handshake */
     233             : enum_func_status
     234        1686 : mysqlnd_auth_handshake(MYSQLND_CONN_DATA * conn,
     235             :                                                           const char * const user,
     236             :                                                           const char * const passwd,
     237             :                                                           const size_t passwd_len,
     238             :                                                           const char * const db,
     239             :                                                           const size_t db_len,
     240             :                                                           const MYSQLND_SESSION_OPTIONS * const session_options,
     241             :                                                           zend_ulong mysql_flags,
     242             :                                                           unsigned int server_charset_no,
     243             :                                                           zend_bool use_full_blown_auth_packet,
     244             :                                                           const char * const auth_protocol,
     245             :                                                           const zend_uchar * const auth_plugin_data,
     246             :                                                           const size_t auth_plugin_data_len,
     247             :                                                           char ** switch_to_auth_protocol,
     248             :                                                           size_t * switch_to_auth_protocol_len,
     249             :                                                           zend_uchar ** switch_to_auth_protocol_data,
     250             :                                                           size_t * switch_to_auth_protocol_data_len
     251             :                                                          )
     252             : {
     253        1686 :         enum_func_status ret = FAIL;
     254        1686 :         const MYSQLND_CHARSET * charset = NULL;
     255        1686 :         MYSQLND_PACKET_CHANGE_AUTH_RESPONSE * change_auth_resp_packet = NULL;
     256        1686 :         MYSQLND_PACKET_AUTH_RESPONSE * auth_resp_packet = NULL;
     257        1686 :         MYSQLND_PACKET_AUTH * auth_packet = NULL;
     258             : 
     259        1686 :         DBG_ENTER("mysqlnd_auth_handshake");
     260             : 
     261        1686 :         auth_resp_packet = conn->payload_decoder_factory->m.get_auth_response_packet(conn->payload_decoder_factory, FALSE);
     262             : 
     263        1686 :         if (!auth_resp_packet) {
     264           0 :                 SET_OOM_ERROR(conn->error_info);
     265           0 :                 goto end;
     266             :         }
     267             : 
     268        1686 :         if (use_full_blown_auth_packet != TRUE) {
     269           0 :                 change_auth_resp_packet = conn->payload_decoder_factory->m.get_change_auth_response_packet(conn->payload_decoder_factory, FALSE);
     270           0 :                 if (!change_auth_resp_packet) {
     271           0 :                         SET_OOM_ERROR(conn->error_info);
     272           0 :                         goto end;
     273             :                 }
     274             : 
     275           0 :                 change_auth_resp_packet->auth_data = auth_plugin_data;
     276           0 :                 change_auth_resp_packet->auth_data_len = auth_plugin_data_len;
     277             : 
     278           0 :                 if (!PACKET_WRITE(change_auth_resp_packet)) {
     279           0 :                         SET_CONNECTION_STATE(&conn->state, CONN_QUIT_SENT);
     280           0 :                         SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
     281           0 :                         goto end;
     282             :                 }
     283             :         } else {
     284        1686 :                 auth_packet = conn->payload_decoder_factory->m.get_auth_packet(conn->payload_decoder_factory, FALSE);
     285             : 
     286        1686 :                 auth_packet->client_flags = mysql_flags;
     287        1686 :                 auth_packet->max_packet_size = session_options->max_allowed_packet;
     288        1690 :                 if (session_options->charset_name && (charset = mysqlnd_find_charset_name(session_options->charset_name))) {
     289           4 :                         auth_packet->charset_no      = charset->nr;
     290             :                 } else {
     291        1682 :                         auth_packet->charset_no      = server_charset_no;
     292             :                 }
     293             : 
     294        1686 :                 auth_packet->send_auth_data = TRUE;
     295        1686 :                 auth_packet->user            = user;
     296        1686 :                 auth_packet->db                      = db;
     297        1686 :                 auth_packet->db_len          = db_len;
     298             : 
     299        1686 :                 auth_packet->auth_data = auth_plugin_data;
     300        1686 :                 auth_packet->auth_data_len = auth_plugin_data_len;
     301        1686 :                 auth_packet->auth_plugin_name = auth_protocol;
     302             : 
     303        1686 :                 if (conn->server_capabilities & CLIENT_CONNECT_ATTRS) {
     304           0 :                         auth_packet->connect_attr = conn->options->connect_attr;
     305             :                 }
     306             : 
     307        1686 :                 if (!PACKET_WRITE(auth_packet)) {
     308           0 :                         goto end;
     309             :                 }
     310             :         }
     311        1686 :         if (use_full_blown_auth_packet == TRUE) {
     312        1686 :                 conn->charset = mysqlnd_find_charset_nr(auth_packet->charset_no);
     313             :         }
     314             : 
     315        1686 :         if (FAIL == PACKET_READ(auth_resp_packet) || auth_resp_packet->response_code >= 0xFE) {
     316          24 :                 if (auth_resp_packet->response_code == 0xFE) {
     317             :                         /* old authentication with new server  !*/
     318           0 :                         if (!auth_resp_packet->new_auth_protocol) {
     319           0 :                                 DBG_ERR(mysqlnd_old_passwd);
     320           0 :                                 SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, mysqlnd_old_passwd);
     321             :                         } else {
     322           0 :                                 *switch_to_auth_protocol = mnd_pestrndup(auth_resp_packet->new_auth_protocol, auth_resp_packet->new_auth_protocol_len, FALSE);
     323           0 :                                 *switch_to_auth_protocol_len = auth_resp_packet->new_auth_protocol_len;
     324           0 :                                 if (auth_resp_packet->new_auth_protocol_data) {
     325           0 :                                         *switch_to_auth_protocol_data_len = auth_resp_packet->new_auth_protocol_data_len;
     326           0 :                                         *switch_to_auth_protocol_data = mnd_emalloc(*switch_to_auth_protocol_data_len);
     327           0 :                                         memcpy(*switch_to_auth_protocol_data, auth_resp_packet->new_auth_protocol_data, *switch_to_auth_protocol_data_len);
     328             :                                 } else {
     329           0 :                                         *switch_to_auth_protocol_data = NULL;
     330           0 :                                         *switch_to_auth_protocol_data_len = 0;
     331             :                                 }
     332             :                         }
     333          24 :                 } else if (auth_resp_packet->response_code == 0xFF) {
     334          24 :                         if (auth_resp_packet->sqlstate[0]) {
     335          24 :                                 strlcpy(conn->error_info->sqlstate, auth_resp_packet->sqlstate, sizeof(conn->error_info->sqlstate));
     336          24 :                                 DBG_ERR_FMT("ERROR:%u [SQLSTATE:%s] %s", auth_resp_packet->error_no, auth_resp_packet->sqlstate, auth_resp_packet->error);
     337             :                         }
     338          24 :                         SET_CLIENT_ERROR(conn->error_info, auth_resp_packet->error_no, UNKNOWN_SQLSTATE, auth_resp_packet->error);
     339             :                 }
     340          24 :                 goto end;
     341             :         }
     342             : 
     343        1662 :         SET_NEW_MESSAGE(conn->last_message.s, conn->last_message.l, auth_resp_packet->message, auth_resp_packet->message_len, conn->persistent);
     344        1662 :         ret = PASS;
     345             : end:
     346        1686 :         PACKET_FREE(change_auth_resp_packet);
     347        1686 :         PACKET_FREE(auth_packet);
     348        1686 :         PACKET_FREE(auth_resp_packet);
     349        1686 :         DBG_RETURN(ret);
     350             : }
     351             : /* }}} */
     352             : 
     353             : 
     354             : /* {{{ mysqlnd_auth_change_user */
     355             : enum_func_status
     356          76 : mysqlnd_auth_change_user(MYSQLND_CONN_DATA * const conn,
     357             :                                                                 const char * const user,
     358             :                                                                 const size_t user_len,
     359             :                                                                 const char * const passwd,
     360             :                                                                 const size_t passwd_len,
     361             :                                                                 const char * const db,
     362             :                                                                 const size_t db_len,
     363             :                                                                 const zend_bool silent,
     364             :                                                                 zend_bool use_full_blown_auth_packet,
     365             :                                                                 const char * const auth_protocol,
     366             :                                                                 zend_uchar * auth_plugin_data,
     367             :                                                                 size_t auth_plugin_data_len,
     368             :                                                                 char ** switch_to_auth_protocol,
     369             :                                                                 size_t * switch_to_auth_protocol_len,
     370             :                                                                 zend_uchar ** switch_to_auth_protocol_data,
     371             :                                                                 size_t * switch_to_auth_protocol_data_len
     372             :                                                                 )
     373             : {
     374          76 :         enum_func_status ret = FAIL;
     375          76 :         const MYSQLND_CHARSET * old_cs = conn->charset;
     376          76 :         MYSQLND_PACKET_CHANGE_AUTH_RESPONSE * change_auth_resp_packet = NULL;
     377          76 :         MYSQLND_PACKET_CHG_USER_RESPONSE * chg_user_resp = NULL;
     378          76 :         MYSQLND_PACKET_AUTH * auth_packet = NULL;
     379             : 
     380          76 :         DBG_ENTER("mysqlnd_auth_change_user");
     381             : 
     382          76 :         chg_user_resp = conn->payload_decoder_factory->m.get_change_user_response_packet(conn->payload_decoder_factory, FALSE);
     383             : 
     384          76 :         if (!chg_user_resp) {
     385           0 :                 SET_OOM_ERROR(conn->error_info);
     386           0 :                 goto end;
     387             :         }
     388             : 
     389          76 :         if (use_full_blown_auth_packet != TRUE) {
     390           0 :                 change_auth_resp_packet = conn->payload_decoder_factory->m.get_change_auth_response_packet(conn->payload_decoder_factory, FALSE);
     391           0 :                 if (!change_auth_resp_packet) {
     392           0 :                         SET_OOM_ERROR(conn->error_info);
     393           0 :                         goto end;
     394             :                 }
     395             : 
     396           0 :                 change_auth_resp_packet->auth_data = auth_plugin_data;
     397           0 :                 change_auth_resp_packet->auth_data_len = auth_plugin_data_len;
     398             : 
     399           0 :                 if (!PACKET_WRITE(change_auth_resp_packet)) {
     400           0 :                         SET_CONNECTION_STATE(&conn->state, CONN_QUIT_SENT);
     401           0 :                         SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
     402           0 :                         goto end;
     403             :                 }
     404             :         } else {
     405          76 :                 auth_packet = conn->payload_decoder_factory->m.get_auth_packet(conn->payload_decoder_factory, FALSE);
     406             : 
     407          76 :                 if (!auth_packet) {
     408           0 :                         SET_OOM_ERROR(conn->error_info);
     409           0 :                         goto end;
     410             :                 }
     411             : 
     412          76 :                 auth_packet->is_change_user_packet = TRUE;
     413          76 :                 auth_packet->user            = user;
     414          76 :                 auth_packet->db                      = db;
     415          76 :                 auth_packet->db_len          = db_len;
     416          76 :                 auth_packet->silent          = silent;
     417             : 
     418          76 :                 auth_packet->auth_data = auth_plugin_data;
     419          76 :                 auth_packet->auth_data_len = auth_plugin_data_len;
     420          76 :                 auth_packet->auth_plugin_name = auth_protocol;
     421             : 
     422             : 
     423          76 :                 if (conn->m->get_server_version(conn) >= 50123) {
     424          76 :                         auth_packet->charset_no      = conn->charset->nr;
     425             :                 }
     426             : 
     427          76 :                 if (!PACKET_WRITE(auth_packet)) {
     428           1 :                         SET_CONNECTION_STATE(&conn->state, CONN_QUIT_SENT);
     429           1 :                         SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
     430           1 :                         goto end;
     431             :                 }
     432             :         }
     433             : 
     434          75 :         ret = PACKET_READ(chg_user_resp);
     435          75 :         COPY_CLIENT_ERROR(conn->error_info, chg_user_resp->error_info);
     436             : 
     437          75 :         if (0xFE == chg_user_resp->response_code) {
     438           0 :                 ret = FAIL;
     439           0 :                 if (!chg_user_resp->new_auth_protocol) {
     440           0 :                         DBG_ERR(mysqlnd_old_passwd);
     441           0 :                         SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, mysqlnd_old_passwd);
     442             :                 } else {
     443           0 :                         *switch_to_auth_protocol = mnd_pestrndup(chg_user_resp->new_auth_protocol, chg_user_resp->new_auth_protocol_len, FALSE);
     444           0 :                         *switch_to_auth_protocol_len = chg_user_resp->new_auth_protocol_len;
     445           0 :                         if (chg_user_resp->new_auth_protocol_data) {
     446           0 :                                 *switch_to_auth_protocol_data_len = chg_user_resp->new_auth_protocol_data_len;
     447           0 :                                 *switch_to_auth_protocol_data = mnd_emalloc(*switch_to_auth_protocol_data_len);
     448           0 :                                 memcpy(*switch_to_auth_protocol_data, chg_user_resp->new_auth_protocol_data, *switch_to_auth_protocol_data_len);
     449             :                         } else {
     450           0 :                                 *switch_to_auth_protocol_data = NULL;
     451           0 :                                 *switch_to_auth_protocol_data_len = 0;
     452             :                         }
     453             :                 }
     454             :         }
     455             : 
     456          75 :         if (conn->error_info->error_no) {
     457          14 :                 ret = FAIL;
     458             :                 /*
     459             :                   COM_CHANGE_USER is broken in 5.1. At least in 5.1.15 and 5.1.14, 5.1.11 is immune.
     460             :                   bug#25371 mysql_change_user() triggers "packets out of sync"
     461             :                   When it gets fixed, there should be one more check here
     462             :                 */
     463          14 :                 if (conn->m->get_server_version(conn) > 50113L &&conn->m->get_server_version(conn) < 50118L) {
     464           0 :                         MYSQLND_PACKET_OK * redundant_error_packet = conn->payload_decoder_factory->m.get_ok_packet(conn->payload_decoder_factory, FALSE);
     465           0 :                         if (redundant_error_packet) {
     466           0 :                                 PACKET_READ(redundant_error_packet);
     467           0 :                                 PACKET_FREE(redundant_error_packet);
     468           0 :                                 DBG_INF_FMT("Server is %u, buggy, sends two ERR messages", conn->m->get_server_version(conn));
     469             :                         } else {
     470           0 :                                 SET_OOM_ERROR(conn->error_info);
     471             :                         }
     472             :                 }
     473             :         }
     474          75 :         if (ret == PASS) {
     475          61 :                 char * tmp = NULL;
     476             :                 /* if we get conn->username as parameter and then we first free it, then estrndup it, we will crash */
     477          61 :                 tmp = mnd_pestrndup(user, user_len, conn->persistent);
     478          61 :                 if (conn->username.s) {
     479          61 :                         mnd_pefree(conn->username.s, conn->persistent);
     480             :                 }
     481          61 :                 conn->username.s = tmp;
     482             : 
     483          61 :                 tmp = mnd_pestrdup(passwd, conn->persistent);
     484          61 :                 if (conn->password.s) {
     485          61 :                         mnd_pefree(conn->password.s, conn->persistent);
     486             :                 }
     487          61 :                 conn->password.s = tmp;
     488             : 
     489          61 :                 if (conn->last_message.s) {
     490           0 :                         mnd_pefree(conn->last_message.s, conn->persistent);
     491           0 :                         conn->last_message.s = NULL;
     492             :                 }
     493          61 :                 UPSERT_STATUS_RESET(conn->upsert_status);
     494             :                 /* set charset for old servers */
     495          61 :                 if (conn->m->get_server_version(conn) < 50123) {
     496           0 :                         ret = conn->m->set_charset(conn, old_cs->name);
     497             :                 }
     498          14 :         } else if (ret == FAIL && chg_user_resp->server_asked_323_auth == TRUE) {
     499             :                 /* old authentication with new server  !*/
     500           0 :                 DBG_ERR(mysqlnd_old_passwd);
     501           0 :                 SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, mysqlnd_old_passwd);
     502             :         }
     503             : end:
     504          76 :         PACKET_FREE(change_auth_resp_packet);
     505          76 :         PACKET_FREE(auth_packet);
     506          76 :         PACKET_FREE(chg_user_resp);
     507          76 :         DBG_RETURN(ret);
     508             : }
     509             : /* }}} */
     510             : 
     511             : 
     512             : /******************************************* MySQL Native Password ***********************************/
     513             : 
     514             : #include "ext/standard/sha1.h"
     515             : 
     516             : /* {{{ php_mysqlnd_crypt */
     517             : static void
     518        1754 : php_mysqlnd_crypt(zend_uchar *buffer, const zend_uchar *s1, const zend_uchar *s2, size_t len)
     519             : {
     520        1754 :         const zend_uchar *s1_end = s1 + len;
     521       38588 :         while (s1 < s1_end) {
     522       35080 :                 *buffer++= *s1++ ^ *s2++;
     523             :         }
     524        1754 : }
     525             : /* }}} */
     526             : 
     527             : 
     528             : /* {{{ php_mysqlnd_scramble */
     529        1754 : void php_mysqlnd_scramble(zend_uchar * const buffer, const zend_uchar * const scramble, const zend_uchar * const password, const size_t password_len)
     530             : {
     531             :         PHP_SHA1_CTX context;
     532             :         zend_uchar sha1[SHA1_MAX_LENGTH];
     533             :         zend_uchar sha2[SHA1_MAX_LENGTH];
     534             : 
     535             :         /* Phase 1: hash password */
     536        1754 :         PHP_SHA1Init(&context);
     537        1754 :         PHP_SHA1Update(&context, password, password_len);
     538        1754 :         PHP_SHA1Final(sha1, &context);
     539             : 
     540             :         /* Phase 2: hash sha1 */
     541        1754 :         PHP_SHA1Init(&context);
     542        1754 :         PHP_SHA1Update(&context, (zend_uchar*)sha1, SHA1_MAX_LENGTH);
     543        1754 :         PHP_SHA1Final(sha2, &context);
     544             : 
     545             :         /* Phase 3: hash scramble + sha2 */
     546        1754 :         PHP_SHA1Init(&context);
     547        1754 :         PHP_SHA1Update(&context, scramble, SCRAMBLE_LENGTH);
     548        1754 :         PHP_SHA1Update(&context, (zend_uchar*)sha2, SHA1_MAX_LENGTH);
     549        1754 :         PHP_SHA1Final(buffer, &context);
     550             : 
     551             :         /* let's crypt buffer now */
     552        1754 :         php_mysqlnd_crypt(buffer, (const zend_uchar *)buffer, (const zend_uchar *)sha1, SHA1_MAX_LENGTH);
     553        1754 : }
     554             : /* }}} */
     555             : 
     556             : 
     557             : /* {{{ mysqlnd_native_auth_get_auth_data */
     558             : static zend_uchar *
     559        1762 : mysqlnd_native_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self,
     560             :                                                                   size_t * auth_data_len,
     561             :                                                                   MYSQLND_CONN_DATA * conn, const char * const user, const char * const passwd,
     562             :                                                                   const size_t passwd_len, zend_uchar * auth_plugin_data, size_t auth_plugin_data_len,
     563             :                                                                   const MYSQLND_SESSION_OPTIONS * const session_options,
     564             :                                                                   const MYSQLND_PFC_DATA * const pfc_data,
     565             :                                                                   zend_ulong mysql_flags
     566             :                                                                  )
     567             : {
     568        1762 :         zend_uchar * ret = NULL;
     569        1762 :         DBG_ENTER("mysqlnd_native_auth_get_auth_data");
     570        1762 :         *auth_data_len = 0;
     571             : 
     572             :         /* 5.5.x reports 21 as scramble length because it needs to show the length of the data before the plugin name */
     573        1762 :         if (auth_plugin_data_len < SCRAMBLE_LENGTH) {
     574             :                 /* mysql_native_password only works with SCRAMBLE_LENGTH scramble */
     575           0 :                 SET_CLIENT_ERROR(conn->error_info, CR_MALFORMED_PACKET, UNKNOWN_SQLSTATE, "The server sent wrong length for scramble");
     576           0 :                 DBG_ERR_FMT("The server sent wrong length for scramble %u. Expected %u", auth_plugin_data_len, SCRAMBLE_LENGTH);
     577           0 :                 DBG_RETURN(NULL);
     578             :         }
     579             : 
     580             :         /* copy scrambled pass*/
     581        1762 :         if (passwd && passwd_len) {
     582        1754 :                 ret = malloc(SCRAMBLE_LENGTH);
     583        1754 :                 *auth_data_len = SCRAMBLE_LENGTH;
     584             :                 /* In 4.1 we use CLIENT_SECURE_CONNECTION and thus the len of the buf should be passed */
     585        1754 :                 php_mysqlnd_scramble((zend_uchar*)ret, auth_plugin_data, (zend_uchar*)passwd, passwd_len);
     586             :         }
     587        1762 :         DBG_RETURN(ret);
     588             : }
     589             : /* }}} */
     590             : 
     591             : 
     592             : static struct st_mysqlnd_authentication_plugin mysqlnd_native_auth_plugin =
     593             : {
     594             :         {
     595             :                 MYSQLND_PLUGIN_API_VERSION,
     596             :                 "auth_plugin_mysql_native_password",
     597             :                 MYSQLND_VERSION_ID,
     598             :                 PHP_MYSQLND_VERSION,
     599             :                 "PHP License 3.01",
     600             :                 "Andrey Hristov <andrey@mysql.com>,  Ulf Wendel <uwendel@mysql.com>, Georg Richter <georg@mysql.com>",
     601             :                 {
     602             :                         NULL, /* no statistics , will be filled later if there are some */
     603             :                         NULL, /* no statistics */
     604             :                 },
     605             :                 {
     606             :                         NULL /* plugin shutdown */
     607             :                 }
     608             :         },
     609             :         {/* methods */
     610             :                 mysqlnd_native_auth_get_auth_data
     611             :         }
     612             : };
     613             : 
     614             : 
     615             : /******************************************* PAM Authentication ***********************************/
     616             : 
     617             : /* {{{ mysqlnd_pam_auth_get_auth_data */
     618             : static zend_uchar *
     619           0 : mysqlnd_pam_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self,
     620             :                                                            size_t * auth_data_len,
     621             :                                                            MYSQLND_CONN_DATA * conn, const char * const user, const char * const passwd,
     622             :                                                            const size_t passwd_len, zend_uchar * auth_plugin_data, size_t auth_plugin_data_len,
     623             :                                                            const MYSQLND_SESSION_OPTIONS * const session_options,
     624             :                                                            const MYSQLND_PFC_DATA * const pfc_data,
     625             :                                                            zend_ulong mysql_flags
     626             :                                                           )
     627             : {
     628           0 :         zend_uchar * ret = NULL;
     629             : 
     630             :         /* copy pass*/
     631           0 :         if (passwd && passwd_len) {
     632           0 :                 ret = (zend_uchar*) zend_strndup(passwd, passwd_len);
     633             :         }
     634           0 :         *auth_data_len = passwd_len;
     635             : 
     636           0 :         return ret;
     637             : }
     638             : /* }}} */
     639             : 
     640             : 
     641             : static struct st_mysqlnd_authentication_plugin mysqlnd_pam_authentication_plugin =
     642             : {
     643             :         {
     644             :                 MYSQLND_PLUGIN_API_VERSION,
     645             :                 "auth_plugin_mysql_clear_password",
     646             :                 MYSQLND_VERSION_ID,
     647             :                 PHP_MYSQLND_VERSION,
     648             :                 "PHP License 3.01",
     649             :                 "Andrey Hristov <andrey@php.net>,  Ulf Wendel <uw@php.net>, Georg Richter <georg@php.net>",
     650             :                 {
     651             :                         NULL, /* no statistics , will be filled later if there are some */
     652             :                         NULL, /* no statistics */
     653             :                 },
     654             :                 {
     655             :                         NULL /* plugin shutdown */
     656             :                 }
     657             :         },
     658             :         {/* methods */
     659             :                 mysqlnd_pam_auth_get_auth_data
     660             :         }
     661             : };
     662             : 
     663             : 
     664             : /******************************************* SHA256 Password ***********************************/
     665             : #ifdef MYSQLND_HAVE_SSL
     666             : static void
     667           0 : mysqlnd_xor_string(char * dst, const size_t dst_len, const char * xor_str, const size_t xor_str_len)
     668             : {
     669             :         unsigned int i;
     670           0 :         for (i = 0; i <= dst_len; ++i) {
     671           0 :                 dst[i] ^= xor_str[i % xor_str_len];
     672             :         }
     673           0 : }
     674             : 
     675             : 
     676             : #include <openssl/rsa.h>
     677             : #include <openssl/pem.h>
     678             : #include <openssl/err.h>
     679             : 
     680             : 
     681             : /* {{{ mysqlnd_sha256_get_rsa_key */
     682             : static RSA *
     683           0 : mysqlnd_sha256_get_rsa_key(MYSQLND_CONN_DATA * conn,
     684             :                                                    const MYSQLND_SESSION_OPTIONS * const session_options,
     685             :                                                    const MYSQLND_PFC_DATA * const pfc_data
     686             :                                                   )
     687             : {
     688           0 :         RSA * ret = NULL;
     689           0 :         const char * fname = (pfc_data->sha256_server_public_key && pfc_data->sha256_server_public_key[0] != '\0')?
     690             :                                                                 pfc_data->sha256_server_public_key:
     691           0 :                                                                 MYSQLND_G(sha256_server_public_key);
     692             :         php_stream * stream;
     693           0 :         DBG_ENTER("mysqlnd_sha256_get_rsa_key");
     694           0 :         DBG_INF_FMT("options_s256_pk=[%s] MYSQLND_G(sha256_server_public_key)=[%s]",
     695           0 :                                  pfc_data->sha256_server_public_key? pfc_data->sha256_server_public_key:"n/a",
     696           0 :                                  MYSQLND_G(sha256_server_public_key)? MYSQLND_G(sha256_server_public_key):"n/a");
     697           0 :         if (!fname || fname[0] == '\0') {
     698           0 :                 MYSQLND_PACKET_SHA256_PK_REQUEST * pk_req_packet = NULL;
     699           0 :                 MYSQLND_PACKET_SHA256_PK_REQUEST_RESPONSE * pk_resp_packet = NULL;
     700             : 
     701             :                 do {
     702           0 :                         DBG_INF("requesting the public key from the server");
     703           0 :                         pk_req_packet = conn->payload_decoder_factory->m.get_sha256_pk_request_packet(conn->payload_decoder_factory, FALSE);
     704           0 :                         if (!pk_req_packet) {
     705           0 :                                 SET_OOM_ERROR(conn->error_info);
     706           0 :                                 break;
     707             :                         }
     708           0 :                         pk_resp_packet = conn->payload_decoder_factory->m.get_sha256_pk_request_response_packet(conn->payload_decoder_factory, FALSE);
     709           0 :                         if (!pk_resp_packet) {
     710           0 :                                 SET_OOM_ERROR(conn->error_info);
     711           0 :                                 PACKET_FREE(pk_req_packet);
     712           0 :                                 break;
     713             :                         }
     714             : 
     715           0 :                         if (! PACKET_WRITE(pk_req_packet)) {
     716           0 :                                 DBG_ERR_FMT("Error while sending public key request packet");
     717           0 :                                 php_error(E_WARNING, "Error while sending public key request packet. PID=%d", getpid());
     718           0 :                                 SET_CONNECTION_STATE(&conn->state, CONN_QUIT_SENT);
     719           0 :                                 break;
     720             :                         }
     721           0 :                         if (FAIL == PACKET_READ(pk_resp_packet) || NULL == pk_resp_packet->public_key) {
     722           0 :                                 DBG_ERR_FMT("Error while receiving public key");
     723           0 :                                 php_error(E_WARNING, "Error while receiving public key. PID=%d", getpid());
     724           0 :                                 SET_CONNECTION_STATE(&conn->state, CONN_QUIT_SENT);
     725           0 :                                 break;
     726             :                         }
     727           0 :                         DBG_INF_FMT("Public key(%d):\n%s", pk_resp_packet->public_key_len, pk_resp_packet->public_key);
     728             :                         /* now extract the public key */
     729             :                         {
     730           0 :                                 BIO * bio = BIO_new_mem_buf(pk_resp_packet->public_key, pk_resp_packet->public_key_len);
     731           0 :                                 ret = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
     732           0 :                                 BIO_free(bio);
     733             :                         }
     734             :                 } while (0);
     735           0 :                 PACKET_FREE(pk_req_packet);
     736           0 :                 PACKET_FREE(pk_resp_packet);
     737             : 
     738           0 :                 DBG_INF_FMT("ret=%p", ret);
     739           0 :                 DBG_RETURN(ret);
     740             : 
     741             :                 SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE,
     742             :                         "sha256_server_public_key is not set for the connection or as mysqlnd.sha256_server_public_key");
     743             :                 DBG_ERR("server_public_key is not set");
     744             :                 DBG_RETURN(NULL);
     745             :         } else {
     746             :                 zend_string * key_str;
     747           0 :                 DBG_INF_FMT("Key in a file. [%s]", fname);
     748           0 :                 stream = php_stream_open_wrapper((char *) fname, "rb", REPORT_ERRORS, NULL);
     749             : 
     750           0 :                 if (stream) {
     751           0 :                         if ((key_str = php_stream_copy_to_mem(stream, PHP_STREAM_COPY_ALL, 0)) != NULL) {
     752           0 :                                 BIO * bio = BIO_new_mem_buf(ZSTR_VAL(key_str), ZSTR_LEN(key_str));
     753           0 :                                 ret = PEM_read_bio_RSA_PUBKEY(bio, NULL, NULL, NULL);
     754           0 :                                 BIO_free(bio);
     755           0 :                                 DBG_INF("Successfully loaded");
     756           0 :                                 DBG_INF_FMT("Public key:%*.s", ZSTR_LEN(key_str), ZSTR_VAL(key_str));
     757             :                                 zend_string_release(key_str);
     758             :                         }
     759           0 :                         php_stream_close(stream);
     760             :                 }
     761             :         }
     762           0 :         DBG_RETURN(ret);
     763             : }
     764             : /* }}} */
     765             : 
     766             : 
     767             : /* {{{ mysqlnd_sha256_auth_get_auth_data */
     768             : static zend_uchar *
     769           0 : mysqlnd_sha256_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self,
     770             :                                                                   size_t * auth_data_len,
     771             :                                                                   MYSQLND_CONN_DATA * conn, const char * const user, const char * const passwd,
     772             :                                                                   const size_t passwd_len, zend_uchar * auth_plugin_data, size_t auth_plugin_data_len,
     773             :                                                                   const MYSQLND_SESSION_OPTIONS * const session_options,
     774             :                                                                   const MYSQLND_PFC_DATA * const pfc_data,
     775             :                                                                   zend_ulong mysql_flags
     776             :                                                                  )
     777             : {
     778             :         RSA * server_public_key;
     779           0 :         zend_uchar * ret = NULL;
     780           0 :         DBG_ENTER("mysqlnd_sha256_auth_get_auth_data");
     781           0 :         DBG_INF_FMT("salt(%d)=[%.*s]", auth_plugin_data_len, auth_plugin_data_len, auth_plugin_data);
     782             : 
     783             : 
     784           0 :         if (conn->protocol_frame_codec->data->ssl) {
     785           0 :                 DBG_INF("simple clear text under SSL");
     786             :                 /* clear text under SSL */
     787           0 :                 *auth_data_len = passwd_len;
     788           0 :                 ret = malloc(passwd_len);
     789           0 :                 memcpy(ret, passwd, passwd_len);
     790             :         } else {
     791           0 :                 *auth_data_len = 0;
     792           0 :                 server_public_key = mysqlnd_sha256_get_rsa_key(conn, session_options, pfc_data);
     793             : 
     794           0 :                 if (server_public_key) {
     795             :                         int server_public_key_len;
     796           0 :                         char xor_str[passwd_len + 1];
     797           0 :                         memcpy(xor_str, passwd, passwd_len);
     798           0 :                         xor_str[passwd_len] = '\0';
     799           0 :                         mysqlnd_xor_string(xor_str, passwd_len, (char *) auth_plugin_data, auth_plugin_data_len);
     800             : 
     801           0 :                         server_public_key_len = RSA_size(server_public_key);
     802             :                         /*
     803             :                           Because RSA_PKCS1_OAEP_PADDING is used there is a restriction on the passwd_len.
     804             :                           RSA_PKCS1_OAEP_PADDING is recommended for new applications. See more here:
     805             :                           http://www.openssl.org/docs/crypto/RSA_public_encrypt.html
     806             :                         */
     807           0 :                         if ((size_t) server_public_key_len - 41 <= passwd_len) {
     808             :                                 /* password message is to long */
     809           0 :                                 SET_CLIENT_ERROR(conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "password is too long");
     810           0 :                                 DBG_ERR("password is too long");
     811           0 :                                 DBG_RETURN(NULL);
     812             :                         }
     813             : 
     814           0 :                         *auth_data_len = server_public_key_len;
     815           0 :                         ret = malloc(*auth_data_len);
     816           0 :                         RSA_public_encrypt(passwd_len + 1, (zend_uchar *) xor_str, ret, server_public_key, RSA_PKCS1_OAEP_PADDING);
     817             :                 }
     818             :         }
     819             : 
     820           0 :         DBG_RETURN(ret);
     821             : }
     822             : /* }}} */
     823             : 
     824             : 
     825             : static struct st_mysqlnd_authentication_plugin mysqlnd_sha256_authentication_plugin =
     826             : {
     827             :         {
     828             :                 MYSQLND_PLUGIN_API_VERSION,
     829             :                 "auth_plugin_sha256_password",
     830             :                 MYSQLND_VERSION_ID,
     831             :                 PHP_MYSQLND_VERSION,
     832             :                 "PHP License 3.01",
     833             :                 "Andrey Hristov <andrey@mysql.com>,  Ulf Wendel <uwendel@mysql.com>",
     834             :                 {
     835             :                         NULL, /* no statistics , will be filled later if there are some */
     836             :                         NULL, /* no statistics */
     837             :                 },
     838             :                 {
     839             :                         NULL /* plugin shutdown */
     840             :                 }
     841             :         },
     842             :         {/* methods */
     843             :                 mysqlnd_sha256_auth_get_auth_data
     844             :         }
     845             : };
     846             : #endif
     847             : 
     848             : /* {{{ mysqlnd_register_builtin_authentication_plugins */
     849             : void
     850       22570 : mysqlnd_register_builtin_authentication_plugins(void)
     851             : {
     852       22570 :         mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_native_auth_plugin);
     853       22570 :         mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_pam_authentication_plugin);
     854             : #ifdef MYSQLND_HAVE_SSL
     855       22570 :         mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_sha256_authentication_plugin);
     856             : #endif
     857       22570 : }
     858             : /* }}} */
     859             : 
     860             : 
     861             : /*
     862             :  * Local variables:
     863             :  * tab-width: 4
     864             :  * c-basic-offset: 4
     865             :  * End:
     866             :  * vim600: noet sw=4 ts=4 fdm=marker
     867             :  * vim<600: noet sw=4 ts=4
     868             :  */

Generated by: LCOV version 1.10

Generated at Tue, 09 Feb 2016 10:48:45 +0000 (39 hours ago)

Copyright © 2005-2016 The PHP Group
All rights reserved.