1
0
mirror of https://github.com/altlinux/admc.git synced 2025-03-23 18:50:26 +03:00

merge adldap into adinterface

lots of adldap f-ns were just wrapping calls to ldap lib
also adldap code was held back by lack of string/list class
This commit is contained in:
Dmitry Degtyarev 2021-01-12 14:55:47 +04:00
parent 5722ebcc27
commit f9a9e1de49
11 changed files with 364 additions and 800 deletions

View File

@ -62,7 +62,6 @@ cd BUILD
%files
%doc README.md
%_bindir/admc
%_libdir/libadldap.so
%files gpgui
%_bindir/gpgui

View File

@ -6,5 +6,4 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_compile_options(-Wall -Wshadow -Werror=switch)
add_subdirectory(admc)
add_subdirectory(adldap)
add_subdirectory(gpgui)

View File

@ -1,29 +0,0 @@
option(ENABLE_TESTS "Enable unit tests for libadldap" ON)
find_package(Ldap REQUIRED)
find_package(Resolv REQUIRED)
add_library(adldap SHARED
active_directory.c
)
target_include_directories(adldap
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(adldap
PUBLIC
Ldap::Ldap
Resolv::Resolv
)
install(TARGETS adldap)
if(ENABLE_TESTS)
find_package(CMocka CONFIG REQUIRED)
include(AddCMockaTest)
include(AddMockedTest)
enable_testing()
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/test)
endif()

View File

