From d79541de4574cb825da40278eefe64670310d543 Mon Sep 17 00:00:00 2001
From: Benjamin Franzke <benjaminfranzke@googlemail.com>
Date: Thu, 26 Sep 2013 10:27:33 +0200
Subject: [PATCH] Add CIFS idmap plugin

https://fedorahosted.org/sssd/ticket/1534
---
 Makefile.am                             |  21 ++
 configure.ac                            |   2 +
 src/conf_macros.m4                      |  14 ++
 src/external/cifsidmap.m4               |  16 ++
 src/lib/cifs_idmap_sss/cifs_idmap_sss.c | 340 ++++++++++++++++++++++++++++++++
 src/tests/dlopen-tests.c                |   3 +
 6 files changed, 396 insertions(+)
 create mode 100644 src/external/cifsidmap.m4
 create mode 100644 src/lib/cifs_idmap_sss/cifs_idmap_sss.c

diff --git a/Makefile.am b/Makefile.am
index ff1e71e72d90a6eff658a40e2ca24c1929b31aa5..98a298eae9e853051e9c569649d08cf8f7ce7354 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -32,6 +32,9 @@ endif
 if BUILD_PAC_RESPONDER
 krb5authdata_plugindir = @krb5authdatapluginpath@
 endif
+if BUILD_CIFS_IDMAP_PLUGIN
+cifsplugindir = @cifspluginpath@
+endif
 sssdconfdir = $(sysconfdir)/sssd
 sssddatadir = $(datadir)/sssd
 sssdapiplugindir = $(sssddatadir)/sssd.api.d
@@ -192,6 +195,11 @@ krb5authdata_plugin_LTLIBRARIES = \
     sssd_pac_plugin.la
 endif
 
+if BUILD_CIFS_IDMAP_PLUGIN
+cifsplugin_LTLIBRARIES = \
+    cifs_idmap_sss.la
+endif
+
 noinst_LTLIBRARIES =
 
 pkglib_LTLIBRARIES = \
@@ -1878,6 +1886,19 @@ pysss_nss_idmap_la_LDFLAGS = \
     -module
 endif
 
+if BUILD_CIFS_IDMAP_PLUGIN
+cifs_idmap_sss_la_SOURCES = \
+    src/lib/cifs_idmap_sss/cifs_idmap_sss.c
+cifs_idmap_sss_la_LIBADD = \
+    libsss_idmap.la \
+    libsss_nss_idmap.la
+cifs_idmap_sss_la_CFLAGS = \
+    $(AM_CFLAGS)
+cifs_idmap_sss_la_LDFLAGS = \
+    -avoid-version \
+    -module
+endif
+
 ################
 # TRANSLATIONS #
 ################
diff --git a/configure.ac b/configure.ac
index d28d55f3b0eb35cc4ecc02ae9b1fafbcb9588dcf..9934b50b90591cc46867fbb17a84a9aada2cd25e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -115,6 +115,7 @@ WITH_KRB5_RCACHE_DIR
 WITH_KRB5AUTHDATA_PLUGIN_PATH
 WITH_KRB5_CONF
 WITH_PYTHON_BINDINGS
+WITH_CIFS_PLUGIN_PATH
 WITH_SELINUX
 WITH_NSCD
 WITH_SEMANAGE
@@ -153,6 +154,7 @@ m4_include([src/external/libkeyutils.m4])
 m4_include([src/external/libnl.m4])
 m4_include([src/external/systemd.m4])
 m4_include([src/external/pac_responder.m4])
+m4_include([src/external/cifsidmap.m4])
 m4_include([src/external/signal.m4])
 m4_include([src/external/inotify.m4])
 m4_include([src/external/libndr_nbt.m4])
diff --git a/src/conf_macros.m4 b/src/conf_macros.m4
index 1aecaea4d60140f9f99d6f6038a4f77205d2e1bd..4be819d4a3389dfcf89a0452aab8165c0ec216e4 100644
--- a/src/conf_macros.m4
+++ b/src/conf_macros.m4
@@ -265,6 +265,20 @@ AC_DEFUN([WITH_KRB5_PLUGIN_PATH],
     AC_SUBST(krb5pluginpath)
   ])
 
