summaryrefslogtreecommitdiffstats
path: root/plugin/win_auth_client/handshake_client.cc
blob: 28a228cc2103a0bbe8060456be5381ec9d5fce92 (plain)
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-1335  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= (uint)(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 initial
  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-dbug="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);
}