diff --git a/.gear/admc.spec b/.gear/admc.spec index a67faf78..f0c2c449 100644 --- a/.gear/admc.spec +++ b/.gear/admc.spec @@ -62,7 +62,6 @@ cd BUILD %files %doc README.md %_bindir/admc -%_libdir/libadldap.so %files gpgui %_bindir/gpgui diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 768ff86f..bfc36b7c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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) diff --git a/src/adldap/CMakeLists.txt b/src/adldap/CMakeLists.txt deleted file mode 100644 index 4fe39ed8..00000000 --- a/src/adldap/CMakeLists.txt +++ /dev/null @@ -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() diff --git a/src/adldap/active_directory.c b/src/adldap/active_directory.c deleted file mode 100644 index 772f4720..00000000 --- a/src/adldap/active_directory.c +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// 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; - } -} diff --git a/src/adldap/active_directory.h b/src/adldap/active_directory.h deleted file mode 100644 index 4b21b840..00000000 --- a/src/adldap/active_directory.h +++ /dev/null @@ -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 -#include - -#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 */ diff --git a/src/adldap/test/CMakeLists.txt b/src/adldap/test/CMakeLists.txt deleted file mode 100644 index e7aadf93..00000000 --- a/src/adldap/test/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -add_mocked_test(adldap - SOURCES test_adldap.c - COMPILE_OPTIONS -g3 -I${CMAKE_CURRENT_SOURCE_DIR}/.. - LINK_LIBRARIES adldap) - diff --git a/src/adldap/test/test_adldap b/src/adldap/test/test_adldap deleted file mode 100755 index 649e3b1a..00000000 Binary files a/src/adldap/test/test_adldap and /dev/null differ diff --git a/src/adldap/test/test_adldap.c b/src/adldap/test/test_adldap.c deleted file mode 100644 index 795955ea..00000000 --- a/src/adldap/test/test_adldap.c +++ /dev/null @@ -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 . - */ -#include -#include -#include -#include -#include - -#include - -#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); -} diff --git a/src/admc/CMakeLists.txt b/src/admc/CMakeLists.txt index 04cb3a79..8a2d917b 100644 --- a/src/admc/CMakeLists.txt +++ b/src/admc/CMakeLists.txt @@ -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) diff --git a/src/admc/ad_interface.cpp b/src/admc/ad_interface.cpp index cd234ce5..f3bc827a 100644 --- a/src/admc/ad_interface.cpp +++ b/src/admc/ad_interface.cpp @@ -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 #include +#include #include #include #include +#include +#include +#include +#include #include #include #include +// 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 get_domain_hosts(const QString &domain, const QString &site); +QList 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 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 &att } bool AdInterface::attribute_replace_values(const QString &dn, const QString &attribute, const QList &values, const DoStatusMsg do_msg) { - int result = AD_SUCCESS; - const AdObject object = search_object(dn, {attribute}); const QList 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 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 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 hosts; - if (hosts_result == AD_SUCCESS) { - auto hosts = QList(); + // 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(); + const QList 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 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 query_server_for_hosts(const char *dname) { + union dns_msg { + HEADER header; + unsigned char buf[NS_MAXMSG]; + } msg; + + auto error = + []() { + return QList(); + }; + + 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 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; } diff --git a/src/admc/ad_interface.h b/src/admc/ad_interface.h index 367a665d..e6507565 100644 --- a/src/admc/ad_interface.h +++ b/src/admc/ad_interface.h @@ -29,8 +29,9 @@ #include /** - * 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;