From 8c3a4809b3420657289b42f028a1c9019b112991 Mon Sep 17 00:00:00 2001 From: Stephen Gallagher Date: Sat, 21 Jan 2012 16:16:41 -0500 Subject: NSS: Add getservbyname and getservbyport support to the NSS Responder --- src/responder/nss/nsssrv_cmd.c | 6 + src/responder/nss/nsssrv_services.c | 1170 +++++++++++++++++++++++++++++++++++ src/responder/nss/nsssrv_services.h | 33 + 3 files changed, 1209 insertions(+) create mode 100644 src/responder/nss/nsssrv_services.c create mode 100644 src/responder/nss/nsssrv_services.h (limited to 'src') diff --git a/src/responder/nss/nsssrv_cmd.c b/src/responder/nss/nsssrv_cmd.c index 4b5f6784..579c7602 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 00000000..6e857ff1 --- /dev/null +++ b/src/responder/nss/nsssrv_services.c @@ -0,0 +1,1170 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + 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 . +*/ + + +#include +#include +#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 : "", + 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 : "", + 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 [%lu:%s] does not exist in [%s]! " + "(negative cache)\n", + port, + SVC_PROTO_CASED ? SVC_PROTO_CASED : "", + 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 [%lu:%s@%s]\n", + port, + SVC_PROTO_CASED ? SVC_PROTO_CASED : "", + 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 [%lu][%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 : "", + dom->name)); + + ret = sysdb_getservbyname(state, sysdb, + state->cased_name, + state->cased_proto, + &state->res); + } else { + DEBUG(SSSDBG_TRACE_FUNC, + ("Re-checking cache for [%lu:%s@%s]\n", + state->port, + state->cased_proto ? state->cased_proto : "", + 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 [%lu][%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_UINT32(&body[rzero + rsize], (uint32_t)htons(port), &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 : "", + domname ? domname : "")); + + 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, namelen; + 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++; + namelen = 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 - namelen - 1) { + 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; + size_t port_and_padding_len; + 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); + + port_and_padding_len = 2 * sizeof(uint16_t) + sizeof(uint32_t); + i = port_and_padding_len; + 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. + */ + 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 - port_and_padding_len - 1) { + 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 [%lu/%s]\n", + port, service_protocol ? service_protocol : "")); + + /* 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, 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); +} + + +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 00000000..a334ddc6 --- /dev/null +++ b/src/responder/nss/nsssrv_services.h @@ -0,0 +1,33 @@ +/* + SSSD + + Authors: + Stephen Gallagher + + 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 . +*/ + +#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_ */ -- cgit