krb5_plugin: Add winbind localauth plugin for MIT Kerberos
authorAndreas Schneider <asn@samba.org>
Fri, 15 Jun 2018 12:59:00 +0000 (14:59 +0200)
committerAndreas Schneider <asn@cryptomilk.org>
Thu, 21 Jun 2018 13:52:02 +0000 (15:52 +0200)
Applications (like OpenSSH) don't know about users and and
their relationship to Kerberos principals. This plugin allows that
Kerberos principals can be validated against local user accounts.

Administrator@WURST.WORLD -> WURST\Administrator

https://web.mit.edu/kerberos/krb5-latest/doc/plugindev/localauth.html

BUG: https://bugzilla.samba.org/show_bug.cgi?id=13480

Signed-off-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Alexander Bokovoy <ab@samba.org>
Autobuild-User(master): Andreas Schneider <asn@cryptomilk.org>
Autobuild-Date(master): Thu Jun 21 15:52:02 CEST 2018 on sn-devel-144

WHATSNEW.txt
nsswitch/krb5_plugin/winbind_krb5_localauth.c [new file with mode: 0644]
nsswitch/wscript_build
wscript_configure_system_mitkrb5

index b9c80cf..2ceacc4 100644 (file)
@@ -53,6 +53,19 @@ net ads keytab create no longer tries to generate SPN(s) from existing
 entries in a keytab file. If it is required to add Windows SPN(s) then
 'net ads setspn add' should be used instead.
 
+Local authorization plugin for MIT Kerberos
+-------------------------------------------
+
+This plugin controls the relationship between Kerberos principals and AD
+accounts through winbind. The module receives the Kerberos principal and the
+local account name as inputs and can then check if they match. This can resolve
+issues with canonicalized names returned by Kerberos within AD. If the user
+tries to log in as 'alice', but the samAccountName is set to ALICE (uppercase),
+Kerberos would return ALICE as the username. Kerberos would not be able to map
+'alice' to 'ALICE' in this case and auth would fail.  With this plugin account
+names can be correctly mapped. This only applies to GSSAPI authentication,
+not for the geting the initial ticket granting ticket.
+
 REMOVED FEATURES
 ================
 
