idmap_nss: Add a parameter to use UPNs instead of plain names
authorSamuel Cabrero <scabrero@suse.de>
Mon, 27 Nov 2023 07:05:29 +0000 (08:05 +0100)
committerSamuel Cabrero <scabrero@samba.org>
Wed, 13 Dec 2023 15:07:38 +0000 (15:07 +0000)
idmap config <DOMAIN> : backend = nss
idmap config <DOMAIN> : use_upn = yes|no

When translating a Unix ID to a SID the module calls get[pwu|grg]id() but the
name returned by some NSS modules might be a UPN instead of a plain name. If
the new parameter is enabled the returned name will be parsed and correctly
handled.

On the other hand, when translating a SID to a Unix ID the module first
resolves the SID to a domain + name, and then calls get[pw|gr]name() with the
plain name, or the UPN if the new parameter is enabled.

Signed-off-by: Samuel Cabrero <scabrero@samba.org>
Reviewed-by: Alexander Bokovoy <ab@samba.org>
docs-xml/manpages/idmap_nss.8.xml
source3/winbindd/idmap_nss.c

index 254f38a5f5bc2e0071844dce045ecf77d331ac0f..a9c6eceedbcf2c61b79259c80313852a1c30f0ec 100644 (file)
                        remotely defined IDs.
                </para></listitem>
                </varlistentry>
+
+               <varlistentry>
+               <term>use_upn = &lt;yes | no&gt;</term>
+               <listitem>
+               <para>
+                       Some NSS modules can return and handle UPNs and/or down-level
+                       logon names (e.g., DOMAIN\user or user@REALM).
+               </para>
+               <para>
+                       If this parameter is enabled the returned names from NSS will be
+                       parsed and the resulting namespace will be used as the authoritative
+                       namespace instead of the IDMAP domain name. Also, down-level logon
+                       names will be sent to NSS instead of the plain username to give NSS
+                       modules a hint about the user's correct domain.
+               </para>
+               <para>Default: no</para>
+               </listitem>
+               </varlistentry>
+
        </variablelist>
 </refsect1>
 
@@ -76,4 +95,4 @@
        </para>
 </refsect1>
 
-</refentry>
\ No newline at end of file
+</refentry>
index 8d2b23b4165c20b76f2ca21a2bc08bdb88cf4b75..957c5c569cfb0e5499096f44bb01b4a04b137228 100644 (file)
@@ -1,4 +1,4 @@
-/* 
+/*
    Unix SMB/CIFS implementation.
 
    idmap NSS backend
 #undef DBGC_CLASS
 #define DBGC_CLASS DBGC_IDMAP
 
+struct idmap_nss_context {
+       struct idmap_domain *dom;
+       bool use_upn;
+};
+
+static int idmap_nss_context_destructor(struct idmap_nss_context *ctx)
+{
+       if ((ctx->dom != NULL) && (ctx->dom->private_data == ctx)) {
+               ctx->dom->private_data = NULL;
+       }
+       return 0;
+}
+
+static NTSTATUS idmap_nss_context_create(TALLOC_CTX *mem_ctx,
+                                        struct idmap_domain *dom,
+                                        struct idmap_nss_context **pctx)
+{
+       struct idmap_nss_context *ctx = NULL;
+
+       ctx = talloc_zero(mem_ctx, struct idmap_nss_context);
+       if (ctx == NULL) {
+               return NT_STATUS_NO_MEMORY;
+       }
+       ctx->dom = dom;
+
+       talloc_set_destructor(ctx, idmap_nss_context_destructor);
+
+       ctx->use_upn = idmap_config_bool(dom->name, "use_upn", false);
+
+       *pctx = ctx;
+       return NT_STATUS_OK;
+}
+
+static NTSTATUS idmap_nss_get_context(struct idmap_domain *dom,
+                                     struct idmap_nss_context **pctx)
+{
+       struct idmap_nss_context *ctx = NULL;
+       NTSTATUS status;
+
+       if (dom->private_data != NULL) {
+               *pctx = talloc_get_type_abort(dom->private_data,
+                                             struct idmap_nss_context);
+               return NT_STATUS_OK;
+       }
+
+       status = idmap_nss_context_create(dom, dom, &ctx);
+       if (!NT_STATUS_IS_OK(status)) {
+               DBG_WARNING("idmap_nss_context_create failed: %s\n",
+                           nt_errstr(status));
+               return status;
+       }
+
+       dom->private_data = ctx;
+       *pctx = ctx;
+       return NT_STATUS_OK;
+}
+
 /*****************************
  Initialise idmap database.
 *****************************/
 
 static NTSTATUS idmap_nss_int_init(struct idmap_domain *dom)
 {
+       struct idmap_nss_context *ctx = NULL;
+       NTSTATUS status;
+
+       status = idmap_nss_context_create(dom, dom, &ctx);
+       if (NT_STATUS_IS_ERR(status)) {
+               return status;
+       }
+
+       dom->private_data = ctx;
+
+       return status;
+}
+
+static NTSTATUS idmap_nss_lookup_name(const char *namespace,
+                                     const char *username,
+                                     struct dom_sid *sid,
+                                     enum lsa_SidType *type)
+{
+       bool ret;
+
+       /*
+        * By default calls to winbindd are disabled
+        * the following call will not recurse so this is safe
+        */
+       (void)winbind_on();
+       ret = winbind_lookup_name(namespace, username, sid, type);
+       (void)winbind_off();
+
+       if (!ret) {
+               DBG_NOTICE("Failed to lookup name [%s] in namespace [%s]\n",
+                          username, namespace);
+               return NT_STATUS_NOT_FOUND;
+       }
+
        return NT_STATUS_OK;
 }
 
