From b641e99221c5ec1686281007f7d7bbe7a5d65b0d Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Wed, 4 Jan 2012 14:22:22 -0500
Subject: [PATCH 04/10] SYSDB: Add sysdb routines for manipulating service
 entries

---
 Makefile.am             |    2 +
 src/db/sysdb_services.c |  623 +++++++++++++++++++++++++++++++++++++++++++++++
 src/db/sysdb_services.h |  101 ++++++++
 src/tests/sysdb-tests.c |  358 +++++++++++++++++++++++++++
 4 files changed, 1084 insertions(+), 0 deletions(-)
 create mode 100644 src/db/sysdb_services.c
 create mode 100644 src/db/sysdb_services.h

diff --git a/Makefile.am b/Makefile.am
index 1d241d2ba893ebdf4bd8de47105426f549147657..93c5ff56a3e7b9ba4aae1a7ccd4be52d35c02820 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -323,6 +323,7 @@ dist_noinst_HEADERS = \
     src/db/sysdb.h \
     src/db/sysdb_sudo.h \
     src/db/sysdb_private.h \
+    src/db/sysdb_services.h \
     src/confdb/confdb.h \
     src/confdb/confdb_private.h \
     src/confdb/confdb_setup.h \
@@ -380,6 +381,7 @@ libsss_util_la_SOURCES = \
     src/db/sysdb_ops.c \
     src/db/sysdb_search.c \
     src/db/sysdb_upgrade.c \
+    src/db/sysdb_services.c \
     src/monitor/monitor_sbus.c \
     src/providers/dp_auth_util.c \
     src/providers/dp_pam_data_util.c \