@ -1,549 +0,0 @@
/**
* Copyright (c) by: Mike Dawson mike _at_ no spam gp2x.org
* Copyright (C) 2020 BaseALT Ltd.
*
* This file may be used subject to the terms and conditions of the
* GNU Library General Public License Version 2, or any later version
* at your option, as published by the Free Software Foundation.
* 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 Library General Public License for more details.
*
**/
#include "active_directory.h"
#include <ldap.h>
#include <sasl/sasl.h>
#include <lber.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <resolv.h>
#include <stdbool.h>
// NOTE: LDAP library char* inputs are non-const in the API but are
// actually const so we opt to discard const qualifiers rather
// than allocate copies
#ifdef __GNUC__
# define UNUSED(x) x __attribute__((unused))
#else
# define UNUSED(x) x
#endif
#define MAX_DN_LENGTH 1024
#define MAX_PASSWORD_LENGTH 255
typedef struct sasl_defaults_gssapi {
char *mech;
char *realm;
char *authcid;
char *passwd;
char *authzid;
} sasl_defaults_gssapi;
int sasl_interact_gssapi(LDAP *ld, unsigned flags, void *indefaults, void *in);
int query_server_for_hosts(const char *dname, char ***hosts_out);
int ad_get_ldap_result(LDAP *ld) {
int result;
ldap_get_option(ld, LDAP_OPT_RESULT_CODE, &result);
return result;
}
int ad_get_domain_hosts(const char *domain, const char *site, char ***hosts_out) {
int result = AD_SUCCESS;
char **hosts = NULL;
char **site_hosts = NULL;
char **default_hosts = NULL;
// TODO: confirm site query is formatted properly, currently getting no answer back (might be working as intended, since tested on domain without sites?)
// Query site hosts
if (site != NULL && strlen(site) > 0) {
char dname[1000];
snprintf(dname, sizeof(dname), "_ldap._tcp.%s._sites.%s", site, domain);
int query_result = query_server_for_hosts(dname, &site_hosts);
if (query_result != AD_SUCCESS) {
result = AD_ERROR;
goto end;
}
}
const size_t site_hosts_size = ad_array_size(site_hosts);
// Query default hosts
char dname_default[1000];
snprintf(dname_default, sizeof(dname_default), "_ldap._tcp.%s", domain);
int query_result = query_server_for_hosts(dname_default, &default_hosts);
if (query_result != AD_SUCCESS) {
result = AD_ERROR;
goto end;
}
const size_t default_hosts_size = ad_array_size(default_hosts);
// Combine site and default hosts
const int hosts_max_size = site_hosts_size + default_hosts_size + 1;
hosts = calloc(hosts_max_size, sizeof(char *));
size_t hosts_current_i = 0;
// Load all site hosts first
for (int i = 0; i < site_hosts_size; i++) {
char *site_host = site_hosts[i];
hosts[hosts_current_i] = strdup(site_host);
hosts_current_i++;
}
// Add default hosts that aren't already in list
for (int i = 0; i < default_hosts_size; i++) {
char *default_host = default_hosts[i];
bool already_in_list = false;
for (int j = 0; j < hosts_current_i; j++) {
char *other_host = hosts[j];
if (strcmp(default_host, other_host) == 0) {
already_in_list = true;
break;
}
}
if (!already_in_list) {
hosts[hosts_current_i] = strdup(default_host);
hosts_current_i++;
}
}
end:
{
ad_array_free(site_hosts);
ad_array_free(default_hosts);
if (result == AD_SUCCESS) {
*hosts_out = hosts;
} else {
*hosts_out = NULL;
ad_array_free(hosts);
}
return result;
}
}
int ad_connect(const char* uri, LDAP **ld_out) {
int result = AD_SUCCESS;
LDAP *ld = NULL;
const int result_init = ldap_initialize(&ld, uri);
if (result_init != LDAP_SUCCESS) {
result = AD_LDAP_ERROR;
goto end;
}
// Set version
const int version = LDAP_VERSION3;
const int result_set_version = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version);
if (result_set_version != LDAP_OPT_SUCCESS) {
result = AD_LDAP_ERROR;
goto end;
}
// Disable referrals
const int result_referral =ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
if (result_referral != LDAP_OPT_SUCCESS) {
result = AD_LDAP_ERROR;
goto end;
}
// Set maxssf
const char* sasl_secprops = "maxssf=56";
const int result_secprops = ldap_set_option(ld, LDAP_OPT_X_SASL_SECPROPS, sasl_secprops);
if (result_secprops != LDAP_SUCCESS) {
result = AD_LDAP_ERROR;
goto end;
}
ldap_set_option(ld, LDAP_OPT_X_SASL_NOCANON, LDAP_OPT_ON);
// TODO: add option to turn off
ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER);
// Setup sasl_defaults_gssapi
struct sasl_defaults_gssapi defaults;
defaults.mech = "GSSAPI";
ldap_get_option(ld, LDAP_OPT_X_SASL_REALM, &defaults.realm);
ldap_get_option(ld, LDAP_OPT_X_SASL_AUTHCID, &defaults.authcid);
ldap_get_option(ld, LDAP_OPT_X_SASL_AUTHZID, &defaults.authzid);
defaults.passwd = NULL;
// Perform bind operation
unsigned sasl_flags = LDAP_SASL_QUIET;
const int result_sasl = ldap_sasl_interactive_bind_s(ld, NULL,defaults.mech, NULL, NULL, sasl_flags, sasl_interact_gssapi, &defaults);
ldap_memfree(defaults.realm);
ldap_memfree(defaults.authcid);
ldap_memfree(defaults.authzid);
if (result_sasl != LDAP_SUCCESS) {
result = AD_LDAP_ERROR;
goto end;
}
// NOTE: not using this for now but might need later
// The Man says: this function is used when an application needs to bind to another server in order to follow a referral or search continuation reference
// ldap_set_rebind_proc(ld, sasl_rebind_gssapi, NULL);
end:
{
if (result == AD_SUCCESS) {
*ld_out = ld;
} else {
ldap_memfree(ld);
*ld_out = NULL;
}
return result;
}
}
size_t ad_array_size(char **array) {
if (array == NULL) {
return 0;
} else {
size_t count = 0;
for (int i = 0; array[i] != NULL; i++) {
count++;
}
return count;
}
}
void ad_array_free(char **array) {
if (array != NULL) {
for (int i = 0; array[i] != NULL; i++) {
free(array[i]);
}
free(array);
}
}
int ad_add(LDAP *ld, const char *dn, const char **objectClass) {
LDAPMod attr;
attr.mod_op = LDAP_MOD_ADD;
attr.mod_type = "objectClass";
attr.mod_values = (char **)objectClass;
LDAPMod *attrs[] = {&attr, NULL};
const int result_add = ldap_add_ext_s(ld, dn, attrs, NULL, NULL);
if (result_add != LDAP_SUCCESS) {
return AD_LDAP_ERROR;
} else {
return AD_SUCCESS;
}
}
int ad_delete(LDAP *ld, const char *dn) {
int result = AD_SUCCESS;
const int result_delete = ldap_delete_ext_s(ld, dn, NULL, NULL);
if (result_delete != LDAP_SUCCESS) {
result = AD_LDAP_ERROR;
}
return result;
}
int ad_attribute_add_value(LDAP *ld, const char *dn, const char *attribute, const char *data, int data_length) {
int result = AD_SUCCESS;
char *data_copy = strdup(data);
struct berval ber_data;
ber_data.bv_val = data_copy;
ber_data.bv_len = data_length;
struct berval *values[] = {&ber_data, NULL};
LDAPMod attr;
attr.mod_op = LDAP_MOD_ADD | LDAP_MOD_BVALUES;
attr.mod_type = (char *)attribute;
attr.mod_bvalues = values;
LDAPMod *attrs[] = {&attr, NULL};
const int result_modify = ldap_modify_ext_s(ld, dn, attrs, NULL, NULL);
if (result_modify != LDAP_SUCCESS) {
result = AD_LDAP_ERROR;
goto end;
}
end:
{
free(data_copy);
return result;
}
}
int ad_attribute_delete_value(LDAP *ld, const char *dn, const char *attribute, const char *data, const int data_length) {
int result = AD_SUCCESS;
char *data_copy = (char *) malloc(data_length);
memcpy(data_copy, data, data_length);
struct berval ber_data;
ber_data.bv_val = data_copy;
ber_data.bv_len = data_length;
LDAPMod attr;
struct berval *values[] = {&ber_data, NULL};
attr.mod_op = LDAP_MOD_DELETE | LDAP_MOD_BVALUES;
attr.mod_type = (char *)attribute;
attr.mod_bvalues = values;
LDAPMod *attrs[] = {&attr, NULL};
const int result_modify = ldap_modify_ext_s(ld, dn, attrs, NULL, NULL);
if (result_modify != LDAP_SUCCESS) {
result = AD_LDAP_ERROR;
}
free(data_copy);
return result;
}
int ad_rename(LDAP *ld, const char *dn, const char *new_rdn) {
int result = AD_SUCCESS;
const int result_rename = ldap_rename_s(ld, dn, new_rdn, NULL, 1, NULL, NULL);
if (result_rename != LDAP_SUCCESS) {
result = AD_LDAP_ERROR;
}
return result;
}
int ad_move(LDAP *ld, const char *current_dn, const char *new_container) {
int result = AD_SUCCESS;
char *rdn = strdup(current_dn);
char *comma_ptr = strchr(rdn, ',');
if (comma_ptr == NULL) {
// Failed to extract RDN from DN
result = AD_INVALID_DN;
goto end;
}
*comma_ptr = '\0';
const int result_rename = ldap_rename_s(ld, current_dn, rdn, new_container, 1, NULL, NULL);
if (result_rename != LDAP_SUCCESS) {
result = AD_LDAP_ERROR;
goto end;
}
end:
{
free(rdn);
return result;
}
}
/**
* Callback for ldap_sasl_interactive_bind_s
*/
int sasl_interact_gssapi(LDAP *ld, unsigned flags, void *indefaults, void *in) {
sasl_defaults_gssapi *defaults = indefaults;
sasl_interact_t *interact = (sasl_interact_t*)in;
if (ld == NULL) {
return LDAP_PARAM_ERROR;
}
while (interact->id != SASL_CB_LIST_END) {
const char *dflt = interact->defresult;
switch (interact->id) {
case SASL_CB_GETREALM:
if (defaults)
dflt = defaults->realm;
break;
case SASL_CB_AUTHNAME:
if (defaults)
dflt = defaults->authcid;
break;
case SASL_CB_PASS:
if (defaults)
dflt = defaults->passwd;
break;
case SASL_CB_USER:
if (defaults)
dflt = defaults->authzid;
break;
case SASL_CB_NOECHOPROMPT:
break;
case SASL_CB_ECHOPROMPT:
break;
}
if (dflt && !*dflt) {
dflt = NULL;
}
/* input must be empty */
interact->result = (dflt && *dflt) ? dflt : "";
interact->len = strlen(interact->result);
interact++;
}
return LDAP_SUCCESS;
}
/**
* Perform a query for dname and output hosts
* dname is a combination of protocols (ldap, tcp), domain and site
* NOTE: this is rewritten from
* https://github.com/paleg/libadclient/blob/master/adclient.cpp
* which itself is copied from
* https://www.ccnx.org/releases/latest/doc/ccode/html/ccndc-srv_8c_source.html
* Another example of similar procedure:
* https://www.gnu.org/software/shishi/coverage/shishi/lib/resolv.c.gcov.html
*/
int query_server_for_hosts(const char *dname, char ***hosts_out) {
union dns_msg {
HEADER header;
unsigned char buf[NS_MAXMSG];
} msg;
int result = AD_SUCCESS;
char **hosts = NULL;
const int msg_len = res_search(dname, ns_c_in, ns_t_srv, msg.buf, sizeof(msg.buf));
if (msg_len < 0 || msg_len < sizeof(HEADER)) {
result = AD_ERROR;
goto end;
}
const int packet_count = ntohs(msg.header.qdcount);
const int answer_count = ntohs(msg.header.ancount);
unsigned char *curr = msg.buf + sizeof(msg.header);
const unsigned char *eom = msg.buf + msg_len;
// Skip over packet records
for (int i = packet_count; i > 0 && curr < eom; i--) {
const int packet_len = dn_skipname(curr, eom);
if (packet_len < 0) {
result = AD_ERROR;
goto end;
}
curr = curr + packet_len + QFIXEDSZ;
}
// Init hosts list
const size_t hosts_size = answer_count + 1;
hosts = calloc(hosts_size, sizeof(char *));
// Process answers by collecting hosts into list
size_t hosts_current_i = 0;
for (int i = 0; i < answer_count; i++) {
// Get server
char server[NS_MAXDNAME];
const int server_len = dn_expand(msg.buf, eom, curr, server, sizeof(server));
if (server_len < 0) {
result = AD_ERROR;
goto end;
}
curr = curr + server_len;
int record_type;
int UNUSED(record_class);
int UNUSED(ttl);
int record_len;
GETSHORT(record_type, curr);
GETSHORT(record_class, curr);
GETLONG(ttl, curr);
GETSHORT(record_len, curr);
unsigned char *record_end = curr + record_len;
if (record_end > eom) {
result = AD_ERROR;
goto end;
}
// Skip non-server records
if (record_type != ns_t_srv) {
curr = record_end;
continue;
}
int UNUSED(priority);
int UNUSED(weight);
int UNUSED(port);
GETSHORT(priority, curr);
GETSHORT(weight, curr);
GETSHORT(port, curr);
// TODO: need to save port field? maybe to incorporate into uri
// Get host
char host[NS_MAXDNAME];
const int host_len = dn_expand(msg.buf, eom, curr, host, sizeof(host));
if (host_len < 0) {
result = AD_ERROR;
goto end;
}
hosts[hosts_current_i] = strdup(host);
hosts_current_i++;
curr = record_end;
}
end:
{
if (result == AD_SUCCESS) {
*hosts_out = hosts;
} else {
*hosts_out = NULL;
ad_array_free(hosts);
}
return result;
}
}

