Blob Blame History Raw
From f546088226872f24722bdd94388816792bd5891a Mon Sep 17 00:00:00 2001
From: Alexey Tikhonov <atikhono@redhat.com>
Date: Sat, 1 May 2021 22:56:15 +0200
Subject: [PATCH] Basics of 'subid ranges' support for IPA provider.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

:feature: Basic support of user's 'subuid and subgid ranges' for IPA
provider and corresponding plugin for shadow-utils were introduced.
Limitations:
 - single subid interval pair (subuid+subgid) per user
 - idviews aren't supported
 - only forward lookup (user -> subid ranges)
Take a note, this is MVP of experimental feature. Significant changes
might be required later, after initial feedback.
Corresponding support in shadow-utils was merged upstream, but since there
is no upstream release available yet, SSSD feature isn't built by default.
Build can be enabled with `--with-subid` configure option.
Plugin's install path can be configured with `--with-subid-lib-path=`
("${libdir}" by default)

For additional details about support in shadow-utils please see discussion
in https://github.com/shadow-maint/shadow/issues/154 and in related PRs.

:config: New IPA provider's option `ipa_subid_ranges_search_base` allows
configuration of search base for user's subid ranges.
Default: `cn=subids,%basedn`

Resolves: https://github.com/SSSD/sssd/issues/5197

Reviewed-by: Iker Pedrosa <ipedrosa@redhat.com>
Reviewed-by: Pavel Březina <pbrezina@redhat.com>
---
 Makefile.am                                   |  34 +++
 configure.ac                                  |   2 +
 src/conf_macros.m4                            |  30 +++
 src/config/SSSDConfig/sssdoptions.py          |   1 +
 src/config/cfg_rules.ini                      |   1 +
 src/config/etc/sssd.api.d/sssd-ipa.conf       |   1 +
 src/db/sysdb.h                                |   6 +
 src/db/sysdb_subid.c                          | 163 +++++++++++
 src/db/sysdb_subid.h                          |  39 +++
 src/man/sssd-ipa.5.xml                        |  14 +
 src/providers/data_provider_req.c             |   2 +
 src/providers/data_provider_req.h             |   1 +
 src/providers/ipa/ipa_common.c                |  38 +++
 src/providers/ipa/ipa_common.h                |   1 +
 src/providers/ipa/ipa_id.c                    |  17 ++
 src/providers/ipa/ipa_opts.c                  |  12 +
 src/providers/ipa/ipa_opts.h                  |   2 +
 src/providers/ldap/ldap_common.h              |  14 +
 src/providers/ldap/ldap_id.c                  |  31 +++
 src/providers/ldap/ldap_id_subid.c            | 255 ++++++++++++++++++
 src/providers/ldap/sdap.h                     |  19 ++
 src/responder/common/cache_req/cache_req.c    |   4 +
 src/responder/common/cache_req/cache_req.h    |   4 +
 .../common/cache_req/cache_req_data.c         |   3 +
 .../common/cache_req/cache_req_plugin.h       |   3 +
 .../plugins/cache_req_subid_ranges_by_name.c  | 143 ++++++++++
 src/responder/common/responder.h              |   1 +
 src/responder/common/responder_dp.c           |   4 +
 src/responder/nss/nss_cmd.c                   |  20 ++
 src/responder/nss/nss_protocol.h              |   8 +
 src/responder/nss/nss_protocol_subid.c        |  60 +++++
 src/sss_client/common.c                       |   2 +-
 src/sss_client/sss_cli.h                      |  11 +
 src/sss_client/subid/sss_subid.c              | 209 ++++++++++++++
 src/sss_client/subid/sss_subid.exports        |  12 +
 src/systemtap/sssd_functions.stp              |   2 +-
 src/tests/cwrap/Makefile.am                   |   3 +
 src/tests/dlopen-tests.c                      |   3 +
 src/util/sss_cli_cmd.c                        |   5 +
 src/util/util_errors.c                        |   1 +
 src/util/util_errors.h                        |   1 +
 41 files changed, 1180 insertions(+), 2 deletions(-)
 create mode 100644 src/db/sysdb_subid.c
 create mode 100644 src/db/sysdb_subid.h
 create mode 100644 src/providers/ldap/ldap_id_subid.c
 create mode 100644 src/responder/common/cache_req/plugins/cache_req_subid_ranges_by_name.c
 create mode 100644 src/responder/nss/nss_protocol_subid.c
 create mode 100644 src/sss_client/subid/sss_subid.c
 create mode 100644 src/sss_client/subid/sss_subid.exports

diff --git a/Makefile.am b/Makefile.am
index 1dd06d74c..577935e7e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -65,6 +65,7 @@ localedir = @localedir@
 nsslibdir = @nsslibdir@
 pamlibdir = @pammoddir@
 autofslibdir = @appmodpath@
+subidlibdir = @subidlibpath@
 nfslibdir = @nfsidmaplibdir@
 
 dbpath = @dbpath@
@@ -596,6 +597,9 @@ SSSD_CACHE_REQ_OBJ = \
 	src/responder/common/cache_req/plugins/cache_req_ip_network_by_name.c \
 	src/responder/common/cache_req/plugins/cache_req_ip_network_by_addr.c \
 	$(NULL)
+if BUILD_SUBID
+    SSSD_CACHE_REQ_OBJ += src/responder/common/cache_req/plugins/cache_req_subid_ranges_by_name.c
+endif
 
 SSSD_RESPONDER_IFACE_OBJ = \
     src/responder/common/responder_iface.c \
@@ -810,6 +814,7 @@ dist_noinst_HEADERS = \
     src/db/sysdb_private.h \
     src/db/sysdb_services.h \
     src/db/sysdb_ssh.h \
+    src/db/sysdb_subid.h \
     src/db/sysdb_domain_resolution_order.h \
     src/db/sysdb_computer.h \
     src/db/sysdb_iphosts.h \
@@ -1247,6 +1252,7 @@ libsss_util_la_SOURCES = \
     src/db/sysdb_ipnetworks.c \
     src/util/sss_pam_data.c \
     src/db/sysdb_computer.c \
+    src/db/sysdb_subid.c \
     src/util/util.c \
     src/util/util_ext.c \
     src/util/util_preauth.c \
@@ -1558,6 +1564,9 @@ sssd_nss_LDADD = \
     libsss_iface.la \
     libsss_sbus.la \
     $(NULL)
+if BUILD_SUBID
+    sssd_nss_SOURCES += src/responder/nss/nss_protocol_subid.c
+endif
 
 sssd_pam_SOURCES = \
     src/responder/pam/pam_LOCAL_domain.c \
@@ -2691,6 +2700,9 @@ nss_srv_tests_LDADD = \
     libsss_iface.la \
     libsss_sbus.la \
     $(NULL)
+if BUILD_SUBID
+     nss_srv_tests_SOURCES += src/responder/nss/nss_protocol_subid.c
+endif
 
 EXTRA_pam_srv_tests_DEPENDENCIES = \
     $(ldblib_LTLIBRARIES) \
@@ -4217,6 +4229,21 @@ libsss_autofs_la_LDFLAGS = \
     -Wl,--version-script,$(srcdir)/src/sss_client/autofs/sss_autofs.exports
 endif
 
+if BUILD_SUBID
+subidlib_LTLIBRARIES = libsubid_sss.la
+libsubid_sss_la_SOURCES = \
+    src/sss_client/common.c \
+    src/sss_client/sss_cli.h \
+    src/sss_client/subid/sss_subid.c
+
+libsubid_sss_la_LIBADD = \
+    $(CLIENT_LIBS)
+libsubid_sss_la_LDFLAGS = \
+    -module \
+    -avoid-version \
+    -Wl,--version-script,$(srcdir)/src/sss_client/subid/sss_subid.exports
+endif
+
 dist_noinst_DATA += \
     src/sss_client/sss_nss.exports \
     src/sss_client/sss_pam.exports \
@@ -4231,6 +4258,10 @@ if BUILD_AUTOFS
 dist_noinst_DATA += src/sss_client/autofs/sss_autofs.exports
 endif
 
+if BUILD_SUBID
+dist_noinst_DATA += src/sss_client/subid/sss_subid.exports
+endif
+
 ####################
 # Plugin Libraries #
 ####################
@@ -4315,6 +4346,9 @@ libsss_ldap_common_la_LDFLAGS = \
 if BUILD_SYSTEMTAP
 libsss_ldap_common_la_LIBADD += stap_generated_probes.lo
 endif
+if BUILD_SUBID
+libsss_ldap_common_la_SOURCES += src/providers/ldap/ldap_id_subid.c
+endif
 
 if BUILD_SSH
 libsss_ldap_common_la_SOURCES += src/providers/ldap/sdap_hostid.c
diff --git a/configure.ac b/configure.ac
index e98487cae..c14a59eef 100644
--- a/configure.ac
+++ b/configure.ac
@@ -161,6 +161,8 @@ WITH_APP_LIBS
 WITH_SUDO
 WITH_SUDO_LIB_PATH
 WITH_AUTOFS