diff --git a/src/db/sysdb_services.c b/src/db/sysdb_services.c
new file mode 100644
index 0000000000000000000000000000000000000000..962e7b17e743c55c3839782ccd7ae75373e424a5
--- /dev/null
+++ b/src/db/sysdb_services.c
@@ -0,0 +1,623 @@
+/*
+    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 "util/util.h"
+#include "db/sysdb.h"
+#include "db/sysdb_private.h"
+#include "db/sysdb_services.h"
+
+errno_t
+sysdb_getservbyname(TALLOC_CTX *mem_ctx,
+                    struct sysdb_ctx *sysdb,
+                    const char *name,
+                    const char *proto,
+                    struct ldb_result **_res)
+{
+    errno_t ret;
+    int lret;
+    TALLOC_CTX *tmp_ctx;
+    static const char *attrs[] = SYSDB_SVC_ATTRS;
+    struct ldb_dn *base_dn;
+    struct ldb_result *res;
+    char *sanitized_name;
+    char *sanitized_proto;
+
+    *_res = NULL;
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) {
+        return ENOMEM;
+    }
+
+    base_dn = ldb_dn_new_fmt(tmp_ctx, sysdb->ldb,
+                             SYSDB_TMPL_SVC_BASE,
+                             sysdb->domain->name);
+    if (!base_dn) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    ret = sss_filter_sanitize(tmp_ctx, name, &sanitized_name);
+    if (ret != EOK) {
+        goto done;
+    }
+
+    if (proto) {
+        ret = sss_filter_sanitize(tmp_ctx, proto, &sanitized_proto);
+        if (ret != EOK) {
+            goto done;
+        }
+    }
+
+    lret = ldb_search(sysdb->ldb, tmp_ctx, &res, base_dn,
+                      LDB_SCOPE_SUBTREE, attrs,
+                      SYSDB_SVC_BYNAME_FILTER,
+                      proto?sanitized_proto:"*",
+                      sanitized_name, sanitized_name);
+    if (lret != LDB_SUCCESS) {
+        ret = sysdb_error_to_errno(lret);
+        goto done;
+    }
+
+    if (res->count == 0) {
+        ret = ENOENT;
+        goto done;
+    }
+
+    *_res = talloc_steal(mem_ctx, res);
+
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+errno_t
+sysdb_getservbyport(TALLOC_CTX *mem_ctx,
+                    struct sysdb_ctx *sysdb,
+                    int port,
+                    const char *proto,
+                    struct ldb_result **_res)
+{
+    errno_t ret;
+    int lret;
+    TALLOC_CTX *tmp_ctx;
+    static const char *attrs[] = SYSDB_SVC_ATTRS;
+    struct ldb_dn *base_dn;
+    struct ldb_result *res;
+    char *sanitized_proto;
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) {
+        return ENOMEM;
+    }
+
+    base_dn = ldb_dn_new_fmt(tmp_ctx, sysdb->ldb,
+                             SYSDB_TMPL_SVC_BASE,
+                             sysdb->domain->name);
+    if (!base_dn) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    if (proto) {
+        ret = sss_filter_sanitize(tmp_ctx, proto, &sanitized_proto);
+        if (ret != EOK) {
+            goto done;
+        }
+    }
+
+    lret = ldb_search(sysdb->ldb, tmp_ctx, &res, base_dn,
+                      LDB_SCOPE_SUBTREE, attrs,
+                      SYSDB_SVC_BYPORT_FILTER,
+                      proto?sanitized_proto:"*",
+                      (unsigned int) port);
+    if (lret) {
+        ret = sysdb_error_to_errno(lret);
+        goto done;
+    }
+
+    if (res->count == 0) {
+        ret = ENOENT;
+        goto done;
+    }
+
+    *_res = talloc_steal(mem_ctx, res);
+
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+errno_t
+sysdb_store_service(struct sysdb_ctx *sysdb,
+                    const char *primary_name,
+                    int port,
+                    const char **aliases,
+                    const char **protocols,
+                    uint64_t cache_timeout,
+                    time_t now)
+{
+    errno_t ret;
+    errno_t sret;
+    TALLOC_CTX *tmp_ctx;
+    bool in_transaction = false;
+    struct ldb_result *res = NULL;
+    const char *name;
+    unsigned int i;
+    struct ldb_dn *update_dn = NULL;
+    struct sysdb_attrs *attrs;
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) return ENOMEM;
+
+    ret = sysdb_transaction_start(sysdb);
+    if (ret != EOK) goto done;
+
+    in_transaction = true;
+
+    /* Check that the port is unique
+     * If the port appears for any service other than
+     * the one matching the primary_name, we need to
+     * remove them so that getservbyport() can work
+     * properly. Last entry saved to the cache should
+     * always "win".
+     */
+    ret = sysdb_getservbyport(tmp_ctx, sysdb, port, NULL, &res);
+    if (ret != EOK && ret != ENOENT) {
+        goto done;
+    } else if (ret != ENOENT) {
+        if (res->count != 1) {
+            /* Somehow the cache has multiple entries with
+             * the same port. This is corrupted. We'll delete
+             * them all to sort it out.
+             */
+            for (i = 0; i < res->count; i++) {
+                ret = sysdb_delete_entry(sysdb, res->msgs[i]->dn, true);
+                if (ret != EOK) {
+                    DEBUG(SSSDBG_MINOR_FAILURE,
+                          ("Could not delete corrupt cache entry [%s]\n",
+                           ldb_dn_canonical_string(tmp_ctx,
+                                                   res->msgs[i]->dn)));
+                    goto done;
+                }
+            }
+        } else {
+            /* Check whether this is the same name as we're currently
+             * saving to the cache.
+             */
+            name = ldb_msg_find_attr_as_string(res->msgs[0],
+                                               SYSDB_NAME,
+                                               NULL);
+            if (!name || strcmp(name, primary_name) != 0) {
+
+                if (!name) {
+                    DEBUG(SSSDBG_CRIT_FAILURE,
+                          ("A service with no name?\n"));
+                    /* Corrupted */
+                }
+
+                /* Either this is a corrupt entry or it's another service
+                 * claiming ownership of this port. In order to account
+                 * for port reassignments, we need to delete the old entry.
+                 */
+
+                ret = sysdb_delete_entry(sysdb, res->msgs[0]->dn, true);
+                if (ret != EOK) {
+                    DEBUG(SSSDBG_MINOR_FAILURE,
+                          ("Could not delete cache entry [%s]\n",
+                           ldb_dn_canonical_string(tmp_ctx,
+                                                   res->msgs[0]->dn)));
+                }
+            }
+        }
+    }
+    talloc_zfree(res);
+
+    /* Ok, ports should now be unique. Now look
+     * the service up by name to determine if we
+     * need to update existing entries or modify
+     * aliases.
+     */
+    ret = sysdb_getservbyname(tmp_ctx, sysdb, primary_name, NULL, &res);
+    if (ret != EOK && ret != ENOENT) {
+        goto done;
+    } else if (ret != ENOENT) { /* Found entries */
+        for (i = 0; i < res->count; i++) {
+            /* Check whether this is the same name as we're currently
+             * saving to the cache.
+             */
+            name = ldb_msg_find_attr_as_string(res->msgs[0],
+                                               SYSDB_NAME,
+                                               NULL);
+            if (!name) {
+
+                /* Corrupted */
+                DEBUG(SSSDBG_CRIT_FAILURE,
+                      ("A service with no name?\n"));
+
+                ret = sysdb_delete_entry(sysdb, res->msgs[0]->dn, true);
+                if (ret != EOK) {
+                    DEBUG(SSSDBG_MINOR_FAILURE,
+                          ("Could not delete corrupt cache entry [%s]\n",
+                           ldb_dn_canonical_string(tmp_ctx,
+                                                   res->msgs[0]->dn)));
+                }
+            } else if (strcmp(name, primary_name) == 0) {
+                /* This is the same service name, so we need
+                 * to update this entry with the values
+                 * provided.
+                 */
+                if(update_dn) {
+                    DEBUG(SSSDBG_CRIT_FAILURE,
+                          ("Two services with the same name: [%s]?\n",
+                           primary_name));
+                    ret = EINVAL;
+                    goto done;
+                }
+                update_dn = talloc_steal(tmp_ctx, res->msgs[0]->dn);
+            } else {
+                /* Another service is claiming this name as an alias.
+                 * In order to account for aliases being promoted to
+                 * primary names, we need to make sure to remove the
+                 * old alias entry.
+                 */
+                ret = sysdb_svc_remove_alias(sysdb,
+                                             res->msgs[0]->dn,
+                                             primary_name);
+                if (ret != EOK) goto done;
+            }
+        }
+        talloc_zfree(res);
+    }
+
+    if (update_dn) {
+        /* Update the existing entry */
+        ret = sysdb_svc_update(sysdb, update_dn, port, aliases, protocols);
+    } else {
+        /* Add a new entry */
+        ret = sysdb_svc_add(tmp_ctx, sysdb, primary_name, port,
+                            aliases, protocols, &update_dn);
+    }
+    if (ret != EOK) goto done;
+
+    /* Set the cache timeout */
+    attrs = sysdb_new_attrs(tmp_ctx);
+    if (!attrs) {
+        ret = ENOMEM;
+        goto done;
+    }
+    ret = sysdb_attrs_add_time_t(attrs, SYSDB_LAST_UPDATE, now);
+    if (ret) goto done;
+
+    ret = sysdb_attrs_add_time_t(attrs, SYSDB_CACHE_EXPIRE,
+                                 ((cache_timeout) ?
+                                  (now + cache_timeout) : 0));
+    if (ret) goto done;
+
+    ret = sysdb_set_entry_attr(sysdb, update_dn, attrs, SYSDB_MOD_REP);
+    if (ret != EOK) goto done;
+
+    ret = sysdb_transaction_commit(sysdb);
+    if (ret == EOK) in_transaction = false;
+
+done:
+    if (in_transaction) {
+        sret = sysdb_transaction_cancel(sysdb);
+        if (sret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, ("Could not cancel transaction\n"));
+        }
+    }
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+struct ldb_dn *
+sysdb_svc_dn(struct sysdb_ctx *sysdb, TALLOC_CTX *mem_ctx,
+             const char *domain, const char *name)
+{
+    errno_t ret;
+    char *clean_name;
+    struct ldb_dn *dn;
+
+    ret = sysdb_dn_sanitize(NULL, name, &clean_name);
+    if (ret != EOK) {
+        return NULL;
+    }
+
+    dn = ldb_dn_new_fmt(mem_ctx, sysdb->ldb, SYSDB_TMPL_SVC,
+                        clean_name, domain);
+    talloc_free(clean_name);
+
+    return dn;
+}
+
+errno_t
+sysdb_svc_add(TALLOC_CTX *mem_ctx,
+              struct sysdb_ctx *sysdb,
+              const char *primary_name,
+              int port,
+              const char **aliases,
+              const char **protocols,
+              struct ldb_dn **dn)
+{
+    errno_t ret;
+    int lret;
+    TALLOC_CTX *tmp_ctx;
+    struct ldb_message *msg;
+    unsigned long i;
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) return ENOMEM;
+
+    msg = ldb_msg_new(tmp_ctx);
+    if (!msg) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    /* svc dn */
+    msg->dn = sysdb_svc_dn(sysdb, msg, sysdb->domain->name, primary_name);
+    if (!msg->dn) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    /* Objectclass */
+    ret = add_string(msg, LDB_FLAG_MOD_ADD,
+                     SYSDB_OBJECTCLASS, SYSDB_SVC_CLASS);
+    if (ret != EOK) goto done;
+
+    /* Set the primary name */
+    ret = add_string(msg, LDB_FLAG_MOD_ADD,
+                     SYSDB_NAME, primary_name);
+    if (ret != EOK) goto done;
+
+    /* Set the port number */
+    ret = add_ulong(msg, LDB_FLAG_MOD_ADD,
+                    SYSDB_SVC_PORT, port);
+    if (ret != EOK) goto done;
+
+    /* If this service has any aliases, include them */
+    if (aliases[0]) {
+        /* Set the name aliases */
+        lret = ldb_msg_add_empty(msg, SYSDB_NAME_ALIAS,
+                                 LDB_FLAG_MOD_ADD, NULL);
+        if (lret != LDB_SUCCESS) {
+            ret = sysdb_error_to_errno(lret);
+            goto done;
+        }
+        for (i=0; aliases[i]; i++) {
+            lret = ldb_msg_add_string(msg, SYSDB_NAME_ALIAS, aliases[i]);
+            if (lret != LDB_SUCCESS) {
+                ret = sysdb_error_to_errno(lret);
+                goto done;
+            }
+        }
+    }
+
+    /* Set the protocols */
+    lret = ldb_msg_add_empty(msg, SYSDB_SVC_PROTO,
+                             LDB_FLAG_MOD_ADD, NULL);
+    if (lret != LDB_SUCCESS) {
+        ret = sysdb_error_to_errno(lret);
+        goto done;
+    }
+    for (i=0; protocols[i]; i++) {
+        lret = ldb_msg_add_string(msg, SYSDB_SVC_PROTO, protocols[i]);
+        if (lret != LDB_SUCCESS) {
+            ret = sysdb_error_to_errno(lret);
+            goto done;
+        }
+    }
+
+    /* creation time */
+    ret = add_ulong(msg, LDB_FLAG_MOD_ADD, SYSDB_CREATE_TIME,
+                    (unsigned long)time(NULL));
+    if (ret) goto done;
+
+    lret = ldb_add(sysdb->ldb, msg);
+    ret = sysdb_error_to_errno(lret);
+
+    if (ret == EOK && dn) {
+        *dn = talloc_steal(mem_ctx, msg->dn);
+    }
+
+done:
+    if (ret) {
+        DEBUG(SSSDBG_TRACE_INTERNAL,
+              ("Error: %d (%s)\n", ret, strerror(ret)));
+    }
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+errno_t
+sysdb_svc_update(struct sysdb_ctx *sysdb,
+                 struct ldb_dn *dn,
+                 int port,
+                 const char **aliases,
+                 const char **protocols)
+{
+    errno_t ret;
+    struct ldb_message *msg;
+    int lret;
+    unsigned int i;
+
+    msg = ldb_msg_new(NULL);
+    if (!msg) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    msg->dn = dn;
+
+    /* Update the port */
+    ret = add_ulong(msg, SYSDB_MOD_REP,
+                    SYSDB_SVC_PORT, port);
+    if (ret != EOK) goto done;
+
+    /* Update the aliases */
+    lret = ldb_msg_add_empty(msg, SYSDB_NAME_ALIAS, SYSDB_MOD_REP, NULL);
+    if (lret != LDB_SUCCESS) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    for (i = 0; aliases[i]; i++) {
+        lret = ldb_msg_add_fmt(msg, SYSDB_NAME_ALIAS, "%s", aliases[i]);
+        if (lret != LDB_SUCCESS) {
+            ret = EINVAL;
+            goto done;
+        }
+    }
+
+    /* Update the protocols */
+    lret = ldb_msg_add_empty(msg, SYSDB_SVC_PROTO, SYSDB_MOD_REP, NULL);
+    if (lret != LDB_SUCCESS) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    for (i = 0; protocols[i]; i++) {
+        lret = ldb_msg_add_fmt(msg, SYSDB_SVC_PROTO, "%s", protocols[i]);
+        if (lret != LDB_SUCCESS) {
+            ret = EINVAL;
+            goto done;
+        }
+    }
+
+    lret = ldb_modify(sysdb->ldb, msg);
+    ret = sysdb_error_to_errno(lret);
+
+done:
+    if (ret) {
+        DEBUG(SSSDBG_TRACE_INTERNAL,
+              ("Error: %d (%s)\n", ret, strerror(ret)));
+    }
+    talloc_free(msg);
+    return ret;
+}
+
+errno_t
+sysdb_svc_remove_alias(struct sysdb_ctx *sysdb,
+                       struct ldb_dn *dn,
+                       const char *alias)
+{
+    errno_t ret;
+    struct ldb_message *msg;
+    int lret;
+
+    msg = ldb_msg_new(NULL);
+    if (!msg) {
+        ret = ENOMEM;
+        goto done;
+    }
+
+    msg->dn = dn;
+
+    ret = add_string(msg, SYSDB_MOD_DEL,
+                     SYSDB_NAME_ALIAS, alias);
+    if (ret != EOK) goto done;
+
+    lret = ldb_modify(sysdb->ldb, msg);
+    ret = sysdb_error_to_errno(lret);
+
+done:
+    if (ret) {
+        DEBUG(SSSDBG_TRACE_INTERNAL,
+              ("Error: %d (%s)\n", ret, strerror(ret)));
+    }
+    talloc_zfree(msg);
+    return ret;
+}
+
+errno_t
+sysdb_svc_delete(struct sysdb_ctx *sysdb,
+                 const char *name,
+                 int port,
+                 const char *proto)
+{
+    errno_t ret, sret;
+    TALLOC_CTX *tmp_ctx;
+    struct ldb_result *res;
+    unsigned int i;
+    bool in_transaction;
+
+    tmp_ctx = talloc_new(NULL);
+    if (!tmp_ctx) {
+        return ENOMEM;
+    }
+
+    ret = sysdb_transaction_start(sysdb);
+    if (ret != EOK) goto done;
+
+    in_transaction = true;
+
+    if (name) {
+        ret = sysdb_getservbyname(tmp_ctx, sysdb, name, proto, &res);
+        if (ret != EOK && ret != ENOENT) goto done;
+        if (ret == ENOENT) {
+            /* Doesn't exist in the DB. Nothing to do */
+            ret = EOK;
+            goto done;
+        }
+    } else {
+        ret = sysdb_getservbyport(tmp_ctx, sysdb, port, proto, &res);
+        if (ret != EOK && ret != ENOENT) goto done;
+        if (ret == ENOENT) {
+            /* Doesn't exist in the DB. Nothing to do */
+            ret = EOK;
+            goto done;
+        }
+    }
+
+    /* There should only be one matching entry,
+     * but if there are multiple, we should delete
+     * them all to de-corrupt the DB.
+     */
+    for (i = 0; i < res->count; i++) {
+        ret = sysdb_delete_entry(sysdb, res->msgs[i]->dn, false);
+        if (ret != EOK) goto done;
+    }
+
+    ret = sysdb_transaction_commit(sysdb);
+
+done:
+    if (in_transaction) {
+        sret = sysdb_transaction_cancel(sysdb);
+        if (sret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  ("Could not cancel transaction\n"));
+        }
+    }
+    if (ret != EOK && ret != ENOENT) {
+        DEBUG(SSSDBG_TRACE_INTERNAL,
+              ("Error: %d (%s)\n", ret, strerror(ret)));
+    }
+    talloc_zfree(tmp_ctx);
+    return ret;
+}
diff --git a/src/db/sysdb_services.h b/src/db/sysdb_services.h
new file mode 100644
index 0000000000000000000000000000000000000000..a29a08f64dbdfb3f63790c8ad32bd8ca6efe15a4
--- /dev/null
+++ b/src/db/sysdb_services.h
@@ -0,0 +1,101 @@
+/*
+    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 SYSDB_SERVICES_H_
+#define SYSDB_SERVICES_H_
+
+#define SYSDB_SVC_CLASS "service"
+#define SYSDB_SVC_CONTAINER "cn=services"
+#define SYSDB_SC "objectclass="SYSDB_SVC_CLASS
+
+#define SYSDB_SVC_PORT "servicePort"
+#define SYSDB_SVC_PROTO "serviceProtocol"
+
+#define SYSDB_TMPL_SVC_BASE SYSDB_SVC_CONTAINER",cn=%s,"SYSDB_BASE
+#define SYSDB_TMPL_SVC SYSDB_NAME"=%s,"SYSDB_TMPL_SVC_BASE
+
+#define SYSDB_SVC_BYNAME_FILTER "(&("SYSDB_SC")("SYSDB_SVC_PROTO"=%s)(|("SYSDB_NAME_ALIAS"=%s)("SYSDB_NAME"=%s)))"
+#define SYSDB_SVC_BYPORT_FILTER "(&("SYSDB_SC")("SYSDB_SVC_PROTO"=%s)("SYSDB_SVC_PORT"=%u))"
+
+
+#define SYSDB_SVC_ATTRS { \
+    SYSDB_NAME, \
+    SYSDB_NAME_ALIAS, \
+    SYSDB_SVC_PORT, \
+    SYSDB_SVC_PROTO, \
+    SYSDB_DEFAULT_ATTRS, \
+    NULL }
+
+errno_t
+sysdb_getservbyname(TALLOC_CTX *mem_ctx,
+                    struct sysdb_ctx *sysdb,
+                    const char *name,
+                    const char *proto,
+                    struct ldb_result **_res);
+
+errno_t
+sysdb_getservbyport(TALLOC_CTX *mem_ctx,
+                    struct sysdb_ctx *sysdb,
+                    int port,
+                    const char *proto,
+                    struct ldb_result **_res);
+
+errno_t
+sysdb_store_service(struct sysdb_ctx *sysdb,
+                    const char *primary_name,
+                    int port,
+                    const char **aliases,
+                    const char **protocols,
+                    uint64_t cache_timeout,
+                    time_t now);
+
+struct ldb_dn *
+sysdb_svc_dn(struct sysdb_ctx *sysdb, TALLOC_CTX *mem_ctx,
+             const char *domain, const char *name);
+
+errno_t
+sysdb_svc_add(TALLOC_CTX *mem_ctx,
+              struct sysdb_ctx *sysdb,
+              const char *primary_name,
+              int port,
+              const char **aliases,
+              const char **protocols,
+              struct ldb_dn **dn);
+
+errno_t
+sysdb_svc_delete(struct sysdb_ctx *sysdb,
+                 const char *name,
+                 int port,
+                 const char *proto);
+
+errno_t
+sysdb_svc_update(struct sysdb_ctx *sysdb,
+                 struct ldb_dn *dn,
+                 int port,
+                 const char **aliases,
+                 const char **protocols);
+
+errno_t
+sysdb_svc_remove_alias(struct sysdb_ctx *sysdb,
+                       struct ldb_dn *dn,
+                       const char *alias);
+#endif /* SYSDB_SERVICES_H_ */
diff --git a/src/tests/sysdb-tests.c b/src/tests/sysdb-tests.c
index 9c080c9e27dbf1ef56d9b412a2c30e6395df2daa..8b78ff32047b7c10e7cf26e6d704462d8f3584c9 100644
--- a/src/tests/sysdb-tests.c
+++ b/src/tests/sysdb-tests.c
@@ -29,6 +29,7 @@
 #include "util/util.h"
 #include "confdb/confdb_setup.h"
 #include "db/sysdb_private.h"
