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