1 /* 2 * Copyright (c) 2015, 2021, Oracle and/or its affiliates. 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License, version 2.0, 6 * as published by the Free Software Foundation. 7 * 8 * This program is also distributed with certain software (including 9 * but not limited to OpenSSL) that is licensed under separate terms, 10 * as designated in a particular file or component or in included license 11 * documentation. The authors of MySQL hereby grant you an additional 12 * permission to link the program and your derivative works with the 13 * separately licensed software that they have included with MySQL. 14 * 15 * This program is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License, version 2.0, for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with this program; if not, write to the Free Software 22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 23 * 02110-1301 USA 24 */ 25 26 #ifndef XPL_USER_VERIFICATION_HELPER_H_ 27 #define XPL_USER_VERIFICATION_HELPER_H_ 28 29 #include "ngs_common/connection_type.h" 30 31 #include "xpl_log.h" 32 #include "sql_data_context.h" 33 #include "sql_user_require.h" 34 #include "query_string_builder.h" 35 36 37 namespace xpl { 38 39 class User_verification_helper { 40 public: 41 typedef Command_delegate::Field_types Field_types; 42 typedef Sql_data_context::Result_info Result_info; 43 typedef Buffering_command_delegate::Resultset Resultset; 44 User_verification_helper(const On_user_password_hash & hash_verification_cb,ngs::IOptions_session_ptr & options_session,const ngs::Connection_type type)45 User_verification_helper( 46 const On_user_password_hash &hash_verification_cb, 47 ngs::IOptions_session_ptr &options_session, 48 const ngs::Connection_type type) 49 : m_hash_verification_cb(hash_verification_cb), 50 m_options_session(options_session), 51 m_type(type) { 52 } 53 verify_mysql_account(Sql_data_context & sql_data_context,const std::string & user,const std::string & host)54 ngs::Error_code verify_mysql_account(Sql_data_context &sql_data_context, const std::string &user, const std::string &host) { 55 Resultset r_result_set; 56 Result_info r_info; 57 58 ngs::PFS_string query = get_sql(user.c_str(), host.c_str()); 59 ngs::Error_code error = sql_data_context.execute_sql_and_collect_results( 60 query.c_str(), 61 query.length(), 62 m_fields_type, 63 r_result_set, 64 r_info); 65 66 if (error) { 67 log_debug("Error %i occurred while executing query: %s", error.error, error.message.c_str()); 68 return error; 69 } 70 71 try { 72 // The query asks for primary key, thus here we should get 73 // only one row 74 if (!r_result_set.empty()) { 75 assert(1 == r_result_set.size()); 76 if (this->verify_mysql_account_entry(r_result_set.front())) 77 return ngs::Error_code(); 78 } 79 } 80 catch (ngs::Error_code &e) 81 { 82 return e; 83 } 84 85 return ngs::Error_code(ER_NO_SUCH_USER, "Invalid user or password"); 86 } 87 88 private: get_sql(const char * user,const char * host)89 ngs::PFS_string get_sql(const char *user, const char *host) const { 90 Query_string_builder qb; 91 92 // Query for a concrete users primary key (USER,HOST columns) which was chosen by MySQL Server 93 // and verify hash and plugin column. 94 // There are also other informations, like account lock, if password expired and user can be connected, if server is in offline mode and the user is with super priv. 95 // column `is_password_expired` is set to true if 96 // - password expired 97 // column `disconnect_on_expired_password` is set to true if 98 // - disconnect_on_expired_password 99 // column `is_offline_mode_and_isnt_super_user` is set true if it 100 // - offline mode and user has not super priv 101 qb.put("/* xplugin authentication */ SELECT @@require_secure_transport, `authentication_string`,`account_locked`, " 102 "(`password_expired`!='N') as `is_password_expired`, " 103 "@@disconnect_on_expired_password as `disconnect_on_expired_password`, " 104 "@@offline_mode and (`Super_priv`='N') as `is_offline_mode_and_isnt_super_user`," 105 "`ssl_type`, `ssl_cipher`, `x509_issuer`, `x509_subject` " 106 "FROM mysql.user WHERE ") 107 .quote_string(user).put(" = `user` AND ") 108 .quote_string(host).put(" = `host` "); 109 110 log_debug("Query user '%s'", qb.get().c_str()); 111 return qb.get(); 112 } 113 verify_mysql_account_entry(const Row_data & row)114 bool verify_mysql_account_entry(const Row_data &row) { 115 bool require_secure_transport = false; 116 std::string db_password_hash; 117 bool is_account_not_locked = false; 118 bool is_password_expired = false; 119 bool disconnect_on_expired_password = false; 120 bool is_offline_mode_and_isnt_super_user = false; 121 Sql_user_require required; 122 123 assert(10 == row.fields.size()); 124 125 if (!get_bool_from_int_value(row, 0, require_secure_transport) || 126 !get_string_value(row, 1, db_password_hash) || 127 !get_bool_from_string_value(row, 2, "N", is_account_not_locked) || 128 !get_bool_from_int_value(row, 3, is_password_expired) || 129 !get_bool_from_int_value(row, 4, disconnect_on_expired_password) || 130 !get_bool_from_int_value(row, 5, is_offline_mode_and_isnt_super_user) || 131 !get_string_value(row, 6, required.ssl_type) || 132 !get_string_value(row, 7, required.ssl_cipher) || 133 !get_string_value(row, 8, required.ssl_x509_issuer) || 134 !get_string_value(row, 9, required.ssl_x509_subject)) 135 return false; 136 137 if (m_hash_verification_cb(db_password_hash)) { 138 // Password check succeeded but... 139 140 if (!is_account_not_locked) 141 throw ngs::Error_code(ER_ACCOUNT_HAS_BEEN_LOCKED, "Account is locked."); 142 143 if (is_offline_mode_and_isnt_super_user) 144 throw ngs::Error_code(ER_SERVER_OFFLINE_MODE, "Server works in offline mode."); 145 146 // password expiration check must come last, because password expiration is not a fatal error 147 // a client that supports expired password state, will be let in... so the user can only 148 // get this error if the auth succeeded 149 if (is_password_expired) { 150 // if the password is expired, it's only a fatal error if disconnect_on_expired_password is enabled 151 // AND the client doesn't support expired passwords (this check is done by the caller of this) 152 // if it's NOT enabled, then the user will be allowed to login in sandbox mode, even if the client 153 // doesn't support expired passwords 154 if (disconnect_on_expired_password) 155 throw ngs::Fatal(ER_MUST_CHANGE_PASSWORD_LOGIN, "Your password has expired. To log in you must change it using a client that supports expired passwords."); 156 else 157 throw ngs::Error(ER_MUST_CHANGE_PASSWORD_LOGIN, "Your password has expired."); 158 } 159 160 if (require_secure_transport) { 161 if (!ngs::Connection_type_helper::is_secure_type(m_type)) 162 throw ngs::Error(ER_SECURE_TRANSPORT_REQUIRED, "Secure transport required. To log in you must use TCP+SSL or UNIX socket connection."); 163 } 164 165 ngs::Error_code error = required.validate(m_options_session); 166 if (error) 167 throw error; 168 169 return true; 170 } 171 172 return false; 173 } 174 get_string_value(const Row_data & row,const std::size_t index,std::string & value)175 bool get_string_value(const Row_data &row, const std::size_t index, std::string &value) const { 176 Field_value *field = row.fields[index]; 177 178 if (!field) 179 return false; 180 181 if (MYSQL_TYPE_STRING != m_fields_type[index].type && 182 MYSQL_TYPE_BLOB != m_fields_type[index].type) 183 return false; 184 185 value = *field->value.v_string; 186 187 return true; 188 } 189 get_bool_from_string_value(const Row_data & row,const std::size_t index,const std::string & match,bool & value)190 bool get_bool_from_string_value( 191 const Row_data &row, 192 const std::size_t index, 193 const std::string &match, 194 bool &value) const { 195 std::string string_value; 196 197 if (get_string_value(row, index, string_value)) { 198 value = string_value == match; 199 200 return true; 201 } 202 203 return false; 204 } 205 get_bool_from_int_value(const Row_data & row,const std::size_t index,bool & value)206 bool get_bool_from_int_value(const Row_data &row, const std::size_t index, bool &value) const { 207 std::string string_value; 208 Field_value *field = row.fields[index]; 209 210 if (!field) 211 return false; 212 213 if (MYSQL_TYPE_LONGLONG != m_fields_type[index].type) 214 return false; 215 216 value = 0 != field->value.v_long; 217 218 return true; 219 } 220 221 Field_types m_fields_type; 222 On_user_password_hash m_hash_verification_cb; 223 ngs::IOptions_session_ptr &m_options_session; 224 ngs::Connection_type m_type; 225 }; 226 227 } // namespace xpl 228 229 #endif // XPL_USER_VERIFICATION_HELPER_H_ 230