Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow multiple services associated to the same port #7790

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 17 additions & 49 deletions src/db/sysdb_services.c
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,10 @@ sysdb_getservbyport(TALLOC_CTX *mem_ctx,
goto done;
}

ret = sysdb_search_services(mem_ctx, domain, subfilter,
ret = sysdb_search_services(tmp_ctx, domain, subfilter,
attrs, &msgs_count, &msgs);
if (ret == EOK) {
res = talloc_zero(mem_ctx, struct ldb_result);
res = talloc_zero(tmp_ctx, struct ldb_result);
if (!res) {
ret = ENOMEM;
goto done;
Expand All @@ -151,8 +151,7 @@ sysdb_getservbyport(TALLOC_CTX *mem_ctx,
res->msgs = talloc_steal(res, msgs);
}

*_res = res;

*_res = talloc_move(mem_ctx, &res);
aplopez marked this conversation as resolved.
Show resolved Hide resolved

done:
talloc_free(tmp_ctx);
Expand Down Expand Up @@ -194,51 +193,21 @@ sysdb_store_service(struct sss_domain_info *domain,

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".
*/
/* RFC6335 Section 5 allows more than one service associated with a
* particular transport protocol and port. In such case the names
* must be different so check that all entries have a name. */
ret = sysdb_getservbyport(tmp_ctx, domain, 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++) {
DEBUG(SSSDBG_TRACE_FUNC,
"Corrupt cache entry [%s] detected. Deleting\n",
ldb_dn_canonical_string(tmp_ctx,
res->msgs[i]->dn));

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],
/* Check whether this is the same name as we're currently
* saving to the cache. */
for (i = 0; i < res->count; i++) {
name = ldb_msg_find_attr_as_string(res->msgs[i],
SYSDB_NAME,
NULL);
if (!name || strcmp(name, primary_name) != 0) {

if (!name) {
DEBUG(SSSDBG_CRIT_FAILURE,
"A service with no name?\n");
/* Corrupted */
}
if (!name) {
DEBUG(SSSDBG_CRIT_FAILURE, "A service with no name?\n");

/* Either this is a corrupt entry or it's another service
* claiming ownership of this port. In order to account
Expand All @@ -248,22 +217,21 @@ sysdb_store_service(struct sss_domain_info *domain,
"Corrupt or replaced cache entry [%s] detected. "
"Deleting\n",
ldb_dn_canonical_string(tmp_ctx,
res->msgs[0]->dn));
res->msgs[i]->dn));

ret = sysdb_delete_entry(sysdb, res->msgs[0]->dn, true);
ret = sysdb_delete_entry(sysdb, res->msgs[i]->dn, true);
if (ret != EOK) {
DEBUG(SSSDBG_OP_FAILURE,
"Could not delete cache entry [%s]\n",
ldb_dn_canonical_string(tmp_ctx,
res->msgs[0]->dn));
res->msgs[i]->dn));
}
}
}
}
talloc_zfree(res);

/* Ok, ports should now be unique. Now look
* the service up by name to determine if we
/* Now look the service up by name to determine if we
* need to update existing entries or modify
* aliases.
*/
Expand Down Expand Up @@ -684,7 +652,7 @@ sysdb_svc_delete(struct sss_domain_info *domain,

/* There should only be one matching entry,
* but if there are multiple, we should delete
* them all to de-corrupt the DB.
* them all.
*/
for (i = 0; i < res->count; i++) {
ret = sysdb_delete_entry(sysdb, res->msgs[i]->dn, false);
Expand Down
20 changes: 17 additions & 3 deletions src/responder/common/cache_req/plugins/cache_req_svc_by_port.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,22 @@ cache_req_svc_by_port_lookup(TALLOC_CTX *mem_ctx,
struct sss_domain_info *domain,
struct ldb_result **_result)
{
return sysdb_getservbyport(mem_ctx, domain, data->svc.port,
data->svc.protocol.lookup, _result);
struct ldb_result *res = NULL;
errno_t ret;

ret = sysdb_getservbyport(mem_ctx, domain, data->svc.port,
data->svc.protocol.lookup, &res);

/* RFC6335 Section 5 allows more than one service associated with a
* particular transport protocol and port. In such case return only
* the first result. */
if (ret == EOK && res->count > 1) {
res->count = 1;
}

scabrero marked this conversation as resolved.
Show resolved Hide resolved
*_result = talloc_move(mem_ctx, &res);

return ret;
}

static struct tevent_req *
Expand All @@ -111,7 +125,7 @@ const struct cache_req_plugin cache_req_svc_by_port = {
.parse_name = false,
.ignore_default_domain = false,
.bypass_cache = false,
.only_one_result = false,
.only_one_result = true,
.search_all_domains = false,
.require_enumeration = false,
.allow_missing_fqn = false,
Expand Down
23 changes: 23 additions & 0 deletions src/tests/intg/ldap_ent.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,24 @@ def ip_net(base_dn, name, address, aliases=()):
return ("cn=" + name + ",ou=Networks," + base_dn, attr_list)


def ip_service(base_dn, name, proto, port, aliases=()):
"""
Generate an RFC2307 ipService add-modlist for passing to ldap.add*.
"""
attr_list = [
('objectClass', [b'top', b'ipService']),
('ipServicePort', [str(port).encode('utf-8')]),
('ipServiceProtocol', [proto.encode('utf-8')]),
]
if (len(aliases)) > 0:
alias_list = [alias.encode('utf-8') for alias in aliases]
alias_list.insert(0, name.encode('utf-8'))
attr_list.append(('cn', alias_list))
else:
attr_list.append(('cn', [name.encode('utf-8')]))
return ("cn=" + name + ",ou=Services," + base_dn, attr_list)


class List(list):
"""LDAP add-modlist list"""

Expand Down Expand Up @@ -233,3 +251,8 @@ def add_ipnet(self, name, address, aliases=[], base_dn=None):
"""Add an RFC2307 ipNetwork add-modlist."""
self.append(ip_net(base_dn or self.base_dn,
name, address, aliases))

def add_service(self, name, proto, port, aliases=[], base_dn=None):
"""Add an RFC2307 ipService add-modlist."""
self.append(ip_service(base_dn or self.base_dn,
name, proto, port, aliases))
163 changes: 163 additions & 0 deletions src/tests/intg/sssd_services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
#
# Module for simulation of utility "getent services -s sss" from coreutils
#
# Authors:
# Samuel Cabrero <[email protected]>
#
# Copyright (C) 2025 SUSE LINUX GmbH, Nuernberg, Germany.
#
# 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/>.

from ctypes import (
c_int,
c_char_p,
c_ulong,
POINTER,
Structure,
create_string_buffer,
)
from sssd_nss import NssReturnCode, SssdNssError, nss_sss_ctypes_loader
import socket

SERVICE_BUFLEN = 1024


# struct servent from netdb.h
class Servent(Structure):
_fields_ = [
("s_name", c_char_p),
("s_aliases", POINTER(c_char_p)),
("s_port", c_int),
("s_proto", c_char_p),
]


def getservbyname_r(name, proto, result_p, buffer_p, buflen):
"""
ctypes wrapper for:
enum nss_status _nss_sss_getservbyname_r(const char *name,
const char *protocol,
struct servent *result,
char *buffer, size_t buflen,
int *errnop)
"""
func = nss_sss_ctypes_loader("_nss_sss_getservbyname_r")
func.restype = c_int
func.argtypes = [
c_char_p,
c_char_p,
POINTER(Servent),
c_char_p,
c_ulong,
POINTER(c_int),
]

errno = POINTER(c_int)(c_int(0))

name = name.encode("utf-8")
proto = proto.encode("utf-8")
res = func(c_char_p(name), c_char_p(proto), result_p, buffer_p, buflen, errno)

return (int(res), int(errno[0]), result_p)


def getservbyport_r(port, proto, result_p, buffer_p, buflen):
"""
ctypes wrapper for:
enum nss_status _nss_sss_getservbyport_r(int port, const char *protocol,
struct servent *result,
char *buffer, size_t buflen,
int *errnop)
"""
func = nss_sss_ctypes_loader("_nss_sss_getservbyport_r")
func.restype = c_int
func.argtypes = [
c_int,
c_char_p,
POINTER(Servent),
c_char_p,
c_ulong,
POINTER(c_int),
]

errno = POINTER(c_int)(c_int(0))

port = socket.htons(port)
proto = proto.encode("utf-8")
res = func(port, c_char_p(proto), result_p, buffer_p, buflen, errno)

return (int(res), int(errno[0]), result_p)


def set_servent_dict(res, result_p):
if res != NssReturnCode.SUCCESS:
return dict()

servent_dict = dict()
servent_dict["name"] = result_p[0].s_name.decode("utf-8")
servent_dict["aliases"] = list()
servent_dict["port"] = result_p[0].s_port
servent_dict["proto"] = result_p[0].s_proto

i = 0
while result_p[0].s_aliases[i] is not None:
alias = result_p[0].s_aliases[i].decode("utf-8")
servent_dict["aliases"].append(alias)
i = i + 1

return servent_dict


def call_sssd_getservbyname(name, proto):
"""
A Python wrapper to retrieve a service by name and protocol. Returns:
(res, servent_dict)
if res is NssReturnCode.SUCCESS, then servent_dict contains the keys
corresponding to the C servent structure fields. Otherwise, the dictionary
is empty and errno indicates the error code
"""
result = Servent()
result_p = POINTER(Servent)(result)
buff = create_string_buffer(SERVICE_BUFLEN)

(res, errno, result_p) = getservbyname_r(
name, proto, result_p, buff, SERVICE_BUFLEN
)
if errno != 0:
raise SssdNssError(errno, "getservbyname_r")

servent_dict = set_servent_dict(res, result_p)
return (res, servent_dict)


def call_sssd_getservbyport(port, proto):
"""
A Python wrapper to retrieve a service by port and protocol. Returns:
(res, servent_dict)
if res is NssReturnCode.SUCCESS, then servent_dict contains the keys
corresponding to the C servent structure fields. Otherwise, the dictionary
is empty and errno indicates the error code
"""
result = Servent()
result_p = POINTER(Servent)(result)
buff = create_string_buffer(SERVICE_BUFLEN)

(res, errno, result_p) = getservbyport_r(
port, proto, result_p, buff, SERVICE_BUFLEN
)
if errno != 0:
raise SssdNssError(errno, "getservbyport_r")

servent_dict = set_servent_dict(res, result_p)
return (res, servent_dict)
Loading
Loading