summaryrefslogtreecommitdiffstats
path: root/plugin/win_auth_client/common.cc
blob: 8b7319252acdaf87f3c74a932e475648f6510628 (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
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
/* Copyright (c) 2011, 2019, 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 "common.h"
#include <sddl.h>   // for ConvertSidToStringSid()
#include <secext.h> // for GetUserNameEx()


template <> void error_log_print<error_log_level::INFO>(const char *fmt, ...);
template <> void error_log_print<error_log_level::WARNING>(const char *fmt, ...);
template <> void error_log_print<error_log_level::ERROR>(const char *fmt, ...);

/**
  Option indicating desired level of logging. Values:

  0 - no logging
  1 - log only error messages
  2 - additionally log warnings
  3 - additionally log info notes
  4 - also log debug messages

  Value of this option should be taken into account in the 
  implementation of  error_log_vprint() function (see 
  log_client.cc).

  Note: No error or debug messages are logged in production code
  (see logging macros in common.h).
*/
int opt_auth_win_log_level= 2;


/** Connection class **************************************************/

/**
  Create connection out of an active MYSQL_PLUGIN_VIO object.

  @param[in] vio  pointer to a @c MYSQL_PLUGIN_VIO object used for
                  connection - it can not be NULL
*/

Connection::Connection(MYSQL_PLUGIN_VIO *vio): m_vio(vio), m_error(0)
{
  DBUG_ASSERT(vio);
}


/**
  Write data to the connection.

  @param[in]  blob  data to be written

  @return 0 on success, VIO error code on failure.

  @note In case of error, VIO error code is stored in the connection object
  and can be obtained with @c error() method.
*/

int Connection::write(const Blob &blob)
{
  m_error= m_vio->write_packet(m_vio, blob.ptr(), (int)blob.len());

#ifndef DBUG_OFF
  if (m_error)
    DBUG_PRINT("error", ("vio write error %d", m_error));
#endif

  return m_error;
}


/**
  Read data from connection.

  @return A Blob containing read packet or null Blob in case of error.

  @note In case of error, VIO error code is stored in the connection object
  and can be obtained with @c error() method.
*/

Blob Connection::read()
{
  unsigned char *ptr;
  int len= m_vio->read_packet(m_vio, &ptr);

  if (len < 0)
  {
    m_error= true;
    return Blob();
  }

  return Blob(ptr, len);
}


/** Sid class *****************************************************/


/**
  Create Sid object corresponding to a given account name.

  @param[in]  account_name  name of a Windows account

  The account name can be in any form accepted by @c LookupAccountName()
  function.

  @note In case of errors created object is invalid and its @c is_valid()
  method returns @c false.
*/

Sid::Sid(const wchar_t *account_name): m_data(NULL)
#ifndef DBUG_OFF
, m_as_string(NULL)
#endif
{
  DWORD sid_size= 0, domain_size= 0;
  bool success;

  // Determine required buffer sizes

  success= LookupAccountNameW(NULL, account_name, NULL, &sid_size,
                             NULL, &domain_size, &m_type);

  if (!success && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
  {
#ifndef DBUG_OFF
    Error_message_buf error_buf;
    DBUG_PRINT("error", ("Could not determine SID buffer size, "
                         "LookupAccountName() failed with error %X (%s)",
                         GetLastError(), get_last_error_message(error_buf)));
#endif
    return;
  }

  // Query for SID (domain is ignored)

  wchar_t *domain= new wchar_t[domain_size];
  m_data= (TOKEN_USER*) new BYTE[sid_size + sizeof(TOKEN_USER)];
  m_data->User.Sid= (BYTE*)m_data + sizeof(TOKEN_USER);

  success= LookupAccountNameW(NULL, account_name,
                             m_data->User.Sid, &sid_size,
                             domain, &domain_size,
                             &m_type);

  if (!success || !is_valid())
  {
#ifndef DBUG_OFF
    Error_message_buf error_buf;
    DBUG_PRINT("error", ("Could not determine SID of '%S', "
                         "LookupAccountName() failed with error %X (%s)",
                         account_name, GetLastError(),
                         get_last_error_message(error_buf)));
#endif
    goto fail;
  }

  goto end;

fail:
  if (m_data)
    delete [] m_data;
  m_data= NULL;

end:
  if (domain)
    delete [] domain;
}


/**
  Create Sid object corresponding to a given security token.

  @param[in]  token   security token of a Windows account

  @note In case of errors created object is invalid and its @c is_valid()
  method returns @c false.
*/

Sid::Sid(HANDLE token): m_data(NULL)
#ifndef DBUG_OFF
, m_as_string(NULL)
#endif
{
  DWORD             req_size= 0;
  bool              success;

  // Determine required buffer size

  success= GetTokenInformation(token, TokenUser, NULL, 0, &req_size);
  if (!success && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
  {
#ifndef DBUG_OFF
    Error_message_buf error_buf;
    DBUG_PRINT("error", ("Could not determine SID buffer size, "
                         "GetTokenInformation() failed with error %X (%s)",
                         GetLastError(), get_last_error_message(error_buf)));
#endif
    return;
  }

  m_data= (TOKEN_USER*) new BYTE[req_size];
  success= GetTokenInformation(token, TokenUser, m_data, req_size, &req_size);

  if (!success || !is_valid())
  {
    delete [] m_data;
    m_data= NULL;
#ifndef DBUG_OFF
    if (!success)
    {
      Error_message_buf error_buf;
      DBUG_PRINT("error", ("Could not read SID from security token, "
                           "GetTokenInformation() failed with error %X (%s)",
                           GetLastError(), get_last_error_message(error_buf)));
    }
#endif
  }
}


Sid::~Sid()
{
  if (m_data)
    delete [] m_data;
#ifndef DBUG_OFF
  if (m_as_string)
    LocalFree(m_as_string);
#endif
}

/// Check if Sid object is valid.
bool Sid::is_valid(void) const
{
  return m_data && m_data->User.Sid && IsValidSid(m_data->User.Sid);
}


#ifndef DBUG_OFF

/**
  Produces string representation of the SID.

  @return String representation of the SID or NULL in case of errors.

  @note Memory allocated for the string is automatically freed in Sid's
  destructor.
*/

const char* Sid::as_string()
{
  if (!m_data)
    return NULL;

  if (!m_as_string)
  {
    bool success= ConvertSidToStringSid(m_data->User.Sid, &m_as_string);

    if (!success)
    {
#ifndef DBUG_OFF
      Error_message_buf error_buf;
      DBUG_PRINT("error", ("Could not get textual representation of a SID, "
                           "ConvertSidToStringSid() failed with error %X (%s)",
                           GetLastError(), get_last_error_message(error_buf)));
#endif
      m_as_string= NULL;
      return NULL;
    }
  }

  return m_as_string;
}

#endif


bool Sid::operator ==(const Sid &other)
{
  if (!is_valid() || !other.is_valid())
    return false;

  return EqualSid(m_data->User.Sid, other.m_data->User.Sid);
}


/** Generating User Principal Name *************************/

/**
  Call Windows API functions to get UPN of the current user and store it
  in internal buffer.
*/

UPN::UPN(): m_buf(NULL)
{
  wchar_t  buf1[MAX_SERVICE_NAME_LENGTH];

  // First we try to use GetUserNameEx.

  m_len= sizeof(buf1)/sizeof(wchar_t);

  if (!GetUserNameExW(NameUserPrincipal, buf1, (PULONG)&m_len))
  {
    if (GetLastError())
    {
#ifndef DBUG_OFF
      Error_message_buf error_buf;
      DBUG_PRINT("note", ("When determining UPN"
                          ", GetUserNameEx() failed with error %X (%s)",
                          GetLastError(), get_last_error_message(error_buf)));
#endif
      if (ERROR_MORE_DATA == GetLastError())
        ERROR_LOG(INFO, ("Buffer overrun when determining UPN:"
                         " need %ul characters but have %ul",
                         m_len, sizeof(buf1)/sizeof(WCHAR)));
    }

    m_len= 0;   // m_len == 0 indicates invalid UPN
    return;
  }

  /*
    UPN is stored in buf1 in wide-char format - convert it to utf8
    for sending over network.
  */

  m_buf= wchar_to_utf8(buf1, &m_len);

  if(!m_buf)
    ERROR_LOG(ERROR, ("Failed to convert UPN to utf8"));

  // Note: possible error would be indicated by the fact that m_buf is NULL.
  return;
}


UPN::~UPN()
{
  if (m_buf)
    free(m_buf);
}


/**
  Convert a wide-char string to utf8 representation.

  @param[in]     string   null-terminated wide-char string to be converted
  @param[in,out] len      length of the string to be converted or 0; on
                          return length (in bytes, excluding terminating
                          null character) of the converted string

  If len is 0 then the length of the string will be computed by this function.

  @return Pointer to a buffer containing utf8 representation or NULL in
          case of error.

  @note The returned buffer must be freed with @c free() call.          
*/

char* wchar_to_utf8(const wchar_t *string, size_t *len)
{
  char   *buf= NULL; 
  size_t  str_len= len && *len ? *len : wcslen(string);

  /*
    A conversion from utf8 to wchar_t will never take more than 3 bytes per
    character, so a buffer of length 3 * str_len should be sufficient. 
    We check that assumption with an assertion later.
  */

  size_t  buf_len= 3 * str_len;

  buf= (char*)malloc(buf_len + 1);
  if (!buf)
  {
    DBUG_PRINT("error",("Out of memory when converting string '%S' to utf8",
                        string));
    return NULL;
  }

  int res= WideCharToMultiByte(CP_UTF8,              // convert to UTF-8
                               0,                    // conversion flags
                               string,               // input buffer
                               (int)str_len,         // its length
                               buf, (int)buf_len,         // output buffer and its size
                               NULL, NULL);          // default character (not used)

  if (res)
  {
    buf[res]= '\0';
    if (len)
      *len= res;
    return buf;
  }

  // res is 0 which indicates error

#ifndef DBUG_OFF
  Error_message_buf error_buf;
  DBUG_PRINT("error", ("Could not convert string '%S' to utf8"
                       ", WideCharToMultiByte() failed with error %X (%s)",
                       string, GetLastError(), 
                       get_last_error_message(error_buf)));
#endif

  // Let's check our assumption about sufficient buffer size
  DBUG_ASSERT(ERROR_INSUFFICIENT_BUFFER != GetLastError());

  return NULL;
}


/**
  Convert an utf8 string to a wide-char string.

  @param[in]     string   null-terminated utf8 string to be converted
  @param[in,out] len      length of the string to be converted or 0; on
                          return length (in chars) of the converted string

  If len is 0 then the length of the string will be computed by this function.

  @return Pointer to a buffer containing wide-char representation or NULL in
          case of error.

  @note The returned buffer must be freed with @c free() call.          
*/

wchar_t* utf8_to_wchar(const char *string, size_t *len)
{
  size_t buf_len;

  /* 
    Note: length (in bytes) of an utf8 string is always bigger than the
    number of characters in this string. Hence a buffer of size len will
    be sufficient. We add 1 for the terminating null character.
  */

  buf_len= len && *len ? *len : strlen(string);
  wchar_t *buf=  (wchar_t*)malloc((buf_len+1)*sizeof(wchar_t));

  if (!buf)
  {
    DBUG_PRINT("error",("Out of memory when converting utf8 string '%s'"
                        " to wide-char representation", string));
    return NULL;
  }

  size_t  res;
  res= MultiByteToWideChar(CP_UTF8,            // convert from UTF-8
                           0,                  // conversion flags
                           string,             // input buffer
                           (int)buf_len,            // its size
                           buf, (int)buf_len);      // output buffer and its size
  if (res)
  {
    buf[res]= '\0';
    if (len)
      *len= res;
    return buf;
  }

  // error in MultiByteToWideChar()

#ifndef DBUG_OFF
  Error_message_buf error_buf;
  DBUG_PRINT("error", ("Could not convert UPN from UTF-8"
                       ", MultiByteToWideChar() failed with error %X (%s)",
                       GetLastError(), get_last_error_message(error_buf)));
#endif

  // Let's check our assumption about sufficient buffer size
  DBUG_ASSERT(ERROR_INSUFFICIENT_BUFFER != GetLastError());

  return NULL;
}


/** Error handling ****************************************************/


/**
  Returns error message corresponding to the last Windows error given
  by GetLastError().

  @note Error message is overwritten by next call to
  @c get_last_error_message().
*/

const char* get_last_error_message(Error_message_buf buf)
{
  int error= GetLastError();

  buf[0]= '\0';
  FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
		NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR)buf, ERRMSG_BUFSIZE , NULL );

  return buf;
}