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 
27 #include "auth_mysql41.h"
28 #include "sql_data_context.h"
29 
30 #include "mysql_com.h"
31 #include "crypt_genhash_impl.h"
32 #include "password.h"
33 
34 // C -> S: authenticationStart(MYSQL41)
35 // S -> C: authenticationContinue(20 byte salt/scramble)
36 // C -> S: authenticationContinue(schema\0user\0sha1(sha1(password))+salt)
37 // S -> C: Notice(password expired etc)
38 // S -> C: authenticationOk/Error
39 
40 using namespace xpl;
41 
42 
handle_start(const std::string & mechanism,const std::string & data,const std::string & initial_response)43 ngs::Authentication_handler::Response Sasl_mysql41_auth::handle_start(const std::string &mechanism,
44                                                                       const std::string &data,
45                                                                       const std::string &initial_response)
46 {
47   Response r;
48 
49   if (m_state == S_starting)
50   {
51     m_salt.resize(SCRAMBLE_LENGTH);
52     ::generate_user_salt(&m_salt[0], static_cast<int>(m_salt.size()));
53     r.data = m_salt;
54     r.status = Ongoing;
55     r.error_code = 0;
56 
57     m_state = S_waiting_response;
58   }
59   else
60   {
61     r.status = Error;
62     r.error_code = ER_NET_PACKETS_OUT_OF_ORDER;
63 
64     m_state = S_error;
65   }
66 
67   return r;
68 }
69 
handle_continue(const std::string & data)70 ngs::Authentication_handler::Response Sasl_mysql41_auth::handle_continue(const std::string &data)
71 {
72   Response r;
73 
74   if (m_state == S_waiting_response)
75   {
76     const char*     client_address  = m_session->client().client_address();
77     std::string     client_hostname = m_session->client().client_hostname();
78     ngs::Error_code error = sasl_message(client_hostname.empty() ? NULL : client_hostname.c_str(), client_address, data);
79 
80     // data is the username and initial_response is password
81     if (!error)
82     {
83       r.status = Succeeded;
84       r.error_code = 0;
85     }
86     else
87     {
88       r.status = Failed;
89       r.data = error.message;
90       r.error_code = error.error;
91     }
92     m_state = S_done;
93   }
94   else
95   {
96     m_state = S_error;
97     r.status = Error;
98     r.error_code = ER_NET_PACKETS_OUT_OF_ORDER;
99   }
100   return r;
101 }
102 
103 
sasl_message(const char * client_hostname,const char * client_address,const std::string & message)104 ngs::Error_code Sasl_mysql41_auth::sasl_message(const char *client_hostname, const char *client_address, const std::string &message)
105 {
106   try
107   {
108     const std::size_t sasl_element_max_with_two_additional_bytes = 256;
109     std::size_t       message_position = 0;
110 
111     char authzid[sasl_element_max_with_two_additional_bytes];
112     char authcid[sasl_element_max_with_two_additional_bytes];
113     char passwd[sasl_element_max_with_two_additional_bytes];
114 
115     if (!extract_null_terminated_element(message, message_position, sasl_element_max_with_two_additional_bytes, authzid) ||
116         !extract_null_terminated_element(message, message_position, sasl_element_max_with_two_additional_bytes, authcid) ||
117         !extract_null_terminated_element(message, message_position, sasl_element_max_with_two_additional_bytes, passwd))
118     {
119       //throw ngs::Error_code(ER_INVALID_CHARACTER_STRING, "Invalid format of login string");
120       throw ngs::Error_code(ER_NO_SUCH_USER, "Invalid user or password");
121     }
122 
123     if (strlen(authcid) == 0)
124       throw ngs::Error_code(ER_NO_SUCH_USER, "Invalid user or password");
125 
126     On_user_password_hash      verify_password_hash = ngs::bind(&Sasl_mysql41_auth::check_password_hash, this, passwd, ngs::placeholders::_1);
127     ngs::IOptions_session_ptr  options_session = m_session->client().connection().options();
128     const ngs::Connection_type connection_type = m_session->client().connection().connection_type();
129 
130     return m_session->data_context().authenticate(authcid, client_hostname, client_address, authzid, verify_password_hash,
131                                                   ((xpl::Client&)m_session->client()).supports_expired_passwords(), options_session, connection_type);
132   }
133   catch(const ngs::Error_code &error_code)
134   {
135     return error_code;
136   }
137   return ngs::Error_code();
138 }
139 
140 #include "mysql_com.h"
141 
check_password_hash(const std::string & password_scramble,const std::string & password_hash)142 bool Sasl_mysql41_auth::check_password_hash(const std::string &password_scramble, const std::string &password_hash)
143 {
144   try
145   {
146     if (password_scramble.empty())
147     {
148       // client gave no password, this can only login to a no password acct
149       if (password_hash.empty())
150         return true;
151       return false;
152     }
153     if (!password_hash.empty())
154     {
155       uint8 db_hash_stage2[SCRAMBLE_LENGTH+1] = {0};
156       uint8 user_hash_stage2[SCRAMBLE_LENGTH+1] = {0};
157 
158       ::get_salt_from_password(db_hash_stage2, password_hash.c_str());
159       ::get_salt_from_password(user_hash_stage2, password_scramble.c_str());
160 
161       return 0 == ::check_scramble((const uchar*)user_hash_stage2, m_salt.c_str(), db_hash_stage2);
162     }
163     return false;
164   }
165   catch(const ngs::Error_code&)
166   {
167     return false;
168   }
169 }
170