diff --git a/nsswitch/krb5_plugin/winbind_krb5_localauth.c b/nsswitch/krb5_plugin/winbind_krb5_localauth.c
new file mode 100644 (file)
index 0000000..7c77609
--- /dev/null
@@ -0,0 +1,267 @@
+/*
+   Unix SMB/CIFS implementation.
+
+   A localauth plugin for MIT Kerberos
+
+   Copyright (C) 2018      Andreas Schneider <asn@samba.org>
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include <krb5/localauth_plugin.h>
+#include <wbclient.h>
+#if HAVE_COM_ERR_H
+#include <com_err.h>
+#endif
+
+struct krb5_localauth_moddata_st {
+       struct wbcContext *wbc_ctx;
+};
+
+/*
+ * Initialize the module data.
+ *
+ * This creates the wbclient context.
+ */
+static krb5_error_code winbind_init(krb5_context context,
+                                   krb5_localauth_moddata *data)
+{
+       krb5_localauth_moddata d;
+
+       *data = NULL;
+       d = malloc(sizeof(struct krb5_localauth_moddata_st));
+       if (d == NULL) {
+               return ENOMEM;
+       }
+
+       d->wbc_ctx = wbcCtxCreate();
+       if (d->wbc_ctx == NULL) {
+               free(d);
+               return ENOMEM;
+       }
+
+       *data = d;
+
+       return 0;
+}
+
+/*
+ * Release resources used by module data.
+ */
+static void winbind_fini(krb5_context context, krb5_localauth_moddata data)
+{
+       wbcCtxFree(data->wbc_ctx);
+       free(data);
+       data = NULL;
+}
+
+/*
+ * Determine whether aname is authorized to log in as the local account lname.
+ *
+ * Return 0 if aname is authorized, EPERM if aname is authoritatively not
+ * authorized, KRB5_PLUGIN_NO_HANDLE if the module cannot determine whether
+ * aname is authorized, and any other error code for a serious failure to
+ * process the request.  aname will be considered authorized if at least one
+ * module returns 0 and all other modules return KRB5_PLUGIN_NO_HANDLE.
+ */
+static krb5_error_code winbind_userok(krb5_context context,
+                                     krb5_localauth_moddata data,
+                                     krb5_const_principal aname,
+                                     const char *lname)
+{
+       krb5_error_code code = 0;
+       char *princ_str = NULL;
+       struct passwd *pwd = NULL;
+       uid_t princ_uid;
+       uid_t lname_uid;
+       wbcErr wbc_status;
+       int cmp;
+
+       code = krb5_unparse_name(context, aname, &princ_str);
+       if (code != 0) {
+               return code;
+       }
+
+       cmp = strcasecmp(princ_str, lname);
+       if (cmp == 0) {
+               krb5_free_unparsed_name(context, princ_str);
+               return 0;
+       }
+
+       wbc_status = wbcCtxGetpwnam(data->wbc_ctx,
+                                   princ_str,
+                                   &pwd);
+       krb5_free_unparsed_name(context, princ_str);
+       switch (wbc_status) {
+       case WBC_ERR_SUCCESS:
+               princ_uid = pwd->pw_uid;
+               code = 0;
+               break;
+       case WBC_ERR_UNKNOWN_USER:
+       /* match other insane libwbclient return codes */
+       case WBC_ERR_WINBIND_NOT_AVAILABLE:
+       case WBC_ERR_DOMAIN_NOT_FOUND:
+               code = KRB5_PLUGIN_NO_HANDLE;
+               break;
+       default:
+               code = EIO;
+               break;
+       }
+       wbcFreeMemory(pwd);
+       if (code != 0) {
+               return code;
+       }
+
+       wbc_status = wbcCtxGetpwnam(data->wbc_ctx,
+                                   lname,
+                                   &pwd);
+       switch (wbc_status) {
+       case WBC_ERR_SUCCESS:
+               lname_uid = pwd->pw_uid;
+               break;
+       case WBC_ERR_UNKNOWN_USER:
+       /* match other insane libwbclient return codes */
+       case WBC_ERR_WINBIND_NOT_AVAILABLE:
+       case WBC_ERR_DOMAIN_NOT_FOUND:
+               code = KRB5_PLUGIN_NO_HANDLE;
+               break;
+       default:
+               code = EIO;
+               break;
+       }
+       wbcFreeMemory(pwd);
+       if (code != 0) {
+               return code;
+       }
+
+       if (princ_uid != lname_uid) {
+               code = EPERM;
+       }
+
+       return code;
+}
+
+/*
+ * Determine the local account name corresponding to aname.
+ *
+ * Return 0 and set *lname_out if a mapping can be determined; the contents of
+ * *lname_out will later be released with a call to the module's free_string
+ * method.  Return KRB5_LNAME_NOTRANS if no mapping can be determined.  Return
+ * any other error code for a serious failure to process the request; this will
+ * halt the krb5_aname_to_localname operation.
+ *
+ * If the module's an2ln_types field is set, this method will only be invoked
+ * when a profile "auth_to_local" value references one of the module's types.
+ * type and residual will be set to the type and residual of the auth_to_local
+ * value.
+ *
+ * If the module's an2ln_types field is not set but the an2ln method is
+ * implemented, this method will be invoked independently of the profile's
+ * auth_to_local settings, with type and residual set to NULL.  If multiple
+ * modules are registered with an2ln methods but no an2ln_types field, the
+ * order of invocation is not defined, but all such modules will be consulted
+ * before the built-in mechanisms are tried.
+ */
+static krb5_error_code winbind_an2ln(krb5_context context,
+                                    krb5_localauth_moddata data,
+                                    const char *type,
+                                    const char *residual,
+                                    krb5_const_principal aname,
+                                    char **lname_out)
+{
+       krb5_error_code code = 0;
+       char *princ_str = NULL;
+       char *name = NULL;
+       struct passwd *pwd = NULL;
+       wbcErr wbc_status;
+
+       code = krb5_unparse_name(context, aname, &princ_str);
+       if (code != 0) {
+               return code;
+       }
+
+       wbc_status = wbcCtxGetpwnam(data->wbc_ctx,
+                                   princ_str,
+                                   &pwd);
+       krb5_free_unparsed_name(context, princ_str);
+       switch (wbc_status) {
+       case WBC_ERR_SUCCESS:
+               name = strdup(pwd->pw_name);
+               code = 0;
+               break;
+       case WBC_ERR_UNKNOWN_USER:
+       /* match other insane libwbclient return codes */
+       case WBC_ERR_WINBIND_NOT_AVAILABLE:
+       case WBC_ERR_DOMAIN_NOT_FOUND:
+               code = KRB5_LNAME_NOTRANS;
+               break;
+       default:
+               code = EIO;
+               break;
+       }
+       wbcFreeMemory(pwd);
+       if (code != 0) {
+               return code;
+       }
+
+       if (name == NULL) {
+               return ENOMEM;
+       }
+
+       *lname_out = name;
+
+       return code;
+}
+
+/*
+ * Release the memory returned by an invocation of an2ln.
+ */
+static void winbind_free_string(krb5_context context,
+                               krb5_localauth_moddata data,
+                               char *str)
+{
+       free(str);
+}
+
+krb5_error_code
+localauth_winbind_initvt(krb5_context context,
+                        int maj_ver,
+                        int min_ver,
+                        krb5_plugin_vtable vtable);
+
+krb5_error_code
+localauth_winbind_initvt(krb5_context context,
+                        int maj_ver,
+                        int min_ver,
+                        krb5_plugin_vtable vtable)
+{
+       krb5_localauth_vtable vt = (krb5_localauth_vtable)vtable;
+
+       if (maj_ver != 1) {
+               com_err("winbind_localauth",
+                       EINVAL,
+                       "Failed to load, plugin API changed.");
+               return KRB5_PLUGIN_VER_NOTSUPP;
+       }
+
+       vt->init = winbind_init;
+       vt->fini = winbind_fini;
+       vt->name = "winbind";
+       vt->an2ln = winbind_an2ln;
+       vt->userok = winbind_userok;
+       vt->free_string = winbind_free_string;
+
+       return 0;
+}
index ab8f8ea..15e93db 100644 (file)
@@ -110,6 +110,12 @@ if bld.CONFIG_SET('HAVE_KRB5_LOCATE_PLUGIN_H'):
                deps='wbclient krb5 com_err',
                realname='winbind_krb5_locator.so')
 
