#ifdef __gnu_linux__
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <cx/utils.h>
#include <cx/hash_map.h>
#include <cx/printf.h>
#include "../util/util.h"
#include "ldap_auth.h"
#include "ldap_resource.h"
static cxstring ws_ldap_default_uid_attr[] = {
CX_STR(
"uid")
};
static cxstring ws_ldap_default_member_attr[] = {
CX_STR(
"member"),
CX_STR(
"uniqueMember")
};
static LDAPConfig ws_ldap_default_config = {
NULL,
NULL,
NULL,
NULL,
"(&(objectclass=inetorgperson)(|(cn=%s)(uid=%s)))",
ws_ldap_default_uid_attr,
1,
"(&(|(objectclass=groupOfNames)(objectclass=groupOfUniqueNames))(cn=%s))",
ws_ldap_default_member_attr,
2,
WS_LDAP_GROUP_MEMBER_DN,
TRUE,
FALSE
};
static cxstring ws_ad_default_uid_attr[] = {
CX_STR(
"uid")
};
static cxstring ws_ad_default_member_attr[] = {
CX_STR(
"member"),
CX_STR(
"uniqueMember")
};
static LDAPConfig ws_ldap_ad_config = {
NULL,
NULL,
NULL,
NULL,
"(&(objectclass=inetorgperson)(|(cn=%s)(uid=%s)))",
ws_ad_default_uid_attr,
1,
"",
ws_ad_default_member_attr,
2,
WS_LDAP_GROUP_MEMBER_DN,
TRUE,
FALSE
};
static cxstring ws_posix_default_uid_attr[] = {
CX_STR(
"uid")
};
static cxstring ws_posix_default_member_attr[] = {
CX_STR(
"memberUid")
};
static LDAPConfig ws_ldap_posix_config = {
NULL,
NULL,
NULL,
NULL,
"(&(objectclass=posixAccount)(uid=%s))",
ws_posix_default_uid_attr,
1,
"(&(objectclass=posixGroup)(cn=%s))",
ws_posix_default_member_attr,
1,
WS_LDAP_GROUP_MEMBER_UID,
TRUE,
FALSE
};
AuthDB* create_ldap_authdb(ServerConfiguration *cfg,
const char *name, ConfigNode *node) {
LDAPAuthDB *authdb = cxMalloc(cfg->a,
sizeof(LDAPAuthDB));
if(!authdb) {
return NULL;
}
authdb->authdb.name = pool_strdup(cfg->pool, name);
if(!authdb->authdb.name) {
return NULL;
}
authdb->authdb.get_user = ldap_get_user;
authdb->authdb.use_cache =
0;
cxstring dirtype = serverconfig_object_directive_value(node, cx_str(
"DirectoryType"));
LDAPConfig *default_config;
if(!dirtype.ptr) {
default_config = &ws_ldap_default_config;
}
else if(!cx_strcmp(dirtype, cx_str(
"ldap"))) {
default_config = &ws_ldap_default_config;
}
else if(!cx_strcmp(dirtype, cx_str(
"posix"))) {
default_config = &ws_ldap_posix_config;
}
else if(!cx_strcmp(dirtype, cx_str(
"ad"))) {
default_config = &ws_ldap_ad_config;
}
else {
log_ereport(
LOG_FAILURE,
"cannot create ldap authdb %s: unknown directory type %s", name, dirtype.ptr);
}
memcpy(&authdb->config, default_config,
sizeof(LDAPConfig));
cxstring resource = serverconfig_object_directive_value(node, cx_str(
"Resource"));
cxstring basedn = serverconfig_object_directive_value(node, cx_str(
"Basedn"));
cxstring binddn = serverconfig_object_directive_value(node, cx_str(
"Binddn"));
cxstring bindpw = serverconfig_object_directive_value(node, cx_str(
"Bindpw"));
cxstring userSearchFilter = serverconfig_object_directive_value(node, cx_str(
"UserSearchFilter"));
cxstring uidAttributes = serverconfig_object_directive_value(node, cx_str(
"UidAttributes"));
cxstring groupSearchFilter = serverconfig_object_directive_value(node, cx_str(
"GroupSearchFilter"));
cxstring memberAttributes = serverconfig_object_directive_value(node, cx_str(
"MemberAttributes"));
cxstring memberType = serverconfig_object_directive_value(node, cx_str(
"MemberType"));
cxstring enableGroups = serverconfig_object_directive_value(node, cx_str(
"EnableGroups"));
cxstring userNameIsDn = serverconfig_object_directive_value(node, cx_str(
"UserNameIsDn"));
if(!resource.ptr) {
cxmutstr respool_name = cx_asprintf_a(cfg->a,
"_authdb_%s", name);
if(!respool_name.ptr) {
return NULL;
}
log_ereport(
LOG_INFORM,
"ldap authdb %s: no resource specified: create resource pool %s",
name,
respool_name.ptr);
if(resourcepool_new(cfg, cx_str(
"ldap"), cx_strcast(respool_name), node)) {
log_ereport(
LOG_FAILURE,
"ldap authdb %s: cannot create ldap resource pool",
name);
return NULL;
}
authdb->config.resource = respool_name.ptr;
}
else {
authdb->config.resource = cx_strdup_a(cfg->a, resource).ptr;
if(!authdb->config.resource)
return NULL;
}
if(!basedn.ptr) {
log_ereport(
LOG_FAILURE,
"ldap authdb %s: basedn is required", name);
return NULL;
}
authdb->config.basedn = cx_strdup_a(cfg->a, basedn).ptr;
if(!authdb->config.basedn)
return NULL;
if(binddn.ptr) {
if(!bindpw.ptr) {
log_ereport(
LOG_FAILURE,
"ldap authdb %s: binddn specified, but no bindpw", name);
return NULL;
}
authdb->config.binddn = cx_strdup_a(cfg->a, binddn).ptr;
authdb->config.bindpw = cx_strdup_a(cfg->a, bindpw).ptr;
if(!authdb->config.binddn || !authdb->config.bindpw) {
return NULL;
}
}
if(userSearchFilter.ptr) {
authdb->config.userSearchFilter = cx_strdup_a(cfg->a, userSearchFilter).ptr;
}
if(uidAttributes.ptr) {
cxmutstr uidAttributesCopy = cx_strdup_a(cfg->a, uidAttributes);
if(uidAttributesCopy.ptr) {
authdb->config.numUidAttributes = cx_strsplit_a(
cfg->a,
cx_strcast(uidAttributesCopy),
cx_str(
","),
1024,
&authdb->config.uidAttributes);
}
}
if(groupSearchFilter.ptr) {
authdb->config.groupSearchFilter = groupSearchFilter.ptr;
}
if(memberAttributes.ptr) {
cxmutstr memberAttributesCopy = cx_strdup_a(cfg->a, memberAttributes);
if(memberAttributesCopy.ptr) {
authdb->config.numMemberAttributes = cx_strsplit_a(
cfg->a,
cx_strcast(memberAttributesCopy),
cx_str(
","),
1024,
&authdb->config.memberAttributes);
}
}
if(memberType.ptr) {
if(!cx_strcmp(memberType, cx_str(
"dn"))) {
authdb->config.groupMemberType =
WS_LDAP_GROUP_MEMBER_DN;
}
else if(!cx_strcmp(memberType, cx_str(
"uid"))) {
authdb->config.groupMemberType =
WS_LDAP_GROUP_MEMBER_UID;
}
else {
log_ereport(
LOG_FAILURE,
"ldap authdb %s: unknown MemberType %s", name, memberType.ptr);
return NULL;
}
}
if(enableGroups.ptr) {
authdb->config.enableGroups = util_getboolean_s(enableGroups,
FALSE);
}
if(userNameIsDn.ptr) {
authdb->config.userNameIsDN = util_getboolean_s(userNameIsDn,
FALSE);
}
authdb->groups.first =
NULL;
authdb->groups.last =
NULL;
authdb->groups.map = cxHashMapCreate(cfg->a,
CX_STORE_POINTERS,
32);
if(!authdb->groups.map) {
return NULL;
}
log_ereport(
LOG_INFORM,
"create authdb name=%s type=ldap resource=%s", name, resource.ptr);
return (AuthDB*) authdb;
}
LDAP* get_ldap_session(Session *sn, Request *rq, LDAPAuthDB *authdb) {
ResourceData *res = resourcepool_lookup(sn, rq, authdb->config.resource,
0);
if(!res) {
log_ereport(
LOG_FAILURE,
"AuthDB %s: cannot get resource %s", authdb->authdb.name, authdb->config.resource);
return NULL;
}
LDAP *ldap = res->data;
if(authdb->config.binddn) {
struct berval *server_cred;
int r = ws_ldap_bind(ldap, authdb->config.binddn, authdb->config.bindpw, &server_cred);
if(r !=
LDAP_SUCCESS) {
log_ereport(
LOG_FAILURE,
"AuthDB %s: bind to %s failed: %s", authdb->config.binddn, ldap_err2string(r));
resourcepool_free(sn, rq, res);
return NULL;
}
}
return ldap;
}
static LDAPUser* ldap_msg_to_user(
Session *sn,
Request *rq,
LDAPAuthDB *authdb,
LDAP *ldap,
LDAPMessage *msg)
{
CxAllocator *a = pool_allocator(sn->pool);
LDAPUser *user = pool_malloc(sn->pool,
sizeof(LDAPUser));
if(!user) {
return NULL;
}
char *ldap_dn = ldap_get_dn(ldap, msg);
if(!ldap_dn) {
return NULL;
}
char *dn = pool_strdup(sn->pool, ldap_dn);
ldap_memfree(ldap_dn);
if(!dn) {
return NULL;
}
char *uid =
NULL;
size_t numUidAttributes = authdb->config.numUidAttributes;
cxmutstr *uid_values = pool_calloc(sn->pool, authdb->config.numUidAttributes,
sizeof(cxmutstr));
if(!uid_values) {
return NULL;
}
BerElement *ber =
NULL;
char *attribute = ldap_first_attribute(ldap, msg, &ber);
while(attribute) {
cxstring attr = cx_str(attribute);
for(
int i=
0;i<numUidAttributes;i++) {
if(!uid_values[i].ptr && !cx_strcmp(attr, authdb->config.uidAttributes[i])) {
struct berval **values = ldap_get_values_len(ldap, msg, attribute);
if(values) {
int count = ldap_count_values_len(values);
if(count >
0) {
cxstring attr_val = cx_strn(values[
0]->bv_val, values[
0]->bv_len);
uid_values[i] = cx_strdup_a(a, attr_val);
}
else {
log_ereport(
LOG_FAILURE,
"ldap user: dn: %s attribute %s: no values", dn, attribute);
}
ldap_value_free_len(values);
}
}
}
if(uid_values[
0].ptr) {
break;
}
ldap_memfree(attribute);
attribute = ldap_next_attribute(ldap, msg, ber);
}
if(ber) {
ber_free(ber,
0);
}
for(
int i=
0;i<numUidAttributes;i++) {
if(uid_values[i].ptr) {
if(!uid) {
uid = uid_values[i].ptr;
}
else {
cxFree(a, uid_values[i].ptr);
}
}
}
pool_free(sn->pool, uid_values);
char *username;
if(authdb->config.userNameIsDN) {
username = dn;
}
else {
username = uid;
}
if(!username) {
return NULL;
}
user->authdb = authdb;
user->user.verify_password = ldap_user_verify_password;
user->user.check_group = ldap_user_check_group;
user->user.free = ldap_user_free;
user->user.name = username;
user->sn = sn;
user->rq = rq;
user->user.uid = -
1;
user->user.gid = -
1;
user->ldap = ldap;
user->userdn = dn;
user->uid_attr = uid;
return user;
}
User* ldap_get_user(AuthDB *db, Session *sn, Request *rq,
const char *username) {
LDAPAuthDB *authdb = (LDAPAuthDB*) db;
LDAPConfig *config = &authdb->config;
CxAllocator *a = pool_allocator(sn->pool);
LDAP *ld = get_ldap_session(sn, rq, authdb);
if (ld ==
NULL) {
return NULL;
}
cxstring userSearch = cx_str(config->userSearchFilter);
cxmutstr filter = cx_strreplace_a(a, userSearch, cx_str(
"%s"), cx_str(username));
if(!filter.ptr) {
return NULL;
}
log_ereport(
LOG_DEBUG,
"ldap_get_user: filter: %s", filter.ptr);
LDAPMessage *result;
struct timeval timeout;
timeout.tv_sec =
8;
timeout.tv_usec =
0;
int r = ldap_search_ext_s(
ld,
config->basedn,
LDAP_SCOPE_SUBTREE,
filter.ptr,
NULL,
0,
NULL,
NULL,
&timeout,
2,
&result);
cxFree(a, filter.ptr);
if(r !=
LDAP_SUCCESS) {
if(result) {
ldap_msgfree(result);
}
log_ereport(
LOG_FAILURE,
"ldap_get_user: search failed: %s", ldap_err2string(r));
return NULL;
}
if(!result) {
log_ereport(
LOG_FAILURE,
"ldap_get_user: search failed: no result");
return NULL;
}
LDAPMessage *msg = ldap_first_entry(ld, result);
LDAPUser *user =
NULL;
if(msg) {
if(ldap_count_entries(ld, msg) >
1) {
log_ereport(
LOG_FAILURE,
"ldap_get_user: more than one search result");
}
else {
user = ldap_msg_to_user(sn, rq, authdb, ld, msg);
}
}
ldap_msgfree(result);
return (User*)user;
}
static int is_member_attribute(LDAPAuthDB *auth,
const char *attribute) {
LDAPConfig *config = &auth->config;
cxstring attr = cx_str(attribute);
for(
int i=
0;i<config->numMemberAttributes;i++) {
if(!cx_strcmp(config->memberAttributes[i], attr)) {
return 1;
}
}
return 0;
}
static int group_add_member(LDAPGroup *group,
LDAP *ldap, LDAPMessage *msg,
char *attribute) {
struct berval **values = ldap_get_values_len(ldap, msg, attribute);
int ret =
0;
if(values) {
int count = ldap_count_values_len(values);
for(
int i=
0;i<count;i++) {
cxstring memberValue = cx_strn(values[i]->bv_val, values[i]->bv_len);
CxHashKey key = cx_hash_key(memberValue.ptr, memberValue.length);
char *g_member = cxMapGet(group->members, key);
if(!g_member) {
cxmutstr member = cx_strdup_a(group->members->allocator, memberValue);
if(!member.ptr) {
ret =
1;
break;
}
if(cxMapPut(group->members, key, member.ptr)) {
ret =
1;
break;
}
}
}
ldap_value_free_len(values);
}
return ret;
}
static LDAPGroup* ldap_msg_to_group(
Session *sn,
Request *rq,
LDAPAuthDB *authdb,
LDAP *ldap,
LDAPMessage *msg,
const char *group_name)
{
CxAllocator *a = pool_allocator(sn->pool);
LDAPGroup *group = pool_malloc(sn->pool,
sizeof(LDAPGroup));
if(!group) {
return NULL;
}
group->members = cxHashMapCreate(a,
CX_STORE_POINTERS,
32);
if(!group->members) {
pool_free(sn->pool, group);
return NULL;
}
group->name = pool_strdup(sn->pool, group_name);
BerElement *ber =
NULL;
char *attribute = ldap_first_attribute(ldap, msg, &ber);
while(attribute) {
if(is_member_attribute(authdb, attribute)) {
if(group_add_member(group, ldap, msg, attribute)) {
ldap_memfree(attribute);
cxMapDestroy(group->members);
pool_free(sn->pool, group);
group =
NULL;
break;
}
}
ldap_memfree(attribute);
attribute = ldap_next_attribute(ldap, msg, ber);
}
if(ber) {
ber_free(ber,
0);
}
return group;
}
LDAPGroup* ldap_get_group(Session *sn, Request *rq, LDAPAuthDB *authdb,
const char *group) {
LDAPConfig *config = &authdb->config;
CxAllocator *a = pool_allocator(sn->pool);
LDAP *ld = get_ldap_session(sn, rq, authdb);
if (ld ==
NULL) {
return NULL;
}
char *filterStr;
const char *basedn;
int scope;
if(config->userNameIsDN) {
filterStr =
NULL;
basedn = group;
scope =
LDAP_SCOPE_BASE;
}
else {
cxstring groupSearch = cx_str(config->groupSearchFilter);
cxmutstr filter = cx_strreplace_a(a, groupSearch, cx_str(
"%s"), cx_str(group));
if(!filter.ptr) {
return NULL;
}
filterStr = filter.ptr;
basedn = config->basedn;
scope =
LDAP_SCOPE_SUBTREE;
}
log_ereport(
LOG_DEBUG,
"ldap_get_group: basedn: %s filter: %s", basedn, filterStr);
LDAPMessage *result;
struct timeval timeout;
timeout.tv_sec =
8;
timeout.tv_usec =
0;
int r = ldap_search_ext_s(
ld,
basedn,
scope,
filterStr,
NULL,
0,
NULL,
NULL,
&timeout,
2,
&result);
if(filterStr) {
cxFree(a, filterStr);
}
if (r !=
LDAP_SUCCESS) {
if(result) {
ldap_msgfree(result);
}
log_ereport(
LOG_FAILURE,
"ldap_get_group %s: search failed: %s", group, ldap_err2string(r));
return NULL;
}
LDAPMessage *msg = ldap_first_entry(ld, result);
LDAPGroup *wsgroup =
NULL;
if(msg) {
if(ldap_count_entries(ld, msg) >
1) {
log_ereport(
LOG_FAILURE,
"ldap_get_user: more than one search result");
}
else {
wsgroup = ldap_msg_to_group(sn, rq, authdb, ld, msg, group);
}
}
ldap_msgfree(result);
return wsgroup;
}
int ldap_user_verify_password(User *u,
const char *password) {
LDAPUser *user = (LDAPUser*)u;
struct berval cred;
cred.bv_val = (
char*)password;
cred.bv_len = strlen(password);
struct berval *server_cred;
int r = ldap_sasl_bind_s(
user->ldap,
user->userdn,
LDAP_SASL_SIMPLE,
&cred,
NULL,
NULL,
&server_cred);
if(r ==
LDAP_SUCCESS) {
log_ereport(
LOG_VERBOSE,
"ldap user %s password ok", user->userdn);
return 1;
}
else {
log_ereport(
LOG_VERBOSE,
"ldap user %s password not ok", user->userdn);
return 0;
}
}
int ldap_user_check_group(User *u,
const char *group_str) {
LDAPUser *user = (LDAPUser*)u;
LDAPAuthDB *authdb = user->authdb;
if(!authdb->config.enableGroups) {
log_ereport(
LOG_DEBUG,
"ldap_user_check_group: authdb %s: groups disabled",
authdb->authdb.name);
return 0;
}
int ret =
0;
LDAPGroup *group = ldap_get_group(user->sn, user->rq, authdb, group_str);
if(group) {
const char *usr = authdb->config.groupMemberType ==
WS_LDAP_GROUP_MEMBER_DN ? user->userdn : user->uid_attr;
char *member = cxMapGet(group->members, cx_hash_key_str(usr));
if(member) {
ret =
1;
}
}
return ret;
}
void ldap_user_free(User *u) {
LDAPUser *user = (LDAPUser*)u;
pool_free(user->sn->pool, user->userdn);
pool_free(user->sn->pool, user->uid_attr);
pool_free(user->sn->pool, user);
}