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:
parent
5722ebcc27
commit
f9a9e1de49
@ -62,7 +62,6 @@ cd BUILD
|
||||
%files
|
||||
%doc README.md
|
||||
%_bindir/admc
|
||||
%_libdir/libadldap.so
|
||||
|
||||
%files gpgui
|
||||
%_bindir/gpgui
|
||||
|
@ -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)
|
||||
|
@ -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()
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 */
|
@ -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.
@ -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);
|
||||
}
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user