krb5_plugin: Add winbind localauth plugin for MIT Kerberos
[samba.git] / nsswitch / krb5_plugin / winbind_krb5_localauth.c
1 /*
2    Unix SMB/CIFS implementation.
3
4    A localauth plugin for MIT Kerberos
5
6    Copyright (C) 2018      Andreas Schneider <asn@samba.org>
7
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 3 of the License, or
11    (at your option) any later version.
12
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include "replace.h"
23 #include <krb5/localauth_plugin.h>
24 #include <wbclient.h>
25 #if HAVE_COM_ERR_H
26 #include <com_err.h>
27 #endif
28
29 struct krb5_localauth_moddata_st {
30         struct wbcContext *wbc_ctx;
31 };
32
33 /*
34  * Initialize the module data.
35  *
36  * This creates the wbclient context.
37  */
38 static krb5_error_code winbind_init(krb5_context context,
39                                     krb5_localauth_moddata *data)
40 {
41         krb5_localauth_moddata d;
42
43         *data = NULL;
44         d = malloc(sizeof(struct krb5_localauth_moddata_st));
45         if (d == NULL) {
46                 return ENOMEM;
47         }
48
49         d->wbc_ctx = wbcCtxCreate();
50         if (d->wbc_ctx == NULL) {
51                 free(d);
52                 return ENOMEM;
53         }
54
55         *data = d;
56
57         return 0;
58 }
59
60 /*
61  * Release resources used by module data.
62  */
63 static void winbind_fini(krb5_context context, krb5_localauth_moddata data)
64 {
65         wbcCtxFree(data->wbc_ctx);
66         free(data);
67         data = NULL;
68 }
69
70 /*
71  * Determine whether aname is authorized to log in as the local account lname.
72  *
73  * Return 0 if aname is authorized, EPERM if aname is authoritatively not
74  * authorized, KRB5_PLUGIN_NO_HANDLE if the module cannot determine whether
75  * aname is authorized, and any other error code for a serious failure to
76  * process the request.  aname will be considered authorized if at least one
77  * module returns 0 and all other modules return KRB5_PLUGIN_NO_HANDLE.
78  */
79 static krb5_error_code winbind_userok(krb5_context context,
80                                       krb5_localauth_moddata data,
81                                       krb5_const_principal aname,
82                                       const char *lname)
83 {
84         krb5_error_code code = 0;
85         char *princ_str = NULL;
86         struct passwd *pwd = NULL;
87         uid_t princ_uid;
88         uid_t lname_uid;
89         wbcErr wbc_status;
90         int cmp;
91
92         code = krb5_unparse_name(context, aname, &princ_str);
93         if (code != 0) {
94                 return code;
95         }
96
97         cmp = strcasecmp(princ_str, lname);
98         if (cmp == 0) {
99                 krb5_free_unparsed_name(context, princ_str);
100                 return 0;
101         }
102
103         wbc_status = wbcCtxGetpwnam(data->wbc_ctx,
104                                     princ_str,
105                                     &pwd);
106         krb5_free_unparsed_name(context, princ_str);
107         switch (wbc_status) {
108         case WBC_ERR_SUCCESS:
109                 princ_uid = pwd->pw_uid;
110                 code = 0;
111                 break;
112         case WBC_ERR_UNKNOWN_USER:
113         /* match other insane libwbclient return codes */
114         case WBC_ERR_WINBIND_NOT_AVAILABLE:
115         case WBC_ERR_DOMAIN_NOT_FOUND:
116                 code = KRB5_PLUGIN_NO_HANDLE;
117                 break;
118         default:
119                 code = EIO;
120                 break;
121         }
122         wbcFreeMemory(pwd);
123         if (code != 0) {
124                 return code;
125         }
126
127         wbc_status = wbcCtxGetpwnam(data->wbc_ctx,
128                                     lname,
129                                     &pwd);
130         switch (wbc_status) {
131         case WBC_ERR_SUCCESS:
132                 lname_uid = pwd->pw_uid;
133                 break;
134         case WBC_ERR_UNKNOWN_USER:
135         /* match other insane libwbclient return codes */
136         case WBC_ERR_WINBIND_NOT_AVAILABLE:
137         case WBC_ERR_DOMAIN_NOT_FOUND:
138                 code = KRB5_PLUGIN_NO_HANDLE;
139                 break;
140         default:
141                 code = EIO;
142                 break;
143         }
144         wbcFreeMemory(pwd);
145         if (code != 0) {
146                 return code;
147         }
148
149         if (princ_uid != lname_uid) {
150                 code = EPERM;
151         }
152
153         return code;
154 }
155
156 /*
157  * Determine the local account name corresponding to aname.
158  *
159  * Return 0 and set *lname_out if a mapping can be determined; the contents of
160  * *lname_out will later be released with a call to the module's free_string
161  * method.  Return KRB5_LNAME_NOTRANS if no mapping can be determined.  Return
162  * any other error code for a serious failure to process the request; this will
163  * halt the krb5_aname_to_localname operation.
164  *
165  * If the module's an2ln_types field is set, this method will only be invoked
166  * when a profile "auth_to_local" value references one of the module's types.
167  * type and residual will be set to the type and residual of the auth_to_local
168  * value.
169  *
170  * If the module's an2ln_types field is not set but the an2ln method is
171  * implemented, this method will be invoked independently of the profile's
172  * auth_to_local settings, with type and residual set to NULL.  If multiple
173  * modules are registered with an2ln methods but no an2ln_types field, the
174  * order of invocation is not defined, but all such modules will be consulted
175  * before the built-in mechanisms are tried.
176  */
177 static krb5_error_code winbind_an2ln(krb5_context context,
178                                      krb5_localauth_moddata data,
179                                      const char *type,
180                                      const char *residual,
181                                      krb5_const_principal aname,
182                                      char **lname_out)
183 {
184         krb5_error_code code = 0;
185         char *princ_str = NULL;
186         char *name = NULL;
187         struct passwd *pwd = NULL;
188         wbcErr wbc_status;
189
190         code = krb5_unparse_name(context, aname, &princ_str);
191         if (code != 0) {
192                 return code;
193         }
194
195         wbc_status = wbcCtxGetpwnam(data->wbc_ctx,
196                                     princ_str,
197                                     &pwd);
198         krb5_free_unparsed_name(context, princ_str);
199         switch (wbc_status) {
200         case WBC_ERR_SUCCESS:
201                 name = strdup(pwd->pw_name);
202                 code = 0;
203                 break;
204         case WBC_ERR_UNKNOWN_USER:
205         /* match other insane libwbclient return codes */
206         case WBC_ERR_WINBIND_NOT_AVAILABLE:
207         case WBC_ERR_DOMAIN_NOT_FOUND:
208                 code = KRB5_LNAME_NOTRANS;
209                 break;
210         default:
211                 code = EIO;
212                 break;
213         }
214         wbcFreeMemory(pwd);
215         if (code != 0) {
216                 return code;
217         }
218
219         if (name == NULL) {
220                 return ENOMEM;
221         }
222
223         *lname_out = name;
224
225         return code;
226 }
227
228 /*
229  * Release the memory returned by an invocation of an2ln.
230  */
231 static void winbind_free_string(krb5_context context,
232                                 krb5_localauth_moddata data,
233                                 char *str)
234 {
235         free(str);
236 }
237
238 krb5_error_code
239 localauth_winbind_initvt(krb5_context context,
240                          int maj_ver,
241                          int min_ver,
242                          krb5_plugin_vtable vtable);
243
244 krb5_error_code
245 localauth_winbind_initvt(krb5_context context,
246                          int maj_ver,
247                          int min_ver,
248                          krb5_plugin_vtable vtable)
249 {
250         krb5_localauth_vtable vt = (krb5_localauth_vtable)vtable;
251
252         if (maj_ver != 1) {
253                 com_err("winbind_localauth",
254                         EINVAL,
255                         "Failed to load, plugin API changed.");
256                 return KRB5_PLUGIN_VER_NOTSUPP;
257         }
258
259         vt->init = winbind_init;
260         vt->fini = winbind_fini;
261         vt->name = "winbind";
262         vt->an2ln = winbind_an2ln;
263         vt->userok = winbind_userok;
264         vt->free_string = winbind_free_string;
265
266         return 0;
267 }