View File

@ -1,108 +0,0 @@
/**
* Copyright (c) by: Mike Dawson mike _at_ no spam gp2x.org
*
* This file may be used subject to the terms and conditions of the
* GNU Library General Public License Version 2, or any later version
* at your option, as published by the Free Software Foundation.
* 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 Library General Public License for more details.
*
**/
#include <ldap.h>
#include <stdbool.h>
#ifndef ACTIVE_DIRECTORY_H
#define ACTIVE_DIRECTORY_H 1
// Result codes
#define AD_SUCCESS 0
#define AD_ERROR 1
#define AD_LDAP_ERROR 2
#define AD_INVALID_DN 3
#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */
/**
* Return a result code from last LDAP operation
* When a library function returns AD_LDAP_ERROR, use this to get
* the ldap error code
*/
int ad_get_ldap_result(LDAP *ld);
/**
* Output a list of hosts that exist for given domain and site
* list is NULL terminated
* list should be freed by the caller using ad_array_free()
* Returns AD_SUCCESS, AD_ERROR
*/
int ad_get_domain_hosts(const char *domain, const char *site, char ***hosts_out);
/**
* Connect and authenticate to Active Directory server
* If connected succesfully saves connection handle into ds
* Returns AD_SUCCESS, AD_LDAP_ERROR
*/
int ad_connect(const char* uri, LDAP **ld_out);
/**
* Calculate size of null-terminated array by iterating through it
*/
size_t ad_array_size(char **array);
/**
* Free a null-terminated array that was returned by one of
* the functions in this library
* If array is NULL, nothing is done
*/
void ad_array_free(char **array);
/**
* Adds an object with given DN and objectClass
* objectClass is a NULL terminated array of objectClass values
* All ancestors of object must already exist
* Returns AD_SUCCESS, AD_LDAP_ERROR
*/
int ad_add(LDAP *ld, const char *dn, const char **objectClass);
/**
* Delete object
* Returns AD_SUCCESS, AD_LDAP_ERROR
*/
int ad_delete(LDAP *ld, const char *dn);
/**
* Adds a value to given attribute
* This function works only on multi-valued attributes
* Returns AD_SUCCESS, AD_LDAP_ERROR
*/
int ad_attribute_add_value(LDAP *ld, const char *dn, const char *attribute, const char *data, int data_length);
/**
* Remove (attribute, value) mapping from object
* If given value is NULL, remove all values of this attributes
* Returns AD_SUCCESS, AD_LDAP_ERROR
*/
int ad_attribute_delete_value(LDAP *ld, const char *dn, const char *attribute, const char *data, const int data_length);
/**
* Rename object
* Returns AD_SUCCESS, AD_LDAP_ERROR
*/
int ad_rename(LDAP *ld, const char *dn, const char *new_rdn);
/**
* Move object
* Returns AD_SUCCESS, AD_LDAP_ERROR
*/
int ad_move(LDAP *ld, const char *current_dn, const char *new_container);
#if defined(__cplusplus)
}
#endif /* __cplusplus */
#endif /* ACTIVE_DIRECTORY_H */

