/* 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 "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); return ret; } 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); return ret; } 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; }