1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393
|
/* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */
#include "handshake.h"
#include <mysql.h> // for MYSQL structure
/// Client-side context for authentication handshake
class Handshake_client: public Handshake
{
/**
Name of the server's service for which we authenticate.
The service name is sent by server in the initial packet. If no
service name is used, this member is @c NULL.
*/
SEC_WCHAR *m_service_name;
/// Buffer for storing service name obtained from server.
SEC_WCHAR m_service_name_buf[MAX_SERVICE_NAME_LENGTH];
Connection &m_con;
public:
Handshake_client(Connection &con, const char *target, size_t len);
~Handshake_client();
Blob first_packet();
Blob process_data(const Blob&);
Blob read_packet();
int write_packet(Blob &data);
};
/**
Create authentication handshake context for client.
@param con connection for communication with the peer
@param target name of the target service with which we will authenticate
(can be NULL if not used)
Some security packages (like Kerberos) require providing explicit name
of the service with which a client wants to authenticate. The server-side
authentication plugin sends this name in the greeting packet
(see @c win_auth_handshake_{server,client}() functions).
*/
Handshake_client::Handshake_client(Connection &con,
const char *target, size_t len)
: Handshake(SSP_NAME, CLIENT), m_service_name(NULL), m_con(con)
{
if (!target || 0 == len)
return;
// Convert received UPN to internal WCHAR representation.
m_service_name= utf8_to_wchar(target, &len);
if (m_service_name)
DBUG_PRINT("info", ("Using target service: %S\n", m_service_name));
else
{
/*
Note: we ignore errors here - m_target will be NULL, the target name
will not be used and system will fall-back to NTLM authentication. But
we leave trace in error log.
*/
ERROR_LOG(WARNING, ("Could not decode UPN sent by the server"
"; target service name will not be used"
" and Kerberos authentication will not work"));
}
}
Handshake_client::~Handshake_client()
{
if (m_service_name)
free(m_service_name);
}
Blob Handshake_client::read_packet()
{
/*
We do a fake read in the first round because first
packet from the server containing UPN must be read
before the handshake context is created and the packet
processing loop starts. We return an empty blob here
and process_data() function will ignore it.
*/
if (m_round == 1)
return Blob();
// Otherwise we read packet from the connection.
Blob packet= m_con.read();
m_error= m_con.error();
if (!m_error && packet.is_null())
m_error= true; // (no specific error code assigned)
if (m_error)
return Blob();
DBUG_PRINT("dump", ("Got the following bytes"));
DBUG_DUMP("dump", packet.ptr(), packet.len());
return packet;
}
int Handshake_client::write_packet(Blob &data)
{
/*
Length of the first data payload send by client authentication plugin is
limited to 255 bytes (because it is wrapped inside client authentication
packet and is length-encoded with 1 byte for the length).
If the data payload is longer than 254 bytes, then it is sent in two parts:
first part of length 255 will be embedded in the authentication packet,
second part will be sent in the following packet. Byte 255 of the first
part contains information about the total length of the payload. It is a
number of blocks of size 512 bytes which is sufficient to store the
combined packets.
Server's logic for reading first client's payload is as follows
(see Handshake_server::read_packet()):
1. Read data from the authentication packet, if it is shorter than 255 bytes
then that is all data sent by client.
2. If there is 255 bytes of data in the authentication packet, read another
packet and append it to the data, skipping byte 255 of the first packet
which can be used to allocate buffer of appropriate size.
*/
size_t len2= 0; // length of the second part of first data payload
byte saved_byte; // for saving byte 255 in which data length is stored
if (m_round == 1 && data.len() > 254)
{
len2= data.len() - 254;
DBUG_PRINT("info", ("Splitting first packet of length %lu"
", %lu bytes will be sent in a second part",
data.len(), len2));
/*
Store in byte 255 the number of 512b blocks that are needed to
keep all the data.
*/
unsigned block_count= data.len()/512 + ((data.len() % 512) ? 1 : 0);
#if !defined(DBUG_OFF) && defined(WINAUTH_USE_DBUG_LIB)
/*
For testing purposes, use wrong block count to see how server
handles this.
*/
DBUG_EXECUTE_IF("winauth_first_packet_test",{
block_count= data.len() == 601 ? 0 :
data.len() == 602 ? 1 :
block_count;
});
#endif
DBUG_ASSERT(block_count < (unsigned)0x100);
saved_byte= data[254];
data[254] = block_count;
data.trim(255);
}
DBUG_PRINT("dump", ("Sending the following data"));
DBUG_DUMP("dump", data.ptr(), data.len());
int ret= m_con.write(data);
if (ret)
return ret;
// Write second part if it is present.
if (len2)
{
data[254]= saved_byte;
Blob data2(data.ptr() + 254, len2);
DBUG_PRINT("info", ("Sending second part of data"));
DBUG_DUMP("info", data2.ptr(), data2.len());
ret= m_con.write(data2);
}
return ret;
}
/**
Process data sent by server.
@param[in] data blob with data from server
This method analyses data sent by server during authentication handshake.
If client should continue packet exchange, this method returns data to
be sent to the server next. If no more data needs to be exchanged, an
empty blob is returned and @c is_complete() is @c true. In case of error
an empty blob is returned and @c error() gives non-zero error code.
When invoked for the first time (in the first round of the handshake)
there is no data from the server (data blob is null) and the intial
packet is generated without an input.
@return Data to be sent to the server next or null blob if no more data
needs to be exchanged or in case of error.
*/
Blob Handshake_client::process_data(const Blob &data)
{
#if !defined(DBUG_OFF) && defined(WINAUTH_USE_DBUG_LIB)
/*
Code for testing the logic for sending the first client payload.
A fake data of length given by environment variable TEST_PACKET_LENGTH
(or default 255 bytes) is sent to the server. First 2 bytes of the
payload contain its total length (LSB first). The length of test data
is limited to 2048 bytes.
Upon receiving test data, server will check that data is correct and
refuse connection. If server detects data errors it will crash on
assertion.
This code is executed if debug flag "winauth_first_packet_test" is
set, e.g. using client option:
--debug="d,winauth_first_packet_test"
The same debug flag must be enabled in the server, e.g. using
statement:
SET GLOBAL debug= '+d,winauth_first_packet_test';
*/
static byte test_buf[2048];
if (m_round == 1
&& DBUG_EVALUATE_IF("winauth_first_packet_test", true, false))
{
const char *env= getenv("TEST_PACKET_LENGTH");
size_t len= env ? atoi(env) : 0;
if (!len)
len= 255;
if (len > sizeof(test_buf))
len= sizeof(test_buf);
// Store data length in first 2 bytes.
byte *ptr= test_buf;
*ptr++= len & 0xFF;
*ptr++= len >> 8;
// Fill remaining bytes with known values.
for (byte b= 0; ptr < test_buf + len; ++ptr, ++b)
*ptr= b;
return Blob(test_buf, len);
};
#endif
Security_buffer input(data);
SECURITY_STATUS ret;
m_output.free();
ret= InitializeSecurityContextW(
&m_cred,
m_round == 1 ? NULL : &m_sctx, // partial context
m_service_name, // service name
ASC_REQ_ALLOCATE_MEMORY, // requested attributes
0, // reserved
SECURITY_NETWORK_DREP, // data representation
m_round == 1 ? NULL : &input, // input data
0, // reserved
&m_sctx, // context
&m_output, // output data
&m_atts, // attributes
&m_expire); // expire date
if (process_result(ret))
{
DBUG_PRINT("error",
("InitializeSecurityContext() failed with error %X", ret));
return Blob();
}
return m_output.as_blob();
}
/**********************************************************************/
/**
Perform authentication handshake from client side.
@param[in] vio pointer to @c MYSQL_PLUGIN_VIO instance to be used
for communication with the server
@param[in] mysql pointer to a MySQL connection for which we authenticate
After reading the initial packet from server, containing its UPN to be
used as service name, client starts packet exchange by sending the first
packet in this exchange. While handshake is not yet completed, client
reads packets sent by the server and process them, possibly generating new
data to be sent to the server.
This function reports errors.
@return 0 on success.
*/
int win_auth_handshake_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql)
{
DBUG_ENTER("win_auth_handshake_client");
/*
Check if we should enable logging.
*/
{
const char *opt= getenv("AUTHENTICATION_WIN_LOG");
int opt_val= opt ? atoi(opt) : 0;
if (opt && !opt_val)
{
if (!strncasecmp("on", opt, 2)) opt_val= 2;
if (!strncasecmp("yes", opt, 3)) opt_val= 2;
if (!strncasecmp("true", opt, 4)) opt_val= 2;
if (!strncasecmp("debug", opt, 5)) opt_val= 4;
if (!strncasecmp("dbug", opt, 4)) opt_val= 4;
}
set_log_level(opt_val);
}
ERROR_LOG(INFO, ("Authentication handshake for account %s", mysql->user));
// Create connection object.
Connection con(vio);
DBUG_ASSERT(!con.error());
// Read initial packet from server containing service name.
Blob service_name= con.read();
if (con.error() || service_name.is_null())
{
ERROR_LOG(ERROR, ("Error reading initial packet"));
DBUG_RETURN(CR_ERROR);
}
DBUG_PRINT("info", ("Got initial packet of length %d", service_name.len()));
// Create authentication handshake context using the given service name.
Handshake_client hndshk(con,
service_name[0] ? (char *)service_name.ptr() : NULL,
service_name.len());
if (hndshk.error())
{
ERROR_LOG(ERROR, ("Could not create authentication handshake context"));
DBUG_RETURN(CR_ERROR);
}
DBUG_ASSERT(!hndshk.error());
/*
Read and process packets from server until handshake is complete.
Note that the first read from server is dummy
(see Handshake_client::read_packet()) as we already have read the
first packet to establish service name.
*/
if (hndshk.packet_processing_loop())
DBUG_RETURN(CR_ERROR);
DBUG_ASSERT(!hndshk.error() && hndshk.is_complete());
DBUG_RETURN(CR_OK);
}
|