+WITH_SUBID
+WITH_SUBID_LIB_PATH
 WITH_SSH
 WITH_IFP
 WITH_SYSLOG
diff --git a/src/conf_macros.m4 b/src/conf_macros.m4
index cdffba11c..0a1e6dd8f 100644
--- a/src/conf_macros.m4
+++ b/src/conf_macros.m4
@@ -675,6 +675,36 @@ AC_DEFUN([WITH_AUTOFS],
     AM_CONDITIONAL([BUILD_AUTOFS], [test x"$with_autofs" = xyes])
   ])
 
+AC_DEFUN([WITH_SUBID],
+  [ AC_ARG_WITH([subid],
+                [AC_HELP_STRING([--with-subid],
+                                [Whether to build with subid ranges support [no]]
+                               )
+                ],
+                [with_subid=$withval],
+                with_subid=no
+               )
+
+    if test x"$with_subid" = xyes; then
+        AC_DEFINE(BUILD_SUBID, 1, [whether to build with SUBID ranges support])
+    fi
+    AM_CONDITIONAL([BUILD_SUBID], [test x"$with_subid" = xyes])
+  ])
+
+AC_DEFUN([WITH_SUBID_LIB_PATH],
+  [ AC_ARG_WITH([subid-lib-path],
+                [AC_HELP_STRING([--with-subid-lib-path=<path>],
+                                [Path to the subid library]
+                               )
+                ]
+               )
+    subidlibpath="${libdir}"
+    if test x"$with_subid_lib_path" != x; then
+        subidlibpath=$with_subid_lib_path
+    fi
+    AC_SUBST(subidlibpath)
+  ])
+
 AC_DEFUN([WITH_SSH],
   [ AC_ARG_WITH([ssh],
                 [AC_HELP_STRING([--with-ssh],
diff --git a/src/config/SSSDConfig/sssdoptions.py b/src/config/SSSDConfig/sssdoptions.py
index c4ce2588b..39380c462 100644
--- a/src/config/SSSDConfig/sssdoptions.py
+++ b/src/config/SSSDConfig/sssdoptions.py
@@ -265,6 +265,7 @@ class SSSDOptions(object):
         'ipa_deskprofile_request_interval': _("The amount of time in minutes between lookups of Desktop Profiles "
                                               "rules against the IPA server when the last request did not find any "
                                               "rule"),
+        'ipa_subid_ranges_search_base': _("Search base for SUBID ranges"),
         'ipa_host_fqdn': _('The LDAP attribute that contains FQDN of the host.'),
         'ipa_host_object_class': _('The object class of a host entry in LDAP.'),
         'ipa_host_search_base': _('Use the given string as search base for host objects.'),
diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini
index 0a3c32a97..d8190c2f4 100644
--- a/src/config/cfg_rules.ini
+++ b/src/config/cfg_rules.ini
@@ -538,6 +538,7 @@ option = ipa_backup_server
 option = ipa_deskprofile_refresh
 option = ipa_deskprofile_request_interval
 option = ipa_deskprofile_search_base
+option = ipa_subid_ranges_search_base
 option = ipa_domain
 option = ipa_dyndns_iface
 option = ipa_dyndns_ttl
diff --git a/src/config/etc/sssd.api.d/sssd-ipa.conf b/src/config/etc/sssd.api.d/sssd-ipa.conf
index 9a81389ca..02c9089fd 100644
--- a/src/config/etc/sssd.api.d/sssd-ipa.conf
+++ b/src/config/etc/sssd.api.d/sssd-ipa.conf
@@ -4,6 +4,7 @@ ipa_server = str, None, false
 ipa_backup_server = str, None, false
 ipa_hostname = str, None, false
 ipa_deskprofile_search_base = str, None, false
+ipa_subid_ranges_search_base = str, None, false
 ipa_dyndns_update = bool, None, false
 ipa_dyndns_ttl = int, None, false
 ipa_dyndns_iface = str, None, false
diff --git a/src/db/sysdb.h b/src/db/sysdb.h
index a29232f48..b638f4397 100644
--- a/src/db/sysdb.h
+++ b/src/db/sysdb.h
@@ -143,6 +143,12 @@
 
 #define SYSDB_SSH_PUBKEY "sshPublicKey"
 
+#define SYSDB_SUBID_UID_COUND   "subUidCount"
+#define SYSDB_SUBID_GID_COUNT   "subGidCount"
+#define SYSDB_SUBID_UID_NUMBER  "subUidNumber"
+#define SYSDB_SUBID_GID_NUMBER  "subGidNumber"
+#define SYSDB_SUBID_OWNER       "subidOwner"
+
 #define SYSDB_AUTH_TYPE "authType"
 #define SYSDB_USER_CERT "userCertificate"
 #define SYSDB_USER_MAPPED_CERT "userMappedCertificate"
diff --git a/src/db/sysdb_subid.c b/src/db/sysdb_subid.c
new file mode 100644
index 000000000..519b0834c
--- /dev/null
+++ b/src/db/sysdb_subid.c
@@ -0,0 +1,163 @@
+/*
+    Copyright (C) 2021 Red Hat
+
+    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 <talloc.h>
+
+#include "db/sysdb_private.h"
+#include "db/sysdb_subid.h"
+
+#define SUBID_SUBDIR "subid_ranges"
+
+
+errno_t sysdb_store_subid_range(struct sss_domain_info *domain,
+                                const char *name,
+                                int expiration_period,
+                                struct sysdb_attrs *attrs)
+{
+    TALLOC_CTX *tmp_ctx;
+    errno_t ret, sret;
+    bool in_transaction = false;
+    time_t now = time(NULL);
+
+    DEBUG(SSSDBG_TRACE_FUNC, "Storing subid ranges for %s, expiration period = %d\n",
+          name, expiration_period);
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) {
+        return ENOMEM;
+    }
+
+    ret = sysdb_transaction_start(domain->sysdb);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n");
+        goto done;
+    }
+
+    in_transaction = true;
+
+    ret = sysdb_attrs_add_string(attrs, SYSDB_OBJECTCLASS, SYSDB_SUBID_RANGE_OC);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Could not set object class [%d]: %s\n", ret, strerror(ret));
+        goto done;
+    }
+
+    ret = sysdb_attrs_add_string(attrs, SYSDB_NAME, name);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Could not set name attribute [%d]: %s\n", ret, strerror(ret));
+        goto done;
+    }
+
+    ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_UPDATE, now);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Could not set sysdb lastUpdate [%d]: %s\n",
+               ret, strerror(ret));
+        goto done;
+    }
+
+    ret = sysdb_attrs_add_time_t(attrs, SYSDB_CACHE_EXPIRE,
+                                 expiration_period ? (now + expiration_period) : 0);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "Could not set sysdb cache expire [%d]: %s\n",
+              ret, strerror(ret));
+        goto done;
+    }
+
+    ret = sysdb_store_custom(domain, name, SUBID_SUBDIR, attrs);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    ret = sysdb_transaction_commit(domain->sysdb);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n");
+        goto done;
+    }
+
+    in_transaction = false;
+
+    ret = EOK;
+
+done:
+    if (in_transaction) {
+        sret = sysdb_transaction_cancel(domain->sysdb);
+        if (sret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "Could not cancel transaction\n");
+        }
+    }
+
+    talloc_free(tmp_ctx);
+
+    return ret;
+}
+
+errno_t sysdb_get_subid_ranges(TALLOC_CTX *mem_ctx,
+                               struct sss_domain_info *domain,
+                               const char *name,
+                               const char **attrs,
+                               struct ldb_message **_range)
+{
+    TALLOC_CTX *tmp_ctx;
+    errno_t ret;
+    const char *filter;
+    struct ldb_message **ranges;
+    size_t num_ranges;
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) {
+        return ENOMEM;
+    }
+
+    filter = talloc_asprintf(tmp_ctx, "(&(%s=%s)(%s=%s))",
+                             SYSDB_OBJECTCLASS, SYSDB_SUBID_RANGE_OC,
+                             SYSDB_NAME, name);
+    if (!filter) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = sysdb_search_custom(tmp_ctx, domain, filter,
+                              SUBID_SUBDIR, attrs,
+                              &num_ranges, &ranges);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    if (num_ranges > 1) {
+        ret = EINVAL;
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "Found more than one range with name %s\n", name);
+        goto done;
+    }
+
+    *_range = talloc_steal(mem_ctx, ranges[0]);
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+
+    return ret;
+}
+
+errno_t sysdb_delete_subid_range(struct sss_domain_info *domain,
+                                 const char *name)
+{
+    DEBUG(SSSDBG_TRACE_FUNC, "Deleting subid ranges for %s\n", name);
+    return sysdb_delete_custom(domain, name, SUBID_SUBDIR);
+}
diff --git a/src/db/sysdb_subid.h b/src/db/sysdb_subid.h
new file mode 100644
index 000000000..4b4a86334
--- /dev/null
+++ b/src/db/sysdb_subid.h
@@ -0,0 +1,39 @@
+/*
+    Copyright (C) 2021 Red Hat
+
+    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/>.
+*/
+
+#ifndef _SYSDB_SUBID_H_
+#define _SYSDB_SUBID_H_
+
+#include "db/sysdb.h"
+
+#define SYSDB_SUBID_RANGE_OC "subordinateid"
+
+errno_t sysdb_store_subid_range(struct sss_domain_info *domain,
+                                const char *name,
+                                int expiration_period,
+                                struct sysdb_attrs *attrs);
+
+errno_t sysdb_get_subid_ranges(TALLOC_CTX *mem_ctx,
+                               struct sss_domain_info *domain,
+                               const char *name,
+                               const char **attrs,
+                               struct ldb_message **range);
+
+errno_t sysdb_delete_subid_range(struct sss_domain_info *domain,
+                                 const char *name);
+
+#endif /* _SYSDB_SSH_H_ */
diff --git a/src/man/sssd-ipa.5.xml b/src/man/sssd-ipa.5.xml
index 7b630493d..2bbf67c83 100644
--- a/src/man/sssd-ipa.5.xml
+++ b/src/man/sssd-ipa.5.xml
@@ -356,6 +356,20 @@
                     </listitem>
                 </varlistentry>
 
+                <varlistentry condition="with_subid">
+                    <term>ipa_subid_ranges_search_base (string)</term>
+                    <listitem>
+                        <para>
+                            Optional. Use the given string as search base for
+                            subordinate ranges related objects.
+                        </para>
+                        <para>
+                            Default: the value of
+                            <emphasis>cn=subids,%basedn</emphasis>
+                        </para>
+                    </listitem>
+                </varlistentry>
+
                 <varlistentry>
                     <term>ipa_hbac_search_base (string)</term>
                     <listitem>
diff --git a/src/providers/data_provider_req.c b/src/providers/data_provider_req.c
index e22cfd71a..b7f4d152e 100644
--- a/src/providers/data_provider_req.c
+++ b/src/providers/data_provider_req.c
@@ -44,6 +44,8 @@ const char *be_req2str(dbus_uint32_t req_type)
         return be_req_to_str(BE_REQ_HOST);
     case BE_REQ_IP_NETWORK:
         return be_req_to_str(BE_REQ_IP_NETWORK);
+    case BE_REQ_SUBID_RANGES:
+        return be_req_to_str(BE_REQ_SUBID_RANGES);
     case BE_REQ_BY_SECID:
         return be_req_to_str(BE_REQ_BY_SECID);
     case BE_REQ_USER_AND_GROUP:
diff --git a/src/providers/data_provider_req.h b/src/providers/data_provider_req.h
index 75f7f9713..4c6ab5a7e 100644
--- a/src/providers/data_provider_req.h
+++ b/src/providers/data_provider_req.h
@@ -35,6 +35,7 @@
 #define BE_REQ_SUDO_RULES     0x0007
 #define BE_REQ_HOST           0x0008
 #define BE_REQ_IP_NETWORK     0x0009
+#define BE_REQ_SUBID_RANGES   0x0010
 #define BE_REQ_BY_SECID       0x0011
 #define BE_REQ_USER_AND_GROUP 0x0012
 #define BE_REQ_BY_UUID        0x0013
diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c
index 23b358819..1509cb1ce 100644
--- a/src/providers/ipa/ipa_common.c
+++ b/src/providers/ipa/ipa_common.c
@@ -152,6 +152,9 @@ static errno_t ipa_parse_search_base(TALLOC_CTX *mem_ctx,
     case IPA_DESKPROFILE_SEARCH_BASE:
         class_name = "IPA_DESKPROFILE";
         break;
+    case IPA_SUBID_RANGES_SEARCH_BASE:
+        class_name = "IPA_SUBID_RANGES";
+        break;
     default:
         DEBUG(SSSDBG_CONF_SETTINGS,
               "Unknown search base type: [%d]\n", class);
@@ -481,6 +484,41 @@ int ipa_get_id_options(struct ipa_options *ipa_opts,
                                 &ipa_opts->deskprofile_search_bases);
     if (ret != EOK) goto done;
 
+#ifdef BUILD_SUBID
+    if (NULL == dp_opt_get_string(ipa_opts->basic,
+                                  IPA_SUBID_RANGES_SEARCH_BASE)) {
+        value = talloc_asprintf(tmpctx, "cn=subids,%s",
+                                ipa_opts->id->sdom->search_bases[0]->basedn);
+        if (!value) {
+            ret = ENOMEM;
+            goto done;
+        }
+
+        ret = dp_opt_set_string(ipa_opts->basic, IPA_SUBID_RANGES_SEARCH_BASE, value);
+        if (ret != EOK) {
+            goto done;
+        }
+
+        DEBUG(SSSDBG_TRACE_FUNC, "Option %s set to %s\n",
+                  ipa_opts->basic[IPA_SUBID_RANGES_SEARCH_BASE].opt_name,
+                  dp_opt_get_string(ipa_opts->basic,
+                                    IPA_SUBID_RANGES_SEARCH_BASE));
+    }
+    ret = ipa_parse_search_base(ipa_opts->basic, ipa_opts->basic,
+                                IPA_SUBID_RANGES_SEARCH_BASE,
+                                &ipa_opts->id->sdom->subid_ranges_search_bases);
+    if (ret != EOK) goto done;
+
+    ret = sdap_get_map(ipa_opts->id,
+                       cdb, conf_path,
+                       ipa_subid_map,
+                       SDAP_OPTS_SUBID_RANGE,
+                       &ipa_opts->id->subid_map);
+    if (ret != EOK) {
+        goto done;
+    }
+#endif
+
     value = dp_opt_get_string(ipa_opts->id->basic, SDAP_DEREF);
     if (value != NULL) {
         ret = deref_string_to_val(value, &i);
diff --git a/src/providers/ipa/ipa_common.h b/src/providers/ipa/ipa_common.h
index 480f7b664..eb0eda8eb 100644
--- a/src/providers/ipa/ipa_common.h
+++ b/src/providers/ipa/ipa_common.h
@@ -68,6 +68,7 @@ enum ipa_basic_opt {
     IPA_DESKPROFILE_SEARCH_BASE,
     IPA_DESKPROFILE_REFRESH,
     IPA_DESKPROFILE_REQUEST_INTERVAL,
+    IPA_SUBID_RANGES_SEARCH_BASE,
 
     IPA_OPTS_BASIC /* opts counter */
 };
diff --git a/src/providers/ipa/ipa_id.c b/src/providers/ipa/ipa_id.c
index 2cbe0c9c7..636e07965 100644
--- a/src/providers/ipa/ipa_id.c
+++ b/src/providers/ipa/ipa_id.c
@@ -728,6 +728,22 @@ static errno_t ipa_id_get_account_info_get_original_step(struct tevent_req *req,
                                           struct ipa_id_get_account_info_state);
     struct tevent_req *subreq;
 
+#ifdef BUILD_SUBID
+    if ((ar->entry_type & BE_REQ_TYPE_MASK) == BE_REQ_SUBID_RANGES) {
+        if (!state->ctx->opts->sdom->subid_ranges_search_bases ||
+            !state->ctx->opts->sdom->subid_ranges_search_bases[0] ||
+            !state->ctx->opts->sdom->subid_ranges_search_bases[0]->basedn) {
+            DEBUG(SSSDBG_OP_FAILURE, "subid_ranges_search_bases isn't set\n");
+            return EINVAL;
+        }
+        ar->extra_value = talloc_asprintf(ar,
+            "%s=%s,"SYSDB_USERS_CONTAINER",%s",
+            state->ctx->opts->user_map[SDAP_AT_USER_NAME].name,
+            ar->filter_value,
+            state->ctx->opts->sdom->user_search_bases[0]->basedn);
+    }
+#endif
+
     subreq = sdap_handle_acct_req_send(state, state->ctx->be, ar,
                                        state->ipa_ctx->sdap_id_ctx,
                                        state->ipa_ctx->sdap_id_ctx->opts->sdom,
@@ -769,6 +785,7 @@ static void ipa_id_get_account_info_orig_done(struct tevent_req *subreq)
     }
 
     if (! is_object_overridable(state->ar)) {
+        DEBUG(SSSDBG_FUNC_DATA, "Object not overridable, ending request\n");
         state->dp_error = DP_ERR_OK;
         tevent_req_done(req);
         return;
diff --git a/src/providers/ipa/ipa_opts.c b/src/providers/ipa/ipa_opts.c
index 09e360a43..dc0a2201b 100644
--- a/src/providers/ipa/ipa_opts.c
+++ b/src/providers/ipa/ipa_opts.c
@@ -26,6 +26,7 @@
 #include "db/sysdb_autofs.h"
 #include "db/sysdb_services.h"
 #include "db/sysdb_selinux.h"
+#include "db/sysdb_subid.h"
 #include "providers/ldap/ldap_common.h"
 
 struct dp_option ipa_basic_opts[] = {
@@ -51,6 +52,7 @@ struct dp_option ipa_basic_opts[] = {
     { "ipa_deskprofile_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     { "ipa_deskprofile_refresh", DP_OPT_NUMBER, { .number = 5 }, NULL_NUMBER },
     { "ipa_deskprofile_request_interval", DP_OPT_NUMBER, { .number = 60 }, NULL_NUMBER },
+    { "ipa_subid_ranges_search_base", DP_OPT_STRING, NULL_STRING, NULL_STRING },
     DP_OPTION_TERMINATOR
 };
 
@@ -250,6 +252,16 @@ struct sdap_attr_map ipa_netgroup_map[] = {
     SDAP_ATTR_MAP_TERMINATOR
 };
 
+struct sdap_attr_map ipa_subid_map[] = {
+    { "ipa_subuid_object_class", "ipasubordinateid", SYSDB_SUBID_RANGE_OC, NULL },
+    { "ipa_subuid_count", "ipaSubUidCount", SYSDB_SUBID_UID_COUND, NULL },
+    { "ipa_subgid_count", "ipaSubGidCount", SYSDB_SUBID_GID_COUNT, NULL },
+    { "ipa_subuid_number", "ipaSubUidNumber", SYSDB_SUBID_UID_NUMBER, NULL },
+    { "ipa_subgid_number", "ipaSubGidNumber", SYSDB_SUBID_GID_NUMBER, NULL },
+    { "ipa_owner", "ipaOwner", SYSDB_SUBID_OWNER, NULL },
+    SDAP_ATTR_MAP_TERMINATOR
+};
+
 struct sdap_attr_map ipa_host_map[] = {
     { "ipa_host_object_class", "ipaHost", SYSDB_HOST_CLASS, NULL },
     { "ipa_host_name", "cn", SYSDB_NAME, NULL },
diff --git a/src/providers/ipa/ipa_opts.h b/src/providers/ipa/ipa_opts.h
index 378a9922c..6f54e57a9 100644
--- a/src/providers/ipa/ipa_opts.h
+++ b/src/providers/ipa/ipa_opts.h
@@ -40,6 +40,8 @@ extern struct sdap_attr_map ipa_group_map[];
 
 extern struct sdap_attr_map ipa_netgroup_map[];
 
+extern struct sdap_attr_map ipa_subid_map[];
+
 extern struct sdap_attr_map ipa_host_map[];
 
 extern struct sdap_attr_map ipa_hostgroup_map[];
diff --git a/src/providers/ldap/ldap_common.h b/src/providers/ldap/ldap_common.h
index 13e6d4871..c78338b5d 100644
--- a/src/providers/ldap/ldap_common.h
+++ b/src/providers/ldap/ldap_common.h
@@ -446,4 +446,18 @@ errno_t users_get_handle_no_user(TALLOC_CTX *mem_ctx,
 errno_t groups_get_handle_no_group(TALLOC_CTX *mem_ctx,
                                    struct sss_domain_info *domain,
                                    int filter_type, const char *filter_value);
+
+#ifdef BUILD_SUBID
+struct tevent_req *subid_ranges_get_send(TALLOC_CTX *memctx,
+                                         struct tevent_context *ev,
+                                         struct sdap_id_ctx *ctx,
+                                         struct sdap_domain *sdom,
+                                         struct sdap_id_conn_ctx *conn,
+                                         const char* filter_value,
+                                         const char *extra_value);
+
+int subid_ranges_get_recv(struct tevent_req *req, int *dp_error_out,
+                          int *sdap_ret);
+#endif
+
 #endif /* _LDAP_COMMON_H_ */
diff --git a/src/providers/ldap/ldap_id.c b/src/providers/ldap/ldap_id.c
index ebc0ab8e4..9b67773a8 100644
--- a/src/providers/ldap/ldap_id.c
+++ b/src/providers/ldap/ldap_id.c
@@ -1449,6 +1449,24 @@ sdap_handle_acct_req_send(TALLOC_CTX *mem_ctx,
                                      noexist_delete);
         break;
 
+    case BE_REQ_SUBID_RANGES:
+#ifdef BUILD_SUBID
+        if (!ar->extra_value) {
+            ret = ERR_GET_ACCT_SUBID_RANGES_NOT_SUPPORTED;
+            state->err = "This id_provider doesn't support subid ranges";
+            goto done;
+        }
+        subreq = subid_ranges_get_send(state, be_ctx->ev, id_ctx,
+                                       sdom, conn,
+                                       ar->filter_value,
+                                       ar->extra_value);
+#else
+        ret = ERR_GET_ACCT_SUBID_RANGES_NOT_SUPPORTED;
+        state->err = "Subid ranges are not supported";
+        goto done;
+#endif
+        break;
+
     case BE_REQ_NETGROUP:
         if (ar->filter_type != BE_FILTER_NAME) {
             ret = EINVAL;
@@ -1533,6 +1551,11 @@ sdap_handle_acct_req_send(TALLOC_CTX *mem_ctx,
     default: /*fail*/
         ret = EINVAL;
         state->err = "Invalid request type";
+        DEBUG(SSSDBG_OP_FAILURE,
+              "Unexpected request type: 0x%X [%s:%s] in %s\n",
+              ar->entry_type, ar->filter_value,
+              ar->extra_value?ar->extra_value:"-",
+              ar->domain);
         goto done;
     }
 
@@ -1578,6 +1601,14 @@ sdap_handle_acct_req_done(struct tevent_req *subreq)
         err = "Init group lookup failed";
         ret = groups_by_user_recv(subreq, &state->dp_error, &state->sdap_ret);
         break;
+    case BE_REQ_SUBID_RANGES:
+        err = "Subid ranges lookup failed";
+#ifdef BUILD_SUBID
+        ret = subid_ranges_get_recv(subreq, &state->dp_error, &state->sdap_ret);
+#else
+        ret = EINVAL;
+#endif
+        break;
     case BE_REQ_NETGROUP:
         err = "Netgroup lookup failed";
         ret = ldap_netgroup_get_recv(subreq, &state->dp_error, &state->sdap_ret);
diff --git a/src/providers/ldap/ldap_id_subid.c b/src/providers/ldap/ldap_id_subid.c
new file mode 100644
index 000000000..d25c6aaac
--- /dev/null
+++ b/src/providers/ldap/ldap_id_subid.c
@@ -0,0 +1,255 @@
+/*
+    SSSD
+
+    LDAP Identity Backend Module - subid ranges support
+
+    Copyright (C) 2021 Red Hat
+
+    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 <errno.h>
+
+#include "db/sysdb_subid.h"
+#include "providers/ldap/ldap_common.h"
+#include "providers/ldap/sdap_async.h"
+#include "providers/ldap/sdap_ops.h"
+
+static int subid_ranges_get_retry(struct tevent_req *req);
+static void subid_ranges_get_connect_done(struct tevent_req *subreq);
+static void subid_ranges_get_search(struct tevent_req *req);
+static void subid_ranges_get_done(struct tevent_req *subreq);
+
+
+struct subid_ranges_get_state {
+    struct tevent_context *ev;
+    struct sdap_id_ctx *ctx;
+    struct sdap_domain *sdom;
+    struct sdap_id_conn_ctx *conn;
+    struct sdap_id_op *op;
+    struct sss_domain_info *domain;
+
+    char *filter;
+    char *name;
+    const char **attrs;
+
+    int dp_error;
+    int sdap_ret;
+};
+
+struct tevent_req *subid_ranges_get_send(TALLOC_CTX *memctx,
+                                         struct tevent_context *ev,
+                                         struct sdap_id_ctx *ctx,
+                                         struct sdap_domain *sdom,
+                                         struct sdap_id_conn_ctx *conn,
+                                         const char *filter_value,
+                                         const char *extra_value)
+{
+    struct tevent_req *req;
+    struct subid_ranges_get_state *state;
+    int ret;
+
+    req = tevent_req_create(memctx, &state, struct subid_ranges_get_state);
+    if (!req) return NULL;
+
+    state->ev = ev;
+    state->ctx = ctx;
+    state->sdom = sdom;
+    state->conn = conn;
+    state->dp_error = DP_ERR_FATAL;
+    state->name = talloc_strdup(state, filter_value);
+    if (!state->name) {
+        DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    state->op = sdap_id_op_create(state, state->conn->conn_cache);
+    if (!state->op) {
+        DEBUG(SSSDBG_OP_FAILURE, "sdap_id_op_create failed\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    state->domain = sdom->dom;
+
+    state->filter = talloc_asprintf(state,
+                                    "(&(%s=%s)(%s=%s))",
+                                    SYSDB_OBJECTCLASS,
+                                    ctx->opts->subid_map[SDAP_OC_SUBID_RANGE].name,
+                                    ctx->opts->subid_map[SDAP_AT_SUBID_RANGE_OWNER].name,
+                                    extra_value);
+
+    ret = subid_ranges_get_retry(req);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    return req;
+
+done:
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+    } else {
+        tevent_req_done(req);
+    }
+    return tevent_req_post(req, ev);
+}
+
+static int subid_ranges_get_retry(struct tevent_req *req)
+{
+    struct subid_ranges_get_state *state = tevent_req_data(req,
+                                                    struct subid_ranges_get_state);
+    struct tevent_req *subreq;
+    int ret = EOK;
+
+    subreq = sdap_id_op_connect_send(state->op, state, &ret);
+    if (!subreq) {
+        return ret;
+    }
+
+    tevent_req_set_callback(subreq, subid_ranges_get_connect_done, req);
+    return EOK;
+}
+
+static void subid_ranges_get_connect_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct subid_ranges_get_state *state = tevent_req_data(req,
+                                                     struct subid_ranges_get_state);
+    int dp_error = DP_ERR_FATAL;
+    int ret;
+
+    ret = sdap_id_op_connect_recv(subreq, &dp_error);
+    talloc_zfree(subreq);
+
+    if (ret != EOK) {
+        state->dp_error = dp_error;
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    subid_ranges_get_search(req);
+}
+
+static void subid_ranges_get_search(struct tevent_req *req)
+{
+    struct subid_ranges_get_state *state = tevent_req_data(req,
+                                                     struct subid_ranges_get_state);
+    struct tevent_req *subreq = NULL;
+    const char **attrs;
+    int ret;
+
+    ret = build_attrs_from_map(state, state->ctx->opts->subid_map,
+                               SDAP_OPTS_SUBID_RANGE, NULL, &attrs, NULL);
+    if (ret != EOK) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+
+    subreq = sdap_search_bases_send(state, state->ev, state->ctx->opts,
+                                    sdap_id_op_handle(state->op),
+                                    state->sdom->subid_ranges_search_bases,
+                                    state->ctx->opts->subid_map,
+                                    false, /* allow_paging */
+                                    dp_opt_get_int(state->ctx->opts->basic,
+                                                   SDAP_SEARCH_TIMEOUT),
+                                    state->filter,
+                                    attrs,
+                                    NULL);
+    talloc_free(attrs);
+    if (!subreq) {
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, subid_ranges_get_done, req);
+}
+
+static void subid_ranges_get_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct subid_ranges_get_state *state = tevent_req_data(req,
+                                                     struct subid_ranges_get_state);
+    int dp_error = DP_ERR_FATAL;
+    int ret;
+    struct sysdb_attrs **results;
+    size_t num_results;
+
+    ret = sdap_search_bases_recv(subreq, state, &num_results, &results);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    ret = sdap_id_op_done(state->op, ret, &dp_error);
+    if (dp_error == DP_ERR_OK && ret != EOK) {
+        /* retry */
+        ret = subid_ranges_get_retry(req);
+        if (ret != EOK) {
+            tevent_req_error(req, ret);
+            return;
+        }
+        return;
+    }
+    state->sdap_ret = ret;
+
+    if (ret && ret != ENOENT) {
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to retrieve subid ranges.\n");
+        state->dp_error = dp_error;
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    if (num_results == 0 || !results) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              "No such user '%s' or user doesn't have subid range\n",
+              state->name);
+        sysdb_delete_subid_range(state->domain, state->name);
+    } else {
+        if (num_results > 1) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  "Multiple subid ranges, only first will be processed\n");
+        }
+
+        /* store range */
+        sysdb_store_subid_range(state->domain, state->name,
+                                state->domain->user_timeout,
+                                results[0]);
+    }
+
+    state->dp_error = DP_ERR_OK;
+    tevent_req_done(req);
+}
+
+int subid_ranges_get_recv(struct tevent_req *req, int *dp_error_out,
+                          int *sdap_ret)
+{
+    struct subid_ranges_get_state *state = tevent_req_data(req,
+                                                    struct subid_ranges_get_state);
+
+    if (dp_error_out) {
+        *dp_error_out = state->dp_error;
+    }
+
+    if (sdap_ret) {
+        *sdap_ret = state->sdap_ret;
+    }
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    return EOK;
+}
diff --git a/src/providers/ldap/sdap.h b/src/providers/ldap/sdap.h
index ffcbee8a5..6382bec25 100644
--- a/src/providers/ldap/sdap.h
+++ b/src/providers/ldap/sdap.h
@@ -389,6 +389,19 @@ enum sdap_ipnetwork_entry_attrs {
     SDAP_OPTS_IPNETWORK /* attrs counter */
 };
 
+#ifdef BUILD_SUBID
+enum sdap_subid_range_attrs {
+    SDAP_OC_SUBID_RANGE = 0,
+    SDAP_AT_SUBID_RANGE_UID_COUNT,
+    SDAP_AT_SUBID_RANGE_GID_COUNT,
+    SDAP_AT_SUBID_RANGE_UID_NUMBER,
+    SDAP_AT_SUBID_RANGE_GID_NUMBER,
+    SDAP_AT_SUBID_RANGE_OWNER,
+
+    SDAP_OPTS_SUBID_RANGE /* attrs counter */
+};
+#endif
+
 enum sdap_autofs_map_attrs {
     SDAP_OC_AUTOFS_MAP,
     SDAP_AT_AUTOFS_MAP_NAME,
@@ -453,6 +466,9 @@ struct sdap_domain {
     struct sdap_search_base **iphost_search_bases;
     struct sdap_search_base **ipnetwork_search_bases;
     struct sdap_search_base **autofs_search_bases;
+#ifdef BUILD_SUBID
+    struct sdap_search_base **subid_ranges_search_bases;
+#endif
 
     struct sdap_domain *next, *prev;
     /* Need to modify the list from a talloc destructor */
@@ -495,6 +511,9 @@ struct sdap_options {
     struct sdap_attr_map *service_map;
     struct sdap_attr_map *iphost_map;
     struct sdap_attr_map *ipnetwork_map;
+#ifdef BUILD_SUBID
+    struct sdap_attr_map *subid_map;
+#endif
 
     /* ID-mapping support */
     struct sdap_idmap_ctx *idmap_ctx;
diff --git a/src/responder/common/cache_req/cache_req.c b/src/responder/common/cache_req/cache_req.c
index 889547ba7..750d655c1 100644
--- a/src/responder/common/cache_req/cache_req.c
+++ b/src/responder/common/cache_req/cache_req.c
@@ -45,6 +45,10 @@ cache_req_get_plugin(enum cache_req_type type)
         &cache_req_initgroups_by_name,
         &cache_req_initgroups_by_upn,
 
+#ifdef BUILD_SUBID
+        &cache_req_subid_ranges_by_name,
+#endif
+
         &cache_req_object_by_sid,
         &cache_req_object_by_name,
         &cache_req_object_by_id,
diff --git a/src/responder/common/cache_req/cache_req.h b/src/responder/common/cache_req/cache_req.h
index d5c25ccf0..055fb405b 100644
--- a/src/responder/common/cache_req/cache_req.h
+++ b/src/responder/common/cache_req/cache_req.h
@@ -39,6 +39,10 @@ enum cache_req_type {
     CACHE_REQ_INITGROUPS,
     CACHE_REQ_INITGROUPS_BY_UPN,
 
+#ifdef BUILD_SUBID
+    CACHE_REQ_SUBID_RANGES_BY_NAME,
+#endif
+
     CACHE_REQ_OBJECT_BY_SID,
     CACHE_REQ_OBJECT_BY_NAME,
     CACHE_REQ_OBJECT_BY_ID,
diff --git a/src/responder/common/cache_req/cache_req_data.c b/src/responder/common/cache_req/cache_req_data.c
index e82dc8ab6..3c60eb484 100644
--- a/src/responder/common/cache_req/cache_req_data.c
+++ b/src/responder/common/cache_req/cache_req_data.c
@@ -90,6 +90,9 @@ cache_req_data_create(TALLOC_CTX *mem_ctx,
     case CACHE_REQ_GROUP_BY_FILTER:
     case CACHE_REQ_INITGROUPS:
     case CACHE_REQ_INITGROUPS_BY_UPN:
+#ifdef BUILD_SUBID
+    case CACHE_REQ_SUBID_RANGES_BY_NAME:
+#endif
     case CACHE_REQ_NETGROUP_BY_NAME:
     case CACHE_REQ_OBJECT_BY_NAME:
     case CACHE_REQ_AUTOFS_MAP_ENTRIES:
diff --git a/src/responder/common/cache_req/cache_req_plugin.h b/src/responder/common/cache_req/cache_req_plugin.h
index 9e4986f93..f86a02042 100644
--- a/src/responder/common/cache_req/cache_req_plugin.h
+++ b/src/responder/common/cache_req/cache_req_plugin.h
@@ -302,6 +302,9 @@ extern const struct cache_req_plugin cache_req_group_by_name;
 extern const struct cache_req_plugin cache_req_group_by_id;
 extern const struct cache_req_plugin cache_req_initgroups_by_name;
 extern const struct cache_req_plugin cache_req_initgroups_by_upn;
+#ifdef BUILD_SUBID
+extern const struct cache_req_plugin cache_req_subid_ranges_by_name;
+#endif
 extern const struct cache_req_plugin cache_req_user_by_cert;
 extern const struct cache_req_plugin cache_req_user_by_filter;
 extern const struct cache_req_plugin cache_req_group_by_filter;
diff --git a/src/responder/common/cache_req/plugins/cache_req_subid_ranges_by_name.c b/src/responder/common/cache_req/plugins/cache_req_subid_ranges_by_name.c
new file mode 100644
index 000000000..54852711f
--- /dev/null
+++ b/src/responder/common/cache_req/plugins/cache_req_subid_ranges_by_name.c
@@ -0,0 +1,143 @@
+/*
+    Copyright (C) 2021 Red Hat
+
+    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 <talloc.h>
+#include <ldb.h>
+
+#include "db/sysdb.h"
+#include "db/sysdb_subid.h"
+#include "util/util.h"
+#include "providers/data_provider.h"
+#include "responder/common/cache_req/cache_req_plugin.h"
+
+static errno_t
+cache_req_subid_ranges_by_name_prepare_domain_data(struct cache_req *cr,
+                                                   struct cache_req_data *data,
+                                                   struct sss_domain_info *domain)
+{
+    TALLOC_CTX *tmp_ctx;
+    const char *name;
+    errno_t ret;
+
+    if (cr->data->name.name == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Bug: parsed name is NULL?\n");
+        return ERR_INTERNAL;
+    }
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        return ENOMEM;
+    }
+
+    name = sss_get_cased_name(tmp_ctx, cr->data->name.name,
+                              domain->case_sensitive);
+    if (name == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    name = sss_reverse_replace_space(tmp_ctx, name, cr->rctx->override_space);
+    if (name == NULL) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    talloc_zfree(data->name.lookup);
+    data->name.lookup = talloc_steal(data, name);
+
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+static const char *
+cache_req_subid_ranges_by_name_create_debug_name(TALLOC_CTX *mem_ctx,
+                                                 struct cache_req_data *data,
+                                                 struct sss_domain_info *domain)
+{
+    return talloc_strdup(mem_ctx, data->name.lookup);
+}
+
+static errno_t
+cache_req_subid_ranges_by_name_lookup(TALLOC_CTX *mem_ctx,
+                                      struct cache_req *cr,
+                                      struct cache_req_data *data,
+                                      struct sss_domain_info *domain,
+                                      struct ldb_result **_result)
+{
+    struct ldb_result *result;
+    struct ldb_message *msg;
+    errno_t ret;
+
+    ret = sysdb_get_subid_ranges(mem_ctx, domain, data->name.name,
+                                 data->attrs, &msg);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    result = cache_req_create_ldb_result_from_msg(mem_ctx, msg);
+    if (result == NULL) {
+        return ENOMEM;
+    }
+
+    *_result = result;
+
+    return EOK;
+}
+
+static struct tevent_req *
+cache_req_subid_ranges_by_name_dp_send(TALLOC_CTX *mem_ctx,
+                                       struct cache_req *cr,
+                                       struct cache_req_data *data,
+                                       struct sss_domain_info *domain,
+                                       struct ldb_result *result)
+{
+    /* Views aren't yet supported */
+    return sss_dp_get_account_send(mem_ctx, cr->rctx, domain, true,
+                                   SSS_DP_SUBID_RANGES, cr->data->name.lookup, 0, NULL);
+}
+
+const struct cache_req_plugin cache_req_subid_ranges_by_name = {
+    .name = "SubID ranges by name",
+    .attr_expiration = SYSDB_CACHE_EXPIRE,
+    .parse_name = true,
+    .ignore_default_domain = false,
+    .bypass_cache = false,
+    .only_one_result = false,
+    .search_all_domains = false,
+    .require_enumeration = false,
+    .allow_missing_fqn = false,
+    .allow_switch_to_upn = false,
+    .upn_equivalent = CACHE_REQ_SENTINEL,
+    .get_next_domain_flags = SSS_GND_DESCEND,
+
+    .is_well_known_fn = NULL,
+    .prepare_domain_data_fn = cache_req_subid_ranges_by_name_prepare_domain_data,
+    .create_debug_name_fn = cache_req_subid_ranges_by_name_create_debug_name,
+    .global_ncache_add_fn = NULL,
+    .ncache_check_fn = NULL,
+    .ncache_add_fn = NULL,
+    .ncache_filter_fn = NULL,
+    .lookup_fn = cache_req_subid_ranges_by_name_lookup,
+    .dp_send_fn = cache_req_subid_ranges_by_name_dp_send,
+    .dp_recv_fn = cache_req_common_dp_recv,
+    .dp_get_domain_check_fn = NULL,
+    .dp_get_domain_send_fn = NULL,
+    .dp_get_domain_recv_fn = NULL,
+};
diff --git a/src/responder/common/responder.h b/src/responder/common/responder.h
index a5d6359a0..fbe46f335 100644
--- a/src/responder/common/responder.h
+++ b/src/responder/common/responder.h
@@ -273,6 +273,7 @@ enum sss_dp_acct_type {
     SSS_DP_USER = 1,
     SSS_DP_GROUP,
     SSS_DP_INITGROUPS,
+    SSS_DP_SUBID_RANGES,
     SSS_DP_NETGR,
     SSS_DP_SERVICES,
     SSS_DP_SECID,
diff --git a/src/responder/common/responder_dp.c b/src/responder/common/responder_dp.c
index 8076e1e43..1b016dba1 100644
--- a/src/responder/common/responder_dp.c
+++ b/src/responder/common/responder_dp.c
@@ -70,6 +70,7 @@ sss_dp_account_files_params(struct sss_domain_info *dom,
         *_opt_name_out = opt_name_in;
         return EAGAIN;
     /* These are not handled by the files provider, just fall back */
+    case SSS_DP_SUBID_RANGES:
     case SSS_DP_NETGR:
     case SSS_DP_SERVICES:
     case SSS_DP_SECID:
@@ -109,6 +110,9 @@ sss_dp_get_account_filter(TALLOC_CTX *mem_ctx,
         case SSS_DP_INITGROUPS:
             entry_type = BE_REQ_INITGROUPS;
             break;
+        case SSS_DP_SUBID_RANGES:
+            entry_type = BE_REQ_SUBID_RANGES;
+            break;
         case SSS_DP_NETGR:
             entry_type = BE_REQ_NETGROUP;
             break;
diff --git a/src/responder/nss/nss_cmd.c b/src/responder/nss/nss_cmd.c
index a487e1c24..ef59daea8 100644
--- a/src/responder/nss/nss_cmd.c
+++ b/src/responder/nss/nss_cmd.c
@@ -1041,6 +1041,25 @@ static errno_t nss_cmd_initgroups_ex(struct cli_ctx *cli_ctx)
                           SSS_MC_INITGROUPS, nss_protocol_fill_initgr);
 }
 
+static errno_t nss_cmd_subid_ranges(struct cli_ctx *cli_ctx)
+{
+#ifdef BUILD_SUBID
+    const char *attrs[] =
+        {
+          SYSDB_SUBID_UID_COUND,
+          SYSDB_SUBID_GID_COUNT,
+          SYSDB_SUBID_UID_NUMBER,
+          SYSDB_SUBID_GID_NUMBER,
+          NULL
+        };
+
+    return nss_getby_name(cli_ctx, false, CACHE_REQ_SUBID_RANGES_BY_NAME, attrs,
+                          SSS_MC_NONE, nss_protocol_fill_subid_ranges);
+#else
+    return ENOTSUP;
+#endif
+}
+
 static errno_t nss_cmd_setnetgrent(struct cli_ctx *cli_ctx)
 {
     struct nss_state_ctx *state_ctx;
@@ -1332,6 +1351,7 @@ struct sss_cmd_table *get_nss_cmds(void)
         { SSS_NSS_GETGRENT, nss_cmd_getgrent },
         { SSS_NSS_ENDGRENT, nss_cmd_endgrent },
         { SSS_NSS_INITGR, nss_cmd_initgroups },
+        { SSS_NSS_GET_SUBID_RANGES, nss_cmd_subid_ranges },
         { SSS_NSS_SETNETGRENT, nss_cmd_setnetgrent },
      /* { SSS_NSS_GETNETGRENT, "not needed" }, */
      /* { SSS_NSS_ENDNETGRENT, "not needed" }, */
diff --git a/src/responder/nss/nss_protocol.h b/src/responder/nss/nss_protocol.h
index 364f19c83..949b6291a 100644
--- a/src/responder/nss/nss_protocol.h
+++ b/src/responder/nss/nss_protocol.h
@@ -147,6 +147,14 @@ nss_protocol_fill_initgr(struct nss_ctx *nss_ctx,
                          struct sss_packet *packet,
                          struct cache_req_result *result);
 
+#ifdef BUILD_SUBID
+errno_t
+nss_protocol_fill_subid_ranges(struct nss_ctx *nss_ctx,
+                               struct nss_cmd_ctx *cmd_ctx,
+                               struct sss_packet *packet,
+                               struct cache_req_result *result);
+#endif
+
 errno_t
 nss_protocol_fill_netgrent(struct nss_ctx *nss_ctx,
                            struct nss_cmd_ctx *cmd_ctx,
diff --git a/src/responder/nss/nss_protocol_subid.c b/src/responder/nss/nss_protocol_subid.c
new file mode 100644
index 000000000..29a957762
--- /dev/null
+++ b/src/responder/nss/nss_protocol_subid.c
@@ -0,0 +1,60 @@
+/*
+    Copyright (C) 2021 Red Hat
+
+    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 "responder/nss/nss_protocol.h"
+
+errno_t
+nss_protocol_fill_subid_ranges(struct nss_ctx *nss_ctx,
+                               struct nss_cmd_ctx *cmd_ctx,
+                               struct sss_packet *packet,
+                               struct cache_req_result *result)
+{
+    static const uint32_t one = 1;
+    errno_t ret;
+    uint8_t *body;
+    size_t body_len;
+    size_t rp = 0;
+    uint32_t gid, uid, gidCount, uidCount;
+
+    if (!result->count || !result->msgs) {
+        return ENOENT;
+    }
+
+    uid      = ldb_msg_find_attr_as_uint(result->msgs[0], SYSDB_SUBID_UID_NUMBER, 0);
+    uidCount = ldb_msg_find_attr_as_uint(result->msgs[0], SYSDB_SUBID_UID_COUND,  0);
+    gid      = ldb_msg_find_attr_as_uint(result->msgs[0], SYSDB_SUBID_GID_NUMBER, 0);
+    gidCount = ldb_msg_find_attr_as_uint(result->msgs[0], SYSDB_SUBID_GID_COUNT,  0);
+    if (!uid || !gid || !gidCount || !uidCount) {
+        return ENOENT;
+    }
+
+    /* only single uid & gid range is expected currently */
+    ret = sss_packet_grow(packet, (2 + 2*2) * sizeof(uint32_t));
+    if (ret != EOK) {
+        return ret;
+    }
+
+    sss_packet_get_body(packet, &body, &body_len);
+    SAFEALIGN_COPY_UINT32(&body[rp], &one, &rp);
+    SAFEALIGN_COPY_UINT32(&body[rp], &one, &rp);
+    SAFEALIGN_COPY_UINT32(&body[rp], &uid, &rp);
+    SAFEALIGN_COPY_UINT32(&body[rp], &uidCount, &rp);
+    SAFEALIGN_COPY_UINT32(&body[rp], &gid, &rp);
+    SAFEALIGN_COPY_UINT32(&body[rp], &gidCount, &rp);
+
+    return EOK;
+}
diff --git a/src/sss_client/common.c b/src/sss_client/common.c
index d29332939..9416e21d1 100644
--- a/src/sss_client/common.c
+++ b/src/sss_client/common.c
@@ -1008,7 +1008,7 @@ void sss_pam_close_fd(void)
     sss_pam_unlock();
 }
 
-static enum sss_status
+enum sss_status
 sss_cli_make_request_with_checks(enum sss_cli_command cmd,
                                  struct sss_cli_req_data *rd,
                                  int timeout,
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 2c3c71bc4..1347ce71d 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -284,6 +284,10 @@ SSS_NSS_GETSIDBYGID   = 0x0119, /**< Takes an unsigned 32bit integer (POSIX GID)
                                      and return the zero terminated string
                                      representation of the SID of the object
                                      with the given UID. */
+
+/* subid */
+    SSS_NSS_GET_SUBID_RANGES = 0x0130, /**< Requests both subuid and subgid ranges
+                                            defined for a user. */
 };
 
 /**
@@ -631,6 +635,13 @@ enum sss_cli_error_codes {
 
 const char *ssscli_err2string(int err);
 
+enum sss_status sss_cli_make_request_with_checks(enum sss_cli_command cmd,
+                                                 struct sss_cli_req_data *rd,
+                                                 int timeout,
+                                                 uint8_t **repbuf, size_t *replen,
+                                                 int *errnop,
+                                                 const char *socket_name);
+
 enum nss_status sss_nss_make_request(enum sss_cli_command cmd,
                                      struct sss_cli_req_data *rd,
                                      uint8_t **repbuf, size_t *replen,
diff --git a/src/sss_client/subid/sss_subid.c b/src/sss_client/subid/sss_subid.c
new file mode 100644
index 000000000..ae74ece3c
--- /dev/null
+++ b/src/sss_client/subid/sss_subid.c
@@ -0,0 +1,209 @@
+/*
+    Copyright (C) 2021 Red Hat
+
+    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 <stdlib.h>
+#include <string.h>
+#include <shadow/subid.h>
+#include "sss_cli.h"
+
+/* This shadow-utils plugin contains partial SSSD implementation
+ * of `subid_nss_ops` API as described in
+ * https://github.com/shadow-maint/shadow/blob/d4b6d1549b2af48ce3cb6ff78d9892095fb8fdd9/lib/prototypes.h#L271
+ */
+
+/* Find all subid ranges delegated to a user.
+ *
+ * Usage in shadow-utils:
+ *      libsubid: get_sub?id_ranges() -> list_owner_ranges()
+ *
+ * SUBID_RANGES Reply:
+ *
+ * 0-3: 32bit unsigned number of UID results
+ * 4-7: 32bit unsigned number of GID results
+ * For each result (sub-uid ranges first):
+ * 0-3: 32bit number with "start" id
+ * 4-7: 32bit number with "count" (range size)
+ */
+enum subid_status shadow_subid_list_owner_ranges(const char *user,
+                                                 enum subid_type id_type,
+                                                 struct subid_range **ranges,
+                                                 int *count)
+{
+    size_t user_len;
+    enum sss_status ret;
+    uint8_t *repbuf = NULL;
+    size_t index = 0;
+    size_t replen;
+    int errnop;
+    struct sss_cli_req_data rd;
+    uint32_t num_results = 0;
+    uint32_t val;
+
+    if ( !user || !ranges || !count ||
+          ((id_type != ID_TYPE_UID) && (id_type != ID_TYPE_GID)) ) {
+        return SUBID_STATUS_ERROR;
+    }
+
+    ret = sss_strnlen(user, SSS_NAME_MAX, &user_len);
+    if (ret != 0) {
+        return SUBID_STATUS_UNKNOWN_USER;
+    }
+    rd.len = user_len + 1;
+    rd.data = user;
+
+    sss_nss_lock();
+    /* Anticipated workflow will always request both
+     * sub-uid and sub-gid ranges anyway.
+     * So don't bother with dedicated commands -
+     * just request everything in one shot.
+     * The second request will get data from the cache.
+     */
+    ret = sss_cli_make_request_with_checks(SSS_NSS_GET_SUBID_RANGES, &rd,
+                                           SSS_CLI_SOCKET_TIMEOUT,
+                                           &repbuf, &replen, &errnop,
+                                           SSS_NSS_SOCKET_NAME);
+    sss_nss_unlock();
+
+    if ((ret != SSS_STATUS_SUCCESS) || (errnop != EOK)) {
+        free(repbuf);
+        if (ret == SSS_STATUS_UNAVAIL) {
+            return SUBID_STATUS_ERROR_CONN;
+        }
+        return SUBID_STATUS_ERROR;
+    }
+
+    SAFEALIGN_COPY_UINT32(&num_results, repbuf, NULL);
+    if (id_type == ID_TYPE_UID) {
+        index = 2 * sizeof(uint32_t);
+    } else {
+        index = (2 + 2*num_results) * sizeof(uint32_t);
+        SAFEALIGN_COPY_UINT32(&num_results, repbuf + sizeof(uint32_t), NULL);
+    }
+    if (num_results == 0) {
+        /* TODO: how to distinguish "user not found" vs "user doesn't have ranges defined" here?
+         * Options:
+         *  - special "fake" entry in the cache
+         *  - provide 'nss_protocol_done_fn' to 'nss_getby_name' to avoid "ENOENT -> "empty packet" logic
+         *  - add custom error code for this case and handle in generic 'nss_protocol_done'
+         *
+         * Note: at the moment this is not important, since shadow-utils doesn't use return code internally
+         * and returns -1 from libsubid on any error  anyway.
+         */
+        free(repbuf);
+        return SUBID_STATUS_UNKNOWN_USER;
+    }
+
+    *count = num_results;
+    if (*count < 0) {
+        free(repbuf);
+        return SUBID_STATUS_ERROR;
+    }
+
+    *ranges = malloc(num_results * sizeof(struct subid_range));
+    if (!*ranges) {
+        free(repbuf);
+        return SUBID_STATUS_ERROR;
+    }
+
+    for (uint32_t c = 0; c < num_results; ++c) {
+        SAFEALIGN_COPY_UINT32(&val, repbuf + index, &index);
+        (*ranges)[c].start = val;
+        SAFEALIGN_COPY_UINT32(&val, repbuf + index, &index);
+        (*ranges)[c].count = val;
+    }
+    free(repbuf);
+
+    return SUBID_STATUS_SUCCESS;
+}
+
+/* Does a user own a given subid range?
+ *
+ * Usage in shadow-utils:
+ *      newuidmap/user busy : have_sub_uids() -> has_range()
+ */
+enum subid_status shadow_subid_has_range(const char *owner,
+                                         unsigned long start,
+                                         unsigned long count,
+                                         enum subid_type id_type,
+                                         bool *result)
+{
+    enum subid_status ret;
+    struct subid_range *range;
+    int amount;
+    unsigned long end = start + count;
+
+    if (!result || (end < start)) {
+        return SUBID_STATUS_ERROR;
+    }
+
+    if (count == 0) {
+        *result = true;
+        return SUBID_STATUS_SUCCESS;
+    }
+
+    /* Anticipated workflow is the following:
+     *
+     * 1) Podman figures out ranges available for a user:
+     *     libsubid::get_subid_ranges() -> ... -> list_owner_ranges()
+     *
+     * 2) Podman maps available ranges:
+     *     newuidmap -> have_sub_uids() -> has_range()
+     * At this point all ranges are available in a cache from step (1)
+     * so it doesn't make sense to try "smart" LDAP searches (even if possible)
+     * Let's just reuse list_owner_ranges() and do a check.
+     *
+     * It might have some sense to do a check at responder's side (i.e. without
+     * fetching all ranges), but range is just a couple of numbers (and FreeIPA
+     * only supports a single range per user anyway), so this optimization
+     * wouldn't save much traffic anyway, but would introduce new
+     * `sss_cli_command`/responder handler.
+     */
+
+    ret = shadow_subid_list_owner_ranges(owner, id_type, &range, &amount);
+    if (ret != SUBID_STATUS_SUCCESS) {
+        return ret;
+    }
+
+    *result = false;
+
+    for (int i = 0; i < amount; ++i) {
+        if ((range[i].start <= start) &&
+            (range[i].start + range[i].count >= end)) {
+            *result = true;
+        }
+        /* TODO: handle coverage via multiple ranges (once IPA supports this) */
+    }
+
+    free(range);
+    return ret;
+}
+
+/* Find uids who own a given subid.
+ *
+ * Usage in shadow-utils:
+ *      libsubid: get_sub?id_owners() -> find_subid_owners()
+ */
+enum subid_status shadow_subid_find_subid_owners(unsigned long subid,
+                                                 enum subid_type id_type,
+                                                 uid_t **uids,
+                                                 int *count)
+{
+    /* Not yet implemented.
+     * Currently there are no users of this function.
+     */
+    return SUBID_STATUS_ERROR;
+}
diff --git a/src/sss_client/subid/sss_subid.exports b/src/sss_client/subid/sss_subid.exports
new file mode 100644
index 000000000..87c073b48
--- /dev/null
+++ b/src/sss_client/subid/sss_subid.exports
@@ -0,0 +1,12 @@
+EXPORTED {
+
+    # public functions
+    global:
+        shadow_subid_has_range;
+        shadow_subid_list_owner_ranges;
+        shadow_subid_find_subid_owners;
+
+    # everything else is local
+    local:
+        *;
+};
diff --git a/src/systemtap/sssd_functions.stp b/src/systemtap/sssd_functions.stp
index 01f553177..513029046 100644
--- a/src/systemtap/sssd_functions.stp
+++ b/src/systemtap/sssd_functions.stp
@@ -29,8 +29,8 @@ function acct_req_desc(entry_type)
         str_entry_type = "host"
     } else if (entry_type == 0x0009) {
         str_entry_type = "ip_network"
-    # See src/providers/data_provider_req.h, no 0x0010 there..
     } else if (entry_type == 0x0010) {
+        str_entry_type = "subid_ranges"
     } else if (entry_type == 0x0011) {
         str_entry_type = "by_secid"
     } else if (entry_type == 0x0012) {
diff --git a/src/tests/cwrap/Makefile.am b/src/tests/cwrap/Makefile.am
index 60f71036e..6d3dc45e5 100644
--- a/src/tests/cwrap/Makefile.am
+++ b/src/tests/cwrap/Makefile.am
@@ -74,6 +74,9 @@ SSSD_CACHE_REQ_OBJ = \
     ../../../src/responder/common/cache_req/plugins/cache_req_ip_network_by_name.c \
     ../../../src/responder/common/cache_req/plugins/cache_req_ip_network_by_addr.c \
     $(NULL)
+if BUILD_SUBID
+    SSSD_CACHE_REQ_OBJ += ../../../src/responder/common/cache_req/plugins/cache_req_subid_ranges_by_name.c
+endif
 
 SSSD_RESPONDER_IFACE_OBJ = \
     ../../../src/responder/common/responder_iface.c \
diff --git a/src/tests/dlopen-tests.c b/src/tests/dlopen-tests.c
index ab44c213c..6d3345cbe 100644
--- a/src/tests/dlopen-tests.c
+++ b/src/tests/dlopen-tests.c
@@ -60,6 +60,9 @@ struct so {
 #ifdef BUILD_AUTOFS
     { "libsss_autofs.so", { LIBPFX"libsss_autofs.so", NULL } },
 #endif
+#ifdef BUILD_SUBID
+    { "libsubid_sss.so", { LIBPFX"libsubid_sss.so", NULL } },
+#endif
 #ifdef HAVE_KRB5_LOCATOR_PLUGIN
     { "sssd_krb5_locator_plugin.so", { LIBPFX"sssd_krb5_locator_plugin.so",
                                        NULL } },
diff --git a/src/util/sss_cli_cmd.c b/src/util/sss_cli_cmd.c
index 82dc33b33..bc9332aaa 100644
--- a/src/util/sss_cli_cmd.c
+++ b/src/util/sss_cli_cmd.c
@@ -230,6 +230,11 @@ const char *sss_cmd2str(enum sss_cli_command cmd)
         return "SSS_NSS_GETIDBYSID";
     case SSS_NSS_GETORIGBYNAME:
         return "SSS_NSS_GETORIGBYNAME";
+
+    /* SUBID ranges */
+    case SSS_NSS_GET_SUBID_RANGES:
+        return "SSS_NSS_GET_SUBID_RANGES";
+
     default:
         DEBUG(SSSDBG_MINOR_FAILURE,
               "Translation's string is missing for command [%#x].\n", cmd);
diff --git a/src/util/util_errors.c b/src/util/util_errors.c
index 7ba2621d1..f2d1d5dfc 100644
--- a/src/util/util_errors.c
+++ b/src/util/util_errors.c
@@ -120,6 +120,7 @@ struct err_string error_to_str[] = {
     { "Unable to verify peer" }, /* ERR_UNABLE_TO_VERIFY_PEER */
     { "Unable to resolve host" }, /* ERR_UNABLE_TO_RESOLVE_HOST */
     { "GetAccountDomain() not supported" }, /* ERR_GET_ACCT_DOM_NOT_SUPPORTED */
+    { "Subid ranges are not supported by this provider" }, /* ERR_GET_ACCT_SUBID_RANGES_NOT_SUPPORTED */
     { "The last GetAccountDomain() result is still valid" }, /* ERR_GET_ACCT_DOM_CACHED */
     { "ID is outside the allowed range" }, /* ERR_ID_OUTSIDE_RANGE */
     { "Group ID is duplicated" }, /* ERR_GID_DUPLICATED */
diff --git a/src/util/util_errors.h b/src/util/util_errors.h
index 37ce2de23..4098e818d 100644
--- a/src/util/util_errors.h
+++ b/src/util/util_errors.h
@@ -141,6 +141,7 @@ enum sssd_errors {
     ERR_UNABLE_TO_VERIFY_PEER,
     ERR_UNABLE_TO_RESOLVE_HOST,
     ERR_GET_ACCT_DOM_NOT_SUPPORTED,
+    ERR_GET_ACCT_SUBID_RANGES_NOT_SUPPORTED,
     ERR_GET_ACCT_DOM_CACHED,
     ERR_ID_OUTSIDE_RANGE,
     ERR_GID_DUPLICATED,
-- 
2.26.3