From c9668b8c5d88f2d6ff52088a0bd312538be158ed Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Sat, 21 Jan 2012 16:16:41 -0500
Subject: [PATCH 06/12] NSS: Add getservbyname and getservbyport support to
 the NSS Responder

---
 Makefile.am                         |    2 +
 src/responder/nss/nsssrv_cmd.c      |    6 +
 src/responder/nss/nsssrv_services.c | 1169 +++++++++++++++++++++++++++++++++++
 src/responder/nss/nsssrv_services.h |   33 +
 4 files changed, 1210 insertions(+), 0 deletions(-)
 create mode 100644 src/responder/nss/nsssrv_services.c
 create mode 100644 src/responder/nss/nsssrv_services.h

diff --git a/Makefile.am b/Makefile.am
index 76aa91c2d4121ab2f529363e454a2f26f972f1f2..2e47e96fbd06c454fa06c122f9714475808e3018 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -315,6 +315,7 @@ dist_noinst_HEADERS = \
     src/responder/nss/nsssrv.h \
     src/responder/nss/nsssrv_private.h \
     src/responder/nss/nsssrv_netgroup.h \
+    src/responder/nss/nsssrv_services.h \
     src/responder/common/negcache.h \
     src/responder/sudo/sudosrv_private.h \
     src/sbus/sbus_client.h \
@@ -441,6 +442,7 @@ sssd_nss_SOURCES = \
     src/responder/nss/nsssrv.c \
     src/responder/nss/nsssrv_cmd.c \
     src/responder/nss/nsssrv_netgroup.c \
+    src/responder/nss/nsssrv_services.c \
     $(SSSD_RESPONDER_OBJ)
 sssd_nss_LDADD = \
     $(TDB_LIBS) \
diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c
index 4b5f6784ca026d1ec5dafb4819ab869ca7bee4ab..579c7602690711443a7b9bdf1b70f9113133c468 100644
--- a/src/responder/nss/nsssrv_cmd.c
+++ b/src/responder/nss/nsssrv_cmd.c
@@ -23,6 +23,7 @@
 #include "responder/nss/nsssrv.h"
 #include "responder/nss/nsssrv_private.h"
 #include "responder/nss/nsssrv_netgroup.h"
+#include "responder/nss/nsssrv_services.h"
 #include "responder/common/negcache.h"
 #include "confdb/confdb.h"
 #include "db/sysdb.h"
@@ -3371,6 +3372,11 @@ static struct sss_cmd_table nss_cmds[] = {
     {SSS_NSS_SETNETGRENT, nss_cmd_setnetgrent},
     {SSS_NSS_GETNETGRENT, nss_cmd_getnetgrent},
     {SSS_NSS_ENDNETGRENT, nss_cmd_endnetgrent},
+    {SSS_NSS_GETSERVBYNAME, nss_cmd_getservbyname},
+    {SSS_NSS_GETSERVBYPORT, nss_cmd_getservbyport},
+    {SSS_NSS_SETSERVENT, nss_cmd_setservent},
+    {SSS_NSS_GETSERVENT, nss_cmd_getservent},
+    {SSS_NSS_ENDSERVENT, nss_cmd_endservent},
     {SSS_CLI_NULL, NULL}
 };
 
