1 /* Copyright (c) 2015, Shuang Qiu, Robbie Harwood,
2 Vladislav Vaintroub & MariaDB Corporation
3
4 All rights reserved.
5
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions are met:
8
9 1. Redistributions of source code must retain the above copyright notice,
10 this list of conditions and the following disclaimer.
11
12 2. Redistributions in binary form must reproduce the above copyright notice,
13 this list of conditions and the following disclaimer in the documentation
14 and/or other materials provided with the distribution.
15
16 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "sspi.h"
30 #include "common.h"
31 #include "server_plugin.h"
32 #include <mysql/plugin_auth.h>
33 #include <mysqld_error.h>
34
35
36 /* This sends the error to the client */
log_error(SECURITY_STATUS err,const char * msg)37 static void log_error(SECURITY_STATUS err, const char *msg)
38 {
39 if (err)
40 {
41 char buf[1024];
42 sspi_errmsg(err, buf, sizeof(buf));
43 my_printf_error(ER_UNKNOWN_ERROR, "SSPI server error 0x%x - %s - %s", 0, err, msg, buf);
44 }
45 else
46 {
47 my_printf_error(ER_UNKNOWN_ERROR, "SSPI server error %s", 0, msg);
48 }
49
50 }
51
52 static char INVALID_KERBEROS_PRINCIPAL[] = "localhost";
53
get_default_principal_name()54 static char *get_default_principal_name()
55 {
56 static char default_principal[PRINCIPAL_NAME_MAX +1];
57 ULONG size= sizeof(default_principal);
58
59 if (GetUserNameEx(NameUserPrincipal,default_principal,&size))
60 return default_principal;
61
62 size= sizeof(default_principal);
63 if (GetUserNameEx(NameServicePrincipal,default_principal,&size))
64 return default_principal;
65
66 char domain[PRINCIPAL_NAME_MAX+1];
67 char host[PRINCIPAL_NAME_MAX+1];
68 size= sizeof(domain);
69 if (GetComputerNameEx(ComputerNameDnsDomain,domain,&size) && size > 0)
70 {
71 size= sizeof(host);
72 if (GetComputerNameEx(ComputerNameDnsHostname,host,&size))
73 {
74 _snprintf(default_principal,sizeof(default_principal),"%s$@%s",host, domain);
75 return default_principal;
76 }
77 }
78 /* Unable to retrieve useful name, return something */
79 return INVALID_KERBEROS_PRINCIPAL;
80 }
81
82
83 /* Extract client name from SSPI context */
get_client_name_from_context(CtxtHandle * ctxt,char * name,size_t name_len,int use_full_name)84 static int get_client_name_from_context(CtxtHandle *ctxt,
85 char *name,
86 size_t name_len,
87 int use_full_name)
88 {
89 SecPkgContext_NativeNames native_names;
90 SECURITY_STATUS sspi_ret;
91 char *p;
92
93 sspi_ret= QueryContextAttributes(ctxt, SECPKG_ATTR_NATIVE_NAMES, &native_names);
94 if (sspi_ret == SEC_E_OK)
95 {
96 /* Extract user from Kerberos principal name user@realm */
97 if(!use_full_name)
98 {
99 p = strrchr(native_names.sClientName,'@');
100 if(p)
101 *p = 0;
102 }
103 strncpy(name, native_names.sClientName, name_len);
104
105 if (native_names.sClientName)
106 FreeContextBuffer(native_names.sClientName);
107 if (native_names.sServerName)
108 FreeContextBuffer(native_names.sServerName);
109
110 return CR_OK;
111 }
112
113 sspi_ret= ImpersonateSecurityContext(ctxt);
114 if (sspi_ret == SEC_E_OK)
115 {
116 ULONG len= (ULONG)name_len;
117 if (!GetUserNameEx(NameSamCompatible, name, &len))
118 {
119 log_error(GetLastError(), "GetUserNameEx");
120 RevertSecurityContext(ctxt);
121 return CR_ERROR;
122 }
123 RevertSecurityContext(ctxt);
124
125 /* Extract user from Windows name realm\user */
126 if (!use_full_name)
127 {
128 p = strrchr(name, '\\');
129 if (p)
130 {
131 p++;
132 memmove(name, p, name + len + 1 - p);
133 }
134 }
135 return CR_OK;
136 }
137
138 log_error(sspi_ret, "ImpersonateSecurityContext");
139 return CR_ERROR;
140 }
141
142
auth_server(MYSQL_PLUGIN_VIO * vio,MYSQL_SERVER_AUTH_INFO * auth_info)143 int auth_server(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *auth_info)
144 {
145 int ret;
146 SECURITY_STATUS sspi_ret;
147 ULONG attribs = 0;
148 TimeStamp lifetime;
149 CredHandle cred;
150 CtxtHandle ctxt;
151
152 SecBufferDesc inbuf_desc;
153 SecBuffer inbuf;
154 SecBufferDesc outbuf_desc;
155 SecBuffer outbuf;
156 void* out= NULL;
157 char client_name[MYSQL_USERNAME_LENGTH + 1];
158 const char *user= 0;
159 int compare_full_name;
160
161 ret= CR_ERROR;
162 SecInvalidateHandle(&cred);
163 SecInvalidateHandle(&ctxt);
164
165 out= malloc(SSPI_MAX_TOKEN_SIZE);
166 if (!out)
167 {
168 log_error(SEC_E_OK, "memory allocation failed");
169 goto cleanup;
170 }
171 sspi_ret= AcquireCredentialsHandle(
172 srv_principal_name,
173 (LPSTR)srv_mech_name,
174 SECPKG_CRED_INBOUND,
175 NULL,
176 NULL,
177 NULL,
178 NULL,
179 &cred,
180 &lifetime);
181
182 if (SEC_ERROR(sspi_ret))
183 {
184 log_error(sspi_ret, "AcquireCredentialsHandle failed");
185 goto cleanup;
186 }
187
188 inbuf.cbBuffer= 0;
189 inbuf.BufferType= SECBUFFER_TOKEN;
190 inbuf.pvBuffer= NULL;
191 inbuf_desc.ulVersion= SECBUFFER_VERSION;
192 inbuf_desc.cBuffers= 1;
193 inbuf_desc.pBuffers= &inbuf;
194
195 outbuf.BufferType= SECBUFFER_TOKEN;
196 outbuf.cbBuffer= SSPI_MAX_TOKEN_SIZE;
197 outbuf.pvBuffer= out;
198
199 outbuf_desc.ulVersion= SECBUFFER_VERSION;
200 outbuf_desc.cBuffers= 1;
201 outbuf_desc.pBuffers= &outbuf;
202
203 do
204 {
205 /* Read SSPI blob from client. */
206 int len= vio->read_packet(vio, (unsigned char **)&inbuf.pvBuffer);
207 if (len < 0)
208 {
209 log_error(SEC_E_OK, "communication error(read)");
210 goto cleanup;
211 }
212 if (!user)
213 {
214 if (auth_info->auth_string_length > 0)
215 {
216 compare_full_name= 1;
217 user= auth_info->auth_string;
218 }
219 else
220 {
221 compare_full_name= 0;
222 user= auth_info->user_name;
223 }
224 }
225 inbuf.cbBuffer= len;
226 outbuf.cbBuffer= SSPI_MAX_TOKEN_SIZE;
227 sspi_ret= AcceptSecurityContext(
228 &cred,
229 SecIsValidHandle(&ctxt) ? &ctxt : NULL,
230 &inbuf_desc,
231 attribs,
232 SECURITY_NATIVE_DREP,
233 &ctxt,
234 &outbuf_desc,
235 &attribs,
236 &lifetime);
237
238 if (SEC_ERROR(sspi_ret))
239 {
240 log_error(sspi_ret, "AcceptSecurityContext");
241 goto cleanup;
242 }
243 if (sspi_ret != SEC_E_OK && sspi_ret != SEC_I_CONTINUE_NEEDED)
244 {
245 log_error(sspi_ret, "AcceptSecurityContext unexpected return value");
246 goto cleanup;
247 }
248 if (outbuf.cbBuffer)
249 {
250 /* Send generated blob to client. */
251 if (vio->write_packet(vio, (unsigned char *)outbuf.pvBuffer, outbuf.cbBuffer))
252 {
253 log_error(SEC_E_OK, "communicaton error(write)");
254 goto cleanup;
255 }
256 }
257 } while (sspi_ret == SEC_I_CONTINUE_NEEDED);
258
259 /* Authentication done, now extract and compare user name. */
260 ret= get_client_name_from_context(&ctxt, client_name, MYSQL_USERNAME_LENGTH, compare_full_name);
261 if (ret != CR_OK)
262 goto cleanup;
263
264 /* Always compare case-insensitive on Windows. */
265 ret= _stricmp(client_name, user) == 0 ? CR_OK : CR_ERROR;
266 if (ret != CR_OK)
267 {
268 my_printf_error(ER_ACCESS_DENIED_ERROR,
269 "GSSAPI name mismatch, requested '%s', actual name '%s'",
270 0, user, client_name);
271 }
272
273 cleanup:
274 if (SecIsValidHandle(&ctxt))
275 DeleteSecurityContext(&ctxt);
276
277 if (SecIsValidHandle(&cred))
278 FreeCredentialsHandle(&cred);
279
280 free(out);
281 return ret;
282 }
283
plugin_init()284 int plugin_init()
285 {
286 CredHandle cred;
287 SECURITY_STATUS ret;
288
289 /*
290 Use negotiate by default, which accepts raw kerberos
291 and also NTLM.
292 */
293 if (srv_mech == PLUGIN_MECH_DEFAULT)
294 srv_mech= PLUGIN_MECH_SPNEGO;
295
296 if(srv_mech == PLUGIN_MECH_KERBEROS)
297 srv_mech_name= "Kerberos";
298 else if(srv_mech == PLUGIN_MECH_SPNEGO )
299 srv_mech_name= "Negotiate";
300
301 if(!srv_principal_name[0])
302 {
303 srv_principal_name= get_default_principal_name();
304 }
305 my_printf_error(ER_UNKNOWN_ERROR, "SSPI: using principal name '%s', mech '%s'",
306 ME_ERROR_LOG | ME_NOTE, srv_principal_name, srv_mech_name);
307
308 ret = AcquireCredentialsHandle(
309 srv_principal_name,
310 (LPSTR)srv_mech_name,
311 SECPKG_CRED_INBOUND,
312 NULL,
313 NULL,
314 NULL,
315 NULL,
316 &cred,
317 NULL);
318 if (SEC_ERROR(ret))
319 {
320 log_error(ret, "AcquireCredentialsHandle");
321 return -1;
322 }
323 FreeCredentialsHandle(&cred);
324 return 0;
325 }
326
plugin_deinit()327 int plugin_deinit()
328 {
329 return 0;
330 }
331