1 #include <my_config.h>
2 #include <gssapi/gssapi.h>
3 #include <stdio.h>
4 #include <mysql/plugin_auth.h>
5 #include <mysqld_error.h>
6 #include <string.h>
7 #include "server_plugin.h"
8 #include "gssapi_errmsg.h"
9 
10 static gss_name_t service_name = GSS_C_NO_NAME;
11 
12 /* This sends the error to the client */
log_error(OM_uint32 major,OM_uint32 minor,const char * msg)13 static void log_error( OM_uint32 major, OM_uint32 minor, const char *msg)
14 {
15   if (GSS_ERROR(major))
16   {
17     char sysmsg[1024];
18     gssapi_errmsg(major, minor, sysmsg, sizeof(sysmsg));
19     my_printf_error(ER_UNKNOWN_ERROR,"Server GSSAPI error (major %u, minor %u) : %s -%s",
20       0, major, minor, msg, sysmsg);
21   }
22   else
23   {
24     my_printf_error(ER_UNKNOWN_ERROR, "Server GSSAPI error : %s", 0, msg);
25   }
26 }
27 
28 
29 /*
30   Generate default principal service name formatted as principal name "mariadb/server.fqdn@REALM"
31 */
32 #include <krb5.h>
33 #ifdef HAVE_KRB5_XFREE
34 #define krb5_free_unparsed_name(a,b) krb5_xfree(b)
35 #endif
get_default_principal_name()36 static char* get_default_principal_name()
37 {
38   static char default_name[1024];
39   char *unparsed_name= NULL;
40   krb5_context context= NULL;
41   krb5_principal principal= NULL;
42   krb5_keyblock *key= NULL;
43 
44   if(krb5_init_context(&context))
45   {
46     my_printf_error(1, "GSSAPI plugin : krb5_init_context failed",
47                     ME_ERROR_LOG | ME_WARNING);
48     goto cleanup;
49   }
50 
51   if (krb5_sname_to_principal(context, NULL, "mariadb", KRB5_NT_SRV_HST, &principal))
52   {
53     my_printf_error(1, "GSSAPI plugin :  krb5_sname_to_principal failed",
54                     ME_ERROR_LOG | ME_WARNING);
55     goto cleanup;
56   }
57 
58   if (krb5_unparse_name(context, principal, &unparsed_name))
59   {
60     my_printf_error(1, "GSSAPI plugin :  krb5_unparse_name failed",
61                     ME_ERROR_LOG | ME_WARNING);
62     goto cleanup;
63   }
64 
65   /* Check for entry in keytab */
66   if (krb5_kt_read_service_key(context, NULL, principal, 0, (krb5_enctype)0, &key))
67   {
68     my_printf_error(1, "GSSAPI plugin : default principal '%s' not found in keytab",
69                     ME_ERROR_LOG | ME_WARNING, unparsed_name);
70     goto cleanup;
71   }
72 
73   strncpy(default_name, unparsed_name, sizeof(default_name)-1);
74 
75 cleanup:
76   if (key)
77     krb5_free_keyblock(context, key);
78   if (unparsed_name)
79     krb5_free_unparsed_name(context, unparsed_name);
80   if (principal)
81     krb5_free_principal(context, principal);
82   if (context)
83     krb5_free_context(context);
84 
85   return default_name;
86 }
87 
88 
plugin_init()89 int plugin_init()
90 {
91   gss_buffer_desc principal_name_buf;
92   OM_uint32 major= 0, minor= 0;
93   gss_cred_id_t cred= GSS_C_NO_CREDENTIAL;
94 
95   if(srv_keytab_path && srv_keytab_path[0])
96   {
97      setenv("KRB5_KTNAME", srv_keytab_path, 1);
98   }
99 
100   if(!srv_principal_name || !srv_principal_name[0])
101     srv_principal_name= get_default_principal_name();
102 
103   /* import service principal from plain text */
104   if(srv_principal_name && srv_principal_name[0])
105   {
106     my_printf_error(1, "GSSAPI plugin : using principal name '%s'",
107                     ME_ERROR_LOG | ME_NOTE, srv_principal_name);
108     principal_name_buf.length= strlen(srv_principal_name);
109     principal_name_buf.value= srv_principal_name;
110     major= gss_import_name(&minor, &principal_name_buf, GSS_C_NT_USER_NAME, &service_name);
111     if(GSS_ERROR(major))
112     {
113       log_error(major, minor, "gss_import_name");
114       return -1;
115     }
116   }
117   else
118   {
119     service_name=  GSS_C_NO_NAME;
120   }
121 
122   /* Check if SPN configuration is OK */
123   major= gss_acquire_cred(&minor, service_name, GSS_C_INDEFINITE,
124                             GSS_C_NO_OID_SET, GSS_C_ACCEPT, &cred, NULL,
125                             NULL);
126 
127   if (GSS_ERROR(major))
128   {
129     log_error(major, minor, "gss_acquire_cred failed");
130     return -1;
131   }
132   gss_release_cred(&minor, &cred);
133 
134   return 0;
135 }
136 
plugin_deinit()137 int plugin_deinit()
138 {
139   if (service_name != GSS_C_NO_NAME)
140   {
141     OM_uint32 minor;
142     gss_release_name(&minor, &service_name);
143   }
144   return 0;
145 }
146 
147 
auth_server(MYSQL_PLUGIN_VIO * vio,MYSQL_SERVER_AUTH_INFO * auth_info)148 int auth_server(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *auth_info)
149 {
150 
151   int rc= CR_ERROR; /* return code */
152 
153   /* GSSAPI related fields */
154   OM_uint32 major= 0, minor= 0, flags= 0;
155   gss_cred_id_t cred= GSS_C_NO_CREDENTIAL; /* credential identifier */
156   gss_ctx_id_t ctxt= GSS_C_NO_CONTEXT; /* context identifier */
157   gss_name_t client_name;
158   gss_buffer_desc client_name_buf, input, output;
159   char *client_name_str;
160   const char *user= 0;
161   size_t userlen= 0;
162   int use_full_name= 0;
163 
164   /* server acquires credential */
165   major= gss_acquire_cred(&minor, service_name, GSS_C_INDEFINITE,
166                             GSS_C_NO_OID_SET, GSS_C_ACCEPT, &cred, NULL,
167                             NULL);
168 
169   if (GSS_ERROR(major))
170   {
171     log_error(major, minor, "gss_acquire_cred failed");
172     goto cleanup;
173   }
174 
175   input.length= 0;
176   input.value= NULL;
177   do
178   {
179     /* receive token from peer */
180     int len= vio->read_packet(vio, (unsigned char **) &input.value);
181     if (len < 0)
182     {
183       log_error(0, 0, "fail to read token from client");
184       goto cleanup;
185     }
186     if (!user)
187     {
188       if (auth_info->auth_string_length > 0)
189       {
190         use_full_name= 1;
191         user= auth_info->auth_string;
192         userlen= auth_info->auth_string_length;
193       }
194       else
195       {
196         use_full_name= 0;
197         user= auth_info->user_name;
198         userlen= auth_info->user_name_length;
199       }
200     }
201 
202     input.length= len;
203     major= gss_accept_sec_context(&minor, &ctxt, cred, &input,
204                                   GSS_C_NO_CHANNEL_BINDINGS, &client_name,
205                                   NULL, &output, &flags, NULL, NULL);
206     if (GSS_ERROR(major))
207     {
208 
209       log_error(major, minor, "gss_accept_sec_context");
210       rc= CR_ERROR;
211       goto cleanup;
212     }
213 
214     /* send token to peer */
215     if (output.length)
216     {
217       if (vio->write_packet(vio, (const unsigned char *) output.value, output.length))
218       {
219         gss_release_buffer(&minor, &output);
220         log_error(major, minor, "communication error(write)");
221         goto cleanup;
222       }
223       gss_release_buffer(&minor, &output);
224     }
225   } while (major & GSS_S_CONTINUE_NEEDED);
226 
227   /* extract plain text client name */
228   major= gss_display_name(&minor, client_name, &client_name_buf, NULL);
229   if (GSS_ERROR(major))
230   {
231     log_error(major, minor, "gss_display_name");
232     goto cleanup;
233   }
234 
235   client_name_str= (char *)client_name_buf.value;
236 
237   /*
238    * Compare input user name with the actual one. Return success if
239    * the names match exactly, or if use_full_name parameter is not set
240    * up to the '@' separator.
241    */
242   if ((userlen == client_name_buf.length) ||
243       (!use_full_name
244        && userlen < client_name_buf.length
245        && client_name_str[userlen] == '@'))
246   {
247     if (user && strncmp(client_name_str, user, userlen) == 0)
248     {
249       rc= CR_OK;
250     }
251   }
252 
253   if(rc != CR_OK)
254   {
255     my_printf_error(ER_ACCESS_DENIED_ERROR,
256       "GSSAPI name mismatch, requested '%s', actual name '%.*s'",
257       0, user, (int)client_name_buf.length, client_name_str);
258   }
259 
260   gss_release_buffer(&minor, &client_name_buf);
261 
262 
263 cleanup:
264   if (ctxt != GSS_C_NO_CONTEXT)
265     gss_delete_sec_context(&minor, &ctxt, GSS_C_NO_BUFFER);
266   if (cred != GSS_C_NO_CREDENTIAL)
267     gss_release_cred(&minor, &cred);
268 
269   return(rc);
270 }
271