@@ -45,8 +136,17 @@ static NTSTATUS idmap_nss_int_init(struct idmap_domain *dom)
 
 static NTSTATUS idmap_nss_unixids_to_sids(struct idmap_domain *dom, struct id_map **ids)
 {
+       struct idmap_nss_context *ctx = NULL;
+       NTSTATUS status;
        int i;
 
+       status = idmap_nss_get_context(dom, &ctx);
+       if (NT_STATUS_IS_ERR(status)) {
+               DBG_WARNING("Failed to get idmap nss context: %s\n",
+                           nt_errstr(status));
+               return status;
+       }
+
        /* initialize the status to avoid surprise */
        for (i = 0; ids[i]; i++) {
                ids[i]->status = ID_UNKNOWN;
@@ -58,7 +158,6 @@ static NTSTATUS idmap_nss_unixids_to_sids(struct idmap_domain *dom, struct id_ma
                const char *name;
                struct dom_sid sid;
                enum lsa_SidType type;
-               bool ret;
 
                switch (ids[i]->xid.type) {
                case ID_TYPE_UID:
@@ -96,16 +195,52 @@ static NTSTATUS idmap_nss_unixids_to_sids(struct idmap_domain *dom, struct id_ma
                        continue;
                }
 
-               /* by default calls to winbindd are disabled
-                  the following call will not recurse so this is safe */
-               (void)winbind_on();
                /* Lookup name from PDC using lsa_lookup_names() */
-               ret = winbind_lookup_name(dom->name, name, &sid, &type);
-               (void)winbind_off();
+               if (ctx->use_upn) {
+                       char *p = NULL;
+                       const char *namespace = NULL;
+                       const char *domname = NULL;
+                       const char *domuser = NULL;
+
+                       p = strstr(name, lp_winbind_separator());
+                       if (p != NULL) {
+                               *p = '\0';
+                               domname = name;
+                               namespace = domname;
+                               domuser = p + 1;
+                       } else {
+                               p = strchr(name, '@');
+                               if (p != NULL) {
+                                       *p = '\0';
+                                       namespace = p + 1;
+                                       domname = "";
+                                       domuser = name;
+                               } else {
+                                       namespace = dom->name;
+                                       domuser = name;
+                               }
+                       }
 
-               if (!ret) {
-                       /* TODO: how do we know if the name is really not mapped,
-                        * or something just failed ? */
+                       DBG_DEBUG("Using namespace [%s] from UPN instead "
+                                 "of [%s] to lookup the name [%s]\n",
+                                 namespace, dom->name, domuser);
+
+                       status = idmap_nss_lookup_name(namespace,
+                                                      domuser,
+                                                      &sid,
+                                                      &type);
+               } else {
+                       status = idmap_nss_lookup_name(dom->name,
+                                                      name,
+                                                      &sid,
+                                                      &type);
+                }
+
+               if (NT_STATUS_IS_ERR(status)) {
+                       /*
+                        * TODO: how do we know if the name is really
+                        * not mapped, or something just failed ?
+                        */
                        ids[i]->status = ID_UNMAPPED;
                        continue;
                }
@@ -141,8 +276,17 @@ static NTSTATUS idmap_nss_unixids_to_sids(struct idmap_domain *dom, struct id_ma
 
 static NTSTATUS idmap_nss_sids_to_unixids(struct idmap_domain *dom, struct id_map **ids)
 {
+       struct idmap_nss_context *ctx = NULL;
+       NTSTATUS status;
        int i;
 
+       status = idmap_nss_get_context(dom, &ctx);
+       if (NT_STATUS_IS_ERR(status)) {
+               DBG_WARNING("Failed to get idmap nss context: %s\n",
+                           nt_errstr(status));
+               return status;
+       }
+
        /* initialize the status to avoid surprise */
        for (i = 0; ids[i]; i++) {
                ids[i]->status = ID_UNKNOWN;
@@ -155,6 +299,8 @@ static NTSTATUS idmap_nss_sids_to_unixids(struct idmap_domain *dom, struct id_ma
                const char *_name = NULL;
                char *domain = NULL;
                char *name = NULL;
+               char *fqdn = NULL;
+               char *sname = NULL;
                bool ret;
 
                /* by default calls to winbindd are disabled
@@ -185,13 +331,30 @@ static NTSTATUS idmap_nss_sids_to_unixids(struct idmap_domain *dom, struct id_ma
                        continue;
                }
 
+               if (ctx->use_upn) {
+                       fqdn = talloc_asprintf(talloc_tos(),
+                                              "%s%s%s",
+                                              domain,
+                                              lp_winbind_separator(),
+                                              name);
+                       if (fqdn == NULL) {
+                               DBG_ERR("No memory\n");
+                               ids[i]->status = ID_UNMAPPED;
+                               continue;
+                       }
+                       DBG_DEBUG("Using UPN [%s] instead of plain name [%s]\n",
+                                 fqdn, name);
+                       sname = fqdn;
+               } else {
+                       sname = name;
+               }
+
                switch (type) {
                case SID_NAME_USER: {
                        struct passwd *pw;
 
                        /* this will find also all lower case name and use username level */
-
-                       pw = Get_Pwnam_alloc(talloc_tos(), name);
+                       pw = Get_Pwnam_alloc(talloc_tos(), sname);
                        if (pw) {
                                ids[i]->xid.id = pw->pw_uid;
                                ids[i]->xid.type = ID_TYPE_UID;
@@ -205,7 +368,7 @@ static NTSTATUS idmap_nss_sids_to_unixids(struct idmap_domain *dom, struct id_ma
                case SID_NAME_ALIAS:
                case SID_NAME_WKN_GRP:
 
-                       gr = getgrnam(name);
+                       gr = getgrnam(sname);
                        if (gr) {
                                ids[i]->xid.id = gr->gr_gid;
                                ids[i]->xid.type = ID_TYPE_GID;
@@ -219,6 +382,7 @@ static NTSTATUS idmap_nss_sids_to_unixids(struct idmap_domain *dom, struct id_ma
                }
                TALLOC_FREE(domain);
                TALLOC_FREE(name);
+               TALLOC_FREE(fqdn);
        }
        return NT_STATUS_OK;
 }