+if bld.CONFIG_SET('HAVE_KRB5_LOCALAUTH_PLUGIN_H'):
+    bld.SAMBA_LIBRARY('winbind_krb5_localauth',
+                      source='krb5_plugin/winbind_krb5_localauth.c',
+                      deps='wbclient krb5 com_err',
+                      realname='winbind-krb5-localauth.so')
+
 bld.SAMBA_SUBSYSTEM('WB_REQTRANS',
        source='wb_reqtrans.c',
        deps='talloc tevent LIBASYNC_REQ'
index 803dad7..facf415 100644 (file)
@@ -80,6 +80,7 @@ conf.CHECK_HEADERS('com_err.h', lib='com_err')
 conf.CHECK_HEADERS('kdb.h', lib='kdb5')
 
 conf.CHECK_HEADERS('krb5.h krb5/locate_plugin.h', lib='krb5')
+conf.CHECK_HEADERS('krb5.h krb5/localauth_plugin.h', lib='krb5')
 possible_gssapi_headers="gssapi.h gssapi/gssapi_generic.h gssapi/gssapi.h gssapi/gssapi_ext.h gssapi/gssapi_krb5.h gssapi/gssapi_oid.h"
 conf.CHECK_HEADERS(possible_gssapi_headers, lib='gssapi')