Merge lp:~qiush-summer/maria/krb_auth_plugin into lp:maria

Proposed by Shuang Qiu
Status: Needs review
Proposed branch: lp:~qiush-summer/maria/krb_auth_plugin
Merge into: lp:maria
Diff against target: 1703 lines (+1662/-0)
8 files modified
mysql-test/suite/plugins/r/kerberos.result (+12/-0)
mysql-test/suite/plugins/t/kerberos.test (+30/-0)
plugin/auth_kerberos/CMakeLists.txt (+24/-0)
plugin/auth_kerberos/README.md (+211/-0)
plugin/auth_kerberos/kerberos_client.c (+419/-0)
plugin/auth_kerberos/kerberos_common.c (+152/-0)
plugin/auth_kerberos/kerberos_common.h (+89/-0)
plugin/auth_kerberos/kerberos_server.c (+725/-0)
To merge this branch: bzr merge lp:~qiush-summer/maria/krb_auth_plugin
Reviewer Review Type Date Requested Status
Maria-captains Pending
Review via email: mp+188061@code.launchpad.net

Commit message

Enable Kerberos authentication on MariaDB.

Description of the change

This patch add Kerberos authentication support to MariaDB.

The authentication on *nix and Windows environment are featured.
Interoperation between hybrid OS environment is also allowed.

To post a comment you must log in.
3837. By Shuang Qiu

Change the licensing from GPL to BSD.

3838. By Shuang Qiu

Change the Kerberos authentication plugin licensing from GPL to BSD.

Unmerged revisions

3838. By Shuang Qiu

Change the Kerberos authentication plugin licensing from GPL to BSD.

3837. By Shuang Qiu

Change the licensing from GPL to BSD.

3836. By Shuang Qiu

remove deprecated working directory.

3835. By Shuang Qiu

add user manual for Kerberos authentication plugin.

3834. By Shuang Qiu

minor update.

3833. By Shuang Qiu

Cleanup kerberos_client.c, add cross-platform support to CMakeList.txt.

3832. By Shuang Qiu

update Kerberos unittest file and add unittest result.

3831. By Shuang Qiu

Dot is a valid alphabet in principal names.

3830. By Shuang Qiu

Normailize comments.

3829. By Shuang Qiu

