439 lines
19 KiB
Diff
439 lines
19 KiB
Diff
From da0be382d95f0bdbc6ad5ccb68503456c2ee858b Mon Sep 17 00:00:00 2001
|
|
From: Sumit Bose <sbose@redhat.com>
|
|
Date: Thu, 26 Sep 2019 20:27:09 +0200
|
|
Subject: [PATCH 11/13] ad: add ad_use_ldaps
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
With this new boolean option the AD provider should only use the LDAPS
|
|
port 636 and the Global Catalog port 3629 which is TLS protected as
|
|
well.
|
|
|
|
Related to https://pagure.io/SSSD/sssd/issue/4131
|
|
|
|
Reviewed-by: Pavel Březina <pbrezina@redhat.com>
|
|
---
|
|
src/config/SSSDConfig/__init__.py.in | 1 +
|
|
src/config/cfg_rules.ini | 1 +
|
|
src/config/etc/sssd.api.d/sssd-ad.conf | 1 +
|
|
src/man/sssd-ad.5.xml | 20 +++++++++++++++++++
|
|
src/providers/ad/ad_common.c | 24 +++++++++++++++++++----
|
|
src/providers/ad/ad_common.h | 8 +++++++-
|
|
src/providers/ad/ad_init.c | 8 +++++++-
|
|
src/providers/ad/ad_opts.c | 1 +
|
|
src/providers/ad/ad_srv.c | 16 ++++++++++++---
|
|
src/providers/ad/ad_srv.h | 3 ++-
|
|
src/providers/ad/ad_subdomains.c | 21 ++++++++++++++++++--
|
|
src/providers/ipa/ipa_subdomains_server.c | 4 ++--
|
|
12 files changed, 94 insertions(+), 14 deletions(-)
|
|
|
|
diff --git a/src/config/SSSDConfig/__init__.py.in b/src/config/SSSDConfig/__init__.py.in
|
|
index eba89b461..84631862a 100644
|
|
--- a/src/config/SSSDConfig/__init__.py.in
|
|
+++ b/src/config/SSSDConfig/__init__.py.in
|
|
@@ -252,6 +252,7 @@ option_strings = {
|
|
'ad_site' : _('a particular site to be used by the client'),
|
|
'ad_maximum_machine_account_password_age' : _('Maximum age in days before the machine account password should be renewed'),
|
|
'ad_machine_account_password_renewal_opts' : _('Option for tuning the machine account renewal task'),
|
|
+ 'ad_use_ldaps' : _('Use LDAPS port for LDAP and Global Catalog requests'),
|
|
|
|
# [provider/krb5]
|
|
'krb5_kdcip' : _('Kerberos server address'),
|
|
diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini
|
|
index c56d5a668..1034a1fd6 100644
|
|
--- a/src/config/cfg_rules.ini
|
|
+++ b/src/config/cfg_rules.ini
|
|
@@ -464,6 +464,7 @@ option = ad_machine_account_password_renewal_opts
|
|
option = ad_maximum_machine_account_password_age
|
|
option = ad_server
|
|
option = ad_site
|
|
+option = ad_use_ldaps
|
|
|
|
# IPA provider specific options
|
|
option = ipa_anchor_uuid
|
|
diff --git a/src/config/etc/sssd.api.d/sssd-ad.conf b/src/config/etc/sssd.api.d/sssd-ad.conf
|
|
index aaa0b2345..a2af72603 100644
|
|
--- a/src/config/etc/sssd.api.d/sssd-ad.conf
|
|
+++ b/src/config/etc/sssd.api.d/sssd-ad.conf
|
|
@@ -20,6 +20,7 @@ ad_gpo_default_right = str, None, false
|
|
ad_site = str, None, false
|
|
ad_maximum_machine_account_password_age = int, None, false
|
|
ad_machine_account_password_renewal_opts = str, None, false
|
|
+ad_use_ldaps = bool, None, false
|
|
ldap_uri = str, None, false
|
|
ldap_backup_uri = str, None, false
|
|
ldap_search_base = str, None, false
|
|
diff --git a/src/man/sssd-ad.5.xml b/src/man/sssd-ad.5.xml
|
|
index fdcb4e4b9..ade56cd6d 100644
|
|
--- a/src/man/sssd-ad.5.xml
|
|
+++ b/src/man/sssd-ad.5.xml
|
|
@@ -1015,6 +1015,26 @@ ad_gpo_map_deny = +my_pam_service
|
|
</listitem>
|
|
</varlistentry>
|
|
|
|
+ <varlistentry>
|
|
+ <term>ad_use_ldaps (bool)</term>
|
|
+ <listitem>
|
|
+ <para>
|
|
+ By default SSSD uses the plain LDAP port 389 and the
|
|
+ Global Catalog port 3628. If this option is set to
|
|
+ True SSSD will use the LDAPS port 636 and Global
|
|
+ Catalog port 3629 with LDAPS protection. Since AD
|
|
+ does not allow to have multiple encryption layers on
|
|
+ a single connection and we still want to use
|
|
+ SASL/GSSAPI or SASL/GSS-SPNEGO for authentication
|
|
+ the SASL security property maxssf is set to 0 (zero)
|
|
+ for those connections.
|
|
+ </para>
|
|
+ <para>
|
|
+ Default: False
|
|
+ </para>
|
|
+ </listitem>
|
|
+ </varlistentry>
|
|
+
|
|
<varlistentry>
|
|
<term>dyndns_update (boolean)</term>
|
|
<listitem>
|
|
diff --git a/src/providers/ad/ad_common.c b/src/providers/ad/ad_common.c
|
|
index 600e3ceb2..a2369166a 100644
|
|
--- a/src/providers/ad/ad_common.c
|
|
+++ b/src/providers/ad/ad_common.c
|
|
@@ -729,6 +729,7 @@ ad_failover_init(TALLOC_CTX *mem_ctx, struct be_ctx *bectx,
|
|
const char *ad_gc_service,
|
|
const char *ad_domain,
|
|
bool use_kdcinfo,
|
|
+ bool ad_use_ldaps,
|
|
size_t n_lookahead_primary,
|
|
size_t n_lookahead_backup,
|
|
struct ad_service **_service)
|
|
@@ -746,6 +747,16 @@ ad_failover_init(TALLOC_CTX *mem_ctx, struct be_ctx *bectx,
|
|
goto done;
|
|
}
|
|
|
|
+ if (ad_use_ldaps) {
|
|
+ service->ldap_scheme = "ldaps";
|
|
+ service->port = LDAPS_PORT;
|
|
+ service->gc_port = AD_GC_LDAPS_PORT;
|
|
+ } else {
|
|
+ service->ldap_scheme = "ldap";
|
|
+ service->port = LDAP_PORT;
|
|
+ service->gc_port = AD_GC_PORT;
|
|
+ }
|
|
+
|
|
service->sdap = talloc_zero(service, struct sdap_service);
|
|
service->gc = talloc_zero(service, struct sdap_service);
|
|
if (!service->sdap || !service->gc) {
|
|
@@ -927,7 +938,8 @@ ad_resolve_callback(void *private_data, struct fo_server *server)
|
|
goto done;
|
|
}
|
|
|
|
- new_uri = talloc_asprintf(service->sdap, "ldap://%s", srv_name);
|
|
+ new_uri = talloc_asprintf(service->sdap, "%s://%s", service->ldap_scheme,
|
|
+ srv_name);
|
|
if (!new_uri) {
|
|
DEBUG(SSSDBG_CRIT_FAILURE, "Failed to copy URI\n");
|
|
ret = ENOMEM;
|
|
@@ -935,7 +947,7 @@ ad_resolve_callback(void *private_data, struct fo_server *server)
|
|
}
|
|
DEBUG(SSSDBG_CONF_SETTINGS, "Constructed uri '%s'\n", new_uri);
|
|
|
|
- sockaddr = resolv_get_sockaddr_address(tmp_ctx, srvaddr, LDAP_PORT);
|
|
+ sockaddr = resolv_get_sockaddr_address(tmp_ctx, srvaddr, service->port);
|
|
if (sockaddr == NULL) {
|
|
DEBUG(SSSDBG_CRIT_FAILURE, "resolv_get_sockaddr_address failed.\n");
|
|
ret = EIO;
|
|
@@ -951,8 +963,12 @@ ad_resolve_callback(void *private_data, struct fo_server *server)
|
|
talloc_zfree(service->gc->uri);
|
|
talloc_zfree(service->gc->sockaddr);
|
|
if (sdata && sdata->gc) {
|
|
- new_port = fo_get_server_port(server);
|
|
- new_port = (new_port == 0) ? AD_GC_PORT : new_port;
|
|
+ if (service->gc_port == AD_GC_LDAPS_PORT) {
|
|
+ new_port = service->gc_port;
|
|
+ } else {
|
|
+ new_port = fo_get_server_port(server);
|
|
+ new_port = (new_port == 0) ? service->gc_port : new_port;
|
|
+ }
|
|
|
|
service->gc->uri = talloc_asprintf(service->gc, "%s:%d",
|
|
new_uri, new_port);
|
|
diff --git a/src/providers/ad/ad_common.h b/src/providers/ad/ad_common.h
|
|
index 75f11de2e..820e06124 100644
|
|
--- a/src/providers/ad/ad_common.h
|
|
+++ b/src/providers/ad/ad_common.h
|
|
@@ -29,7 +29,8 @@
|
|
#define AD_SERVICE_NAME "AD"
|
|
#define AD_GC_SERVICE_NAME "AD_GC"
|
|
/* The port the Global Catalog runs on */
|
|
-#define AD_GC_PORT 3268
|
|
+#define AD_GC_PORT 3268
|
|
+#define AD_GC_LDAPS_PORT 3269
|
|
|
|
#define AD_AT_OBJECT_SID "objectSID"
|
|
#define AD_AT_DNS_DOMAIN "DnsDomain"
|
|
@@ -67,6 +68,7 @@ enum ad_basic_opt {
|
|
AD_KRB5_CONFD_PATH,
|
|
AD_MAXIMUM_MACHINE_ACCOUNT_PASSWORD_AGE,
|
|
AD_MACHINE_ACCOUNT_PASSWORD_RENEWAL_OPTS,
|
|
+ AD_USE_LDAPS,
|
|
|
|
AD_OPTS_BASIC /* opts counter */
|
|
};
|
|
@@ -82,6 +84,9 @@ struct ad_service {
|
|
struct sdap_service *sdap;
|
|
struct sdap_service *gc;
|
|
struct krb5_service *krb5_service;
|
|
+ const char *ldap_scheme;
|
|
+ int port;
|
|
+ int gc_port;
|
|
};
|
|
|
|
struct ad_options {
|
|
@@ -147,6 +152,7 @@ ad_failover_init(TALLOC_CTX *mem_ctx, struct be_ctx *ctx,
|
|
const char *ad_gc_service,
|
|
const char *ad_domain,
|
|
bool use_kdcinfo,
|
|
+ bool ad_use_ldaps,
|
|
size_t n_lookahead_primary,
|
|
size_t n_lookahead_backup,
|
|
struct ad_service **_service);
|
|
diff --git a/src/providers/ad/ad_init.c b/src/providers/ad/ad_init.c
|
|
index 290d5b5c1..2b4b9e2e7 100644
|
|
--- a/src/providers/ad/ad_init.c
|
|
+++ b/src/providers/ad/ad_init.c
|
|
@@ -138,6 +138,7 @@ static errno_t ad_init_options(TALLOC_CTX *mem_ctx,
|
|
char *ad_servers = NULL;
|
|
char *ad_backup_servers = NULL;
|
|
char *ad_realm;
|
|
+ bool ad_use_ldaps = false;
|
|
errno_t ret;
|
|
|
|
ad_sasl_initialize();
|
|
@@ -154,12 +155,14 @@ static errno_t ad_init_options(TALLOC_CTX *mem_ctx,
|
|
ad_servers = dp_opt_get_string(ad_options->basic, AD_SERVER);
|
|
ad_backup_servers = dp_opt_get_string(ad_options->basic, AD_BACKUP_SERVER);
|
|
ad_realm = dp_opt_get_string(ad_options->basic, AD_KRB5_REALM);
|
|
+ ad_use_ldaps = dp_opt_get_bool(ad_options->basic, AD_USE_LDAPS);
|
|
|
|
/* Set up the failover service */
|
|
ret = ad_failover_init(ad_options, be_ctx, ad_servers, ad_backup_servers,
|
|
ad_realm, AD_SERVICE_NAME, AD_GC_SERVICE_NAME,
|
|
dp_opt_get_string(ad_options->basic, AD_DOMAIN),
|
|
false, /* will be set in ad_get_auth_options() */
|
|
+ ad_use_ldaps,
|
|
(size_t) -1,
|
|
(size_t) -1,
|
|
&ad_options->service);
|
|
@@ -184,11 +187,13 @@ static errno_t ad_init_srv_plugin(struct be_ctx *be_ctx,
|
|
const char *ad_site_override;
|
|
bool sites_enabled;
|
|
errno_t ret;
|
|
+ bool ad_use_ldaps;
|
|
|
|
hostname = dp_opt_get_string(ad_options->basic, AD_HOSTNAME);
|
|
ad_domain = dp_opt_get_string(ad_options->basic, AD_DOMAIN);
|
|
ad_site_override = dp_opt_get_string(ad_options->basic, AD_SITE);
|
|
sites_enabled = dp_opt_get_bool(ad_options->basic, AD_ENABLE_DNS_SITES);
|
|
+ ad_use_ldaps = dp_opt_get_bool(ad_options->basic, AD_USE_LDAPS);
|
|
|
|
|
|
if (!sites_enabled) {
|
|
@@ -205,7 +210,8 @@ static errno_t ad_init_srv_plugin(struct be_ctx *be_ctx,
|
|
srv_ctx = ad_srv_plugin_ctx_init(be_ctx, be_ctx, be_ctx->be_res,
|
|
default_host_dbs, ad_options->id,
|
|
hostname, ad_domain,
|
|
- ad_site_override);
|
|
+ ad_site_override,
|
|
+ ad_use_ldaps);
|
|
if (srv_ctx == NULL) {
|
|
DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory?\n");
|
|
return ENOMEM;
|
|
diff --git a/src/providers/ad/ad_opts.c b/src/providers/ad/ad_opts.c
|
|
index 1293219ee..30f9b62fd 100644
|
|
--- a/src/providers/ad/ad_opts.c
|
|
+++ b/src/providers/ad/ad_opts.c
|
|
@@ -54,6 +54,7 @@ struct dp_option ad_basic_opts[] = {
|
|
{ "krb5_confd_path", DP_OPT_STRING, { KRB5_MAPPING_DIR }, NULL_STRING },
|
|
{ "ad_maximum_machine_account_password_age", DP_OPT_NUMBER, { .number = 30 }, NULL_NUMBER },
|
|
{ "ad_machine_account_password_renewal_opts", DP_OPT_STRING, { "86400:750" }, NULL_STRING },
|
|
+ { "ad_use_ldaps", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE },
|
|
DP_OPTION_TERMINATOR
|
|
};
|
|
|
|
diff --git a/src/providers/ad/ad_srv.c b/src/providers/ad/ad_srv.c
|
|
index 5fd25f60e..ca15d3715 100644
|
|
--- a/src/providers/ad/ad_srv.c
|
|
+++ b/src/providers/ad/ad_srv.c
|
|
@@ -244,6 +244,7 @@ struct ad_get_client_site_state {
|
|
enum host_database *host_db;
|
|
struct sdap_options *opts;
|
|
const char *ad_domain;
|
|
+ bool ad_use_ldaps;
|
|
struct fo_server_info *dcs;
|
|
size_t num_dcs;
|
|
size_t dc_index;
|
|
@@ -264,6 +265,7 @@ struct tevent_req *ad_get_client_site_send(TALLOC_CTX *mem_ctx,
|
|
enum host_database *host_db,
|
|
struct sdap_options *opts,
|
|
const char *ad_domain,
|
|
+ bool ad_use_ldaps,
|
|
struct fo_server_info *dcs,
|
|
size_t num_dcs)
|
|
{
|
|
@@ -288,6 +290,7 @@ struct tevent_req *ad_get_client_site_send(TALLOC_CTX *mem_ctx,
|
|
state->host_db = host_db;
|
|
state->opts = opts;
|
|
state->ad_domain = ad_domain;
|
|
+ state->ad_use_ldaps = ad_use_ldaps;
|
|
state->dcs = dcs;
|
|
state->num_dcs = num_dcs;
|
|
|
|
@@ -331,8 +334,11 @@ static errno_t ad_get_client_site_next_dc(struct tevent_req *req)
|
|
subreq = sdap_connect_host_send(state, state->ev, state->opts,
|
|
state->be_res->resolv,
|
|
state->be_res->family_order,
|
|
- state->host_db, "ldap", state->dc.host,
|
|
- state->dc.port, false);
|
|
+ state->host_db,
|
|
+ state->ad_use_ldaps ? "ldaps" : "ldap",
|
|
+ state->dc.host,
|
|
+ state->ad_use_ldaps ? 636 : state->dc.port,
|
|
+ false);
|
|
if (subreq == NULL) {
|
|
ret = ENOMEM;
|
|
goto done;
|
|
@@ -491,6 +497,7 @@ struct ad_srv_plugin_ctx {
|
|
const char *ad_domain;
|
|
const char *ad_site_override;
|
|
const char *current_site;
|
|
+ bool ad_use_ldaps;
|
|
};
|
|
|
|
struct ad_srv_plugin_ctx *
|
|
@@ -501,7 +508,8 @@ ad_srv_plugin_ctx_init(TALLOC_CTX *mem_ctx,
|
|
struct sdap_options *opts,
|
|
const char *hostname,
|
|
const char *ad_domain,
|
|
- const char *ad_site_override)
|
|
+ const char *ad_site_override,
|
|
+ bool ad_use_ldaps)
|
|
{
|
|
struct ad_srv_plugin_ctx *ctx = NULL;
|
|
errno_t ret;
|
|
@@ -515,6 +523,7 @@ ad_srv_plugin_ctx_init(TALLOC_CTX *mem_ctx,
|
|
ctx->be_res = be_res;
|
|
ctx->host_dbs = host_dbs;
|
|
ctx->opts = opts;
|
|
+ ctx->ad_use_ldaps = ad_use_ldaps;
|
|
|
|
ctx->hostname = talloc_strdup(ctx, hostname);
|
|
if (ctx->hostname == NULL) {
|
|
@@ -714,6 +723,7 @@ static void ad_srv_plugin_dcs_done(struct tevent_req *subreq)
|
|
state->ctx->host_dbs,
|
|
state->ctx->opts,
|
|
state->discovery_domain,
|
|
+ state->ctx->ad_use_ldaps,
|
|
dcs, num_dcs);
|
|
if (subreq == NULL) {
|
|
ret = ENOMEM;
|
|
diff --git a/src/providers/ad/ad_srv.h b/src/providers/ad/ad_srv.h
|
|
index e553d594d..8e410ec26 100644
|
|
--- a/src/providers/ad/ad_srv.h
|
|
+++ b/src/providers/ad/ad_srv.h
|
|
@@ -31,7 +31,8 @@ ad_srv_plugin_ctx_init(TALLOC_CTX *mem_ctx,
|
|
struct sdap_options *opts,
|
|
const char *hostname,
|
|
const char *ad_domain,
|
|
- const char *ad_site_override);
|
|
+ const char *ad_site_override,
|
|
+ bool ad_use_ldaps);
|
|
|
|
struct tevent_req *ad_srv_plugin_send(TALLOC_CTX *mem_ctx,
|
|
struct tevent_context *ev,
|
|
diff --git a/src/providers/ad/ad_subdomains.c b/src/providers/ad/ad_subdomains.c
|
|
index 2ce34489f..d8c201437 100644
|
|
--- a/src/providers/ad/ad_subdomains.c
|
|
+++ b/src/providers/ad/ad_subdomains.c
|
|
@@ -282,6 +282,7 @@ ad_subdom_ad_ctx_new(struct be_ctx *be_ctx,
|
|
bool use_kdcinfo = false;
|
|
size_t n_lookahead_primary = SSS_KRB5_LOOKAHEAD_PRIMARY_DEFAULT;
|
|
size_t n_lookahead_backup = SSS_KRB5_LOOKAHEAD_BACKUP_DEFAULT;
|
|
+ bool ad_use_ldaps = false;
|
|
|
|
realm = dp_opt_get_cstring(id_ctx->ad_options->basic, AD_KRB5_REALM);
|
|
hostname = dp_opt_get_cstring(id_ctx->ad_options->basic, AD_HOSTNAME);
|
|
@@ -312,6 +313,21 @@ ad_subdom_ad_ctx_new(struct be_ctx *be_ctx,
|
|
return ENOMEM;
|
|
}
|
|
|
|
+ ret = ad_inherit_opts_if_needed(id_ctx->ad_options->basic,
|
|
+ ad_options->basic,
|
|
+ be_ctx->cdb, subdom_conf_path,
|
|
+ AD_USE_LDAPS);
|
|
+ if (ret != EOK) {
|
|
+ DEBUG(SSSDBG_CRIT_FAILURE,
|
|
+ "Failed to inherit option [%s] to sub-domain [%s]. "
|
|
+ "This error is ignored but might cause issues or unexpected "
|
|
+ "behavior later on.\n",
|
|
+ id_ctx->ad_options->basic[AD_USE_LDAPS].opt_name,
|
|
+ subdom->name);
|
|
+
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
ret = ad_inherit_opts_if_needed(id_ctx->sdap_id_ctx->opts->basic,
|
|
ad_options->id->basic,
|
|
be_ctx->cdb, subdom_conf_path,
|
|
@@ -344,6 +360,7 @@ ad_subdom_ad_ctx_new(struct be_ctx *be_ctx,
|
|
|
|
servers = dp_opt_get_string(ad_options->basic, AD_SERVER);
|
|
backup_servers = dp_opt_get_string(ad_options->basic, AD_BACKUP_SERVER);
|
|
+ ad_use_ldaps = dp_opt_get_bool(ad_options->basic, AD_USE_LDAPS);
|
|
|
|
if (id_ctx->ad_options->auth_ctx != NULL
|
|
&& id_ctx->ad_options->auth_ctx->opts != NULL) {
|
|
@@ -362,7 +379,7 @@ ad_subdom_ad_ctx_new(struct be_ctx *be_ctx,
|
|
|
|
ret = ad_failover_init(ad_options, be_ctx, servers, backup_servers,
|
|
subdom->realm, service_name, gc_service_name,
|
|
- subdom->name, use_kdcinfo,
|
|
+ subdom->name, use_kdcinfo, ad_use_ldaps,
|
|
n_lookahead_primary,
|
|
n_lookahead_backup,
|
|
&ad_options->service);
|
|
@@ -386,7 +403,7 @@ ad_subdom_ad_ctx_new(struct be_ctx *be_ctx,
|
|
ad_id_ctx->ad_options->id,
|
|
hostname,
|
|
ad_domain,
|
|
- ad_site_override);
|
|
+ ad_site_override, ad_use_ldaps);
|
|
if (srv_ctx == NULL) {
|
|
DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory?\n");
|
|
return ENOMEM;
|
|
diff --git a/src/providers/ipa/ipa_subdomains_server.c b/src/providers/ipa/ipa_subdomains_server.c
|
|
index fd998877b..9aebf72a5 100644
|
|
--- a/src/providers/ipa/ipa_subdomains_server.c
|
|
+++ b/src/providers/ipa/ipa_subdomains_server.c
|
|
@@ -319,7 +319,7 @@ ipa_ad_ctx_new(struct be_ctx *be_ctx,
|
|
ret = ad_failover_init(ad_options, be_ctx, ad_servers, ad_backup_servers,
|
|
subdom->realm,
|
|
service_name, gc_service_name,
|
|
- subdom->name, use_kdcinfo,
|
|
+ subdom->name, use_kdcinfo, false,
|
|
n_lookahead_primary, n_lookahead_backup,
|
|
&ad_options->service);
|
|
if (ret != EOK) {
|
|
@@ -344,7 +344,7 @@ ipa_ad_ctx_new(struct be_ctx *be_ctx,
|
|
ad_id_ctx->ad_options->id,
|
|
id_ctx->server_mode->hostname,
|
|
ad_domain,
|
|
- ad_site_override);
|
|
+ ad_site_override, false);
|
|
if (srv_ctx == NULL) {
|
|
DEBUG(SSSDBG_FATAL_FAILURE, "Out of memory?\n");
|
|
return ENOMEM;
|
|
--
|
|
2.20.1
|
|
|