+AC_DEFUN([WITH_CIFS_PLUGIN_PATH],
+  [ AC_ARG_WITH([cifs-plugin-path],
+                [AC_HELP_STRING([--with-cifs-plugin-path=PATH],
+                                [Path to cifs-utils plugin store [/usr/lib/cifs-utils]]
+                               )
+                ]
+               )
+    cifspluginpath="${libdir}/cifs-utils"
+    if test x"$with_cifs_plugin_path" != x; then
+        cifspluginpath=$with_cifs_plugin_path
+    fi
+    AC_SUBST(cifspluginpath)
+  ])
+
 AC_DEFUN([WITH_KRB5_RCACHE_DIR],
   [ AC_ARG_WITH([krb5-rcache-dir],
                 [AC_HELP_STRING([--with-krb5-rcache-dir=PATH],
diff --git a/src/external/cifsidmap.m4 b/src/external/cifsidmap.m4
new file mode 100644
index 0000000000000000000000000000000000000000..53cb8b778da9639a9d48c18ac9d86697325b8955
--- /dev/null
+++ b/src/external/cifsidmap.m4
@@ -0,0 +1,16 @@
+AC_ARG_ENABLE([cifs-idmap-plugin],
+              [AS_HELP_STRING([--disable-cifs-idmap-plugin],
+                              [do not build CIFS idmap plugin])],
+              [build_cifs_idmap_plugin=$enableval],
+              [build_cifs_idmap_plugin=yes])
+
+AS_IF([test x$build_cifs_idmap_plugin = xyes],
+      [AC_CHECK_HEADER([cifsidmap.h], [],
+                       [AC_MSG_ERROR([you must have the cifsidmap header installed to build the idmap plugin])])
+      ])
+
+AM_CONDITIONAL([BUILD_CIFS_IDMAP_PLUGIN],
+               [test x$build_cifs_idmap_plugin = xyes])
+
+AM_COND_IF([BUILD_CIFS_IDMAP_PLUGIN],
+           [AC_DEFINE_UNQUOTED(HAVE_CIFS_IDMAP_PLUGIN, 1, [Build with cifs idmap plugin])])
diff --git a/src/lib/cifs_idmap_sss/cifs_idmap_sss.c b/src/lib/cifs_idmap_sss/cifs_idmap_sss.c
new file mode 100644
index 0000000000000000000000000000000000000000..f9149177b54831c86627b8406e69f7e30759d366
--- /dev/null
+++ b/src/lib/cifs_idmap_sss/cifs_idmap_sss.c
@@ -0,0 +1,340 @@
+/*
+    Authors:
+        Benjamin Franzke <benjaminfranzke@googlemail.com>
+
+    Copyright (C) 2013 Benjamin Franzke
+
+    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/>.
+*/
+
+/* TODO: Support well known SIDs as in samba's
+ *        - librpc/idl/security.idl or
+ *        - source4/rpc_server/lsa/lsa_lookup.c?
+ */
+
+/* TODO: Support of [all] samba's Unix SIDs:
+ *         Users:  S-1-22-1-%UID
+ *         Groups: S-1-22-2-%GID
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include <stdarg.h>
+
+#include <cifsidmap.h>
+
+#include "lib/idmap/sss_idmap.h"
+#include "sss_client/idmap/sss_nss_idmap.h"
+
+#define WORLD_SID "S-1-1-0"
+
+#ifdef DEBUG
+#include <syslog.h>
+#define debug(str, ...) \
+    syslog(0, "%s: " str "\n", \
+           __FUNCTION__, ##__VA_ARGS__)
+#else
+#define debug(...) do { } while(0)
+#endif
+
+struct sssd_ctx {
+    struct sss_idmap_ctx *idmap;
+    const char **errmsg;
+};
+
+#define ctx_set_error(ctx, error) \
+    do { \
+        *ctx->errmsg = error; \
+        debug("%s", error ? error : ""); \
+    } while (0);
+
+int cifs_idmap_init_plugin(void **handle, const char **errmsg)
+{
+    struct sssd_ctx *ctx;
+    enum idmap_error_code err;
+
+    if (handle == NULL || errmsg == NULL)
+        return EINVAL;
+
+    ctx = malloc(sizeof *ctx);
+    if (!ctx) {
+        *errmsg = "Failed to allocate context";
+        return -1;
+    }
+    ctx->errmsg = errmsg;
+    ctx_set_error(ctx, NULL);
+
+    err = sss_idmap_init(NULL, NULL, NULL, &ctx->idmap);
+    if (err != IDMAP_SUCCESS) {
+        ctx_set_error(ctx, idmap_error_string(err));
+        free(ctx);
+        return -1;
+    }
+
+    *handle = ctx;
+    return 0;
+}
+
+void cifs_idmap_exit_plugin(void *handle)
+{
+    struct sssd_ctx *ctx = handle;
+
+    debug("exit");
+
+    if (ctx == NULL)
+        return;
+
+    sss_idmap_free(ctx->idmap);
+
+    free(ctx);
+}
+
+
+/* Test with `getcifsacl file` on client. */
+int cifs_idmap_sid_to_str(void *handle, const struct cifs_sid *csid,
+                          char **name)
+{
+    struct sssd_ctx *ctx = handle;
+    enum idmap_error_code iderr;
+    char *sid;
+    enum sss_id_type id_type;
+    int err;
+
+    iderr = sss_idmap_bin_sid_to_sid(ctx->idmap, (const uint8_t *) csid,
+                                     sizeof(*csid), &sid);
+    if (iderr != IDMAP_SUCCESS) {
+        ctx_set_error(ctx, idmap_error_string(iderr));
+        *name = NULL;
+        return -1;
+    }
+
+    debug("sid: %s", sid);
+
+    if (strcmp(sid, WORLD_SID) == 0) {
+        *name = strdup("\\Everyone");
+        if (!*name) {
+            ctx_set_error(ctx, strerror(ENOMEM));
+            return ENOMEM;
+        }
+        return 0;
+    }
+
+    err = sss_nss_getnamebysid(sid, name, &id_type);
+    if (err != 0)  {
+        ctx_set_error(ctx, strerror(err));
+        *name = NULL;
+        return -err;
+    }
+
+    /* FIXME: Map Samba Unix SIDs? (sid->id and use getpwuid)? */
+
+    debug("name: %s", *name);
+
+    return 0;
+}
+
+static int sid_to_cifs_sid(struct sssd_ctx *ctx, const char *sid,
+                           struct cifs_sid *csid)
+{
+    uint8_t *bsid = NULL;
+    enum idmap_error_code err;
+    size_t length;
+
+    err = sss_idmap_sid_to_bin_sid(ctx->idmap,
+                                   sid, &bsid, &length);
+    if (err != IDMAP_SUCCESS) {
+        ctx_set_error(ctx, idmap_error_string(err));
+        return -1;
+    }
+    if (length > sizeof(struct cifs_sid)) {
+        ctx_set_error(ctx, "too large sid length");
+        free(bsid);
+        return -1;
+    }
+
+    memcpy(csid, bsid, length);
+    free(bsid);
+
+    return 0;
+}
+
+/* Test with setcifsacl -a */
+int cifs_idmap_str_to_sid(void *handle, const char *name,
+                          struct cifs_sid *csid)
+{
+    struct sssd_ctx *ctx = handle;
+    int err;
+    enum sss_id_type id_type;
+    char *sid = NULL;
+    int success = 0;
+
+    debug("%s", name);
+
+    err = sss_nss_getsidbyname(name, &sid, &id_type);
+    if (err != 0)  {
+        /* Might be a raw string representation of SID,
+         * try converting that before returning an error. */
+        if (sid_to_cifs_sid(ctx, name, csid) == 0)
+            return 0;
+
+        ctx_set_error(ctx, strerror(err));
+        return -err;
+    }
+
+    if (sid_to_cifs_sid(ctx, sid, csid) != 0)
+        success = -1;
+
+    free(sid);
+
+    return success;
+}
+
+static int samba_unix_sid_to_id(const char *sid, struct cifs_uxid *cuxid)
+{
+    id_t id;
+    uint8_t type;
+
+    if (sscanf(sid, "S-1-22-%hhu-%u", &type, &id) != 2)
+        return -1;
+
+    switch (type) {
+    case 1:
+        cuxid->type = CIFS_UXID_TYPE_UID;
+        cuxid->id.uid = id;
+        break;
+    case 2:
+        cuxid->type = CIFS_UXID_TYPE_GID;
+        cuxid->id.gid = id;
+        break;
+    default:
+        cuxid->type = CIFS_UXID_TYPE_UNKNOWN;
+        return -1;
+    }
+
+    return 0;
+}
+
+static int sss_sid_to_id(struct sssd_ctx *ctx, const char *sid,
+                         struct cifs_uxid *cuxid)
+{
+    int err;
+    enum sss_id_type id_type;
+
+    err = sss_nss_getidbysid(sid, (uint32_t *)&cuxid->id.uid, &id_type);
+    if (err != 0)  {
+        ctx_set_error(ctx, strerror(err));
+        return -1;
+    }
+
+    switch (id_type) {
+    case SSS_ID_TYPE_UID:
+        cuxid->type = CIFS_UXID_TYPE_UID;
+        break;
+    case SSS_ID_TYPE_GID:
+        cuxid->type = CIFS_UXID_TYPE_GID;
+        break;
+    case SSS_ID_TYPE_BOTH:
+        cuxid->type = CIFS_UXID_TYPE_BOTH;
+        break;
+    case SSS_ID_TYPE_NOT_SPECIFIED:
+    default:
+        return -1;
+    }
+
+    return 0;
+}
+
+/**
+ * cifs_idmap_sids_to_ids - convert struct cifs_sids to struct cifs_uxids
+ * usecase: mount.cifs -o sec=krb5,multiuser,cifsacl,nounix
+ * test: ls -n on mounted share
+ */
+int cifs_idmap_sids_to_ids(void *handle, const struct cifs_sid *csid,
+                           const size_t num, struct cifs_uxid *cuxid)
+{
+    struct sssd_ctx *ctx = handle;
+    enum idmap_error_code err;
+    int success = -1;
+    size_t i;
+    char *sid;
+
+    debug("num: %zd", num);
+
+    if (num > UINT_MAX) {
+        ctx_set_error(ctx, "num is too large.");
+        return EINVAL;
+    }
+
+    for (i = 0; i < num; ++i) {
+        err = sss_idmap_bin_sid_to_sid(ctx->idmap, (const uint8_t *) &csid[i],
+                                       sizeof(csid[i]), &sid);
+        if (err != IDMAP_SUCCESS) {
+            ctx_set_error(ctx, idmap_error_string(err));
+            continue;
+        }
+
+        cuxid[i].type = CIFS_UXID_TYPE_UNKNOWN;
+
+        if (sss_sid_to_id(ctx, sid, &cuxid[i]) == 0 ||
+            samba_unix_sid_to_id(sid, &cuxid[i]) == 0) {
+
+            debug("setting uid of %s to %d", sid, cuxid[i].id.uid);
+            success = 0;
+        }
+
+        free(sid);
+    }
+
+    return success;
+}
+
+
+int cifs_idmap_ids_to_sids(void *handle, const struct cifs_uxid *cuxid,
+                           const size_t num, struct cifs_sid *csid)
+{
+    struct sssd_ctx *ctx = handle;
+    int err, success = -1;
+    char *sid;
+    enum sss_id_type id_type;
+    size_t i;
+
+    debug("num ids: %zd", num);
+
+    if (num > UINT_MAX) {
+        ctx_set_error(ctx, "num is too large.");
+        return EINVAL;
+    }
+
+    for (i = 0; i < num; ++i) {
+        err = sss_nss_getsidbyid((uint32_t)cuxid[i].id.uid, &sid, &id_type);
+        if (err != 0)  {
+            ctx_set_error(ctx, strerror(err));
+            csid[i].revision = 0;
+            /* FIXME: would it be safe to map *any* uid/gids unknown by sssd to
+             * SAMBA's UNIX SIDs? */
+            continue;
+        }
+
+        if (sid_to_cifs_sid(ctx, sid, csid) == 0)
+            success = 0;
+        else
+            csid[i].revision = 0;
+        free(sid);
+    }
+
+    return success;
+}
diff --git a/src/tests/dlopen-tests.c b/src/tests/dlopen-tests.c
index 67fc41c7a9187cd765553d7715a704ff260a0e1c..40e02daa2ea59c76b4ca422552b943e3c5d8f302 100644
--- a/src/tests/dlopen-tests.c
+++ b/src/tests/dlopen-tests.c
@@ -56,6 +56,9 @@ struct so {
 #ifdef HAVE_PAC_RESPONDER
     { "sssd_pac_plugin.so", { LIBPFX"sssd_pac_plugin.so", NULL } },
 #endif
+#ifdef HAVE_CIFS_IDMAP_PLUGIN
+    { "cifs_idmap_sss.so", { LIBPFX"cifs_idmap_sss.so", NULL } },
+#endif
     { "memberof.so", { LIBPFX"memberof.so", NULL } },
     { "libsss_child.so", { "libtevent.so",
                            LIBPFX"libsss_debug.so",
-- 
1.8.1.5