diff --git a/src/responder/nss/nsssrv_services.c b/src/responder/nss/nsssrv_services.c
new file mode 100644
index 0000000000000000000000000000000000000000..c651ceb586e00a38c46df9cf316f6a732e23d3b2
--- /dev/null
+++ b/src/responder/nss/nsssrv_services.c
@@ -0,0 +1,1169 @@
+/*
+    SSSD
+
+    Authors:
+        Stephen Gallagher <sgallagh@redhat.com>
+
+    Copyright (C) 2012 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 <collection.h>
+#include <arpa/inet.h>
+#include "util/util.h"
+#include "responder/nss/nsssrv.h"
+#include "responder/nss/nsssrv_private.h"
+#include "responder/nss/nsssrv_services.h"
+#include "responder/common/negcache.h"
+#include "confdb/confdb.h"
+#include "db/sysdb.h"
+#include "db/sysdb_services.h"
+
+struct getserv_ctx {
+    struct tevent_context *ev;
+    uint16_t port;
+    struct nss_dom_ctx *dctx;
+
+    struct sss_domain_info **domains;
+    size_t dom_idx;
+
+    char *name;
+    char *cased_name;
+
+    char *proto;
+    char *cased_proto;
+    struct ldb_result *res;
+};
+
+static errno_t lookup_service_step(struct tevent_req *req);
+static void lookup_service_done(struct tevent_req *req);
+
+#define SVC_NAME_CASED dom->case_sensitive ? state->cased_name \
+                                           : state->name
+#define SVC_PROTO_CASED dom->case_sensitive ? state->cased_proto \
+                                            : state->proto
+
+/* Provider Lookup Logic:
+ * Iterate through the available caches. If the cached entry is
+ * present and not expired, return it immediately(*). If it is
+ * present and expired, add it to a list of domains eligible to
+ * be checked. If it is in the negative cache, skip over it and
+ * do not add it to the eligible domain list.
+ *
+ * Once we have searched all of the caches, if the entry has not
+ * been determined to be available, search all domains in order
+ * to see if any of them contain the requested entry.
+ *
+ * (*) Optionally perform a midpoint cache refresh if appropriate.
+ */
+
+static struct tevent_req *
+getserv_send(TALLOC_CTX *mem_ctx,
+             struct tevent_context *ev,
+             uint16_t port,
+             const char *service_name,
+             const char *service_protocol,
+             struct nss_dom_ctx *dctx)
+{
+    errno_t ret;
+    struct tevent_req *req;
+    struct tevent_req *subreq;
+    struct getserv_ctx *state;
+    struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+    struct cli_ctx *cctx = cmdctx->cctx;
+    struct sss_domain_info *dom;
+    size_t num_domains = 0;
+    size_t dom_idx = 0;
+    struct nss_ctx *nctx =
+            talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+    struct sysdb_ctx *sysdb;
+    time_t now = time(NULL);
+    uint64_t lastUpdate;
+    uint64_t cacheExpire;
+    uint64_t midpoint_refresh;
+
+    req = tevent_req_create(mem_ctx, &state, struct getserv_ctx);
+    if (!req) return NULL;
+    state->dctx = dctx;
+
+    for (dom = cctx->rctx->domains; dom; dom = dom->next) num_domains++;
+
+    /* Create an array of domains to check. To save resizes, we'll
+     * assume that all will be checked
+     */
+    state->domains = talloc_zero_array(state,
+                                       struct sss_domain_info *,
+                                       num_domains + 1);
+    if (!state->domains) {
+        ret = ENOMEM;
+        goto immediate;
+    }
+
+    state->port = port;
+
+    /* Store both the case-sensitive and lowercased names
+     * in the state object, to avoid recalculating the
+     * lowercase in multiple domains.
+     */
+    if (service_protocol) {
+        state->proto = talloc_strdup(state, service_protocol);
+        if (!state->proto) {
+            ret = ENOMEM;
+            goto immediate;
+        }
+        state->cased_proto = sss_get_cased_name(state, service_protocol,
+                                                true);
+        if (!state->cased_proto) {
+            ret = ENOMEM;
+            goto immediate;
+        }
+    } else {
+        state->proto = NULL;
+        state->cased_proto = NULL;
+    }
+
+    /* If we're looking up by name */
+    if (service_name) {
+        /* Store both the case-sensitive and lowercased names
+         * in the state object, to avoid recalculating the
+         * lowercase in multiple domains.
+         */
+        state->name = talloc_strdup(state, service_name);
+        if (!state->name) {
+            ret = ENOMEM;
+            goto immediate;
+        }
+
+        state->cased_name = sss_get_cased_name(state, service_name,
+                                               true);
+        if (!state->cased_name) {
+            ret = ENOMEM;
+            goto immediate;
+        }
+    }
+
+    dom = cctx->rctx->domains;
+    while(dom) {
+        /* if it is a domainless search, skip domains that require fully
+          * qualified names instead */
+         while (dom && cmdctx->check_next && dom->fqnames) {
+             dom = dom->next;
+         }
+         if (!dom) break;
+
+         ret = sysdb_get_ctx_from_list(cctx->rctx->db_list, dom, &sysdb);
+         if (ret != EOK) {
+             DEBUG(SSSDBG_CRIT_FAILURE,
+                   ("Critical: Sysdb CTX not found for [%s]!\n", dom->name));
+             ret = EINVAL;
+             goto immediate;
+         }
+
+         /* If we're looking up by name */
+         if (service_name) {
+             /* Check the negative cache */
+             ret = sss_ncache_check_service(nctx->ncache,
+                                            nctx->neg_timeout,
+                                            dom,
+                                            SVC_NAME_CASED,
+                                            SVC_PROTO_CASED);
+             /* If negatively cached, return we didn't find it */
+             if (ret == EEXIST) {
+                 DEBUG(SSSDBG_TRACE_FUNC,
+                       ("Service [%s:%s] does not exist in [%s]! "
+                        "(negative cache)\n",
+                        SVC_NAME_CASED,
+                        SVC_PROTO_CASED ? SVC_PROTO_CASED : "<ANY>",
+                        dom->name));
+
+                 /* If this is a multi-domain search, try the next one */
+                 if (cmdctx->check_next) {
+                     dom = dom->next;
+                 } else {
+                     /* This was a single-domain search.
+                      * exit the loop. Since it was negatively-
+                      * cached, don't add it to the eligible
+                      * domains list.
+                      */
+                     dom = NULL;
+                 }
+
+                 continue;
+             }
+
+             /* Check the cache */
+             DEBUG(SSSDBG_TRACE_FUNC,
+                   ("Checking cache for [%s:%s@%s]\n",
+                    SVC_NAME_CASED,
+                    SVC_PROTO_CASED ? SVC_PROTO_CASED : "<ANY>",
+                    dom->name));
+
+             ret = sysdb_getservbyname(state, sysdb,
+                                       SVC_NAME_CASED,
+                                       SVC_PROTO_CASED,
+                                       &state->res);
+         } else { /* Looking up by port */
+             /* Check the negative cache */
+             ret = sss_ncache_check_service_port(nctx->ncache,
+                                            nctx->neg_timeout,
+                                            dom, port,
+                                            SVC_PROTO_CASED);
+             /* If negatively cached, return we didn't find it */
+             if (ret == EEXIST) {
+                 DEBUG(SSSDBG_TRACE_FUNC,
+                       ("Service [%ul:%s] does not exist in [%s]! "
+                        "(negative cache)\n",
+                        port,
+                        SVC_PROTO_CASED ? SVC_PROTO_CASED : "<ANY>",
+                        dom->name));
+
+                 /* If this is a multi-domain search, try the next one */
+                 if (cmdctx->check_next) {
+                     dom = dom->next;
+                 } else {
+                     /* This was a single-domain search.
+                      * exit the loop. Since it was negatively-
+                      * cached, don't add it to the eligible
+                      * domains list.
+                      */
+                     dom = NULL;
+                 }
+
+                 continue;
+             }
+
+             /* Check the cache */
+             DEBUG(SSSDBG_TRACE_FUNC,
+                   ("Checking cache for [%ul:%s@%s]\n",
+                    port,
+                    SVC_PROTO_CASED ? SVC_PROTO_CASED : "<ANY>",
+                    dom->name));
+
+             ret = sysdb_getservbyport(state, sysdb, port,
+                                       SVC_PROTO_CASED,
+                                       &state->res);
+         }
+         if (ret != EOK && ret != ENOENT) goto immediate;
+
+         if (ret == ENOENT) {
+             /* Not found in the cache. Add this domain to the
+              * list of eligible domains to check the provider.
+              */
+             if (NEED_CHECK_PROVIDER(dom->provider)) {
+                 state->domains[dom_idx] = dom;
+                 dom_idx++;
+             } else {
+                 /* No provider to check. Set the negative cache here */
+                 if (state->name) {
+                     ret = sss_ncache_set_service_name(nctx->ncache, false,
+                                                       dom,
+                                                       SVC_NAME_CASED,
+                                                       SVC_PROTO_CASED);
+                     if (ret != EOK) {
+                         /* Failure to set the negative cache is non-fatal.
+                          * We'll log an error and continue.
+                          */
+                         DEBUG(SSSDBG_MINOR_FAILURE,
+                               ("Could not set negative cache for [%s][%s]\n",
+                                SVC_NAME_CASED, SVC_PROTO_CASED));
+                     }
+                 } else {
+                     ret = sss_ncache_set_service_port(nctx->ncache, false,
+                                                       dom,
+                                                       state->port,
+                                                       SVC_PROTO_CASED);
+                     if (ret != EOK) {
+                         /* Failure to set the negative cache is non-fatal.
+                          * We'll log an error and continue.
+                          */
+                         DEBUG(SSSDBG_MINOR_FAILURE,
+                               ("Could not set negative cache for [%ul][%s]\n",
+                                state->port, SVC_PROTO_CASED));
+                     }
+                 }
+             }
+
+             /* If this is a multi-domain search, try the next one */
+             if (cmdctx->check_next) {
+                 dom = dom->next;
+             } else {
+                 /* This was a single-domain search.
+                  * exit the loop.
+                  */
+                 dom = NULL;
+             }
+             continue;
+         }
+
+         /* Found a result. Check its validity */
+         if (state->res->count > 1) {
+             DEBUG(SSSDBG_OP_FAILURE,
+                   ("getservby* returned more than one result!\n"));
+             ret = ENOENT;
+             goto immediate;
+         }
+
+         lastUpdate = ldb_msg_find_attr_as_uint64(state->res->msgs[0],
+                                                  SYSDB_LAST_UPDATE, 0);
+
+         cacheExpire = ldb_msg_find_attr_as_uint64(state->res->msgs[0],
+                                                   SYSDB_CACHE_EXPIRE, 0);
+
+         midpoint_refresh = 0;
+         if(nctx->cache_refresh_percent) {
+             midpoint_refresh = lastUpdate +
+               (cacheExpire - lastUpdate)*nctx->cache_refresh_percent/100;
+             if (midpoint_refresh - lastUpdate < 10) {
+                 /* If the percentage results in an expiration
+                  * less than ten seconds after the lastUpdate time,
+                  * that's too often we will simply set it to 10s
+                  */
+                 midpoint_refresh = lastUpdate+10;
+             }
+         }
+
+         if (cacheExpire > now) {
+             /* cache still valid */
+
+             if (NEED_CHECK_PROVIDER(dom->provider)
+                     && midpoint_refresh
+                     && midpoint_refresh < now) {
+                 /* We're past the the cache refresh timeout
+                  * We'll return the value from the cache, but we'll also
+                  * queue the cache entry for update out-of-band.
+                  */
+                 DEBUG(SSSDBG_TRACE_FUNC,
+                       ("Performing midpoint cache update\n"));
+
+                 /* Update the cache */
+                 subreq = sss_dp_get_account_send(cctx, cctx->rctx,
+                                                  dom, true,
+                                                  SSS_DP_SERVICES,
+                                                  SVC_NAME_CASED,
+                                                  port, NULL);
+                 if (!subreq) {
+                     DEBUG(SSSDBG_CRIT_FAILURE,
+                           ("Out of memory sending out-of-band data provider "
+                            "request\n"));
+                     /* This is non-fatal, so we'll continue here */
+                 }
+                 /* We don't need to listen for a reply, so we will free the
+                  * request here.
+                  */
+                 talloc_zfree(subreq);
+             }
+
+             /* The cache is valid. Return it */
+             ret = EOK;
+             goto immediate;
+         } else {
+             /* Cache is expired. Add this domain to the
+              * list of eligible domains to check the provider.
+              */
+             if (NEED_CHECK_PROVIDER(dom->provider)) {
+                 state->domains[dom_idx] = dom;
+                 dom_idx++;
+             }
+
+             /* If this is a multi-domain search, try the next one */
+             if (cmdctx->check_next) {
+                 dom = dom->next;
+             } else {
+                 /* This was a single-domain search.
+                  * exit the loop.
+                  */
+                 dom = NULL;
+             }
+         }
+    }
+
+    /* No valid cached entries found and
+     * not found in negative caches.
+     * Iterate through the domains and try
+     * to look the data up.
+     */
+
+    state->dom_idx = 0;
+    if (!state->domains[state->dom_idx]) {
+        /* No domains to search. Return ENOENT */
+        ret = ENOENT;
+        goto immediate;
+    }
+
+    ret = lookup_service_step(req);
+    if (ret != EOK) goto immediate;
+
+    return req;
+
+immediate:
+    if (ret == EOK) {
+        tevent_req_done(req);
+    } else {
+        tevent_req_error(req, ret);
+    }
+    tevent_req_post(req, ev);
+    return req;
+}
+
+static errno_t lookup_service_step(struct tevent_req *req)
+{
+    struct getserv_ctx *state =
+            tevent_req_data(req, struct getserv_ctx);
+    struct tevent_req *subreq;
+    struct cli_ctx *cctx = state->dctx->cmdctx->cctx;
+    struct sss_domain_info *dom =
+            state->domains[state->dom_idx];
+
+    /* Update the cache */
+    subreq = sss_dp_get_account_send(req,
+                                     cctx->rctx,
+                                     dom,
+                                     true,
+                                     SSS_DP_SERVICES,
+                                     SVC_NAME_CASED,
+                                     state->port,
+                                     SVC_PROTO_CASED);
+    if (!subreq) return ENOMEM;
+    tevent_req_set_callback(subreq, lookup_service_done, req);
+
+    return EOK;
+}
+
+static void lookup_service_done(struct tevent_req *subreq)
+{
+    errno_t ret;
+    dbus_uint16_t err_maj;
+    dbus_uint32_t err_min;
+    char *err_msg;
+    struct sysdb_ctx *sysdb;
+
+    struct tevent_req *req =
+            tevent_req_callback_data(subreq, struct tevent_req);
+    struct getserv_ctx *state =
+            tevent_req_data(req, struct getserv_ctx);
+    struct cli_ctx *cctx = state->dctx->cmdctx->cctx;
+    struct nss_ctx *nctx =
+            talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+    struct sss_domain_info *dom = state->domains[state->dom_idx];
+
+    ret = sss_dp_get_account_recv(state, subreq,
+                                  &err_maj, &err_min,
+                                  &err_msg);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              ("Unable to get information from Data Provider\n"
+               "dp_error: [%u], errno: [%u], error_msg: [%s]\n"
+               "Will try to return what we have in cache\n",
+               (unsigned int)err_maj, (unsigned int)err_min, err_msg));
+    }
+
+    /* Recheck the cache after the lookup.
+     * We can ignore the expiration values here, because
+     * either we have just updated it or the provider is
+     * offline. Either way, whatever is in the cache should
+     * be returned, if it exists. Otherwise, move to the
+     * next provider.
+     */
+    ret = sysdb_get_ctx_from_list(cctx->rctx->db_list,
+                                  dom, &sysdb);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              ("Critical: Sysdb CTX not found for [%s]!\n",
+                dom->name));
+        ret = EINVAL;
+        goto done;
+    }
+
+    if (state->cased_name) {
+        DEBUG(SSSDBG_TRACE_FUNC,
+              ("Re-checking cache for [%s:%s@%s]\n",
+               state->cased_name,
+               state->cased_proto ? state->cased_proto : "<ANY>",
+               dom->name));
+
+        ret = sysdb_getservbyname(state, sysdb,
+                                  state->cased_name,
+                                  state->cased_proto,
+                                  &state->res);
+    } else {
+        DEBUG(SSSDBG_TRACE_FUNC,
+              ("Re-checking cache for [%ul:%s@%s]\n",
+               state->port,
+               state->cased_proto ? state->cased_proto : "<ANY>",
+               dom->name));
+
+        ret = sysdb_getservbyport(state, sysdb,
+                                  state->port,
+                                  state->cased_proto,
+                                  &state->res);
+    }
+
+    if (ret == ENOENT) {
+        /* Nothing in the cache.
+         * Set the negative cache
+         */
+        if (state->name) {
+            ret = sss_ncache_set_service_name(nctx->ncache, false,
+                                              dom,
+                                              SVC_NAME_CASED,
+                                              SVC_PROTO_CASED);
+            if (ret != EOK) {
+                /* Failure to set the negative cache is non-fatal.
+                 * We'll log an error and continue.
+                 */
+                DEBUG(SSSDBG_MINOR_FAILURE,
+                      ("Could not set negative cache for [%s][%s]\n",
+                       SVC_NAME_CASED, SVC_PROTO_CASED));
+            }
+        } else {
+            ret = sss_ncache_set_service_port(nctx->ncache, false,
+                                              dom,
+                                              state->port,
+                                              SVC_PROTO_CASED);
+            if (ret != EOK) {
+                /* Failure to set the negative cache is non-fatal.
+                 * We'll log an error and continue.
+                 */
+                DEBUG(SSSDBG_MINOR_FAILURE,
+                      ("Could not set negative cache for [%ul][%s]\n",
+                       state->port, SVC_PROTO_CASED));
+            }
+        }
+
+        /* Need to check other domains */
+        state->dom_idx++;
+        if (!state->domains[state->dom_idx]) {
+            /* No more domains to search. Return ENOENT */
+            ret = ENOENT;
+            goto done;
+        }
+        ret = lookup_service_step(req);
+        if (ret != EOK) goto done;
+
+        /* Set EAGAIN so we will re-enter the mainloop */
+        ret = EAGAIN;
+    }
+
+done:
+    if (ret == EOK) {
+        /* Cache contained results. Return them */
+        tevent_req_done(req);
+    } else if (ret != EAGAIN) {
+        /* An error occurred, fail the request */
+        tevent_req_error(req, ret);
+    }
+
+    /* ret == EAGAIN: Reenter mainloop */
+    return;
+}
+
+static errno_t
+getserv_recv(TALLOC_CTX *mem_ctx,
+             struct tevent_req *req,
+             struct ldb_result **_res)
+{
+    struct getserv_ctx *state =
+            tevent_req_data(req, struct getserv_ctx);
+
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    *_res = talloc_steal(mem_ctx, state->res);
+
+    return EOK;
+}
+
+static errno_t
+fill_service(struct sss_packet *packet,
+             struct sss_domain_info *dom,
+             struct nss_ctx *nctx,
+             struct ldb_message **msgs,
+             unsigned int *count)
+{
+    errno_t ret;
+    unsigned int msg_count = *count;
+    size_t rzero, rsize;
+    unsigned int num, i, j;
+    uint32_t num_aliases;
+    struct ldb_message *msg;
+    struct ldb_message_element *el;
+    TALLOC_CTX *tmp_ctx = NULL;
+    const char *orig_name;
+    char *orig_proto;
+    struct sized_string cased_name;
+    struct sized_string cased_proto;
+    uint16_t port;
+    char *tmpstr;
+    uint8_t *body;
+    size_t blen;
+    struct sized_string alias;
+
+    /* FIXME: Should we account for fully-qualified
+     * service names?
+     */
+
+    /* first 2 fields (len and reserved), filled up later */
+    ret = sss_packet_grow(packet, 2 * sizeof(uint32_t));
+    if (ret != EOK) goto done;
+
+    rzero = 2 * sizeof(uint32_t);
+    rsize = 0;
+
+    num = 0;
+    for (i = 0; i < msg_count; i++) {
+        talloc_zfree(tmp_ctx);
+        tmp_ctx = talloc_new(NULL);
+        if (!tmp_ctx) return ENOMEM;
+
+        msg = msgs[i];
+
+        /* new service */
+        if (!ldb_msg_check_string_attribute(msg, "objectClass",
+                                            SYSDB_SVC_CLASS)) {
+            DEBUG(1, ("Wrong object (%s) found on stack!\n",
+                      ldb_dn_get_linearized(msg->dn)));
+            continue;
+        }
+
+        /* new result starts at end of previous result */
+        rzero += rsize;
+        rsize = 0;
+
+        /* Get the service name */
+        orig_name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
+        tmpstr = sss_get_cased_name(tmp_ctx, orig_name, dom->case_sensitive);
+        if (tmpstr == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  ("Could not identify service name, skipping\n"));
+            continue;
+        }
+        to_sized_string(&cased_name, tmpstr);
+
+        /* Get the port */
+        port = (uint16_t) ldb_msg_find_attr_as_uint(msg, SYSDB_SVC_PORT, 0);
+        if (!port) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  ("No port for service [%s]. Skipping\n"));
+
+        }
+
+        /* Get the service protocol.
+         * If more than one is available, select the
+         * first in the message.
+         */
+        el = ldb_msg_find_element(msg, SYSDB_SVC_PROTO);
+        if (el->num_values == 0) {
+            ret = EINVAL;
+            num = 0;
+            goto done;
+        }
+        orig_proto = (char *)el->values[0].data;
+
+        tmpstr = sss_get_cased_name(tmp_ctx, orig_proto, dom->case_sensitive);
+        if (tmpstr == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  ("sss_get_cased_name failed, skipping\n"));
+            continue;
+        }
+        to_sized_string(&cased_proto, tmpstr);
+
+        sss_packet_grow(packet, 2 * sizeof(uint16_t)
+                            + sizeof(uint32_t)
+                            + cased_name.len
+                            + cased_proto.len);
+        if (ret != EOK) {
+            num = 0;
+            goto done;
+        }
+        sss_packet_get_body(packet, &body, &blen);
+
+        /* Store the port number */
+        SAFEALIGN_SET_UINT16(&body[rzero + rsize], htons(port), &rsize);
+
+        /* Pad the next two bytes */
+        SAFEALIGN_SET_UINT16(&body[rzero + rsize], 0, &rsize);
+
+        /* Get the aliases */
+        el = ldb_msg_find_element(msg, SYSDB_NAME_ALIAS);
+        if (!el) {
+            /* No aliases for this user */
+            num_aliases = 0;
+        } else {
+            num_aliases = el->num_values;
+        }
+
+        /* Store the alias count */
+        SAFEALIGN_SET_UINT32(&body[rzero + rsize], num_aliases, &rsize);
+
+        /* Store the primary name */
+        safealign_memcpy(&body[rzero + rsize],
+                         cased_name.str,
+                         cased_name.len,
+                         &rsize);
+
+        /* Store the protocol */
+        safealign_memcpy(&body[rzero + rsize],
+                         cased_proto.str,
+                         cased_proto.len,
+                         &rsize);
+
+        for (j = 0; j < num_aliases; j++) {
+            tmpstr = sss_get_cased_name(tmp_ctx,
+                                        (const char *)el->values[j].data,
+                                        dom->case_sensitive);
+            to_sized_string(&alias, tmpstr);
+
+            ret = sss_packet_grow(packet, alias.len);
+            if (ret != EOK) {
+                num = 0;
+                goto done;
+            }
+            sss_packet_get_body(packet, &body, &blen);
+
+            /* Store the alias */
+            safealign_memcpy(&body[rzero + rsize],
+                             alias.str,
+                             alias.len,
+                             &rsize);
+
+            talloc_zfree(tmpstr);
+        }
+
+        num++;
+    }
+
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+
+    if (ret != EOK ||num == 0) {
+        /* if num is 0 most probably something went wrong,
+         * reset packet and return ENOENT */
+        sss_packet_set_size(packet, 0);
+        return ENOENT;
+    }
+
+    ((uint32_t *)body)[0] = num; /* num results */
+    ((uint32_t *)body)[1] = 0; /* reserved */
+
+    return ret;
+}
+/*****************
+ * getservbyname *
+ *****************/
+
+errno_t parse_getservbyname(TALLOC_CTX *mem_ctx,
+                            uint8_t *body, size_t blen,
+                            struct sss_names_ctx *names,
+                            char **domain_name,
+                            char **service_name,
+                            char **service_protocol);
+
+static void
+nss_cmd_getserv_done(struct tevent_req *req);
+
+int nss_cmd_getservbyname(struct cli_ctx *cctx)
+{
+    errno_t ret;
+    struct nss_cmd_ctx *cmdctx;
+    struct nss_dom_ctx *dctx;
+    char *domname;
+    char *service_name;
+    char *service_protocol;
+    uint8_t *body;
+    size_t blen;
+    struct tevent_req *req;
+
+    cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
+    if (!cmdctx) return ENOMEM;
+
+    cmdctx->cctx = cctx;
+
+    dctx = talloc_zero(cmdctx, struct nss_dom_ctx);
+    if (!dctx) {
+        ret = ENOMEM;
+        goto done;
+    }
+    dctx->cmdctx = cmdctx;
+
+    /* get service name and protocol */
+    sss_packet_get_body(cctx->creq->in, &body, &blen);
+    /* if not terminated fail */
+    if (body[blen -1] != '\0') {
+        ret = EINVAL;
+        goto done;
+    }
+
+    ret = parse_getservbyname(cmdctx, body, blen,
+                              cctx->rctx->names,
+                              &domname,
+                              &service_name,
+                              &service_protocol);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              ("Could not parse request\n"));
+        goto done;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC,
+          ("Requesting info for service [%s:%s] from [%s]\n",
+           service_name,
+           service_protocol ? service_protocol : "<ANY>",
+           domname ? domname : "<ALL>"));
+
+    if (domname) {
+        dctx->domain = responder_get_domain(cctx->rctx->domains, domname);
+        if (!dctx->domain) {
+            ret = ENOENT;
+            goto done;
+        }
+    } else {
+        /* this is a multidomain search */
+        dctx->domain = cctx->rctx->domains;
+        cmdctx->check_next = true;
+    }
+
+    /* Identify if this backend requires a provider check */
+    dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+
+    /* Ok, find it! */
+    req = getserv_send(cmdctx, cctx->ev, 0,
+                       service_name,
+                       service_protocol,
+                       dctx);
+    if (!req) {
+        ret = ENOMEM;
+        goto done;
+    }
+    tevent_req_set_callback(req, nss_cmd_getserv_done, dctx);
+
+done:
+    return nss_cmd_done(cmdctx, ret);
+}
+
+errno_t parse_getservbyname(TALLOC_CTX *mem_ctx,
+                            uint8_t *body, size_t blen,
+                            struct sss_names_ctx *names,
+                            char **domain_name,
+                            char **service_name,
+                            char **service_protocol)
+{
+    errno_t ret;
+    size_t i, j;
+    char *rawname;
+    char *domname;
+    char *svc_name;
+    char *protocol;
+    TALLOC_CTX *tmp_ctx;
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) return ENOMEM;
+
+    /* The raw name is at most one character shorter
+     * than the body length (if the protocol wasn't
+     * specified). Since this is a common case, we'll
+     * just assume the maximum memory size for the
+     * rawname.
+     */
+    rawname = talloc_array(tmp_ctx, char, blen - 1);
+    if (!rawname) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    i = j = 0;
+
+    /* Copy in the service name */
+    while (body[i] && i < (blen - 1)) {
+        rawname[j] = body[i];
+        i++;
+        j++;
+    }
+    if (body[i] != '\0') {
+        /* blen - 1 was reached without hitting
+         * a NULL-terminator. No protocol field
+         * is possible.
+         */
+        ret = EINVAL;
+        goto done;
+    }
+    rawname[j] = '\0';
+
+    i++;
+    j = 0;
+
+    /* Copy in the protocol */
+    if (body[i] == '\0') {
+        /* Zero-length protocol
+         * Just set the protocol to NULL
+         */
+        protocol = NULL;
+    } else {
+        /* The protocol must be no longer than the remaining
+         * body space, after the name was copied.
+         */
+        protocol = talloc_array(tmp_ctx, char, blen - i - 1);
+        if (!protocol) {
+            ret = ENOMEM;
+            goto done;
+        }
+
+        while (body[i] && i < blen) {
+            protocol[j] = body[i];
+            i++;
+            j++;
+        }
+        if (body[i] != '\0') {
+            /* blen was reached without hitting
+             * a NULL-terminator.
+             */
+            ret = EINVAL;
+            goto done;
+        }
+
+        protocol[j] = '\0';
+
+        if (j != blen) {
+            DEBUG(SSSDBG_MINOR_FAILURE,
+                  ("Body longer than the name and protocol\n"));
+            ret = EINVAL;
+            goto done;
+        }
+    }
+
+    ret = sss_parse_name(tmp_ctx, names, rawname,
+                         &domname, &svc_name);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              ("Could not split name and domain of [%s]\n",
+               rawname));
+        goto done;
+    }
+
+    *domain_name = talloc_steal(mem_ctx, domname);
+    *service_name = talloc_steal(mem_ctx, svc_name);
+    *service_protocol = talloc_steal(mem_ctx, protocol);
+
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+static void
+nss_cmd_getserv_done(struct tevent_req *req)
+{
+    errno_t ret, reqret;
+    unsigned int i;
+
+    struct nss_dom_ctx *dctx =
+            tevent_req_callback_data(req, struct nss_dom_ctx);
+    struct nss_cmd_ctx *cmdctx = dctx->cmdctx;
+    struct cli_ctx *cctx = cmdctx->cctx;
+
+    struct nss_ctx *nctx =
+            talloc_get_type(cctx->rctx->pvt_ctx, struct nss_ctx);
+
+    reqret = getserv_recv(dctx, req, &dctx->res);
+    talloc_zfree(req);
+    if (reqret != EOK && reqret != ENOENT) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              ("getservbyname failed\n"));
+        nss_cmd_done(cmdctx, reqret);
+        return;
+    }
+
+    /* Either we succeeded or no domains were eligible */
+    ret = sss_packet_new(cmdctx->cctx->creq, 0,
+                         sss_packet_get_cmd(cmdctx->cctx->creq->in),
+                         &cmdctx->cctx->creq->out);
+    if (ret == EOK) {
+        if (reqret == ENOENT) {
+            /* Notify the caller that this entry wasn't found */
+            ret = fill_empty(cmdctx->cctx->creq->out);
+        } else {
+            i = dctx->res->count;
+            ret = fill_service(cmdctx->cctx->creq->out,
+                               dctx->domain,
+                               nctx, dctx->res->msgs,
+                               &i);
+        }
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE,
+                  ("Could not create response packet: [%s]\n",
+                   strerror(ret)));
+        }
+
+        sss_cmd_done(cmdctx->cctx, cmdctx);
+        return;
+    }
+
+    DEBUG(SSSDBG_OP_FAILURE, ("Error creating packet\n"));
+}
+
+errno_t parse_getservbyport(TALLOC_CTX *mem_ctx,
+                            uint8_t *body, size_t blen,
+                            struct sss_names_ctx *names,
+                            uint16_t *service_port,
+                            char **service_protocol)
+{
+    errno_t ret;
+    size_t i, j;
+    uint16_t c, port;
+    char *protocol;
+    TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) return ENOMEM;
+
+    /* Copy in the port */
+    SAFEALIGN_COPY_UINT16(&c, body, NULL);
+    port = ntohs(c);
+
+    i = 2 * sizeof(uint16_t) + sizeof(uint32_t);
+
+    /* Copy in the protocol */
+    if (body[i] == '\0') {
+        /* Zero-length protocol
+         * Just set the protocol to NULL
+         */
+        protocol = NULL;
+    } else {
+        /* The protocol must be no longer than the remaining
+         * body space.
+         */
+        protocol = talloc_array(tmp_ctx, char, blen - i);
+        if (!protocol) {
+            ret = ENOMEM;
+            goto done;
+        }
+
+        while (body[i] && i < blen) {
+            protocol[j] = body[i];
+            i++;
+            j++;
+        }
+        if (body[i] != '\0') {
+            /* blen was reached without hitting
+             * a NULL-terminator.
+             */
+            ret = EINVAL;
+            goto done;
+        }
+
+        protocol[j] = '\0';
+
+        if (j != blen) {
+            DEBUG(SSSDBG_MINOR_FAILURE,
+                  ("Body longer than the name and protocol\n"));
+            ret = EINVAL;
+            goto done;
+        }
+    }
+
+    *service_port = port;
+    *service_protocol = talloc_steal(mem_ctx, protocol);
+
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+/*****************
+ * getservbyport *
+ *****************/
+int nss_cmd_getservbyport(struct cli_ctx *cctx)
+{
+    errno_t ret;
+    struct nss_cmd_ctx *cmdctx;
+    struct nss_dom_ctx *dctx;
+    uint16_t port;
+    char *service_protocol;
+    uint8_t *body;
+    size_t blen;
+    struct tevent_req *req;
+
+    cmdctx = talloc_zero(cctx, struct nss_cmd_ctx);
+    if (!cmdctx) return ENOMEM;
+
+    cmdctx->cctx = cctx;
+
+    dctx = talloc_zero(cmdctx, struct nss_dom_ctx);
+    if (!dctx) {
+        ret = ENOMEM;
+        goto done;
+    }
+    dctx->cmdctx = cmdctx;
+
+    /* get service port and protocol */
+    sss_packet_get_body(cctx->creq->in, &body, &blen);
+    /* if not terminated fail */
+    if (body[blen -1] != '\0') {
+        ret = EINVAL;
+        goto done;
+    }
+
+    ret = parse_getservbyport(cmdctx, body, blen,
+                              cctx->rctx->names,
+                              &port,
+                              &service_protocol);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE,
+              ("Could not parse request\n"));
+        goto done;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC,
+          ("Requesting info for service on port [%ul/%s]\n",
+           port, service_protocol ? service_protocol : "<ANY>"));
+
+    /* All port lookups are multidomain searches */
+    dctx->domain = cctx->rctx->domains;
+    cmdctx->check_next = true;
+
+    /* Identify if this backend requires a provider check */
+    dctx->check_provider = NEED_CHECK_PROVIDER(dctx->domain->provider);
+
+    /* Ok, find it! */
+    req = getserv_send(cmdctx, cctx->ev, port,
+                       NULL, NULL, dctx);
+    if (!req) {
+        ret = ENOMEM;
+        goto done;
+    }
+    tevent_req_set_callback(req, nss_cmd_getserv_done, dctx);
+
+done:
+    return nss_cmd_done(cmdctx, ret);
+}
+
+
+int nss_cmd_setservent(struct cli_ctx *cctx)
+{
+    return EOK;
+}
+
+int nss_cmd_getservent(struct cli_ctx *cctx)
+{
+    return EOK;
+}
+
+int nss_cmd_endservent(struct cli_ctx *cctx)
+{
+    return EOK;
+}
diff --git a/src/responder/nss/nsssrv_services.h b/src/responder/nss/nsssrv_services.h
new file mode 100644
index 0000000000000000000000000000000000000000..a334ddc61d6ef5b99bd0d7d26f539efb98bf9489
--- /dev/null
+++ b/src/responder/nss/nsssrv_services.h
@@ -0,0 +1,33 @@
+/*
+    SSSD
+
+    Authors:
+        Stephen Gallagher <sgallagh@redhat.com>
+
+    Copyright (C) 2012 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 NSSSRV_SERVICES_H_
+#define NSSSRV_SERVICES_H_
+
+int nss_cmd_getservbyname(struct cli_ctx *cctx);
+int nss_cmd_getservbyport(struct cli_ctx *cctx);
+
+int nss_cmd_setservent(struct cli_ctx *cctx);
+int nss_cmd_getservent(struct cli_ctx *cctx);
+int nss_cmd_endservent(struct cli_ctx *cctx);
+
+#endif /* NSSSRV_SERVICES_H_ */
-- 
1.7.7.5