+#include "db/sysdb_services.h"
 #include "tests/common.h"
 
 #define TESTS_PATH "tests_sysdb"
@@ -2811,6 +2812,356 @@ START_TEST(test_odd_characters)
 }
 END_TEST
 
+/* == SERVICE TESTS == */
+void services_check_match(struct sysdb_test_ctx *test_ctx,
+                          bool by_name,
+                          const char *primary_name,
+                          int port,
+                          const char **aliases,
+                          const char **protocols)
+{
+    errno_t ret;
+    unsigned int i, j;
+    bool matched;
+    const char *ret_name;
+    int ret_port;
+    struct ldb_result *res;
+    struct ldb_message *msg;
+    struct ldb_message_element *el;
+
+    if (by_name) {
+        /* Look up the service by name */
+        ret = sysdb_getservbyname(test_ctx, test_ctx->sysdb, primary_name,
+                                  NULL, &res);
+        fail_if(ret != EOK, "sysdb_getservbyname error [%s]\n",
+                             strerror(ret));
+    } else {
+        /* Look up the newly-added service by port */
+        ret = sysdb_getservbyport(test_ctx, test_ctx->sysdb,
+                                  port, NULL, &res);
+        fail_if(ret != EOK, "sysdb_getservbyport error [%s]\n",
+                             strerror(ret));
+    }
+    fail_if(res == NULL, "ENOMEM");
+    fail_if(res->count != 1);
+
+    /* Make sure the returned entry matches */
+    msg = res->msgs[0];
+    ret_name = ldb_msg_find_attr_as_string(msg, SYSDB_NAME, NULL);
+    fail_if(ret_name == NULL);
+    fail_unless(strcmp(ret_name, primary_name) == 0);
+
+    ret_port = ldb_msg_find_attr_as_int(msg, SYSDB_SVC_PORT, 0);
+    fail_if (ret_port != port);
+
+    el = ldb_msg_find_element(msg, SYSDB_NAME_ALIAS);
+    for (i = 0; i < el->num_values; i++) {
+        matched = false;
+        for (j = 0; aliases[j]; j++) {
+            if (strcmp(aliases[j], (const char *)el->values[i].data) == 0) {
+                matched = true;
+            }
+        }
+        fail_if(!matched, "Unexpected value in LDB entry: [%s]",
+                (const char *)el->values[i].data);
+    }
+
+    el = ldb_msg_find_element(msg, SYSDB_SVC_PROTO);
+    for (i = 0; i < el->num_values; i++) {
+        matched = false;
+        for (j = 0; protocols[j]; j++) {
+            if (strcmp(protocols[j], (const char *)el->values[i].data) == 0) {
+                matched = true;
+            }
+        }
+        fail_if(!matched, "Unexpected value in LDB entry: [%s]",
+                (const char *)el->values[i].data);
+    }
+}
+
+#define services_check_match_name(test_ctx, primary_name, port, aliases, protocols) \
+    do { \
+        services_check_match(test_ctx, true, primary_name, port, aliases, protocols); \
+    } while(0);
+
+#define services_check_match_port(test_ctx, primary_name, port, aliases, protocols) \
+    do { \
+        services_check_match(test_ctx, false, primary_name, port, aliases, protocols); \
+    } while(0);
+
+START_TEST(test_sysdb_add_services)
+{
+    errno_t ret;
+    struct sysdb_test_ctx *test_ctx;
+    char *primary_name;
+    const char **aliases;
+    const char **protocols;
+    int port = 3890;
+
+    /* Setup */
+    ret = setup_sysdb_tests(&test_ctx);
+    fail_if(ret != EOK, "Could not set up the test");
+
+    primary_name = talloc_asprintf(test_ctx, "test_service");
+    fail_if(primary_name == NULL);
+
+    aliases = talloc_array(test_ctx, const char *, 3);
+    fail_if(aliases == NULL);
+
+    aliases[0] = talloc_asprintf(aliases, "test_service_alias1");
+    fail_if(aliases[0] == NULL);
+
+    aliases[1] = talloc_asprintf(aliases, "test_service_alias2");
+    fail_if(aliases[1] == NULL);
+
+    aliases[2] = NULL;
+
+    protocols = talloc_array(test_ctx, const char *, 3);
+    fail_if(protocols == NULL);
+
+    protocols[0] = talloc_asprintf(protocols, "tcp");
+    fail_if(protocols[0] == NULL);
+
+    protocols[1] = talloc_asprintf(protocols, "udp");
+    fail_if(protocols[1] == NULL);
+
+    protocols[2] = NULL;
+
+    ret = sysdb_transaction_start(test_ctx->sysdb);
+    fail_if(ret != EOK);
+
+    ret = sysdb_svc_add(NULL, test_ctx->sysdb,
+                        primary_name, port,
+                        aliases, protocols,
+                        NULL);
+    fail_unless(ret == EOK, "sysdb_svc_add error [%s]\n", strerror(ret));
+
+    /* Search by name and make sure the results match */
+    services_check_match_name(test_ctx,
+                              primary_name, port,
+                              aliases, protocols);
+
+    /* Search by port and make sure the results match */
+    services_check_match_port(test_ctx,
+                              primary_name, port,
+                              aliases, protocols);
+
+    ret = sysdb_transaction_commit(test_ctx->sysdb);
+    fail_if (ret != EOK);
+
+    /* Clean up after ourselves (and test deleting by name)
+     *
+     * We have to do this after the transaction, because LDB
+     * doesn't like adding and deleting the same entry in a
+     * single transaction.
+     */
+    ret = sysdb_svc_delete(test_ctx->sysdb, primary_name, 0, NULL);
+    fail_if(ret != EOK);
+
+    talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST(test_sysdb_store_services)
+{
+    errno_t ret;
+    struct sysdb_test_ctx *test_ctx;
+    const char *primary_name = "test_store_service";
+    const char *alt_primary_name = "alt_test_store_service";
+    const char **aliases;
+    const char **protocols;
+    int port = 3890;
+    int altport = 3891;
+
+    /* Setup */
+    ret = setup_sysdb_tests(&test_ctx);
+    fail_if(ret != EOK, "Could not set up the test");
+
+    aliases = talloc_array(test_ctx, const char *, 3);
+    fail_if(aliases == NULL);
+
+    aliases[0] = talloc_asprintf(aliases, "test_service_alias1");
+    fail_if(aliases[0] == NULL);
+
+    aliases[1] = talloc_asprintf(aliases, "test_service_alias2");
+    fail_if(aliases[1] == NULL);
+
+    aliases[2] = NULL;
+
+    protocols = talloc_array(test_ctx, const char *, 3);
+    fail_if(protocols == NULL);
+
+    protocols[0] = talloc_asprintf(protocols, "tcp");
+    fail_if(protocols[0] == NULL);
+
+    protocols[1] = talloc_asprintf(protocols, "udp");
+    fail_if(protocols[1] == NULL);
+
+    protocols[2] = NULL;
+
+    ret = sysdb_transaction_start(test_ctx->sysdb);
+    fail_if(ret != EOK);
+
+    /* Store this group (which will add it) */
+    ret = sysdb_store_service(test_ctx->sysdb,
+                              primary_name, port,
+                              aliases, protocols,
+                              1, 1);
+    fail_if (ret != EOK);
+
+    /* Search by name and make sure the results match */
+    services_check_match_name(test_ctx,
+                              primary_name, port,
+                              aliases, protocols);
+
+    /* Search by port and make sure the results match */
+    services_check_match_port(test_ctx,
+                              primary_name, port,
+                              aliases, protocols);
+
+    /* Change the service name */
+    ret = sysdb_store_service(test_ctx->sysdb,
+                              alt_primary_name, port,
+                              aliases, protocols,
+                              1, 1);
+    fail_if (ret != EOK, "[%s]", strerror(ret));
+
+    services_check_match_name(test_ctx,
+                              alt_primary_name, port,
+                              aliases, protocols);
+
+    /* Search by port and make sure the results match */
+    services_check_match_port(test_ctx,
+                              alt_primary_name, port,
+                              aliases, protocols);
+
+
+    /* Change it back */
+    ret = sysdb_store_service(test_ctx->sysdb,
+                              primary_name, port,
+                              aliases, protocols,
+                              1, 1);
+    fail_if (ret != EOK, "[%s]", strerror(ret));
+
+    /* Change the port number */
+    ret = sysdb_store_service(test_ctx->sysdb,
+                              primary_name, altport,
+                              aliases, protocols,
+                              1, 1);
+    fail_if (ret != EOK, "[%s]", strerror(ret));
+
+    /* Search by name and make sure the results match */
+    services_check_match_name(test_ctx,
+                              primary_name, altport,
+                              aliases, protocols);
+
+    /* Search by port and make sure the results match */
+    services_check_match_port(test_ctx,
+                              primary_name, altport,
+                              aliases, protocols);
+
+    /* TODO: Test changing aliases and protocols */
+
+    ret = sysdb_transaction_commit(test_ctx->sysdb);
+    fail_if(ret != EOK, "[%s]", strerror(ret));
+
+    /* Clean up after ourselves (and test deleting by port)
+     *
+     * We have to do this after the transaction, because LDB
+     * doesn't like adding and deleting the same entry in a
+     * single transaction.
+     */
+    ret = sysdb_svc_delete(test_ctx->sysdb, NULL, altport, NULL);
+    fail_if(ret != EOK);
+
+    talloc_free(test_ctx);
+}
+END_TEST
+
+START_TEST(test_sysdb_svc_remove_alias)
+{
+    errno_t ret;
+    struct sysdb_test_ctx *test_ctx;
+    const char *primary_name = "remove_alias_test";
+    const char **aliases;
+    const char **protocols;
+    int port = 3990;
+    struct ldb_dn *dn;
+
+    /* Setup */
+    ret = setup_sysdb_tests(&test_ctx);
+    fail_if(ret != EOK, "Could not set up the test");
+
+    aliases = talloc_array(test_ctx, const char *, 3);
+    fail_if(aliases == NULL);
+
+    aliases[0] = talloc_asprintf(aliases, "remove_alias_alias1");
+    fail_if(aliases[0] == NULL);
+
+    aliases[1] = talloc_asprintf(aliases, "remove_alias_alias2");
+    fail_if(aliases[1] == NULL);
+
+    aliases[2] = NULL;
+
+    protocols = talloc_array(test_ctx, const char *, 3);
+    fail_if(protocols == NULL);
+
+    protocols[0] = talloc_asprintf(protocols, "tcp");
+    fail_if(protocols[0] == NULL);
+
+    protocols[1] = talloc_asprintf(protocols, "udp");
+    fail_if(protocols[1] == NULL);
+
+    protocols[2] = NULL;
+
+    ret = sysdb_transaction_start(test_ctx->sysdb);
+    fail_if(ret != EOK);
+
+    ret = sysdb_svc_add(NULL, test_ctx->sysdb,
+                        primary_name, port,
+                        aliases, protocols,
+                        NULL);
+    fail_unless(ret == EOK, "sysdb_svc_add error [%s]\n", strerror(ret));
+
+    /* Search by name and make sure the results match */
+    services_check_match_name(test_ctx,
+                              primary_name, port,
+                              aliases, protocols);
+
+    /* Search by port and make sure the results match */
+    services_check_match_port(test_ctx,
+                              primary_name, port,
+                              aliases, protocols);
+
+    /* Now remove an alias */
+    dn = sysdb_svc_dn(test_ctx->sysdb, test_ctx, test_ctx->domain->name, primary_name);
+    fail_if (dn == NULL);
+
+    ret = sysdb_svc_remove_alias(test_ctx->sysdb, dn, aliases[1]);
+    fail_if (ret != EOK, "[%s]", strerror(ret));
+    sysdb_transaction_commit(test_ctx->sysdb);
+    sysdb_transaction_start(test_ctx->sysdb);
+
+    /* Set aliases[1] to NULL to perform validation checks */
+    aliases[1] = NULL;
+
+    /* Search by name and make sure the results match */
+    services_check_match_name(test_ctx,
+                              primary_name, port,
+                              aliases, protocols);
+
+    /* Search by port and make sure the results match */
+    services_check_match_port(test_ctx,
+                              primary_name, port,
+                              aliases, protocols);
+
+    ret = sysdb_transaction_commit(test_ctx->sysdb);
+    fail_if(ret != EOK);
+
+    talloc_free(test_ctx);
+}
+END_TEST
+
 START_TEST(test_sysdb_has_enumerated)
 {
     errno_t ret;
@@ -3052,6 +3403,13 @@ Suite *create_sysdb_suite(void)
     /* Remove the other half by DN */
     tcase_add_loop_test(tc_sysdb, test_sysdb_remove_netgroup_entry, 27005, 27010);
 
+/* ===== SERVICE TESTS ===== */
+
+    /* Create a new service */
+    tcase_add_test(tc_sysdb, test_sysdb_add_services);
+    tcase_add_test(tc_sysdb, test_sysdb_store_services);
+    tcase_add_test(tc_sysdb, test_sysdb_svc_remove_alias);
+
 /* Add all test cases to the test suite */
     suite_add_tcase(s, tc_sysdb);
 
-- 
1.7.7.5

