/*
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 "providers/proxy/proxy.h"
#include "util/util.h"
#include "util/strtonum.h"
#include "db/sysdb_services.h"
#define BUFLEN 1024
errno_t
proxy_save_service(struct sss_domain_info *domain,
struct servent *svc,
bool lowercase,
uint64_t cache_timeout)
{
errno_t ret;
char *cased_name;
const char **protocols;
const char **cased_aliases;
TALLOC_CTX *tmp_ctx;
char *lc_alias = NULL;
time_t now = time(NULL);
tmp_ctx = talloc_new(NULL);
if (!tmp_ctx) return ENOMEM;
cased_name = sss_get_cased_name(tmp_ctx, svc->s_name,
domain->case_preserve);
if (!cased_name) {
ret = ENOMEM;
goto done;
}
protocols = talloc_array(tmp_ctx, const char *, 2);
if (!protocols) {
ret = ENOMEM;
goto done;
}
protocols[0] = sss_get_cased_name(protocols, svc->s_proto,
!lowercase);
if (!protocols[0]) {
ret = ENOMEM;
goto done;
}
protocols[1] = NULL;
/* Count the aliases */
ret = sss_get_cased_name_list(tmp_ctx,
(const char * const *) svc->s_aliases,
!lowercase, &cased_aliases);
if (ret != EOK) {
goto done;
}
if (domain->case_preserve) {
/* Add lowercased alias to allow case-insensitive lookup */
lc_alias = sss_tc_utf8_str_tolower(tmp_ctx, svc->s_name);
if (lc_alias == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "Cannot convert name to lowercase.\n");
ret = ENOMEM;
goto done;
}
ret = add_string_to_list(tmp_ctx, lc_alias,
discard_const_p(char **, &cased_aliases));
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE,
"Failed to add lowercased name alias.\n");
goto done;
}
}
ret = sysdb_store_service(domain,
cased_name,
ntohs(svc->s_port),
cased_aliases,
protocols,
NULL, NULL,
cache_timeout,
now);
done:
talloc_free(tmp_ctx);
return ret;
}
errno_t
get_serv_byname(struct proxy_id_ctx *ctx,
struct sss_domain_info *dom,
const char *name,
const char *protocol)
{
errno_t ret;
enum nss_status status;
struct servent *result;
TALLOC_CTX *tmp_ctx;
char buffer[BUFLEN];
tmp_ctx = talloc_new(NULL);
if (!tmp_ctx) return ENOMEM;
result = talloc_zero(tmp_ctx, struct servent);
if (!result) {
ret = ENOMEM;
goto done;
}
status = ctx->ops.getservbyname_r(name, protocol, result,
buffer, BUFLEN, &ret);
if (status != NSS_STATUS_SUCCESS && status != NSS_STATUS_NOTFOUND) {
DEBUG(SSSDBG_MINOR_FAILURE,
"getservbyname_r failed for service [%s].\n", name);
goto done;
}
if (status == NSS_STATUS_NOTFOUND) {
/* Make sure we remove it from the cache */
ret = sysdb_svc_delete(dom, name, 0, protocol);
} else {
/* Results found. Save them into the cache */
ret = proxy_save_service(dom, result,
!dom->case_sensitive,
dom->service_timeout);
}
done:
talloc_free(tmp_ctx);
return ret;
}
errno_t
get_serv_byport(struct proxy_id_ctx *ctx,
struct sss_domain_info *dom,
const char *be_filter,
const char *protocol)
{
errno_t ret;
enum nss_status status;
struct servent *result;
TALLOC_CTX *tmp_ctx;
uint16_t port;
char buffer[BUFLEN];
tmp_ctx = talloc_new(NULL);
if (!tmp_ctx) return ENOMEM;
result = talloc_zero(tmp_ctx, struct servent);
if (!result) {
ret = ENOMEM;
goto done;
}
errno = 0;
port = htons(strtouint16(be_filter, NULL, 0));
if (errno) {
ret = errno;
goto done;
}
status = ctx->ops.getservbyport_r(port, protocol, result,
buffer, BUFLEN, &ret);
if (status != NSS_STATUS_SUCCESS && status != NSS_STATUS_NOTFOUND) {
DEBUG(SSSDBG_MINOR_FAILURE,
"getservbyport_r failed for service [%s].\n", be_filter);
goto done;
}
if (status == NSS_STATUS_NOTFOUND) {
/* Make sure we remove it from the cache */
ret = sysdb_svc_delete(dom, NULL, port, protocol);
} else {
/* Results found. Save them into the cache */
ret = proxy_save_service(dom, result,
!dom->case_sensitive,
dom->service_timeout);
}
done:
talloc_free(tmp_ctx);
return ret;
}
errno_t
enum_services(struct proxy_id_ctx *ctx,
struct sysdb_ctx *sysdb,
struct sss_domain_info *dom)
{
TALLOC_CTX *tmpctx;
bool in_transaction = false;
struct servent *svc;
enum nss_status status;
size_t buflen;
char *buffer;
char *newbuf;
errno_t ret, sret;
time_t now = time(NULL);
const char **protocols;
const char **cased_aliases;
bool again;
DEBUG(SSSDBG_TRACE_FUNC, "Enumerating services\n");
tmpctx = talloc_new(NULL);
if (!tmpctx) {
return ENOMEM;
}
svc = talloc(tmpctx, struct servent);
if (!svc) {
ret = ENOMEM;
goto done;
}
buflen = DEFAULT_BUFSIZE;
buffer = talloc_size(tmpctx, buflen);
if (!buffer) {
ret = ENOMEM;
goto done;
}
protocols = talloc_zero_array(tmpctx, const char *, 2);
if (protocols == NULL) {
ret = ENOMEM;
goto done;
}
ret = sysdb_transaction_start(sysdb);
if (ret) {
DEBUG(SSSDBG_CRIT_FAILURE, "Failed to start transaction\n");
goto done;
}
in_transaction = true;
status = ctx->ops.setservent();
if (status != NSS_STATUS_SUCCESS) {
ret = EIO;
goto done;
}
do {
again = false;
/* always zero out the svc structure */
memset(svc, 0, sizeof(struct servent));
/* get entry */
status = ctx->ops.getservent_r(svc, buffer, buflen, &ret);
switch (status) {
case NSS_STATUS_TRYAGAIN:
/* buffer too small ? */
if (buflen < MAX_BUF_SIZE) {
buflen *= 2;
}
if (buflen > MAX_BUF_SIZE) {
buflen = MAX_BUF_SIZE;
}
newbuf = talloc_realloc_size(tmpctx, buffer, buflen);
if (!newbuf) {
ret = ENOMEM;
goto done;
}
buffer = newbuf;
again = true;
break;
case NSS_STATUS_NOTFOUND:
/* we are done here */
DEBUG(SSSDBG_TRACE_FUNC, "Enumeration completed.\n");
ret = sysdb_transaction_commit(sysdb);
if (ret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE, "Failed to commit transaction\n");
goto done;
}
in_transaction = false;
break;
case NSS_STATUS_SUCCESS:
DEBUG(SSSDBG_TRACE_INTERNAL,
"Service found (%s, %d/%s)\n",
svc->s_name, svc->s_port, svc->s_proto);
protocols[0] = sss_get_cased_name(protocols, svc->s_proto,
dom->case_sensitive);
if (!protocols[0]) {
ret = ENOMEM;
goto done;
}
protocols[1] = NULL;
ret = sss_get_cased_name_list(tmpctx,
(const char * const *) svc->s_aliases,
dom->case_sensitive, &cased_aliases);
if (ret != EOK) {
/* Do not fail completely on errors.
* Just report the failure to save and go on */
DEBUG(SSSDBG_OP_FAILURE,
"Failed to store service [%s]. Ignoring.\n",
strerror(ret));
again = true;
break;
}
ret = sysdb_store_service(dom,
svc->s_name,
svc->s_port,
cased_aliases,
protocols,
NULL, NULL,
dom->service_timeout,
now);
if (ret) {
/* Do not fail completely on errors.
* Just report the failure to save and go on */
DEBUG(SSSDBG_OP_FAILURE,
"Failed to store service [%s]. Ignoring.\n",
strerror(ret));
}
again = true;
break;
case NSS_STATUS_UNAVAIL:
/* "remote" backend unavailable. Enter offline mode */
ret = ENXIO;
break;
default:
ret = EIO;
DEBUG(SSSDBG_CRIT_FAILURE,
"proxy -> getservent_r failed (%d)[%s]\n",
ret, strerror(ret));
break;
}
} while (again);
done:
talloc_zfree(tmpctx);
if (in_transaction) {
sret = sysdb_transaction_cancel(sysdb);
if (sret != EOK) {
DEBUG(SSSDBG_CRIT_FAILURE,
"Could not cancel transaction! [%s]\n",
strerror(sret));
}
}
ctx->ops.endservent();
return ret;
}