Skip to content
Merged
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
18 changes: 10 additions & 8 deletions ad_connector/ad_connector.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ struct ad_connector
gint ldap_port; /**< LDAP port number or 0 for default
(389 for LDAP, 636 for LDAPS). */
ad_connector_tls_mode_t tls_mode; /**< TLS mode to use for the connection. */
gchar *cacert_file; /**< CA Certificate, or NULL. */
gchar *ca_cert; /**< CA Certificate, or NULL. */
gchar *bind_dn; /**< Distinguished Name (DN) to bind as. */
guint network_timeout; /**< Network timeout in seconds. */
guint operation_timeout; /**< Operation timeout in seconds. */
Expand Down Expand Up @@ -109,7 +109,7 @@ static const gchar *ad_group_attributes[] = {"sAMAccountName", "displayName",
/**
* @brief Create a new Active Directory connector instance.
*
* The connector defaults to LDAPS, port 0, no CA certificate file,
* The connector defaults to LDAPS, port 0, no CA certificate,
* no bind DN, and zero network/operation timeouts.
*
* Port 0 lets the LDAP layer choose the default port for the
Expand Down Expand Up @@ -188,7 +188,7 @@ ad_connector_builder (ad_connector_t connector, ad_connector_opt_t opt,
if (connector->ldap_connection)
return AD_CONNECTOR_CONNECTION_ERROR;

if (val == NULL && opt != AD_CONNECTOR_OPT_CA_CERT_FILE)
if (val == NULL && opt != AD_CONNECTOR_OPT_CA_CERT)
return AD_CONNECTOR_INVALID_VALUE;

switch (opt)
Expand All @@ -213,9 +213,11 @@ ad_connector_builder (ad_connector_t connector, ad_connector_opt_t opt,
connector->tls_mode = tls_mode;
break;
}
case AD_CONNECTOR_OPT_CA_CERT_FILE:
g_free (connector->cacert_file);
connector->cacert_file = val ? g_strdup ((const gchar *) val) : NULL;
case AD_CONNECTOR_OPT_CA_CERT:
g_free (connector->ca_cert);
connector->ca_cert = val && *((const gchar *) val) != '\0'
? g_strdup ((const gchar *) val)
: NULL;
break;
case AD_CONNECTOR_OPT_BIND_DN:
return set_non_empty_string_value (val, &connector->bind_dn);
Expand Down Expand Up @@ -427,7 +429,7 @@ ad_connector_connect (ad_connector_t connector, const gchar *password)

ret =
gvm_ldap_open (&connector->ldap_connection, connector->ldap_host,
connector->ldap_port, connector->cacert_file, tls_mode,
connector->ldap_port, connector->ca_cert, tls_mode,
connector->network_timeout, connector->operation_timeout);

if (ret != GVM_LDAP_SUCCESS)
Expand Down Expand Up @@ -1074,7 +1076,7 @@ ad_connector_free (ad_connector_t connector)
}

g_free (connector->ldap_host);
g_free (connector->cacert_file);
g_free (connector->ca_cert);
g_free (connector->bind_dn);

g_free (connector);
Expand Down
14 changes: 7 additions & 7 deletions ad_connector/ad_connector.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@
*/
typedef enum
{
AD_CONNECTOR_OPT_HOST, /**< non-empty host/IP without port */
AD_CONNECTOR_OPT_PORT, /**< LDAP server port, 0 uses the
TLS mode default */
AD_CONNECTOR_OPT_TLS_MODE, /**< Instance of ad_connector_tls_mode_t */
AD_CONNECTOR_OPT_CA_CERT_FILE, /**< Optional CA certificate path */
AD_CONNECTOR_OPT_BIND_DN, /**< Non-empty bind name/DN for the LDAP connection
*/
AD_CONNECTOR_OPT_HOST, /**< non-empty host/IP without port */
AD_CONNECTOR_OPT_PORT, /**< LDAP server port, 0 uses the
TLS mode default */
AD_CONNECTOR_OPT_TLS_MODE, /**< Instance of ad_connector_tls_mode_t */
AD_CONNECTOR_OPT_CA_CERT, /**< Optional CA certificate in PEM format */
AD_CONNECTOR_OPT_BIND_DN, /**< Non-empty bind name/DN
for the LDAP connection */
AD_CONNECTOR_OPT_NETWORK_TIMEOUT, /**< Network timeout (seconds),
0 for no explicit timeout/default LDAP
behavior*/
Expand Down
41 changes: 32 additions & 9 deletions ad_connector/ad_connector_tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ static gint stub_bind_calls;
static gint stub_close_calls;

static gchar *stub_last_open_host;
static gchar *stub_last_open_ca_cert;
static gint stub_last_open_port;
static gvm_ldap_tls_mode_t stub_last_open_tls_mode;
static guint stub_last_open_network_timeout;
Expand All @@ -38,13 +39,14 @@ static GHashTable *stub_entry_attributes;
/* -------------------- Mock Functions -------------------- */
gvm_ldap_return_t
gvm_ldap_open (gvm_ldap_connection_t **connection, const gchar *host, gint port,
const gchar *cacert_file, gvm_ldap_tls_mode_t tls_mode,
const gchar *ca_cert, gvm_ldap_tls_mode_t tls_mode,
guint network_timeout, guint operation_timeout)
{
(void) cacert_file;
stub_open_calls++;
g_free (stub_last_open_host);
stub_last_open_host = g_strdup (host);
g_free (stub_last_open_ca_cert);
stub_last_open_ca_cert = g_strdup (ca_cert);
stub_last_open_port = port;
stub_last_open_tls_mode = tls_mode;
stub_last_open_network_timeout = network_timeout;
Expand Down Expand Up @@ -155,6 +157,8 @@ reset_ldap_stubs (void)

g_free (stub_last_open_host);
stub_last_open_host = NULL;
g_free (stub_last_open_ca_cert);
stub_last_open_ca_cert = NULL;
stub_last_open_port = 0;
stub_last_open_tls_mode = GVM_LDAP_TLS_LDAPS;
stub_last_open_network_timeout = 0;
Expand Down Expand Up @@ -361,6 +365,9 @@ AfterEach (ad_connector)
g_free (stub_last_open_host);
stub_last_open_host = NULL;

g_free (stub_last_open_ca_cert);
stub_last_open_ca_cert = NULL;

g_free (stub_last_bind_dn);
stub_last_bind_dn = NULL;

Expand Down Expand Up @@ -448,23 +455,31 @@ Ensure (ad_connector, builder_validates_non_negative_timeouts)
ad_connector_free (connector);
}

Ensure (ad_connector, builder_allows_null_ca_cert_file)
Ensure (ad_connector, builder_accepts_ca_cert_and_allows_null_or_empty)
{
ad_connector_t connector;
const gchar *ca = "/tmp/test-ca.pem";
const gchar *ca_cert = "-----BEGIN CERTIFICATE-----\n"
"mock certificate data\n"
"-----END CERTIFICATE-----\n";

connector = ad_connector_new ();
assert_that (connector, is_not_null);

assert_that (
ad_connector_builder (connector, AD_CONNECTOR_OPT_CA_CERT_FILE, ca),
ad_connector_builder (connector, AD_CONNECTOR_OPT_CA_CERT, ca_cert),
is_equal_to (AD_CONNECTOR_OK));
assert_that (connector->cacert_file, is_equal_to_string ("/tmp/test-ca.pem"));
assert_that (connector->ca_cert, is_equal_to_string (ca_cert));

assert_that (ad_connector_builder (connector, AD_CONNECTOR_OPT_CA_CERT, ""),
is_equal_to (AD_CONNECTOR_OK));
assert_that (connector->ca_cert, is_null);

assert_that (
ad_connector_builder (connector, AD_CONNECTOR_OPT_CA_CERT_FILE, NULL),
ad_connector_builder (connector, AD_CONNECTOR_OPT_CA_CERT, ca_cert),
is_equal_to (AD_CONNECTOR_OK));
assert_that (connector->cacert_file, is_null);
assert_that (ad_connector_builder (connector, AD_CONNECTOR_OPT_CA_CERT, NULL),
is_equal_to (AD_CONNECTOR_OK));
assert_that (connector->ca_cert, is_null);

ad_connector_free (connector);
}
Expand Down Expand Up @@ -816,11 +831,17 @@ Ensure (ad_connector, connect_succeeds_with_valid_configuration)
{
ad_connector_t connector = ad_connector_new ();
ad_connector_tls_mode_t starttls = AD_CONNECTOR_TLS_STARTTLS;
const gchar *ca_cert = "-----BEGIN CERTIFICATE-----\n"
"mock certificate data\n"
"-----END CERTIFICATE-----\n";
gint port = 1389;
assert_that (connector, is_not_null);

set_valid_connection_config (connector);

assert_that (
ad_connector_builder (connector, AD_CONNECTOR_OPT_CA_CERT, ca_cert),
is_equal_to (AD_CONNECTOR_OK));
assert_that (ad_connector_builder (connector, AD_CONNECTOR_OPT_PORT, &port),
is_equal_to (AD_CONNECTOR_OK));
assert_that (
Expand All @@ -835,6 +856,7 @@ Ensure (ad_connector, connect_succeeds_with_valid_configuration)

assert_that (connector->ldap_connection, is_equal_to (stub_open_connection));
assert_that (stub_last_open_host, is_equal_to_string ("ad.example.org"));
assert_that (stub_last_open_ca_cert, is_equal_to_string (ca_cert));
assert_that (stub_last_open_port, is_equal_to (1389));
assert_that (stub_last_open_tls_mode, is_equal_to (GVM_LDAP_TLS_STARTTLS));
assert_that (stub_last_open_network_timeout, is_equal_to (12));
Expand Down Expand Up @@ -1557,7 +1579,8 @@ main (int argc, char **argv)
builder_rejects_empty_host_or_bind_dn);
add_test_with_context (suite, ad_connector,
builder_validates_non_negative_timeouts);
add_test_with_context (suite, ad_connector, builder_allows_null_ca_cert_file);
add_test_with_context (suite, ad_connector,
builder_accepts_ca_cert_and_allows_null_or_empty);
add_test_with_context (suite, ad_connector,
builder_rejects_changes_when_connection_exists);

Expand Down
128 changes: 112 additions & 16 deletions ldap/gvmldap.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@

#include "gvmldap.h"

#include <errno.h>
#include <glib/gstdio.h>
#include <ldap.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#define LDAP_DEFAULT_PORT 389
#define LDAPS_DEFAULT_PORT 636
Expand Down Expand Up @@ -108,41 +112,133 @@ ldap_set_timeout_option (LDAP *ldap, int option, guint timeout_seconds)
return ldap_set_option (ldap, option, &tv);
}

/**
* @brief Writes the CA certificate to a temporary file.
*
* @param ca_cert CA certificate content.
*
* @return Path to the temporary file containing the CA certificate,
* or NULL on failure. The caller should call g_unlink() and g_free()
* on the returned path after use.
*/
static gchar *
ldap_write_ca_cert_tmp (const gchar *ca_cert)
{
GError *error = NULL;
gchar *path = NULL;
gint fd;
gsize remaining = 0;
const gchar *p = NULL;

if (ca_cert == NULL || *ca_cert == '\0')
return NULL;

fd = g_file_open_tmp ("gvmldap-ca-XXXXXX", &path, &error);
if (fd < 0)
{
g_warning ("Could not create temporary LDAP CA file: %s", error->message);
g_clear_error (&error);
return NULL;
}

if (fchmod (fd, 0600) != 0)
{
g_warning ("Could not set LDAP CA file permissions: %s",
g_strerror (errno));
goto fail;
}

p = ca_cert;
remaining = strlen (ca_cert);

while (remaining > 0)
{
ssize_t written = write (fd, p, remaining);

if (written < 0 && errno == EINTR)
continue;
if (written <= 0)
{
g_warning ("Could not write temporary LDAP CA file: %s",
written < 0 ? g_strerror (errno)
: "write made no progress");
goto fail;
}

p += written;
remaining -= written;
}

if (close (fd) != 0)
{
g_warning ("Could not close temporary LDAP CA file: %s",
g_strerror (errno));
fd = -1;
goto fail;
}

return path;

fail:
if (fd >= 0)
close (fd);

if (g_unlink (path) != 0)
g_warning ("Could not remove temporary LDAP CA file: %s",
g_strerror (errno));

g_free (path);
return NULL;
}

/*
* @brief Configure TLS options for an LDAP connection.
*
* @param ldap LDAP connection.
* @param ca_cert_file Path to the CA certificate file for TLS, can be
* NULL.
* @param ca_cert CA certificate in PEM format for TLS, can be NULL.
*
* @return 0 on success, -1 on failure.
*/
static int
ldap_configure_tls (LDAP *ldap, const gchar *ca_cert_file)
ldap_configure_tls (LDAP *ldap, const gchar *ca_cert)
{
int require_cert = LDAP_OPT_X_TLS_DEMAND;
int new_ctx = 0;
int ret;
gchar *ca_path = NULL;
int ret = -1;

if (!ldap)
return -1;

ret = ldap_set_option (ldap, LDAP_OPT_X_TLS_REQUIRE_CERT, &require_cert);
if (ret != LDAP_OPT_SUCCESS)
if (ldap_set_option (ldap, LDAP_OPT_X_TLS_REQUIRE_CERT, &require_cert)
!= LDAP_OPT_SUCCESS)
return -1;

if (ca_cert_file && ca_cert_file[0] != '\0')
if (ca_cert && ca_cert[0] != '\0')
{
ret = ldap_set_option (ldap, LDAP_OPT_X_TLS_CACERTFILE, ca_cert_file);
if (ret != LDAP_OPT_SUCCESS)
ca_path = ldap_write_ca_cert_tmp (ca_cert);
if (ca_path == NULL)
return -1;

if (ldap_set_option (ldap, LDAP_OPT_X_TLS_CACERTFILE, ca_path)
!= LDAP_OPT_SUCCESS)
goto cleanup;
}

ret = ldap_set_option (ldap, LDAP_OPT_X_TLS_NEWCTX, &new_ctx);
if (ret != LDAP_OPT_SUCCESS)
return -1;
if (ldap_set_option (ldap, LDAP_OPT_X_TLS_NEWCTX, &new_ctx)
== LDAP_OPT_SUCCESS)
ret = 0;

return 0;
cleanup:
if (ca_path != NULL)
{
if (g_unlink (ca_path) != 0)
g_warning ("Could not remove temporary LDAP CA file: %s",
g_strerror (errno));
g_free (ca_path);
}

return ret;
}

/**
Expand All @@ -151,7 +247,7 @@ ldap_configure_tls (LDAP *ldap, const gchar *ca_cert_file)
* @param connection Pointer to the LDAP connection structure.
* @param host The LDAP server host.
* @param port The LDAP server port, 0 to use the mode's default.
* @param ca_cert_file Path to the CA certificate file for TLS.
* @param ca_cert CA certificate for TLS in PEM format.
* @param tls_mode The TLS mode to use (PLAINTEXT, STARTTLS, or TLS).
* @param network_timeout Network timeout in seconds.
* @param operation_timeout Operation timeout in seconds.
Expand All @@ -161,7 +257,7 @@ ldap_configure_tls (LDAP *ldap, const gchar *ca_cert_file)
*/
gvm_ldap_return_t
gvm_ldap_open (gvm_ldap_connection_t **connection, const gchar *host, gint port,
const gchar *ca_cert_file, gvm_ldap_tls_mode_t tls_mode,
const gchar *ca_cert, gvm_ldap_tls_mode_t tls_mode,
guint network_timeout, guint operation_timeout)
{
gvm_ldap_connection_t *new_connection;
Expand Down Expand Up @@ -218,7 +314,7 @@ gvm_ldap_open (gvm_ldap_connection_t **connection, const gchar *host, gint port,

if (tls_mode != GVM_LDAP_TLS_PLAINTEXT)
{
if (ldap_configure_tls (new_connection->ldap, ca_cert_file) != 0)
if (ldap_configure_tls (new_connection->ldap, ca_cert) != 0)
{
g_warning ("%s: Failed to configure LDAP TLS", __func__);
goto option_error;
Expand Down
Loading
Loading