nsssrv_services.c revision 8126906ab783494204dc853c814b0882d2a76523
/*
SSSD
Authors:
Stephen Gallagher <sgallagh@redhat.com>
Copyright (C) 2012 Red Hat
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 "responder/nss/nsssrv_private.h"
#include "responder/nss/nsssrv_services.h"
#include "responder/common/negcache.h"
#include "db/sysdb_services.h"
struct getserv_ctx {
struct tevent_context *ev;
struct nss_dom_ctx *dctx;
struct sss_domain_info **domains;
char *name;
char *cased_name;
char *proto;
char *cased_proto;
struct ldb_result *res;
};
: state->cased_name)
: state->cased_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 *
struct tevent_context *ev,
const char *service_name,
const char *service_protocol,
struct nss_dom_ctx *dctx)
{
struct tevent_req *req;
struct tevent_req *subreq;
struct getserv_ctx *state;
struct sss_domain_info *dom;
size_t num_domains = 0;
/* Create an array of domains to check. To save resizes, we'll
* assume that all will be checked
*/
struct sss_domain_info *,
num_domains + 1);
goto immediate;
}
/* Store both the case-sensitive and lowercased names
* in the state object, to avoid recalculating the
* lowercase in multiple domains.
*/
if (service_protocol) {
goto immediate;
}
false);
if (!state->cased_proto) {
goto immediate;
}
} else {
}
/* 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.
*/
goto immediate;
}
false);
if (!state->cased_name) {
goto immediate;
}
}
while(dom) {
/* if it is a domainless search, skip domains that require fully
* qualified names instead */
}
if (!dom) break;
goto immediate;
}
/* If we're looking up by name */
if (service_name) {
/* Check the negative cache */
dom,
/* If negatively cached, return we didn't find it */
("Service [%s:%s] does not exist in [%s]! "
"(negative cache)\n",
/* If this is a multi-domain search, try the next one */
if (cmdctx->check_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.
*/
}
continue;
}
/* Check the cache */
("Checking cache for [%s:%s@%s]\n",
} else { /* Looking up by port */
/* Check the negative cache */
/* If negatively cached, return we didn't find it */
("Service [%lu:%s] does not exist in [%s]! "
"(negative cache)\n",
port,
/* If this is a multi-domain search, try the next one */
if (cmdctx->check_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.
*/
}
continue;
}
/* Check the cache */
("Checking cache for [%lu:%s@%s]\n",
port,
}
/* Not found in the cache. Add this domain to the
* list of eligible domains to check the provider.
*/
dom_idx++;
} else {
/* No provider to check. Set the negative cache here */
dom,
/* Failure to set the negative cache is non-fatal.
* We'll log an error and continue.
*/
("Could not set negative cache for [%s][%s]\n",
}
} else {
dom,
/* Failure to set the negative cache is non-fatal.
* We'll log an error and continue.
*/
("Could not set negative cache for [%lu][%s]\n",
}
}
}
/* If this is a multi-domain search, try the next one */
if (cmdctx->check_next) {
} else {
/* This was a single-domain search.
* exit the loop.
*/
}
continue;
}
/* Found a result. Check its validity */
("getservby* returned more than one result!\n"));
goto immediate;
}
SYSDB_LAST_UPDATE, 0);
SYSDB_CACHE_EXPIRE, 0);
midpoint_refresh = 0;
if(nctx->cache_refresh_percent) {
/* 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
*/
}
}
if (cacheExpire > now) {
/* cache still valid */
&& 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.
*/
("Performing midpoint cache update\n"));
/* Update the cache */
dom, true,
if (!subreq) {
("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.
*/
}
/* The cache is valid. Return it */
goto immediate;
} else {
/* Cache is expired. Add this domain to the
* list of eligible domains to check the provider.
*/
dom_idx++;
}
/* If this is a multi-domain search, try the next one */
if (cmdctx->check_next) {
} else {
/* This was a single-domain search.
* exit the loop.
*/
}
}
}
/* No valid cached entries found and
* not found in negative caches.
* Iterate through the domains and try
* to look the data up.
*/
/* No domains to search. Return ENOENT */
goto immediate;
}
return req;
} else {
}
return req;
}
{
struct getserv_ctx *state =
struct tevent_req *subreq;
struct sss_domain_info *dom =
/* Update the cache */
dom,
true,
return EOK;
}
{
char *err_msg;
struct tevent_req *req =
struct getserv_ctx *state =
&err_msg);
("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",
}
/* 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.
*/
("Critical: Sysdb CTX not found for [%s]!\n",
goto done;
}
("Re-checking cache for [%s:%s@%s]\n",
} else {
("Re-checking cache for [%lu:%s@%s]\n",
}
/* Nothing in the cache.
* Set the negative cache
*/
dom,
/* Failure to set the negative cache is non-fatal.
* We'll log an error and continue.
*/
("Could not set negative cache for [%s][%s]\n",
}
} else {
dom,
/* Failure to set the negative cache is non-fatal.
* We'll log an error and continue.
*/
("Could not set negative cache for [%lu][%s]\n",
}
}
/* Need to check other domains */
/* No more domains to search. Return ENOENT */
goto done;
}
/* Set EAGAIN so we will re-enter the mainloop */
}
done:
/* Cache contained results. Return them */
/* An error occurred, fail the request */
}
/* ret == EAGAIN: Reenter mainloop */
return;
}
static errno_t
struct tevent_req *req,
struct ldb_result **_res)
{
struct getserv_ctx *state =
return EOK;
}
static errno_t
struct sss_domain_info *dom,
const char *protocol,
struct ldb_message **msgs,
unsigned int *count)
{
unsigned int num, i, j;
struct ldb_message *msg;
struct ldb_message_element *el;
const char *orig_name;
const char *orig_proto;
struct sized_string cased_name;
struct sized_string cased_proto;
char *tmpstr;
struct sized_string alias;
/* FIXME: Should we account for fully-qualified
* service names?
*/
/* first 2 fields (len and reserved), filled up later */
rsize = 0;
num = 0;
for (i = 0; i < msg_count; i++) {
/* new service */
SYSDB_SVC_CLASS)) {
continue;
}
/* new result starts at end of previous result */
rsize = 0;
/* Get the service name */
("Could not identify service name, skipping\n"));
continue;
}
/* Get the port */
if (!port) {
("No port for service [%s]. Skipping\n"));
}
/* Get the service protocol.
* Use the requested protocol if present,
* otherwise take the first protocol returned
* by the sysdb.
* If more than one is available, select the
* first in the message.
*/
if (protocol) {
} else {
if (el->num_values == 0) {
num = 0;
goto done;
}
}
("sss_get_cased_name failed, skipping\n"));
continue;
}
+ sizeof(uint32_t)
+ cased_proto.len);
num = 0;
goto done;
}
/* Store the port number */
/* Get the aliases */
if (!el) {
/* No aliases for this user */
num_aliases = 0;
} else {
}
/* We'll store the alias count here */
/* Store the primary name */
&rsize);
/* Store the protocol */
&rsize);
written_aliases = 0;
for (j = 0; j < num_aliases; j++) {
cased_name.str)) {
continue;
}
num = 0;
goto done;
}
/* Store the alias */
&rsize);
}
num++;
}
done:
/* if num is 0 most probably something went wrong,
* reset packet and return ENOENT */
return ENOENT;
}
return ret;
}
/*****************
* getservbyname *
*****************/
struct sss_names_ctx *names,
char **domain_name,
char **service_name,
char **service_protocol);
static void
{
struct nss_cmd_ctx *cmdctx;
struct nss_dom_ctx *dctx;
char *domname;
char *service_name;
char *service_protocol;
struct tevent_req *req;
if (!dctx) {
goto done;
}
/* get service name and protocol */
/* if not terminated fail */
goto done;
}
&domname,
("Could not parse request\n"));
goto done;
}
("Requesting info for service [%s:%s] from [%s]\n",
if (domname) {
goto done;
}
} else {
/* this is a multidomain search */
cmdctx->check_next = true;
}
/* Identify if this backend requires a provider check */
/* Ok, find it! */
dctx);
if (!req) {
goto done;
}
done:
}
struct sss_names_ctx *names,
char **domain_name,
char **service_name,
char **service_protocol)
{
char *rawname;
char *domname;
char *svc_name;
char *protocol;
/* 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.
*/
if (!rawname) {
goto done;
}
i = j = 0;
/* Copy in the service name */
i++;
j++;
}
if (body[i] != '\0') {
/* blen - 1 was reached without hitting
* a NULL-terminator. No protocol field
* is possible.
*/
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
*/
} else {
/* The protocol must be no longer than the remaining
* body space, after the name was copied.
*/
if (!protocol) {
goto done;
}
i++;
j++;
}
if (body[i] != '\0') {
/* blen was reached without hitting
* a NULL-terminator.
*/
goto done;
}
protocol[j] = '\0';
("Body longer than the name and protocol\n"));
goto done;
}
}
("Could not split name and domain of [%s]\n",
rawname));
goto done;
}
done:
return ret;
}
static void
{
unsigned int i;
struct nss_dom_ctx *dctx =
("getservbyname failed\n"));
return;
}
/* Either we succeeded or no domains were eligible */
/* Notify the caller that this entry wasn't found */
} else {
nctx,
&i);
}
("Could not create response packet: [%s]\n",
}
return;
}
}
struct sss_names_ctx *names,
char **service_protocol)
{
size_t i, j;
char *protocol;
/* Copy in the port */
i = port_and_padding_len;
j = 0;
/* Copy in the protocol */
if (body[i] == '\0') {
/* Zero-length protocol
* Just set the protocol to NULL
*/
} else {
/* The protocol must be no longer than the remaining
* body space.
*/
if (!protocol) {
goto done;
}
i++;
j++;
}
if (body[i] != '\0') {
/* blen was reached without hitting
* a NULL-terminator.
*/
goto done;
}
protocol[j] = '\0';
("Body longer than the name and protocol\n"));
goto done;
}
}
*service_port = port;
done:
return ret;
}
/*****************
* getservbyport *
*****************/
{
struct nss_cmd_ctx *cmdctx;
struct nss_dom_ctx *dctx;
char *service_protocol;
struct tevent_req *req;
if (!dctx) {
goto done;
}
/* get service port and protocol */
/* if not terminated fail */
goto done;
}
&port,
("Could not parse request\n"));
goto done;
}
("Requesting info for service on port [%lu/%s]\n",
/* All port lookups are multidomain searches */
cmdctx->check_next = true;
/* Identify if this backend requires a provider check */
/* Ok, find it! */
if (!req) {
goto done;
}
done:
}
struct setservent_ctx {
struct nss_dom_ctx *dctx;
struct getent_ctx *getent_ctx;
};
static errno_t
static void
static struct tevent_req *
struct sss_domain_info *dom);
static struct tevent_req *
{
unsigned int num_domains;
struct tevent_req *req;
struct setservent_ctx *state;
struct sss_domain_info *dom;
struct setent_step_ctx *step_ctx;
/* Reset the read pointers */
cctx->svc_dom_idx = 0;
cctx->svcent_cur = 0;
goto immediate;
}
/* Is the result context already available */
/* All of the necessary data is in place
* We can return now, getservent requests will work at this point
*/
goto immediate;
}
else {
/* Object is still being constructed
* Register for notification when it's
* ready.
*/
}
return req;
}
/* Create a new result context
* We are creating it on the nss_ctx so that it doesn't
* go away if the original request does. We will delete
* it when the refcount goes to zero;
*/
goto immediate;
}
/* Assume that all domains will have results (to avoid having
* to reallocate later
*/
num_domains = 0;
while (dom) {
num_domains++;
}
struct dom_ctx,
goto immediate;
}
/* Add a callback reference for ourselves */
goto immediate;
}
/* ok, start the searches */
if (!step_ctx) {
goto immediate;
}
/* Steal the dom_ctx onto the step_ctx so it doesn't go out of scope if
* this request is canceled while other requests are in-progress.
*/
step_ctx->returned_to_mainloop = false;
/* There are more domains to check */
/* Re-enter the mainloop */
return req;
}
("Error [%s] requesting info from domain [%s]. Skipping.\n",
}
/* All domains failed */
} else {
}
return req;
}
static errno_t
{
struct tevent_req *req;
if (!req) {
return ENOMEM;
}
return EOK;
}
struct lookup_servent_ctx {
struct sss_domain_info *dom;
struct ldb_result *res;
};
static void
static void
static struct tevent_req *
struct sss_domain_info *dom)
{
struct tevent_req *req;
struct tevent_req *subreq;
struct lookup_servent_ctx *state;
goto immediate;
}
/* No provider check required. Just ask the
* sysdb.
*/
goto immediate;
}
/* Whatever the result, we're done, so report it */
goto immediate;
}
/* We need to ask the provider for an enumeration */
/* Update the cache */
true, SSS_DP_SERVICES,
if (!subreq) {
goto immediate;
}
return req;
} else {
}
return req;
}
static void
{
char *err_msg;
struct tevent_req *req =
struct lookup_servent_ctx *state =
("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",
}
/* Check the cache now */
goto done;
}
/* Whatever the result, we're done, so report it */
done:
} else {
}
}
static errno_t
struct tevent_req *req,
struct ldb_result **res)
{
struct lookup_servent_ctx *state =
return EOK;
}
static void
{
struct ldb_result *res;
struct setent_step_ctx *step_ctx =
("Error [%s] while retrieving info from domain [%s]. "
/* Continue on */
} else {
/* Got some results
* Add the retrieved results to the list
*/
}
/* There are more domains to check */
/* Re-enter the mainloop */
return;
}
("Error [%s] requesting info from domain [%s]. Skipping.\n",
}
/* All domains have been checked */
}
static void
struct tevent_timer *te,
struct timeval current_time,
void *pvt);
static void
{
struct tevent_timer *te;
/* We've finished all our lookups
* The result object is now safe to read.
*/
/* Set up a lifetime timer for this result object
* We don't want this result object to outlive the
* enum cache refresh timeout
*/
if (!te) {
("Could not set up life timer for setservent result object. "
"Entries may become stale.\n"));
}
}
static void
struct tevent_timer *te,
struct timeval current_time,
void *pvt)
{
("setservent result object has expired. Cleaning up.\n"));
/* Free the service enumeration context.
* If additional getservent requests come in, they will invoke
* an implicit setservent and refresh the result object.
*/
}
static errno_t
{
return EOK;
}
static void
int
{
struct nss_cmd_ctx *cmdctx;
struct tevent_req *req;
if (!cmdctx) {
return ENOMEM;
}
if (!req) {
("Fatal error calling nss_cmd_setservent_send\n"));
goto done;
}
done:
}
static void
{
struct nss_cmd_ctx *cmdctx =
/* Either we succeeded or no domains
* were eligible.
* Return an acknowledgment
*/
return;
}
}
/* Something bad happened.
* Return an error
*/
}
static void
static errno_t
static errno_t
{
struct nss_cmd_ctx *cmdctx;
struct tevent_req *req;
("Requesting info for all services\n"));
if (!cmdctx) {
return ENOMEM;
}
/* Save the current index and cursor locations
* If we end up calling setservent implicitly, because the response object
* expired and has to be recreated, we want to resume from the same
* location.
*/
/* Make sure we invoke setservent if it hasn't been run or is still
* processing from another client
*/
if (!req) {
return EIO;
}
cmdctx);
return EOK;
}
return nss_cmd_getservent_immediate(cmdctx);
}
static void
{
struct nss_cmd_ctx *cmdctx =
/* ENOENT is acceptable, as it just means that there were no entries
* to be returned. This will be handled gracefully in retservent
* later.
*/
("Implicit setservent failed with unexpected error [%d][%s]\n",
}
/* Restore the saved index and cursor locations */
("Immediate retrieval failed with unexpected error "
}
}
static errno_t
{
int ret;
/* get max num of entries to return in one call */
return EINVAL;
}
/* create response packet */
return ret;
}
return EOK;
}
static errno_t
{
struct getent_ctx *svcctx;
unsigned int n = 0;
cctx->svc_dom_idx++;
cctx->svcent_cur = 0;
}
if (!n) break;
&n);
cctx->svcent_cur += n;
}
none:
}
return ret;
}
{
int ret;
("Terminating request info for all accounts\n"));
/* create response packet */
return ret;
}
/* Reset the indices so that subsequent requests start at zero */
cctx->svc_dom_idx = 0;
cctx->svcent_cur = 0;
done:
return EOK;
}