View File

@ -1,5 +0,0 @@
add_mocked_test(adldap
SOURCES test_adldap.c
COMPILE_OPTIONS -g3 -I${CMAKE_CURRENT_SOURCE_DIR}/..
LINK_LIBRARIES adldap)

Binary file not shown.

View File

@ -1,46 +0,0 @@
/*
* ADMC - AD Management Center
*
* Copyright (C) 2020 BaseALT Ltd.
*
* 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 <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <stdint.h>
#include <cmocka.h>
#include <stdlib.h>
#include "active_directory.h"
/* This function is not present in header files */
size_t ad_array_size(char **array);
static void test_ad_array_size(void **state) {
char *test_array[] = {"1", "2", "3", NULL};
size_t size = ad_array_size((char **)test_array);
assert_true(size == 3);
}
int main(void) {
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_ad_array_size)
};
return cmocka_run_group_tests(tests, NULL, NULL);
}

View File

@ -7,6 +7,8 @@ find_package(Qt5 REQUIRED
find_package(Uuid REQUIRED)
find_package(Smbclient REQUIRED)
find_package(Krb5 REQUIRED)
find_package(Ldap REQUIRED)
find_package(Resolv REQUIRED)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
@ -111,10 +113,11 @@ target_include_directories(admc
target_link_libraries(admc
Qt5::Core
Qt5::Widgets
adldap
Uuid::Uuid
Smbclient::Smbclient
Krb5::Krb5
Ldap::Ldap
Resolv::Resolv
)
install(TARGETS admc)

View File

@ -18,7 +18,7 @@
*/
#include "ad_interface.h"
#include "active_directory.h"
#include "ad_utils.h"
#include "ad_config.h"
#include "ad_object.h"
@ -28,15 +28,44 @@
#include <ldap.h>
#include <lber.h>
#include <resolv.h>
#include <libsmbclient.h>
#include <uuid/uuid.h>
#include <krb5.h>
#include <sasl/sasl.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <QTextCodec>
#include <QDebug>
#include <QCoreApplication>
// NOTE: LDAP library char* inputs are non-const in the API
// but are const for practical purposes so we use forced
// casts (const char *) -> (char *)
#ifdef __GNUC__
# define UNUSED(x) x __attribute__((unused))
#else
# define UNUSED(x) x
#endif
#define MAX_DN_LENGTH 1024
#define MAX_PASSWORD_LENGTH 255
typedef struct sasl_defaults_gssapi {
char *mech;
char *realm;
char *authcid;
char *passwd;
char *authzid;
} sasl_defaults_gssapi;
QList<QString> get_domain_hosts(const QString &domain, const QString &site);
QList<QString> query_server_for_hosts(const char *dname);
bool ad_connect(const char* uri, LDAP **ld_out);
int sasl_interact_gssapi(LDAP *ld, unsigned flags, void *indefaults, void *in);
AdInterface *AdInterface::instance() {
static AdInterface ad_interface;
@ -106,9 +135,9 @@ bool AdInterface::connect() {
m_configuration_dn = "CN=Configuration," + m_domain_head;
m_schema_dn = "CN=Schema," + m_configuration_dn;
const int result = ad_connect(cstr(uri), &ld);
const bool success = ad_connect(cstr(uri), &ld);
if (result == AD_SUCCESS) {
if (success) {
m_config = new AdConfig(this);
// TODO: can this context expire, for example from a disconnect?
@ -118,7 +147,7 @@ bool AdInterface::connect() {
smbc_setOptionFallbackAfterKerberos(smbc, true);
if (!smbc_init_context(smbc)) {
smbc_free_context(smbc, 0);
printf("Could not initialize smbc context\n");
qDebug() << "Could not initialize smbc context";
}
smbc_set_context(smbc);
@ -334,7 +363,12 @@ QHash<QString, AdObject> AdInterface::search(const QString &filter, const QList<
}
};
ad_array_free(attrs);
if (attrs != NULL) {
for (int i = 0; attrs[i] != NULL; i++) {
free(attrs[i]);
}
free(attrs);
}
ber_bvfree(prev_cookie);
@ -352,10 +386,11 @@ AdObject AdInterface::search_object(const QString &dn, const QList<QString> &att
}
bool AdInterface::attribute_replace_values(const QString &dn, const QString &attribute, const QList<QByteArray> &values, const DoStatusMsg do_msg) {
int result = AD_SUCCESS;
const AdObject object = search_object(dn, {attribute});
const QList<QByteArray> old_values = object.get_values(attribute);
const QString name = dn_get_name(dn);
const QString values_display = attribute_display_values(attribute, values);
const QString old_values_display = attribute_display_values(attribute, old_values);
// Do nothing if both new and old values are empty
if (old_values.isEmpty() && values.isEmpty()) {
@ -383,16 +418,9 @@ bool AdInterface::attribute_replace_values(const QString &dn, const QString &att
LDAPMod *attrs[] = {&attr, NULL};
const int result_modify = ldap_modify_ext_s(ld, cstr(dn), attrs, NULL, NULL);
if (result_modify != LDAP_SUCCESS) {
result = AD_LDAP_ERROR;
}
const int result = ldap_modify_ext_s(ld, cstr(dn), attrs, NULL, NULL);
const QString name = dn_get_name(dn);
const QString values_display = attribute_display_values(attribute, values);
const QString old_values_display = attribute_display_values(attribute, old_values);
if (result == AD_SUCCESS) {
if (result == LDAP_SUCCESS) {
success_status_message(QString(tr("Changed attribute \"%1\" of object \"%2\" from \"%3\" to \"%4\"")).arg(attribute, name, old_values_display, values_display), do_msg);
emit object_changed(dn);
@ -421,14 +449,29 @@ bool AdInterface::attribute_replace_value(const QString &dn, const QString &attr
}
bool AdInterface::attribute_add_value(const QString &dn, const QString &attribute, const QByteArray &value, const DoStatusMsg do_msg) {
const int result = ad_attribute_add_value(ld, cstr(dn), cstr(attribute), value.constData(), value.size());
char *data_copy = (char *) malloc(value.size());
memcpy(data_copy, value.constData(), value.size());
struct berval ber_data;
ber_data.bv_val = data_copy;
ber_data.bv_len = value.size();
struct berval *values[] = {&ber_data, NULL};
LDAPMod attr;
attr.mod_op = LDAP_MOD_ADD | LDAP_MOD_BVALUES;
attr.mod_type = (char *)cstr(attribute);
attr.mod_bvalues = values;
LDAPMod *attrs[] = {&attr, NULL};
const int result = ldap_modify_ext_s(ld, cstr(dn), attrs, NULL, NULL);
free(data_copy);
const QString name = dn_get_name(dn);
const QString new_display_value = attribute_display_value(attribute, value);
;
if (result == AD_SUCCESS) {
if (result == LDAP_SUCCESS) {
const QString context = QString(tr("Added value \"%1\" for attribute \"%2\" of object \"%3\"")).arg(new_display_value, attribute, name);
success_status_message(context, do_msg);
@ -446,13 +489,28 @@ bool AdInterface::attribute_add_value(const QString &dn, const QString &attribut
}
bool AdInterface::attribute_delete_value(const QString &dn, const QString &attribute, const QByteArray &value, const DoStatusMsg do_msg) {
const int result = ad_attribute_delete_value(ld, cstr(dn), cstr(attribute), value.constData(), value.size());
const QString name = dn_get_name(dn);
const QString value_display = attribute_display_value(attribute, value);
if (result == AD_SUCCESS) {
char *data_copy = (char *) malloc(value.size());
memcpy(data_copy, value.constData(), value.size());
struct berval ber_data;
ber_data.bv_val = data_copy;
ber_data.bv_len = value.size();
LDAPMod attr;
struct berval *values[] = {&ber_data, NULL};
attr.mod_op = LDAP_MOD_DELETE | LDAP_MOD_BVALUES;
attr.mod_type = (char *)cstr(attribute);
attr.mod_bvalues = values;
LDAPMod *attrs[] = {&attr, NULL};
const int result = ldap_modify_ext_s(ld, cstr(dn), attrs, NULL, NULL);
free(data_copy);
if (result == LDAP_SUCCESS) {
const QString context = QString(tr("Deleted value \"%1\" for attribute \"%2\" of object \"%3\"")).arg(value_display, attribute, name);
success_status_message(context, do_msg);
@ -492,9 +550,16 @@ bool AdInterface::attribute_replace_datetime(const QString &dn, const QString &a
bool AdInterface::object_add(const QString &dn, const QString &object_class) {
const char *classes[2] = {cstr(object_class), NULL};
const int result = ad_add(ld, cstr(dn), classes);
LDAPMod attr;
attr.mod_op = LDAP_MOD_ADD;
attr.mod_type = (char *) "objectClass";
attr.mod_values = (char **) classes;
if (result == AD_SUCCESS) {
LDAPMod *attrs[] = {&attr, NULL};
const int result = ldap_add_ext_s(ld, cstr(dn), attrs, NULL, NULL);
if (result == LDAP_SUCCESS) {
success_status_message(QString(tr("Created object \"%1\"")).arg(dn));
emit object_added(dn);
@ -510,11 +575,11 @@ bool AdInterface::object_add(const QString &dn, const QString &object_class) {
}
bool AdInterface::object_delete(const QString &dn) {
int result = ad_delete(ld, cstr(dn));
const QString name = dn_get_name(dn);
if (result == AD_SUCCESS) {
const int result = ldap_delete_ext_s(ld, cstr(dn), NULL, NULL);
if (result == LDAP_SUCCESS) {
success_status_message(QString(tr("Deleted object \"%1\"")).arg(name));
emit object_deleted(dn);
@ -530,18 +595,14 @@ bool AdInterface::object_delete(const QString &dn) {
}
bool AdInterface::object_move(const QString &dn, const QString &new_container) {
QList<QString> dn_split = dn.split(',');
QString new_dn = dn_split[0] + "," + new_container;
const int result = ad_move(ld, cstr(dn), cstr(new_container));
// TODO: drag and drop handles checking move compatibility but need
// to do this here as well for CLI?
const QString rdn = dn.split(',')[0];
const QString new_dn = rdn + "," + new_container;
const QString object_name = dn_get_name(dn);
const QString container_name = dn_get_name(new_container);
if (result == AD_SUCCESS) {
const int result = ldap_rename_s(ld, cstr(dn), cstr(rdn), cstr(new_container), 1, NULL, NULL);
if (result == LDAP_SUCCESS) {
success_status_message(QString(tr("Moved object \"%1\" to \"%2\"")).arg(object_name, container_name));
emit object_deleted(dn);
@ -560,12 +621,11 @@ bool AdInterface::object_move(const QString &dn, const QString &new_container) {
bool AdInterface::object_rename(const QString &dn, const QString &new_name) {
const QString new_dn = dn_rename(dn, new_name);
const QString new_rdn = new_dn.split(",")[0];
int result = ad_rename(ld, cstr(dn), cstr(new_rdn));
const QString old_name = dn_get_name(dn);
if (result == AD_SUCCESS) {
const int result = ldap_rename_s(ld, cstr(dn), cstr(new_rdn), NULL, 1, NULL, NULL);
if (result == LDAP_SUCCESS) {
success_status_message(QString(tr("Renamed object \"%1\" to \"%2\"")).arg(old_name, new_name));
emit object_deleted(dn);
@ -739,7 +799,7 @@ bool AdInterface::user_set_pass(const QString &dn, const QString &password) {
const QString error =
[this]() {
const int ldap_result = ad_get_ldap_result(ld);
const int ldap_result = get_ldap_result();
if (ldap_result == LDAP_CONSTRAINT_VIOLATION) {
return tr("Password doesn't match rules");
} else {
@ -1101,7 +1161,7 @@ void AdInterface::success_status_message(const QString &msg, const DoStatusMsg d
return;
}
STATUS()->message(msg, StatusType_Success);
Status::instance()->message(msg, StatusType_Success);
}
void AdInterface::error_status_message(const QString &context, const QString &error, const DoStatusMsg do_msg) {
@ -1114,11 +1174,11 @@ void AdInterface::error_status_message(const QString &context, const QString &er
msg += QString(tr(". Error: \"%1\"")).arg(error);;
}
STATUS()->message(msg, StatusType_Error);
Status::instance()->message(msg, StatusType_Error);
}
QString AdInterface::default_error() const {
const int ldap_result = ad_get_ldap_result(ld);
const int ldap_result = get_ldap_result();
switch (ldap_result) {
case LDAP_NO_SUCH_OBJECT: return tr("No such object");
case LDAP_CONSTRAINT_VIOLATION: return tr("Constraint violation");
@ -1132,25 +1192,263 @@ QString AdInterface::default_error() const {
}
}
int AdInterface::get_ldap_result() const {
int result;
ldap_get_option(ld, LDAP_OPT_RESULT_CODE, &result);
return result;
}
AdInterface *AD() {
return AdInterface::instance();
}
QList<QString> get_domain_hosts(const QString &domain, const QString &site) {
char **hosts_raw = NULL;
int hosts_result = ad_get_domain_hosts(cstr(domain), cstr(site), &hosts_raw);
QList<QString> hosts;
if (hosts_result == AD_SUCCESS) {
auto hosts = QList<QString>();
// TODO: confirm site query is formatted properly, currently getting no answer back (might be working as intended, since tested on domain without sites?)
for (int i = 0; hosts_raw[i] != NULL; i++) {
auto host = QString(hosts_raw[i]);
hosts.append(host);
}
ad_array_free(hosts_raw);
// Query site hosts
if (!site.isEmpty()) {
char dname[1000];
snprintf(dname, sizeof(dname), "_ldap._tcp.%s._sites.%s", cstr(site), cstr(domain));
return hosts;
} else {
return QList<QString>();
const QList<QString> site_hosts = query_server_for_hosts(dname);
hosts.append(site_hosts);
}
// Query default hosts
char dname_default[1000];
snprintf(dname_default, sizeof(dname_default), "_ldap._tcp.%s", cstr(domain));
const QList<QString> default_hosts = query_server_for_hosts(dname_default);
hosts.append(default_hosts);
hosts.removeDuplicates();
return hosts;
}
/**
* Perform a query for dname and output hosts
* dname is a combination of protocols (ldap, tcp), domain and site
* NOTE: this is rewritten from
* https://github.com/paleg/libadclient/blob/master/adclient.cpp
* which itself is copied from
* https://www.ccnx.org/releases/latest/doc/ccode/html/ccndc-srv_8c_source.html
* Another example of similar procedure:
* https://www.gnu.org/software/shishi/coverage/shishi/lib/resolv.c.gcov.html
*/
QList<QString> query_server_for_hosts(const char *dname) {
union dns_msg {
HEADER header;
unsigned char buf[NS_MAXMSG];
} msg;
auto error =
[]() {
return QList<QString>();
};
const long unsigned msg_len = res_search(dname, ns_c_in, ns_t_srv, msg.buf, sizeof(msg.buf));
const bool message_error = (msg_len < 0 || msg_len < sizeof(HEADER));
if (message_error) {
error();
}
const int packet_count = ntohs(msg.header.qdcount);
const int answer_count = ntohs(msg.header.ancount);
unsigned char *curr = msg.buf + sizeof(msg.header);
const unsigned char *eom = msg.buf + msg_len;
// Skip over packet records
for (int i = packet_count; i > 0 && curr < eom; i--) {
const int packet_len = dn_skipname(curr, eom);
const bool packet_error = (packet_len < 0);
if (packet_error) {
error();
}
curr = curr + packet_len + QFIXEDSZ;
}
QList<QString> hosts;
// Process answers by collecting hosts into list
for (int i = 0; i < answer_count; i++) {
// Get server
char server[NS_MAXDNAME];
const int server_len = dn_expand(msg.buf, eom, curr, server, sizeof(server));
const bool server_error = (server_len < 0);
if (server_error) {
error();
}
curr = curr + server_len;
int record_type;
int UNUSED(record_class);
int UNUSED(ttl);
int record_len;
GETSHORT(record_type, curr);
GETSHORT(record_class, curr);
GETLONG(ttl, curr);
GETSHORT(record_len, curr);
unsigned char *record_end = curr + record_len;
if (record_end > eom) {
error();
}
// Skip non-server records
if (record_type != ns_t_srv) {
curr = record_end;
continue;
}
int UNUSED(priority);
int UNUSED(weight);
int UNUSED(port);
GETSHORT(priority, curr);
GETSHORT(weight, curr);
GETSHORT(port, curr);
// TODO: need to save port field? maybe to incorporate into uri
// Get host
char host[NS_MAXDNAME];
const int host_len = dn_expand(msg.buf, eom, curr, host, sizeof(host));
const bool host_error = (host_len < 0);
if (host_error) {
error();
}
hosts.append(QString(host));
curr = record_end;
}
return hosts;
}
bool ad_connect(const char* uri, LDAP **ld_out) {
int result;
LDAP *ld = NULL;
result = ldap_initialize(&ld, uri);
if (result != LDAP_SUCCESS) {
ldap_memfree(ld);
return false;
}
// Set version
const int version = LDAP_VERSION3;
result = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version);
if (result != LDAP_OPT_SUCCESS) {
ldap_memfree(ld);
return false;
}
// Disable referrals
result =ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
if (result != LDAP_OPT_SUCCESS) {
ldap_memfree(ld);
return false;
}
// Set maxssf
const char* sasl_secprops = "maxssf=56";
result = ldap_set_option(ld, LDAP_OPT_X_SASL_SECPROPS, sasl_secprops);
if (result != LDAP_SUCCESS) {
ldap_memfree(ld);
return false;
}
ldap_set_option(ld, LDAP_OPT_X_SASL_NOCANON, LDAP_OPT_ON);
// TODO: add option to turn off
ldap_set_option(ld, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER);
// Setup sasl_defaults_gssapi
struct sasl_defaults_gssapi defaults;
defaults.mech = (char *)"GSSAPI";
ldap_get_option(ld, LDAP_OPT_X_SASL_REALM, &defaults.realm);
ldap_get_option(ld, LDAP_OPT_X_SASL_AUTHCID, &defaults.authcid);
ldap_get_option(ld, LDAP_OPT_X_SASL_AUTHZID, &defaults.authzid);
defaults.passwd = NULL;
// Perform bind operation
unsigned sasl_flags = LDAP_SASL_QUIET;
result = ldap_sasl_interactive_bind_s(ld, NULL,defaults.mech, NULL, NULL, sasl_flags, sasl_interact_gssapi, &defaults);
ldap_memfree(defaults.realm);
ldap_memfree(defaults.authcid);
ldap_memfree(defaults.authzid);
if (result != LDAP_SUCCESS) {
ldap_memfree(ld);
return false;
}
// NOTE: not using this for now but might need later
// The Man says: this function is used when an application needs to bind to another server in order to follow a referral or search continuation reference
// ldap_set_rebind_proc(ld, sasl_rebind_gssapi, NULL);
if (ld_out != NULL) {
*ld_out = ld;
}
return true;
}
/**
* Callback for ldap_sasl_interactive_bind_s
*/
int sasl_interact_gssapi(LDAP *ld, unsigned flags, void *indefaults, void *in) {
sasl_defaults_gssapi *defaults = (sasl_defaults_gssapi *) indefaults;
sasl_interact_t *interact = (sasl_interact_t*)in;
if (ld == NULL) {
return LDAP_PARAM_ERROR;
}
while (interact->id != SASL_CB_LIST_END) {
const char *dflt = interact->defresult;
switch (interact->id) {
case SASL_CB_GETREALM:
if (defaults)
dflt = defaults->realm;
break;
case SASL_CB_AUTHNAME:
if (defaults)
dflt = defaults->authcid;
break;
case SASL_CB_PASS:
if (defaults)
dflt = defaults->passwd;
break;
case SASL_CB_USER:
if (defaults)
dflt = defaults->authzid;
break;
case SASL_CB_NOECHOPROMPT:
break;
case SASL_CB_ECHOPROMPT:
break;
}
if (dflt && !*dflt) {
dflt = NULL;
}
/* input must be empty */
interact->result = (dflt && *dflt) ? dflt : "";
interact->len = strlen((const char *) interact->result);
interact++;
}
return LDAP_SUCCESS;
}

View File

@ -29,8 +29,9 @@
#include <QDateTime>
/**
* Interface to AD server that provides a way to search for
* objects and modify them.
* Interface to AD server. Provides a way to search and
* modify objects . Success and error messages resulting
* from operations are sent to Status.
*/
enum SearchScope {
@ -133,6 +134,7 @@ private:
void success_status_message(const QString &msg, const DoStatusMsg do_msg = DoStatusMsg_Yes);
void error_status_message(const QString &context, const QString &error, const DoStatusMsg do_msg = DoStatusMsg_Yes);
QString default_error() const;
int get_ldap_result() const;
AdInterface(const AdInterface&) = delete;
AdInterface& operator=(const AdInterface&) = delete;