Update Kerberos authentication test script.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'mysql-test/suite/plugins/r/kerberos.result'
2--- mysql-test/suite/plugins/r/kerberos.result 1970-01-01 00:00:00 +0000
3+++ mysql-test/suite/plugins/r/kerberos.result 2015-10-18 04:21:02 +0000
4@@ -0,0 +1,12 @@
5+INSTALL PLUGIN kerberos SONAME 'kerberos';
6+CREATE USER test_kerberos IDENTIFIED VIA kerberos AS 'MTR_KERBEROS_UPN';
7+SET GLOBAL kerberos_principal_name='MTR_KERBEROS_SPN';
8+#
9+# user principal is accepted, and authentication is successful.
10+#
11+SELECT USER();
12+USER()
13+test_kerberos@localhost
14+# connection is persistent.
15+DROP USER test_kerberos;
16+UNINSTALL PLUGIN kerberos;
17
18=== added file 'mysql-test/suite/plugins/t/kerberos.test'
19--- mysql-test/suite/plugins/t/kerberos.test 1970-01-01 00:00:00 +0000
20+++ mysql-test/suite/plugins/t/kerberos.test 2015-10-18 04:21:02 +0000
21@@ -0,0 +1,30 @@
22+
23+--source include/not_embedded.inc
24+
25+if (!$MTR_KERBEROS_ENABLED) {
26+ skip No Kerberos auth plugin;
27+}
28+
29+eval INSTALL PLUGIN kerberos SONAME 'kerberos';
30+--replace_result $MTR_KERBEROS_UPN MTR_KERBEROS_UPN
31+eval CREATE USER test_kerberos IDENTIFIED VIA kerberos AS '$MTR_KERBEROS_UPN';
32+
33+--replace_result $MTR_KERBEROS_SPN MTR_KERBEROS_SPN
34+eval SET GLOBAL kerberos_principal_name='$MTR_KERBEROS_SPN';
35+
36+let $plugindir=`SELECT @@global.plugin_dir`;
37+
38+--echo #
39+--echo # user principal is accepted, and authentication is successful.
40+--echo #
41+--exec echo "SELECT USER();" | $MYSQL_TEST -u test_kerberos --plugin-dir=$plugindir
42+--echo # connection is persistent.
43+
44+# --echo #
45+# --echo # athentication is unsuccessful
46+# --echo #
47+# --error 1
48+# --exec $MYSQL_TEST -u test_kerberos --plugin-dir=$plugindir
49+
50+DROP USER test_kerberos;
51+UNINSTALL PLUGIN kerberos;
52
53=== modified file 'mysys/charset.c' (properties changed: -x to +x)
54=== added directory 'plugin/auth_kerberos'
55=== added file 'plugin/auth_kerberos/CMakeLists.txt'
56--- plugin/auth_kerberos/CMakeLists.txt 1970-01-01 00:00:00 +0000
57+++ plugin/auth_kerberos/CMakeLists.txt 2015-10-18 04:21:02 +0000
58@@ -0,0 +1,24 @@
59+IF (WIN32)
60+ # on Windows-platform, secur32.lib is certainly there.
61+ SET(KRB_LIB secur32)
62+ELSE (WIN32)
63+ # check include file
64+ CHECK_INCLUDE_FILES("gssapi/gssapi.h;gssapi/gssapi_generic.h;gssapi/gssapi_krb5.h" HAVE_GSSAPI_H)
65+ IF (NOT HAVE_GSSAPI_H)
66+ RETURN()
67+ ENDIF (NOT HAVE_GSSAPI_H)
68+ # on non-Windows-platform, check the existence of library gssapi_krb5.
69+ FIND_LIBRARY(KRB_LIB gssapi_krb5)
70+ IF (NOT KRB_LIB)
71+ # library is not there on the PATH.
72+ message("gssapi_krb5 not found.")
73+ RETURN()
74+ ENDIF (NOT KRB_LIB)
75+ENDIF (WIN32)
76+
77+MYSQL_ADD_PLUGIN(kerberos kerberos_server.c kerberos_common.c
78+ LINK_LIBRARIES ${KRB_LIB}
79+ MODULE_ONLY COMPONENT SharedLibraries)
80+MYSQL_ADD_PLUGIN(kerberos_client kerberos_client.c kerberos_common.c
81+ LINK_LIBRARIES ${KRB_LIB}
82+ MODULE_ONLY COMPONENT SharedLibraries)
83
84=== added file 'plugin/auth_kerberos/README.md'
85--- plugin/auth_kerberos/README.md 1970-01-01 00:00:00 +0000
86+++ plugin/auth_kerberos/README.md 2015-10-18 04:21:02 +0000
87@@ -0,0 +1,211 @@
88+## Kerberos Authentication Plugin for MariaDB
89+
90+This article gives instructions on configuring Kerberos authentication
91+plugin for MariaDB. With Kerberos authentication plugin enabled, you can
92+login MariaDB as a Kerberos domain user passwordlessly.
93+
94+### System Settings
95+
96+To use the full feature of Kerberos authentication plugin, make sure a
97+Kerberos authentication domain is properly set up.
98+
99+* For a pure *nix Kerberos domain, an [MIT Kerberos Key Distribution Centre][1]
100+(krb5kdc service) should be running.
101+
102+* As to a pure Windows domain, an active directory domain controller should
103+be connectable.
104+
105+* Hybrid Kerberos domains are also allowed.
106+
107+Detailed guides to set up a Kerberos authentication domain is beyond the scope
108+of this document. You can refer to the links in the References section on
109+how to setup a Kerberos authentication domain.
110+
111+### Compile
112+
113+The compilation of Kerberos authentication plugin has been integrated into
114+the CMake building framework of MariaDB.
115+
116+##### *nix
117+
118+If you are a *nix user, guarantee the Kerberos libraries and headers are
119+installed.
120+
121+##### Windows
122+
123+The Windows version requires no additional libraries.
124+
125+The Kerberos authentication plugin is separated into two isolated parts:
126+the server-side plugin and client-side plugin. If no errors occur
127+during compilation, two shared libraries will be created, `kerberos` and
128+`kerberos_client` for server-side and client-side plugin respectively.
129+
130+### Installation
131+
132+Install the server-side Kerberos authentication plugin before used.
133+Client-side plugin will be automatically loaded when required.
134+
135+Connect MariaDB server as a superuser, then issue the command
136+
137+ INSTALL PLUGIN kerberos SONAME 'kerberos';
138+
139+This will instruct MariaDB server to load the Kerberos authentication plugin.
140+
141+### Set a Service Principal Name to MariaDB
142+
143+Before we can authenticate against Kerberos service, the last step is assign
144+a service principal name to MariaDB server.
145+
146+#### Figure out a Valid Principal Name
147+
148+Services and users are identified via principal names internally in Kerberos
149+authentication service. Generally, a principal name is in the format of
150+`username/hostname@DOMAIN.NAME`.
151+
152+##### *nix
153+
154+For Kerberos services on *nix platform, a user specified service principal
155+name is required, since no default principal name can be derived from the
156+effective user of the server process.
157+
158+##### Windows
159+
160+As to Windows Active Directory services
161+
162+* if MariaDB is running as `NetworkService` by default, the principal name
163+is akin `host$@DOMAIN.NAME`. In this case, no need to specify service
164+principal name manually. The Kerberos plugin can derive a valid default
165+service principal name.
166+
167+* Otherwise, if you run MariaDB as a customized domain user, the principal
168+name is `username@DOMAIN.NAME` by default. In this case, no need to specify
169+service principal name manually. The Kerberos plugin can derive a valid
170+default service principal name.
171+
172+* Finally, if a principal name is preferred over the default `username@DOMAIN`,
173+feel free to update it with the `setspn.exe` tool. Correspondingly, the
174+new principal name must be specified in the MariaDB configuration file to
175+have it work.
176+
177+A valid Kerberos principal name is case sensitive in both *nix and Windows.
178+For example, on *nix platform a valid service principal name is like
179+`MySQL/localhost@EXAMPLE.COM` (capitalised realm name is recommended by
180+Kerberos service) or `MySQL@EXAMPLE.COM` on Windows (`hostname` is not
181+emphasised).
182+
183+#### Assign the Principal Name
184+
185+To assign a service principal name to server, Kerberos authentication plugin
186+exposes a system variable `named kerberos_principal_name`.
187+
188+One can specify the name (say, `MySQL/localhost@EXAMPLE.COM`) in three ways:
189+
190+* Specify the name in configure file: edit local configure file ~/.my.cnf by
191+inserting the line `kerberos_principal_name=MySQL/localhost@EXAMPLE.COM` in the
192+server section. * Pass as command line parameter: start MariaDB server with
193+command line parameter `--kerberos_principal_name=MySQL/localhost@EXAMPLE.COM`.
194+* If you can login MariaDB as a superuser, you can set by the following
195+commands: `SET GLOBAL kerberos_principal_name='MySQL/localhost@EXAMPLE.COM';`
196+The parameter should be set each time after the service restarts.
197+
198+You can verify service principal name is properly set by
199+
200+ SELECT @@global.kerberos_principal_name;
201+
202+#### Create New MariaDB Users
203+
204+If all the steps above are completed, you can now create a new user identified
205+via Kerberos authentication plugin.
206+
207+ CREATE USER user_name IDENTIFIED VIA kerberos AS 'user_principal_name';
208+
209+We need the `AS` clause to specify the principal name instead of embedded
210+into `user_name` directly for the length gap between MariaDB username and
211+Kerberos principal name.
212+
213+#### Connect and Login as Kerboers Principal
214+
215+To connect to MariaDB as `user_name`, first check a valid Kerberos tgt ticket
216+is cached with `klist`. You can obtain a tgt ticket either by login as a
217+domain user on Windows platform or by `kinit principal_name` on Linux box.
218+
219+If all these steps are done, you should now connect to MariaDB as `user_name`
220+successfully.
221+
222+### Run Test Suite
223+
224+This section describes how to run unit test for Kerberos authentication plugin.
225+In case of setting up a Kerberos domain, which is tedious to an ordinary user,
226+the unit test for Kerberos authentication plugin is by default skipped.
227+
228+To run the unit test, an OS environment variable `MTR_KERBEROS_ENABLED`
229+should be set to a valid value
230+
231+ MTR_KERBEROS_ENABLED=1
232+
233+Two more OS environment variables `MTR_KERBEROS_SPN` and `MTR_KERBEROS_UPN`
234+should be set for MariaDB service principal name and login user principal
235+name respectively.
236+
237+#### Extending the Ticket Lifetime
238+
239+Make sure the tgt ticket is not expired for login user when running the
240+unit test. In case early expiration of the ticket, we can extend the ticket
241+lifetime in configuration.
242+
243+##### *nix
244+
245+You can extend the ticket lifetime by editing `/etc/krb5.conf` in *nix by
246+updating two parameter `ticket_lifetime` and `renew_lifetime`.
247+
248+##### Windows
249+
250+Extending ticket lifetime can also be done within several clicks in Windows,
251+here is a step-by-step instruction:
252+
253+ 1. Open "Group Policy Management" (Start -> All Programs -> Administrative
254+ Tools -> Group Policy Management). 2. Select default domain (Domains
255+ -> default.domain -> Domain Controllers -> Default Domain Controllers;
256+ then on the right detail panel Settings -> Policies -> Windows Settings
257+ -> Security Settings -> right click "Local Policies/Security Options"
258+ -> Edit... to open Group Policy Management Editor). 3. Then in the GPM
259+ Editor Default Domain Controllers Policy -> Computer Configure -> Polices
260+ -> Windows Settings -> Security Settings -> Account Policies -> Kerberos
261+ Policies -> Maximum lifetime for service/user ticket.
262+
263+Once the tgt ticket is expired, on Linux use command `kinit -R` to renew
264+the ticket, while on Windows, one should logout and logon again.
265+
266+### Trouble Shoot Authentication Problems
267+
268+ 1. Check the Kerberos KDC log first, on Linux it is `/var/log/krb5kdc.log`
269+ by default or specified in /etc/krb5.conf. On Windows, steps are: Start
270+ -> Administrative Tools -> Event Viewer, Windows Logs -> Security, to see
271+ whether the ticket to requested service has been issued. If not issued,
272+ verify whether both service principal name and user principal name are
273+ correct. In addition, corresponding principals have been created in the
274+ KDC server. 2. If tickets are issued, while authentication still fails
275+ and you're on *nix box, make sure the initial credential to the service
276+ principal's ticket is saved in the keytab. What's more, the keytab can be
277+ read by Kerberos KDC.
278+
279+### References
280+
281+* [MIT Kerberos Official Documents for Administrators][2]
282+* [A Step-by-step Guide for Windows Server 2008 Domain Controller and DNS Setup][3]
283+* [Add Windows Box to a *nix krb5kdc Domain][5]
284+* [Authenticate Linux Clients with Active Directory][6]
285+* [Install Plugin in MariaDB][7]
286+* [Kerberos Principal Name Specification][8]
287+* [Renew a ticket on Windows][9]
288+* [\*nix krenew command: Extend Ticket Lifetime on \*nix][10]
289+
290+[1]: http://web.mit.edu/kerberos/
291+[2]: http://web.mit.edu/kerberos/krb5-latest/doc/admin/index.html
292+[3]: http://www.windowsreference.com/windows-server-2008/step-by-step-guide-for-windows-server-2008-domain-controller-and-dns-server-setup/
293+[5]: http://social.technet.microsoft.com/wiki/contents/articles/2751.kerberos-interoperability-step-by-step-guide-for-windows-server-2003.aspx#Using_an_MIT_KDC_with_a_Stand-alone_Windows_Server_TwentyOhThree_Client
294+[6]: http://technet.microsoft.com/en-us/magazine/2008.12.linux.aspx
295+[7]: https://kb.askmonty.org/en/plugin-overview/#installing-plugins
296+[8]: http://pic.dhe.ibm.com/infocenter/iseries/v6r1m0/index.jsp?topic=/cl/addkrbtkt.htm
297+[9]: http://technet.microsoft.com/en-us/library/cc738673(v=ws.10).aspx#w2k8tr_kerb_tools_iybi
298+[10]: http://stackoverflow.com/questions/14682153/lifetime-of-kerberos-tickets#15457265
299
300=== added file 'plugin/auth_kerberos/kerberos_client.c'
301--- plugin/auth_kerberos/kerberos_client.c 1970-01-01 00:00:00 +0000
302+++ plugin/auth_kerberos/kerberos_client.c 2015-10-18 04:21:02 +0000
303@@ -0,0 +1,419 @@
304+/* Copyright (c) 2015, Shuang Qiu
305+ All rights reserved.
306+
307+ Redistribution and use in source and binary forms, with or without
308+ modification, are permitted provided that the following conditions are met:
309+
310+ 1. Redistributions of source code must retain the above copyright notice,
311+ this list of conditions and the following disclaimer.
312+
313+ 2. Redistributions in binary form must reproduce the above copyright notice,
314+ this list of conditions and the following disclaimer in the documentation
315+ and/or other materials provided with the distribution.
316+
317+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
318+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
319+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
320+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
321+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
322+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
323+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
324+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
325+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
326+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
327+ POSSIBILITY OF SUCH DAMAGE.
328+*/
329+
330+/**
331+ @file
332+
333+ Kerberos server authentication plugin
334+
335+ kerberos_server is a general purpose server authentication plugin, it
336+ authenticates user against Kerberos principal.
337+
338+ This is the client side implementation.
339+*/
340+#include <stdarg.h>
341+#include <mysqld_error.h>
342+#include <mysql/client_plugin.h>
343+#include "kerberos_common.h"
344+
345+#define KERBEROS_UNKNOWN_ERROR "HY000"
346+#define KERBEROS_OUTOFMEMORY_ERROR "HY001"
347+#define KERBEROS_NET_ERROR_ON_WRITE "08S01"
348+#define KERBEROS_NET_READ_ERROR "08S01"
349+
350+/**
351+ * set client error message
352+ */
353+static void set_krb_client_auth_error(MYSQL *, int, const char *, const char *, ...);
354+
355+#ifdef _WIN32
356+static int sspi_kerberos_auth_client(const char *spn, MYSQL *mysql,
357+ MYSQL_PLUGIN_VIO *vio)
358+{
359+ int read_len= 0;
360+ int max_token_sz= SSPI_MAX_TOKEN_SIZE;
361+ int ret= 0; /* return code */
362+ const char *err_msg= NULL;
363+ /* SSPI related */
364+ BOOL have_ctxt= FALSE;
365+ BOOL have_cred= FALSE;
366+ BOOL have_input= FALSE;
367+ BOOL context_established= FALSE;
368+ ULONG attribs= 0;
369+ TimeStamp lifetime;
370+
371+ SECURITY_STATUS ss;
372+ CredHandle cred_handle; /* credential handle */
373+ CtxtHandle ctxt_handle; /* security context */
374+
375+ SecPkgInfo *sec_pkg_info; /* package information */
376+ SecBufferDesc input_buf_desc;
377+ SecBuffer input_buf;
378+ SecBufferDesc output_buf_desc;
379+ SecBuffer output_buf;
380+ PBYTE output_con;
381+
382+ /* query package information */
383+ ss= QuerySecurityPackageInfo(SECURITY_PACKAGE_NAME, &sec_pkg_info);
384+ if (ss == SEC_E_OK)
385+ {
386+ max_token_sz= sec_pkg_info->cbMaxToken;
387+ }
388+
389+ /* allocate memory */
390+ output_con= (PBYTE) calloc(max_token_sz, sizeof(BYTE));
391+ if (!output_con)
392+ {
393+ set_krb_client_auth_error(mysql, ER_OUTOFMEMORY, \
394+ KERBEROS_OUTOFMEMORY_ERROR, \
395+ "Kerberos: insufficient memory to allocate for output token buffer.");
396+ return CR_ERROR;
397+ }
398+
399+ /* acquire credentials */
400+ ss= AcquireCredentialsHandle(
401+ NULL,
402+ SECURITY_PACKAGE_NAME,
403+ SECPKG_CRED_OUTBOUND,
404+ NULL,
405+ NULL,
406+ NULL,
407+ NULL,
408+ &cred_handle,
409+ &lifetime);
410+ if (SEC_ERROR(ss))
411+ {
412+ err_msg= error_msg(ss);
413+ set_krb_client_auth_error(mysql, ER_UNKNOWN_ERROR,
414+ KERBEROS_UNKNOWN_ERROR, err_msg);
415+ return CR_ERROR;
416+ }
417+ else
418+ {
419+ have_cred= TRUE;
420+ }
421+
422+ /* prepare input buffer */
423+ input_buf_desc.ulVersion= SECBUFFER_VERSION;
424+ input_buf_desc.cBuffers= 1;
425+ input_buf_desc.pBuffers= &input_buf;
426+ input_buf.BufferType= SECBUFFER_TOKEN;
427+ input_buf.cbBuffer= 0;
428+ input_buf.pvBuffer= NULL;
429+
430+ /* prepare output buffer */
431+ output_buf_desc.ulVersion= SECBUFFER_VERSION;
432+ output_buf_desc.cBuffers= 1;
433+ output_buf_desc.pBuffers= &output_buf;
434+
435+ output_buf.BufferType= SECBUFFER_TOKEN;
436+ output_buf.cbBuffer= max_token_sz;
437+ output_buf.pvBuffer= output_con;
438+
439+ do
440+ {
441+ ss= InitializeSecurityContext(
442+ &cred_handle, /* credential handle */
443+ have_ctxt?&ctxt_handle:NULL, /* context handle */
444+ (SEC_CHAR *) spn, /* target principal name */
445+ 0, /* no special attributes req-ed*/
446+ 0, /* reserved */
447+ SECURITY_NATIVE_DREP,
448+ have_input ? &input_buf_desc : NULL,
449+ 0, /* reserved */
450+ &ctxt_handle,
451+ &output_buf_desc,
452+ &attribs,
453+ &lifetime);
454+ /* reset used flag */
455+ have_input= FALSE;
456+
457+ if (output_buf.cbBuffer)
458+ {
459+ /* send credential to server */
460+ if (vio->write_packet(vio,
461+ (const unsigned char *) output_buf.pvBuffer,
462+ output_buf.cbBuffer))
463+ {
464+ ret= CR_ERROR;
465+ set_krb_client_auth_error(mysql, ER_NET_ERROR_ON_WRITE,
466+ KERBEROS_NET_ERROR_ON_WRITE,
467+ "Kerberos: fail send credentials to server.");
468+ goto cleanup;
469+ }
470+ }
471+
472+ if (SEC_ERROR(ss))
473+ {
474+ err_msg= error_msg(ss);
475+ ret= CR_ERROR;
476+ set_krb_client_auth_error(mysql, ER_UNKNOWN_ERROR,
477+ KERBEROS_UNKNOWN_ERROR, err_msg);
478+ goto cleanup;
479+ }
480+
481+ have_ctxt= TRUE;
482+
483+ if ((ss == SEC_I_COMPLETE_NEEDED) ||
484+ (ss == SEC_I_COMPLETE_AND_CONTINUE))
485+ {
486+ ss= CompleteAuthToken(&ctxt_handle, &output_buf_desc);
487+
488+ if (SEC_ERROR(ss))
489+ {
490+ err_msg= error_msg(ss);
491+ ret= CR_ERROR;
492+ set_krb_client_auth_error(mysql, ER_UNKNOWN_ERROR,
493+ KERBEROS_UNKNOWN_ERROR, err_msg);
494+ goto cleanup;
495+ }
496+ }
497+
498+ context_established= !((ss == SEC_I_CONTINUE_NEEDED) ||
499+ (ss == SEC_I_COMPLETE_AND_CONTINUE));
500+ if (!context_established)
501+ {
502+ read_len= vio->read_packet(vio, (unsigned char **) &input_buf.pvBuffer);
503+ if (read_len < 0)
504+ {
505+ ret= CR_ERROR;
506+ set_krb_client_auth_error(mysql, ER_NET_READ_ERROR,
507+ KERBEROS_NET_READ_ERROR,
508+ "Kerberos: fail to read credential from server.");
509+ goto cleanup;
510+ }
511+ else
512+ {
513+ input_buf.cbBuffer= read_len;
514+ have_input= TRUE;
515+ }
516+ }
517+
518+ output_buf.cbBuffer= max_token_sz;
519+ } while (!context_established);
520+
521+ ret= CR_OK;
522+
523+cleanup:
524+ /* free dynamic memory */
525+ if (have_ctxt) DeleteSecurityContext(&ctxt_handle);
526+ free(output_con);
527+ ss= FreeContextBuffer(sec_pkg_info);
528+ if (ss != SEC_E_OK)
529+ {
530+ set_krb_client_auth_error(mysql, ER_UNKNOWN_ERROR,
531+ KERBEROS_UNKNOWN_ERROR,
532+ "Kerberos: fail to free SecurityPackageInfo object.");
533+ }
534+
535+ return ret;
536+}
537+#else /* !_WIN32 */
538+static int gssapi_kerberos_auth_client(const char *spn, MYSQL *mysql,
539+ MYSQL_PLUGIN_VIO *vio)
540+{
541+ int r_len= 0; /* packet read length */
542+ int context_established= 0; /* indicate ctxt avail */
543+ int rc= CR_OK;
544+ int have_cred= FALSE;
545+ int have_ctxt= FALSE;
546+ int have_name= FALSE;
547+ const char *err_msg= NULL;
548+ /* GSSAPI related fields */
549+ OM_uint32 major= 0, minor= 0;
550+ gss_name_t service_name;
551+ gss_ctx_id_t ctxt;
552+ gss_cred_id_t cred= GSS_C_NO_CREDENTIAL; /* use default credential */
553+ gss_buffer_desc spn_buf, input, output;
554+
555+ /* import principal from plain text */
556+ /* initialize plain text service principal name */
557+ spn_buf.length= strlen(spn);
558+ spn_buf.value= (void *) spn;
559+ /* import service principal */
560+ major= gss_import_name(&minor,
561+ &spn_buf,
562+ (gss_OID) gss_nt_user_name,
563+ &service_name);
564+ /* gss_import_name error checking */
565+ if (GSS_ERROR(major))
566+ {
567+ err_msg= error_msg(major, minor);
568+ rc= CR_ERROR;
569+ set_krb_client_auth_error(mysql, ER_UNKNOWN_ERROR,
570+ KERBEROS_UNKNOWN_ERROR, err_msg);
571+ goto cleanup;
572+ }
573+ have_name= TRUE;
574+
575+ /* initial context */
576+ ctxt= GSS_C_NO_CONTEXT;
577+ input.length= 0;
578+ input.value= NULL;
579+
580+ while (!context_established)
581+ {
582+ major= gss_init_sec_context(&minor,
583+ cred,
584+ &ctxt,
585+ service_name,
586+ GSS_C_NO_OID, /* for an impl-spec mech */
587+ 0, /* no flags requested */
588+ 0, /* request default time */
589+ GSS_C_NO_CHANNEL_BINDINGS,
590+ &input, /* token input */
591+ NULL, /* actual mech */
592+ &output, /* token output */
593+ NULL, /* actual flags */
594+ NULL); /* actual valid time */
595+
596+ if (output.length)
597+ {
598+ /* send credential */
599+ if (vio->write_packet(vio, output.value, output.length))
600+ {
601+ gss_release_buffer(&minor, &output);
602+ rc= CR_ERROR;
603+ set_krb_client_auth_error(mysql, ER_NET_ERROR_ON_WRITE,
604+ KERBEROS_NET_ERROR_ON_WRITE,
605+ "Kerberos: fail to send credential to server.");
606+ goto cleanup;
607+ }
608+ gss_release_buffer(&minor, &output);
609+ }
610+
611+ if (GSS_ERROR(major))
612+ {
613+ /* fatal error */
614+ if (ctxt != GSS_C_NO_CONTEXT)
615+ {
616+ gss_delete_sec_context(&minor,
617+ &ctxt,
618+ GSS_C_NO_BUFFER);
619+ }
620+ err_msg= error_msg(major, minor);
621+ rc= CR_ERROR;
622+ set_krb_client_auth_error(mysql, ER_UNKNOWN_ERROR,
623+ KERBEROS_UNKNOWN_ERROR, err_msg);
624+ goto cleanup;
625+ }
626+
627+ if (major & GSS_S_CONTINUE_NEEDED)
628+ {
629+ r_len= vio->read_packet(vio, (unsigned char **) &input.value);
630+ if (r_len < 0)
631+ {
632+ rc= CR_ERROR;
633+ set_krb_client_auth_error(mysql, ER_NET_READ_ERROR,
634+ KERBEROS_NET_READ_ERROR,
635+ "Error read credential packet from server.");
636+ goto cleanup;
637+ }
638+ }
639+ else
640+ {
641+ context_established= 1;
642+ }
643+ }
644+
645+cleanup:
646+ if (have_name) gss_release_name(&minor, &service_name);
647+ if (have_ctxt) gss_delete_sec_context(&minor, &ctxt, GSS_C_NO_BUFFER);
648+ if (have_cred) gss_release_cred(&minor, &cred);
649+
650+ return rc;
651+}
652+#endif /* _WIN32 */
653+
654+/**
655+ * The main client function of the Kerberos plugin.
656+ */
657+static int kerberos_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql)
658+{
659+ int r_len= 0;
660+ int rc= CR_OK;
661+ /* service principal name */
662+ char *spn= NULL;
663+ char *spn_buff= (char *) malloc(PRINCIPAL_NAME_LEN);
664+
665+ /* read from server for service principal name */
666+ r_len= vio->read_packet(vio, (unsigned char **) &spn);
667+ if (r_len < 0)
668+ {
669+ set_krb_client_auth_error(mysql, ER_NET_READ_ERROR,
670+ KERBEROS_NET_READ_ERROR,
671+ "Kerberos: fail to read service principal name.");
672+
673+ return CR_ERROR;
674+ }
675+ strncpy(spn_buff, spn, PRINCIPAL_NAME_LEN);
676+
677+ rc =
678+#ifdef _WIN32
679+ sspi_kerberos_auth_client((const char *) spn_buff, mysql, vio);
680+#else /* !_WIN32 */
681+ gssapi_kerberos_auth_client((const char *) spn_buff, mysql, vio);
682+#endif /* _WIN32 */
683+
684+ free(spn_buff);
685+ return rc;
686+}
687+
688+/**
689+ * set client error message.
690+ * Param:
691+ * mysql connection handle
692+ * errno extended error number
693+ * format error message template
694+ * ... variable argument list
695+ */
696+static void set_krb_client_auth_error(MYSQL *mysql, int errcode,
697+ const char *sqlstate, const char *format, ...)
698+{
699+ NET *net= &mysql->net;
700+ va_list args;
701+
702+ net->last_errno= errcode;
703+ va_start(args, format);
704+ vsnprintf(net->last_error, sizeof(net->last_error) - 1,
705+ format, args);
706+ va_end(args);
707+ memcpy(net->sqlstate, sqlstate, sizeof(net->sqlstate));
708+}
709+
710+/* register client plugin */
711+mysql_declare_client_plugin(AUTHENTICATION)
712+ "kerberos_client",
713+ "Shuang Qiu",
714+ "Kerberos based authentication",
715+ {0, 1, 0},
716+ "BSD",
717+ NULL,
718+ NULL,
719+ NULL,
720+ NULL,
721+ kerberos_auth_client
722+mysql_end_client_plugin;
723
724=== added file 'plugin/auth_kerberos/kerberos_common.c'
725--- plugin/auth_kerberos/kerberos_common.c 1970-01-01 00:00:00 +0000
726+++ plugin/auth_kerberos/kerberos_common.c 2015-10-18 04:21:02 +0000
727@@ -0,0 +1,152 @@
728+/* Copyright (c) 2015, Shuang Qiu
729+ All rights reserved.
730+
731+ Redistribution and use in source and binary forms, with or without
732+ modification, are permitted provided that the following conditions are met:
733+
734+ 1. Redistributions of source code must retain the above copyright notice,
735+ this list of conditions and the following disclaimer.
736+
737+ 2. Redistributions in binary form must reproduce the above copyright notice,
738+ this list of conditions and the following disclaimer in the documentation
739+ and/or other materials provided with the distribution.
740+
741+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
742+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
743+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
744+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
745+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
746+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
747+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
748+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
749+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
750+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
751+ POSSIBILITY OF SUCH DAMAGE.
752+*/
753+
754+/**
755+ @file
756+
757+ Kerberos authentication utilities
758+
759+ Utility functions are defined.
760+*/
761+#include "kerberos_common.h"
762+
763+#ifdef _WIN32
764+/* translate status code to error message */
765+const char *error_msg(SECURITY_STATUS ss)
766+{
767+ const char *err_msg= NULL;
768+
769+ switch (ss)
770+ {
771+ case SEC_E_INSUFFICIENT_MEMORY:
772+ err_msg= "Kerberos: insufficient memory to complete the request.";
773+ break;
774+ case SEC_E_INTERNAL_ERROR:
775+ err_msg= "Kerberos: an internal error occurs.";
776+ break;
777+ case SEC_E_NO_CREDENTIALS:
778+ err_msg= "Kerberos: no credentials are available.";
779+ break;
780+ case SEC_E_NOT_OWNER:
781+ err_msg= "Kerberos: necessary credentials to "
782+ "acquire the new credential are not found.";
783+ break;
784+ case SEC_E_UNKNOWN_CREDENTIALS:
785+ err_msg= "Kerberos: credentials supplied were not recognized.";
786+ break;
787+ case SEC_E_INVALID_HANDLE:
788+ err_msg= "Kerberos: an invalid handle is provided.";
789+ break;
790+ case SEC_E_INVALID_TOKEN:
791+ err_msg= "Kerberos: an invalid token is provided.";
792+ break;
793+ case SEC_E_LOGON_DENIED:
794+ err_msg= "Kerberos: logon as specified principal failed.";
795+ break;
796+ case SEC_E_NO_AUTHENTICATING_AUTHORITY:
797+ err_msg= "Kerberos: the domain name is invalid.";
798+ break;
799+ case SEC_E_TARGET_UNKNOWN:
800+ err_msg= "Kerberos: the target principal is unknown.";
801+ break;
802+ case SEC_E_WRONG_PRINCIPAL:
803+ err_msg= "Kerberos: the target principle does not "
804+ "match with expected one.";
805+ break;
806+ case SEC_E_TIME_SKEW:
807+ err_msg= "Kerberos: a time skew is detected.";
808+ break;
809+ default:
810+ err_msg = "Kerberos: unknown error.";
811+ break;
812+ }
813+
814+ return err_msg;
815+}
816+#else /* _WIN32 */
817+#define ERR_MSG_BUF_LEN 1024
818+static char err_msg_buf[ERR_MSG_BUF_LEN];
819+
820+const char *error_msg(OM_uint32 major, OM_uint32 minor __attribute__((unused)))
821+{
822+ const char *err_msg= NULL;
823+
824+ switch (major)
825+ {
826+ case GSS_S_BAD_NAMETYPE:
827+ case GSS_S_BAD_NAME:
828+ err_msg= "Kerberos: input name could not be recognied.";
829+ break;
830+ case GSS_S_BAD_MECH:
831+ err_msg= "Kerberos: a bad mechanism is requested.";
832+ break;
833+ case GSS_S_CREDENTIALS_EXPIRED:
834+ err_msg = "Kerberos: the credentials could not be acquired "
835+ "for expiration.";
836+ break;
837+ case GSS_S_NO_CRED:
838+ err_msg = "Kerberos: no credentials were found for the specified name.";
839+ break;
840+ case GSS_S_DEFECTIVE_TOKEN:
841+ err_msg = "Kerberos: consistency checks performed on "
842+ "the input token failed.";
843+ break;
844+ case GSS_S_DEFECTIVE_CREDENTIAL:
845+ err_msg = "Kerberos: consistency checks performed on "
846+ "the credential failed.";
847+ break;
848+ case GSS_S_BAD_BINDINGS:
849+ err_msg = "Kerberos: the input token contains "
850+ "different channel bindings as specified.";
851+ break;
852+ case GSS_S_NO_CONTEXT:
853+ err_msg = "Kerberos: the supplied context handle is invalid.";
854+ break;
855+ case GSS_S_BAD_SIG:
856+ err_msg = "Kerberos: input token contains an invalid MIC.";
857+ break;
858+ case GSS_S_OLD_TOKEN:
859+ err_msg = "Kerberos: input token is too old.";
860+ break;
861+ case GSS_S_DUPLICATE_TOKEN:
862+ err_msg = "Kerberos: input token is a duplicate of a token "
863+ "already processed.";
864+ break;
865+ case GSS_S_FAILURE:
866+ snprintf(err_msg_buf, ERR_MSG_BUF_LEN,
867+ "Kerberos: undefined Kerberos error. "
868+ "Make sure a valid ticket-grant-ticket is acquired "
869+ "and refer minor error code %d for details.", minor);
870+ err_msg = err_msg_buf;
871+ break;
872+ default:
873+ err_msg = "Kerberos: unknown error.";
874+ break;
875+ }
876+
877+ return err_msg;
878+}
879+#endif /* _WIN32 */
880
881=== added file 'plugin/auth_kerberos/kerberos_common.h'
882--- plugin/auth_kerberos/kerberos_common.h 1970-01-01 00:00:00 +0000
883+++ plugin/auth_kerberos/kerberos_common.h 2015-10-18 04:21:02 +0000
884@@ -0,0 +1,89 @@
885+/* Copyright (c) 2015, Shuang Qiu
886+ All rights reserved.
887+
888+ Redistribution and use in source and binary forms, with or without
889+ modification, are permitted provided that the following conditions are met:
890+
891+ 1. Redistributions of source code must retain the above copyright notice,
892+ this list of conditions and the following disclaimer.
893+
894+ 2. Redistributions in binary form must reproduce the above copyright notice,
895+ this list of conditions and the following disclaimer in the documentation
896+ and/or other materials provided with the distribution.
897+
898+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
899+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
900+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
901+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
902+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
903+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
904+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
905+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
906+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
907+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
908+ POSSIBILITY OF SUCH DAMAGE.
909+*/
910+
911+/**
912+ @file
913+
914+ Kerberos authentication utilities
915+
916+ Utility functions are declared.
917+*/
918+#ifndef KERBEROS_COMMON_H
919+#define KERBEROS_COMMON_H
920+
921+#include <my_sys.h>
922+#include <my_global.h>
923+#include <mysql.h>
924+#include <mysql/plugin_auth_common.h>
925+#include <ctype.h>
926+#include <string.h>
927+
928+/* global define directives */
929+#define PRINCIPAL_NAME_LEN 256 /* TODO need a reference */
930+
931+#define MF_ERROR MYF(0)
932+#define MF_WARNING MYF(1)
933+
934+/* platform dependent header */
935+#ifdef _WIN32
936+ /* on Windows platform, SSPI is used to perform the authentication */
937+ #include <windows.h>
938+
939+ #define SECURITY_WIN32 /* User-mode SSPI application */
940+ #include <Security.h>
941+ #include <SecExt.h>
942+ #include <sspi.h>
943+#else /* !_WIN32 */
944+ /**
945+ * on other platform, make sure the Kerberos environment is pre-configured
946+ * GSSAPI is used for inter-operation purpose between Windows platform
947+ */
948+ #include <gssapi/gssapi.h>
949+ #include <gssapi/gssapi_generic.h>
950+ #include <gssapi/gssapi_krb5.h>
951+#endif /* _WIN32 */
952+
953+/* platform dependent define directives */
954+#ifdef _WIN32
955+ #define UNUSED(x) __pragma(warning(suppress:4100)) x /* warns suppressor */
956+#else
957+ #define UNUSED(x) x __attribute__((unused))
958+#endif /* _WIN32 */
959+
960+#ifdef _WIN32
961+ #define SECURITY_PACKAGE_NAME "Kerberos"
962+ #define SSPI_MAX_TOKEN_SIZE 12000
963+
964+ #define SEC_ERROR(ss) ((ss) < 0)
965+
966+ /* translate SECURITY_STATUS to error text */
967+ const char *error_msg(SECURITY_STATUS);
968+#else /* _WIN32 */
969+ /* translate symbolic error number to text error message */
970+ const char *error_msg(OM_uint32, OM_uint32);
971+#endif /* _WIN32 */
972+
973+#endif /* KERBEROS_COMMON_H */
974
975=== added file 'plugin/auth_kerberos/kerberos_server.c'
976--- plugin/auth_kerberos/kerberos_server.c 1970-01-01 00:00:00 +0000
977+++ plugin/auth_kerberos/kerberos_server.c 2015-10-18 04:21:02 +0000
978@@ -0,0 +1,725 @@
979+/* Copyright (c) 2015, Shuang Qiu
980+ All rights reserved.
981+
982+ Redistribution and use in source and binary forms, with or without
983+ modification, are permitted provided that the following conditions are met:
984+
985+ 1. Redistributions of source code must retain the above copyright notice,
986+ this list of conditions and the following disclaimer.
987+
988+ 2. Redistributions in binary form must reproduce the above copyright notice,
989+ this list of conditions and the following disclaimer in the documentation
990+ and/or other materials provided with the distribution.
991+
992+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
993+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
994+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
995+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
996+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
997+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
998+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
999+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
1000+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
1001+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
1002+ POSSIBILITY OF SUCH DAMAGE.
1003+*/
1004+
1005+/**
1006+ @file
1007+
1008+ Kerberos server authentication plugin
1009+
1010+ kerberos_server is a general purpose server authentication plugin, it
1011+ authenticates user against Kerberos principal.
1012+
1013+ This is the server side implementation.
1014+*/
1015+#include <mysql/plugin_auth.h>
1016+
1017+#include "kerberos_common.h"
1018+
1019+#define KRB_SERVER_AUTH_ERROR 1
1020+
1021+/* plugin global variables */
1022+char *kerberos_principal_name; /* system-wise declaration for spn */
1023+/**
1024+ * underlying storage for Kerberos service principal name system variable
1025+ */
1026+static char kerberos_spn_storage[PRINCIPAL_NAME_LEN];
1027+
1028+#ifdef _WIN32
1029+static int sspi_kerberos_auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info) {
1030+ int read_len= 0;
1031+ int max_token_sz= SSPI_MAX_TOKEN_SIZE;
1032+ int ret= CR_OK; /* return code */
1033+ const char *err_msg= NULL; /* error message */
1034+ /* SSPI related fields */
1035+ SECURITY_STATUS ss = 0;
1036+ BOOL have_ctxt= FALSE;
1037+ BOOL have_cred= FALSE;
1038+ BOOL context_established= FALSE;
1039+ ULONG attribs= 0;
1040+ TimeStamp lifetime;
1041+
1042+ CredHandle cred_handle; /* credential handle */
1043+ CtxtHandle ctxt_handle; /* context handle */
1044+ SecPkgContext_NativeNames native_names;
1045+
1046+ SecPkgInfo *sec_pkg_info; /* Packet information */
1047+ SecBufferDesc input_buf_desc; /* Input token */
1048+ SecBuffer input_buf;
1049+ SecBufferDesc output_buf_desc; /* Output token */
1050+ SecBuffer output_buf;
1051+ PBYTE output_con;
1052+
1053+ /* query packet information */
1054+ ss= QuerySecurityPackageInfo(SECURITY_PACKAGE_NAME, &sec_pkg_info);
1055+
1056+ if (ss != SEC_E_OK)
1057+ {
1058+ /* error query package information */
1059+ my_error(KRB_SERVER_AUTH_ERROR, MF_WARNING,
1060+ "Kerberos: fail to get maximum token size, use default: %d.",
1061+ max_token_sz);
1062+ }
1063+ else
1064+ {
1065+ max_token_sz= sec_pkg_info->cbMaxToken;
1066+ }
1067+
1068+ /* allocate memory */
1069+ output_con= (PBYTE) calloc(max_token_sz, sizeof(BYTE));
1070+ if (!output_con)
1071+ {
1072+ my_error(KRB_SERVER_AUTH_ERROR, MF_ERROR, "Kerberos: no more memory.");
1073+ return CR_ERROR;
1074+ }
1075+
1076+ /* acquire initial credential */
1077+ ss= AcquireCredentialsHandle(NULL, /* use default credential */
1078+ SECURITY_PACKAGE_NAME,
1079+ SECPKG_CRED_INBOUND, /* cred usage */
1080+ NULL, /* locally unique id */
1081+ NULL, /* use default credential */
1082+ NULL, /* get key func */
1083+ NULL, /* get key argument func */
1084+ &cred_handle,
1085+ &lifetime);
1086+
1087+ /* AcquireCredentialsHandle error checking */
1088+ if (SEC_ERROR(ss))
1089+ {
1090+ err_msg= error_msg(ss);
1091+ ret= CR_ERROR;
1092+ my_error(KRB_SERVER_AUTH_ERROR, MF_ERROR, err_msg);
1093+ goto cleanup;
1094+ }
1095+ else
1096+ {
1097+ have_cred= TRUE;
1098+ }
1099+
1100+ /* prepare input buffer */
1101+ input_buf_desc.ulVersion= SECBUFFER_VERSION;
1102+ input_buf_desc.cBuffers= 1;
1103+ input_buf_desc.pBuffers= &input_buf;
1104+
1105+ input_buf.cbBuffer= 0;
1106+ input_buf.BufferType= SECBUFFER_TOKEN;
1107+ input_buf.pvBuffer= NULL;
1108+
1109+ /* prepare output buffer */
1110+ output_buf_desc.ulVersion= SECBUFFER_VERSION;
1111+ output_buf_desc.cBuffers= 1;
1112+ output_buf_desc.pBuffers= &output_buf;
1113+
1114+ output_buf.BufferType= SECBUFFER_TOKEN;
1115+ output_buf.cbBuffer= max_token_sz;
1116+ output_buf.pvBuffer= output_con;
1117+
1118+ do
1119+ {
1120+ /* read credential from client */
1121+ read_len= vio->read_packet(vio, (unsigned char **) &input_buf.pvBuffer);
1122+ if (read_len < 0) {
1123+ ret= CR_ERROR;
1124+ my_error(KRB_SERVER_AUTH_ERROR, MF_ERROR,
1125+ "Kerberos: fail read client credentials.");
1126+ goto cleanup;
1127+ }
1128+ input_buf.cbBuffer= read_len;
1129+
1130+ ss= AcceptSecurityContext(
1131+ &cred_handle, /* credential */
1132+ have_ctxt ? &ctxt_handle : NULL,
1133+ &input_buf_desc, /* input credentials */
1134+ attribs,
1135+ SECURITY_NATIVE_DREP,
1136+ &ctxt_handle, /* secure context */
1137+ &output_buf_desc, /* output credentials */
1138+ &attribs,
1139+ &lifetime);
1140+
1141+ /* AcceptSecurityContext error checking */
1142+ if (SEC_ERROR(ss))
1143+ {
1144+ err_msg= error_msg(ss);
1145+ ret= CR_ERROR;
1146+ my_error(KRB_SERVER_AUTH_ERROR, MF_ERROR, err_msg);
1147+ goto cleanup;
1148+ }
1149+ /* Security context established (partially) */
1150+ have_ctxt= TRUE;
1151+
1152+ if (output_buf.cbBuffer)
1153+ {
1154+ /* write credential packet */
1155+ if (vio->write_packet(vio,
1156+ (const unsigned char *) output_buf.pvBuffer,
1157+ output_buf.cbBuffer))
1158+ {
1159+ ret= CR_ERROR;
1160+ my_error(KRB_SERVER_AUTH_ERROR, MF_ERROR,
1161+ "Kerberos: fail send crednetials to client.");
1162+ goto cleanup;
1163+ }
1164+ }
1165+ output_buf.cbBuffer= max_token_sz;
1166+
1167+ if ((ss == SEC_I_COMPLETE_NEEDED) ||
1168+ (ss == SEC_I_COMPLETE_AND_CONTINUE))
1169+ {
1170+ ss= CompleteAuthToken(&ctxt_handle, &output_buf_desc);
1171+ if (SEC_ERROR(ss))
1172+ {
1173+ err_msg= error_msg(ss);
1174+ ret= CR_ERROR;
1175+ my_error(KRB_SERVER_AUTH_ERROR, MF_ERROR, err_msg);
1176+ goto cleanup;
1177+ }
1178+ }
1179+
1180+ context_established= !((ss == SEC_I_CONTINUE_NEEDED) ||
1181+ (ss == SEC_I_COMPLETE_AND_CONTINUE));
1182+ } while (!context_established);
1183+
1184+ /* check principal name */
1185+ ss= QueryContextAttributes(&ctxt_handle, SECPKG_ATTR_NATIVE_NAMES, &native_names);
1186+
1187+ if (SEC_ERROR(ss))
1188+ {
1189+ err_msg= error_msg(ss);
1190+ ret= CR_ERROR;
1191+ my_error(KRB_SERVER_AUTH_ERROR, MF_ERROR, err_msg);
1192+ goto cleanup;
1193+ }
1194+
1195+ if (strcmp(native_names.sClientName, info->auth_string))
1196+ {
1197+ ret= CR_ERROR;
1198+ }
1199+ else
1200+ {
1201+ ret= CR_OK;
1202+ }
1203+
1204+cleanup:
1205+ /* free dynamic memory */
1206+ if (have_ctxt) DeleteSecurityContext(&ctxt_handle);
1207+ if (have_cred) FreeCredentialsHandle(&cred_handle);
1208+ free(output_con);
1209+ ss= FreeContextBuffer(sec_pkg_info);
1210+ if (ss != SEC_E_OK) {
1211+ my_error(KRB_SERVER_AUTH_ERROR, MF_ERROR,
1212+ "Kerberos: fail to free SecurityPackageInfo object.");
1213+ /* return code is not modified */
1214+ }
1215+
1216+ return ret;
1217+}
1218+#else /* !_WIN32 */
1219+static int gssapi_kerberos_auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info) {
1220+ int r_len= 0; /* packet read length */
1221+ int rc= CR_OK; /* return code */
1222+ int have_cred= FALSE;
1223+ int have_ctxt= FALSE;
1224+ const char *err_msg= NULL; /* error message text */
1225+ /* GSSAPI related fields */
1226+ OM_uint32 major= 0, minor= 0, flags= 0;
1227+ gss_cred_id_t cred; /* credential identifier */
1228+ gss_ctx_id_t ctxt; /* context identifier */
1229+ gss_name_t client_name, service_name;
1230+ gss_buffer_desc principal_name_buf, client_name_buf, input, output;
1231+
1232+ /* import service principal from plain text */
1233+ /* initialize principal name */
1234+ principal_name_buf.length= strlen(kerberos_principal_name);
1235+ principal_name_buf.value= kerberos_principal_name;
1236+ major= gss_import_name(&minor,
1237+ &principal_name_buf,
1238+ (gss_OID) gss_nt_user_name,
1239+ &service_name);
1240+ /* gss_import_name error checking */
1241+ if (GSS_ERROR(major))
1242+ {
1243+ err_msg= error_msg(major, minor);
1244+ rc= CR_ERROR;
1245+ my_error(KRB_SERVER_AUTH_ERROR, MF_ERROR, err_msg);
1246+ goto cleanup;
1247+ }
1248+
1249+ /* server acquires credential */
1250+ major= gss_acquire_cred(&minor,
1251+ service_name,
1252+ GSS_C_INDEFINITE, /* time_req, ALAP */
1253+ GSS_C_NO_OID_SET, /* desired_mechs */
1254+ GSS_C_ACCEPT, /* cred_usage, ACCEPT only */
1255+ &cred,
1256+ NULL, /* actual_mechs */
1257+ NULL); /* time_rec */
1258+
1259+ /* gss_acquire_cred error checking */
1260+ if (GSS_ERROR(major))
1261+ {
1262+ err_msg= error_msg(major, minor);
1263+ rc= CR_ERROR;
1264+ my_error(KRB_SERVER_AUTH_ERROR, MF_ERROR, err_msg);
1265+ goto cleanup;
1266+ }
1267+ else
1268+ {
1269+ have_cred= TRUE;
1270+ }
1271+
1272+ major= gss_release_name(&minor, &service_name);
1273+ if (major == GSS_S_BAD_NAME)
1274+ {
1275+ rc= CR_ERROR;
1276+ my_error(KRB_SERVER_AUTH_ERROR, MF_ERROR,
1277+ "Kerbeos: fail when invoke gss_release_name, no valid name found.");
1278+ goto cleanup;
1279+ }
1280+
1281+ /* accept security context */
1282+ ctxt= GSS_C_NO_CONTEXT;
1283+ /* first trial */
1284+ input.length= 0;
1285+ input.value= NULL;
1286+ do {
1287+ /* receive token from peer first */
1288+ r_len= vio->read_packet(vio, (unsigned char **) &input.value);
1289+ if (r_len < 0) {
1290+ rc= CR_ERROR;
1291+ my_error(KRB_SERVER_AUTH_ERROR, MF_ERROR,
1292+ "Kerberos: fail to read token from client.");
1293+ goto cleanup;
1294+ }
1295+ else
1296+ {
1297+ /* make length consistent with value */
1298+ input.length= r_len;
1299+ }
1300+
1301+ major= gss_accept_sec_context(&minor,
1302+ &ctxt, /* ctxt handle */
1303+ cred,
1304+ &input, /* input buffer */
1305+ GSS_C_NO_CHANNEL_BINDINGS,
1306+ &client_name, /* source name */
1307+ NULL, /* mech type */
1308+ &output, /* output buffer */
1309+ &flags, /* return flag */
1310+ NULL, /* time rec */
1311+ NULL);
1312+ if (GSS_ERROR(major))
1313+ {
1314+ if (ctxt != GSS_C_NO_CONTEXT)
1315+ {
1316+ gss_delete_sec_context(&minor,
1317+ &ctxt,
1318+ GSS_C_NO_BUFFER);
1319+ }
1320+ err_msg= error_msg(major, minor);
1321+ rc= CR_ERROR;
1322+ my_error(KRB_SERVER_AUTH_ERROR, MF_ERROR, err_msg);
1323+ goto cleanup;
1324+ }
1325+ /* security context established (partially) */
1326+ have_ctxt= TRUE;
1327+
1328+ /* send token to peer */
1329+ if (output.length)
1330+ {
1331+ if (vio->write_packet(vio, output.value, output.length))
1332+ {
1333+ gss_release_buffer(&minor, &output);
1334+ rc= CR_ERROR;
1335+ my_error(KRB_SERVER_AUTH_ERROR, MF_ERROR,
1336+ "Kerberos: fail to send authentication token.");
1337+ goto cleanup;
1338+ }
1339+ gss_release_buffer(&minor, &output);
1340+ }
1341+ } while (major & GSS_S_CONTINUE_NEEDED);
1342+
1343+ /* extrac plain text client name */
1344+ major= gss_display_name(&minor, client_name, &client_name_buf, NULL);
1345+ if (major == GSS_S_BAD_NAME)
1346+ {
1347+ rc= CR_ERROR;
1348+ my_error(KRB_SERVER_AUTH_ERROR, MF_ERROR,
1349+ "Kerberos: fail to display an ill-formed principal name.");
1350+ goto cleanup;
1351+ }
1352+
1353+ /* expected user? */
1354+ if (strncmp(client_name_buf.value, info->auth_string, PRINCIPAL_NAME_LEN))
1355+ {
1356+ gss_release_buffer(&minor, &client_name_buf);
1357+ rc= CR_ERROR;
1358+ my_error(KRB_SERVER_AUTH_ERROR, MF_ERROR,
1359+ "Kerberos: fail authentication user.");
1360+ goto cleanup;
1361+ }
1362+ gss_release_buffer(&minor, &client_name_buf);
1363+
1364+cleanup:
1365+ if (have_ctxt) gss_delete_sec_context(&minor, &ctxt, GSS_C_NO_BUFFER);
1366+ if (have_cred) gss_release_cred(&minor, &cred);
1367+
1368+ return rc;
1369+}
1370+#endif /* _WIN32 */
1371+
1372+/**
1373+ * The main server function of the Kerberos plugin.
1374+ */
1375+static int kerberos_auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info)
1376+{
1377+ size_t p_len= 0; /* length of principal name */
1378+ int rc= CR_OK; /* return code */
1379+ char *principal_name= NULL;
1380+
1381+ /* server sends service principal name first. */
1382+ p_len= strlen(kerberos_principal_name);
1383+ if (!p_len)
1384+ {
1385+ my_error(KRB_SERVER_AUTH_ERROR, MF_ERROR,
1386+ "Kerberos: no principal name specified.");
1387+ return CR_ERROR;
1388+ }
1389+
1390+ if (vio->write_packet(vio,
1391+ (unsigned char *) kerberos_principal_name,
1392+ (int) p_len) < 0)
1393+ {
1394+ my_error(KRB_SERVER_AUTH_ERROR, MF_ERROR,
1395+ "Kerberos: fail to send service principal name.");
1396+ return CR_ERROR;
1397+ }
1398+
1399+ rc =
1400+#ifdef _WIN32
1401+ sspi_kerberos_auth(vio, info);
1402+#else /* !_WIN32 */
1403+ gssapi_kerberos_auth(vio, info);
1404+#endif /* _WIN32 */
1405+
1406+ free(principal_name);
1407+
1408+ return rc;
1409+}
1410+
1411+#ifdef _WIN32
1412+static BOOL GetLogonSID (PSID *ppsid)
1413+{
1414+ BOOL succ= FALSE;
1415+ HANDLE token;
1416+ DWORD index;
1417+ DWORD length= 0;
1418+ PTOKEN_GROUPS ptg= NULL;
1419+
1420+ /* Verify the parameter passed in is not NULL. */
1421+ if (ppsid == NULL)
1422+ goto cleanup;
1423+
1424+ /* Open a handle to the access token for the calling process. */
1425+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token ))
1426+ goto cleanup;
1427+
1428+ /* Get required buffer size and allocate the TOKEN_GROUPS buffer. */
1429+ if (!GetTokenInformation(
1430+ token, /* handle to the access token */
1431+ TokenGroups, /* get information about the token's groups */
1432+ (LPVOID) ptg, /* pointer to TOKEN_GROUPS buffer */
1433+ 0, /* size of buffer */
1434+ &length /* receives required buffer size */
1435+ ))
1436+ {
1437+ if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
1438+ goto cleanup;
1439+
1440+ ptg= (PTOKEN_GROUPS)HeapAlloc(GetProcessHeap(),
1441+ HEAP_ZERO_MEMORY, length);
1442+
1443+ if (ptg == NULL)
1444+ goto cleanup;
1445+ }
1446+
1447+ /* Get the token group information from the access token. */
1448+ if (!GetTokenInformation(
1449+ token, /* handle to the access token */
1450+ TokenGroups, /* get information about the token's groups */
1451+ (LPVOID) ptg, /* pointer to TOKEN_GROUPS buffer */
1452+ length, /* size of buffer */
1453+ &length /* receives required buffer size */
1454+ ))
1455+ {
1456+ goto cleanup;
1457+ }
1458+
1459+ /* Loop through the groups to find the logon SID. */
1460+ for (index= 0; index < ptg->GroupCount; index++)
1461+ {
1462+ if ((ptg->Groups[index].Attributes & SE_GROUP_LOGON_ID)
1463+ == SE_GROUP_LOGON_ID)
1464+ {
1465+ /* Found the logon SID; make a copy of it. */
1466+ length= GetLengthSid(ptg->Groups[index].Sid);
1467+ *ppsid= (PSID) HeapAlloc(GetProcessHeap(),
1468+ HEAP_ZERO_MEMORY, length);
1469+ if (*ppsid == NULL)
1470+ goto cleanup;
1471+ if (!CopySid(length, *ppsid, ptg->Groups[index].Sid))
1472+ {
1473+ HeapFree(GetProcessHeap(), 0, (LPVOID)*ppsid);
1474+ goto cleanup;
1475+ }
1476+ break;
1477+ }
1478+ }
1479+ succ= TRUE;
1480+
1481+cleanup:
1482+ /* Free the buffer for the token groups. */
1483+ if (ptg != NULL)
1484+ HeapFree(GetProcessHeap(), 0, (LPVOID)ptg);
1485+
1486+ return succ;
1487+}
1488+
1489+static VOID FreeLogonSID (PSID *ppsid)
1490+{
1491+ HeapFree(GetProcessHeap(), 0, (LPVOID)*ppsid);
1492+}
1493+
1494+static BOOLEAN LogonAsNetworkService(void)
1495+{
1496+ SID sNetServ;
1497+ PSID psLogon= NULL;
1498+ DWORD szSid= sizeof(sNetServ);
1499+ BOOLEAN bRetCode= FALSE;
1500+
1501+ if (GetLogonSID(&psLogon) &&
1502+ CreateWellKnownSid(WinNetworkServiceSid, NULL, &sNetServ, &szSid) &&
1503+ EqualSid(psLogon, &sNetServ))
1504+ {
1505+ bRetCode= TRUE;
1506+ }
1507+
1508+ if (psLogon) FreeLogonSID(&psLogon);
1509+
1510+ return bRetCode;
1511+}
1512+
1513+static int initialize_principal_name(void *unused)
1514+{
1515+ int ret= 0;
1516+ ULONG len= sizeof(kerberos_spn_storage);
1517+ CHAR *computer_name= NULL;
1518+ CHAR *domain_name= NULL;
1519+ BOOLEAN api_rc= TRUE;
1520+
1521+#define KRB_AUTH_SERVER_INIT_ERROR(ret, msg) \
1522+ do { ret= -1; \
1523+ my_error(ret, MF_ERROR, msg); \
1524+ } while(0)
1525+
1526+ /* principal name has already been set */
1527+ if (kerberos_principal_name && kerberos_principal_name[0]) return ret;
1528+
1529+ api_rc= GetUserNameEx(NameUserPrincipal, kerberos_spn_storage, &len);
1530+ if (!api_rc)
1531+ {
1532+ switch (GetLastError())
1533+ {
1534+ case ERROR_NO_SUCH_DOMAIN:
1535+ ret = CR_ERROR;
1536+ my_error(KRB_SERVER_AUTH_ERROR, MF_WARNING,
1537+ "Kerberos: the domain controller is not up.");
1538+ break;
1539+ case ERROR_NONE_MAPPED:
1540+ /* cannot find UPN for logon user */
1541+ /*
1542+ * If logon sid is NetworkService, a fallback is construct
1543+ * UPN (computer$@domain) manually.
1544+ */
1545+ if (LogonAsNetworkService())
1546+ {
1547+ BOOLEAN done= TRUE;
1548+
1549+ len= PRINCIPAL_NAME_LEN;
1550+ computer_name= (CHAR *) calloc(len, sizeof(CHAR));
1551+ done= (computer_name == NULL);
1552+ if (done && (done= GetComputerNameEx(ComputerNameDnsHostname,
1553+ computer_name, &len)))
1554+ {
1555+ len= PRINCIPAL_NAME_LEN;
1556+ domain_name= (CHAR *) calloc(len, sizeof(CHAR));
1557+ done= (domain_name == NULL);
1558+ if (done && (done= GetComputerNameEx(ComputerNameDnsDomain,
1559+ domain_name, &len)))
1560+ {
1561+ sprintf_s(kerberos_spn_storage,
1562+ sizeof(kerberos_spn_storage),
1563+ "%s$@%s", computer_name, domain_name);
1564+ }
1565+ }
1566+
1567+ /* Release heap memory */
1568+ if (computer_name) free(computer_name);
1569+ if (domain_name) free(domain_name);
1570+
1571+ if (!done)
1572+ {
1573+ KRB_AUTH_SERVER_INIT_ERROR(ret,
1574+ "Kerberos: the name is not available in specific format.");
1575+ }
1576+ else
1577+ {
1578+ /* redirect the variable */
1579+ kerberos_principal_name= kerberos_spn_storage;
1580+ }
1581+ }
1582+ else
1583+ {
1584+ KRB_AUTH_SERVER_INIT_ERROR(ret,
1585+ "Kerberos: the name is not available in specific format.");
1586+ }
1587+ break;
1588+ default:
1589+ break;
1590+ }
1591+ }
1592+ else
1593+ {
1594+ /* redirect the variable */
1595+ kerberos_principal_name= kerberos_spn_storage;
1596+ }
1597+
1598+/* localize macro KRB_AUTH_SERVER_INIT_ERROR */
1599+#undef KRB_AUTH_SERVER_INIT_ERROR
1600+
1601+ return ret;
1602+}
1603+#endif /* _WIN32 */
1604+
1605+static int verify_principal_name(UNUSED(MYSQL_THD thd),
1606+ UNUSED(struct st_mysql_sys_var UNUSED(*var)), UNUSED(void *save),
1607+ UNUSED(struct st_mysql_value *value)) {
1608+ char upn_buf[PRINCIPAL_NAME_LEN];
1609+ int buf_len= PRINCIPAL_NAME_LEN;
1610+ /* UPN should in the form `user@domain` or `user/host@domain` */
1611+ const char *ptr= value->val_str(value, upn_buf, &buf_len);
1612+ const char *itr= ptr;
1613+
1614+#define FWD_ITER(iter) while (*iter && (isalpha(*iter)||(*itr)=='.')) iter++
1615+ /* user part */
1616+ if (*itr && isalpha(*itr))
1617+ {
1618+ FWD_ITER(itr);
1619+ }
1620+ else
1621+ {
1622+ /* name part is required */
1623+ return 1;
1624+ }
1625+
1626+ /* host part, which is optional */
1627+ if (*itr && *itr == '/')
1628+ {
1629+ itr++;
1630+ FWD_ITER(itr);
1631+ }
1632+
1633+ /* domain part */
1634+ if (*itr && *itr == '@')
1635+ {
1636+ itr++;
1637+ FWD_ITER(itr);
1638+ }
1639+ else
1640+ {
1641+ /* domain part is required */
1642+ return 1;
1643+ }
1644+
1645+ /* if validated return 0, or any non-zero value */
1646+ if (!*itr)
1647+ {
1648+ strncpy(kerberos_spn_storage, ptr, PRINCIPAL_NAME_LEN);
1649+ }
1650+ return *itr;
1651+}
1652+
1653+static void update_principal_name(UNUSED(MYSQL_THD thd),
1654+ UNUSED(struct st_mysql_sys_var* var), UNUSED(void * var_ptr),
1655+ UNUSED(const void * save))
1656+{
1657+ kerberos_principal_name= kerberos_spn_storage;
1658+}
1659+
1660+/* system variable */
1661+static MYSQL_SYSVAR_STR(principal_name, kerberos_principal_name,
1662+ PLUGIN_VAR_RQCMDARG,
1663+ "Service principal name in Kerberos authentication.",
1664+ verify_principal_name, /* check function */
1665+ update_principal_name, /* update function */
1666+ "");
1667+
1668+static struct st_mysql_sys_var *system_variables[]= {
1669+ MYSQL_SYSVAR(principal_name),
1670+ NULL
1671+};
1672+
1673+/* register Kerberos authentication plugin */
1674+static struct st_mysql_auth server_handler= {
1675+ MYSQL_AUTHENTICATION_INTERFACE_VERSION,
1676+ "kerberos_client",
1677+ kerberos_auth
1678+};
1679+
1680+maria_declare_plugin(kerberos_server)
1681+{
1682+ MYSQL_AUTHENTICATION_PLUGIN,
1683+ &server_handler,
1684+ "kerberos",
1685+ "Shuang Qiu",
1686+ "Plugin for Kerberos based authentication.",
1687+ PLUGIN_LICENSE_BSD,
1688+#ifdef _WIN32
1689+ initialize_principal_name,
1690+#else /* _WIN32 */
1691+ NULL,
1692+#endif /* _WIN32 */
1693+ NULL, /* destructor */
1694+ 0x0100, /* version */
1695+ NULL, /* status variables */
1696+ system_variables, /* system variables */
1697+ "Kerberos authentication plugin 1.0",
1698+ MariaDB_PLUGIN_MATURITY_EXPERIMENTAL /* TODO change when release */
1699+}
1700+maria_declare_plugin_end;
1701+
1702+/* localize macro KRB_AUTH_SERVER_ERROR */
1703+#undef KRB_AUTH_SERVER_ERROR

Subscribers

People subscribed via source and target branches