#include <stdio.h>
#include <stdlib.h>
#include <alloca.h>
#include <string.h>
#include <strings.h>
#include <lber.h>
#include <ldap.h>
#include <sasl/sasl.h>
#include <string.h>
#include <ctype.h>
#include <pthread.h>
#include <synch.h>
#include <atomic.h>
#include <errno.h>
#include <assert.h>
#include <limits.h>
#include <time.h>
#include <sys/u8_textprep.h>
#include "libadutils.h"
#include "nldaputils.h"
#include "idmapd.h"
#define SAN "sAMAccountName"
#define OBJSID "objectSid"
#define OBJCLASS "objectClass"
#define UIDNUMBER "uidNumber"
#define GIDNUMBER "gidNumber"
#define UIDNUMBERFILTER "(&(objectclass=user)(uidNumber=%u))"
#define GIDNUMBERFILTER "(&(objectclass=group)(gidNumber=%u))"
#define SANFILTER "(sAMAccountName=%s)"
#define OBJSIDFILTER "(|(objectSid=%s)(sIDHistory=%s))"
void idmap_ldap_res_search_cb(LDAP *ld, LDAPMessage **res, int rc,
int qid, void *argp);
typedef struct idmap_q {
char *ecanonname;
char *edomain;
idmap_id_type esidtype;
char **canonname;
char **domain;
char **sid;
rid_t *rid;
idmap_id_type *sid_type;
char **unixname;
char **dn;
char **attr;
char **value;
posix_id_t *pid;
idmap_retcode *rc;
adutils_rc ad_rc;
adutils_result_t *result;
LDAPMessage *search_res;
} idmap_q_t;
struct idmap_query_state {
adutils_query_state_t *qs;
int qsize;
uint32_t qcount;
const char *ad_unixuser_attr;
const char *ad_unixgroup_attr;
int directory_based_mapping;
char *default_domain;
idmap_q_t queries[1];
};
static pthread_t reaperid = 0;
#define ADREAPERSLEEP 60
static
void *
adreaper(void *arg __unused)
{
timespec_t ts;
ts.tv_sec = ADREAPERSLEEP;
ts.tv_nsec = 0;
for (;;) {
(void) nanosleep(&ts, NULL);
adutils_reap_idle_connections();
}
return (NULL);
}
int
idmap_add_ds(adutils_ad_t *ad, const char *host, int port)
{
int ret = -1;
if (adutils_add_ds(ad, host, port) == ADUTILS_SUCCESS)
ret = 0;
if (ret == 0 && reaperid == 0)
(void) pthread_create(&reaperid, NULL, adreaper, NULL);
return (ret);
}
static
idmap_retcode
map_adrc2idmaprc(adutils_rc adrc)
{
switch (adrc) {
case ADUTILS_SUCCESS:
return (IDMAP_SUCCESS);
case ADUTILS_ERR_NOTFOUND:
return (IDMAP_ERR_NOTFOUND);
case ADUTILS_ERR_MEMORY:
return (IDMAP_ERR_MEMORY);
case ADUTILS_ERR_DOMAIN:
return (IDMAP_ERR_DOMAIN);
case ADUTILS_ERR_OTHER:
return (IDMAP_ERR_OTHER);
case ADUTILS_ERR_RETRIABLE_NET_ERR:
return (IDMAP_ERR_RETRIABLE_NET_ERR);
default:
return (IDMAP_ERR_INTERNAL);
}
}
idmap_retcode
idmap_lookup_batch_start(adutils_ad_t *ad, int nqueries,
int directory_based_mapping, const char *default_domain,
idmap_query_state_t **state)
{
idmap_query_state_t *new_state;
adutils_rc rc;
*state = NULL;
assert(ad != NULL);
new_state = calloc(1, sizeof (idmap_query_state_t) +
(nqueries - 1) * sizeof (idmap_q_t));
if (new_state == NULL)
return (IDMAP_ERR_MEMORY);
if ((rc = adutils_lookup_batch_start(ad, nqueries,
idmap_ldap_res_search_cb, new_state, &new_state->qs))
!= ADUTILS_SUCCESS) {
idmap_lookup_release_batch(&new_state);
return (map_adrc2idmaprc(rc));
}
new_state->default_domain = strdup(default_domain);
if (new_state->default_domain == NULL) {
idmap_lookup_release_batch(&new_state);
return (IDMAP_ERR_MEMORY);
}
new_state->directory_based_mapping = directory_based_mapping;
new_state->qsize = nqueries;
*state = new_state;
return (IDMAP_SUCCESS);
}
void
idmap_lookup_batch_set_unixattr(idmap_query_state_t *state,
const char *unixuser_attr, const char *unixgroup_attr)
{
state->ad_unixuser_attr = unixuser_attr;
state->ad_unixgroup_attr = unixgroup_attr;
}
static
void
idmap_setqresults(
idmap_q_t *q,
char *san,
const char *dn,
const char *attr,
char *value,
char *sid,
rid_t rid,
int sid_type,
char *unixname,
posix_id_t pid)
{
char *domain;
int err1;
assert(dn != NULL);
if ((domain = adutils_dn2dns(dn)) == NULL)
goto out;
if (q->ecanonname != NULL && san != NULL) {
if (u8_strcmp(q->ecanonname, san, 0,
U8_STRCMP_CI_LOWER,
U8_UNICODE_LATEST, &err1) != 0 || err1 != 0)
goto out;
}
if (q->edomain != NULL) {
if (!domain_eq(q->edomain, domain))
goto out;
}
if (q->dn != NULL)
*q->dn = strdup(dn);
if (q->attr != NULL && attr != NULL)
*q->attr = strdup(attr);
if (q->value != NULL && value != NULL) {
*q->value = value;
value = NULL;
}
if (q->sid) {
*q->sid = sid;
sid = NULL;
}
if (q->rid)
*q->rid = rid;
if (q->sid_type)
*q->sid_type = sid_type;
if (q->unixname) {
*q->unixname = unixname;
unixname = NULL;
}
if (q->domain != NULL) {
*q->domain = domain;
domain = NULL;
}
if (q->canonname != NULL) {
free(*q->canonname);
*q->canonname = san;
san = NULL;
}
if (q->pid != NULL && pid != IDMAP_SENTINEL_PID) {
*q->pid = pid;
}
q->ad_rc = ADUTILS_SUCCESS;
out:
free(san);
free(sid);
free(domain);
free(unixname);
free(value);
}
#define BVAL_CASEEQ(bv, str) \
(((*(bv))->bv_len == (sizeof (str) - 1)) && \
strncasecmp((*(bv))->bv_val, str, (*(bv))->bv_len) == 0)
static
int
idmap_bv_objclass2sidtype(BerValue **bvalues, int *sid_type)
{
BerValue **cbval;
*sid_type = IDMAP_SID;
if (bvalues == NULL)
return (0);
for (cbval = bvalues; *cbval != NULL; cbval++) {
if (BVAL_CASEEQ(cbval, "group")) {
*sid_type = IDMAP_GSID;
break;
} else if (BVAL_CASEEQ(cbval, "user")) {
*sid_type = IDMAP_USID;
break;
}
}
return (1);
}
static
void
idmap_extract_object(idmap_query_state_t *state, idmap_q_t *q,
LDAPMessage *res, LDAP *ld)
{
BerValue **bvalues;
const char *attr = NULL;
char *value = NULL;
char *unix_name = NULL;
char *dn;
char *san = NULL;
char *sid = NULL;
rid_t rid = 0;
int sid_type;
int ok;
posix_id_t pid = IDMAP_SENTINEL_PID;
assert(q->rc != NULL);
assert(q->domain == NULL || *q->domain == NULL);
if ((dn = ldap_get_dn(ld, res)) == NULL)
return;
bvalues = ldap_get_values_len(ld, res, OBJCLASS);
if (bvalues == NULL) {
idmapdlog(LOG_ERR, "%s has no %s", dn, OBJCLASS);
goto out;
}
ok = idmap_bv_objclass2sidtype(bvalues, &sid_type);
ldap_value_free_len(bvalues);
if (!ok) {
idmapdlog(LOG_ERR, "%s has unexpected %s", dn, OBJCLASS);
goto out;
}
if (state->directory_based_mapping == DIRECTORY_MAPPING_IDMU &&
q->pid != NULL) {
if (sid_type == IDMAP_USID)
attr = UIDNUMBER;
else if (sid_type == IDMAP_GSID)
attr = GIDNUMBER;
if (attr != NULL) {
bvalues = ldap_get_values_len(ld, res, attr);
if (bvalues != NULL) {
value = adutils_bv_str(bvalues[0]);
if (!adutils_bv_uint(bvalues[0], &pid)) {
idmapdlog(LOG_ERR,
"%s has Invalid %s value \"%s\"",
dn, attr, value);
}
ldap_value_free_len(bvalues);
}
}
}
if (state->directory_based_mapping == DIRECTORY_MAPPING_NAME &&
q->unixname != NULL) {
idmap_id_type esidtype;
esidtype = q->esidtype;
if (esidtype == IDMAP_SID)
esidtype = sid_type;
if (esidtype == IDMAP_USID)
attr = state->ad_unixuser_attr;
else if (esidtype == IDMAP_GSID)
attr = state->ad_unixgroup_attr;
if (attr != NULL) {
bvalues = ldap_get_values_len(ld, res, attr);
if (bvalues != NULL) {
unix_name = adutils_bv_str(bvalues[0]);
ldap_value_free_len(bvalues);
value = strdup(unix_name);
}
}
}
bvalues = ldap_get_values_len(ld, res, SAN);
if (bvalues != NULL) {
san = adutils_bv_str(bvalues[0]);
ldap_value_free_len(bvalues);
}
if (q->sid != NULL) {
bvalues = ldap_get_values_len(ld, res, OBJSID);
if (bvalues != NULL) {
sid = adutils_bv_objsid2sidstr(bvalues[0], &rid);
ldap_value_free_len(bvalues);
}
}
idmap_setqresults(q, san, dn,
attr, value,
sid, rid, sid_type,
unix_name, pid);
out:
ldap_memfree(dn);
}
void
idmap_ldap_res_search_cb(LDAP *ld, LDAPMessage **res, int rc, int qid,
void *argp)
{
idmap_query_state_t *state = (idmap_query_state_t *)argp;
idmap_q_t *q = &(state->queries[qid]);
switch (rc) {
case LDAP_RES_SEARCH_RESULT:
if (q->search_res != NULL) {
idmap_extract_object(state, q, q->search_res, ld);
(void) ldap_msgfree(q->search_res);
q->search_res = NULL;
} else
q->ad_rc = ADUTILS_ERR_NOTFOUND;
break;
case LDAP_RES_SEARCH_ENTRY:
if (q->search_res == NULL) {
q->search_res = *res;
*res = NULL;
}
break;
default:
break;
}
}
static
void
idmap_cleanup_batch(idmap_query_state_t *batch)
{
int i;
for (i = 0; i < batch->qcount; i++) {
if (batch->queries[i].ecanonname != NULL)
free(batch->queries[i].ecanonname);
batch->queries[i].ecanonname = NULL;
if (batch->queries[i].edomain != NULL)
free(batch->queries[i].edomain);
batch->queries[i].edomain = NULL;
}
}
void
idmap_lookup_release_batch(idmap_query_state_t **state)
{
if (state == NULL || *state == NULL)
return;
adutils_lookup_batch_release(&(*state)->qs);
idmap_cleanup_batch(*state);
free((*state)->default_domain);
free(*state);
*state = NULL;
}
idmap_retcode
idmap_lookup_batch_end(idmap_query_state_t **state)
{
adutils_rc ad_rc;
int i;
idmap_query_state_t *id_qs = *state;
ad_rc = adutils_lookup_batch_end(&id_qs->qs);
for (i = 0; i < id_qs->qcount; i++) {
*id_qs->queries[i].rc =
map_adrc2idmaprc(id_qs->queries[i].ad_rc);
}
idmap_lookup_release_batch(state);
return (map_adrc2idmaprc(ad_rc));
}
static
idmap_retcode
idmap_batch_add1(idmap_query_state_t *state, const char *filter,
char *ecanonname, char *edomain, idmap_id_type esidtype,
char **dn, char **attr, char **value,
char **canonname, char **dname,
char **sid, rid_t *rid, idmap_id_type *sid_type, char **unixname,
posix_id_t *pid,
idmap_retcode *rc)
{
adutils_rc ad_rc;
int qid, i;
idmap_q_t *q;
char *attrs[20];
qid = atomic_inc_32_nv(&state->qcount) - 1;
q = &(state->queries[qid]);
assert(qid < state->qsize);
q->ecanonname = ecanonname;
q->edomain = edomain;
q->esidtype = esidtype;
q->canonname = canonname;
q->sid = sid;
q->domain = dname;
q->rid = rid;
q->sid_type = sid_type;
q->rc = rc;
q->unixname = unixname;
q->dn = dn;
q->attr = attr;
q->value = value;
q->pid = pid;
i = 0;
attrs[i++] = SAN;
attrs[i++] = OBJSID;
attrs[i++] = OBJCLASS;
if (unixname != NULL) {
if (esidtype != IDMAP_GSID &&
state->ad_unixuser_attr != NULL)
attrs[i++] = (char *)state->ad_unixuser_attr;
if (esidtype != IDMAP_USID &&
state->ad_unixgroup_attr != NULL)
attrs[i++] = (char *)state->ad_unixgroup_attr;
}
if (pid != NULL) {
if (esidtype != IDMAP_GSID)
attrs[i++] = UIDNUMBER;
if (esidtype != IDMAP_USID)
attrs[i++] = GIDNUMBER;
}
attrs[i] = NULL;
*rc = IDMAP_ERR_RETRIABLE_NET_ERR;
if (sid_type != NULL)
*sid_type = IDMAP_SID;
if (sid != NULL)
*sid = NULL;
if (dname != NULL)
*dname = NULL;
if (rid != NULL)
*rid = 0;
if (dn != NULL)
*dn = NULL;
if (attr != NULL)
*attr = NULL;
if (value != NULL)
*value = NULL;
ad_rc = adutils_lookup_batch_add(state->qs, filter,
(const char **)attrs,
edomain, &q->result, &q->ad_rc);
return (map_adrc2idmaprc(ad_rc));
}
idmap_retcode
idmap_name2sid_batch_add1(idmap_query_state_t *state,
const char *name, const char *dname, idmap_id_type esidtype,
char **dn, char **attr, char **value,
char **canonname, char **sid, rid_t *rid,
idmap_id_type *sid_type, char **unixname,
posix_id_t *pid, idmap_retcode *rc)
{
idmap_retcode retcode;
char *filter, *s_name;
char *ecanonname, *edomain;
if ((ecanonname = strdup(name)) == NULL)
return (IDMAP_ERR_MEMORY);
if (dname == NULL || *dname == '\0') {
dname = state->default_domain;
edomain = strdup(dname);
if (edomain == NULL) {
free(ecanonname);
return (IDMAP_ERR_MEMORY);
}
} else {
if ((edomain = strdup(dname)) == NULL) {
free(ecanonname);
return (IDMAP_ERR_MEMORY);
}
}
if (!adutils_lookup_check_domain(state->qs, dname)) {
free(ecanonname);
free(edomain);
return (IDMAP_ERR_DOMAIN_NOTFOUND);
}
s_name = sanitize_for_ldap_filter(name);
if (s_name == NULL) {
free(ecanonname);
free(edomain);
return (IDMAP_ERR_MEMORY);
}
(void) asprintf(&filter, SANFILTER, s_name);
if (s_name != name)
free(s_name);
if (filter == NULL) {
free(ecanonname);
free(edomain);
return (IDMAP_ERR_MEMORY);
}
retcode = idmap_batch_add1(state, filter, ecanonname, edomain,
esidtype, dn, attr, value, canonname, NULL, sid, rid, sid_type,
unixname, pid, rc);
free(filter);
return (retcode);
}
idmap_retcode
idmap_sid2name_batch_add1(idmap_query_state_t *state,
const char *sid, const rid_t *rid, idmap_id_type esidtype,
char **dn, char **attr, char **value,
char **name, char **dname, idmap_id_type *sid_type,
char **unixname, posix_id_t *pid, idmap_retcode *rc)
{
idmap_retcode retcode;
int ret;
char *filter;
char cbinsid[ADUTILS_MAXHEXBINSID + 1];
if (!adutils_lookup_check_sid_prefix(state->qs, sid))
return (IDMAP_ERR_DOMAIN_NOTFOUND);
ret = adutils_txtsid2hexbinsid(sid, rid, &cbinsid[0], sizeof (cbinsid));
if (ret != 0)
return (IDMAP_ERR_SID);
(void) asprintf(&filter, OBJSIDFILTER, cbinsid, cbinsid);
if (filter == NULL)
return (IDMAP_ERR_MEMORY);
retcode = idmap_batch_add1(state, filter, NULL, NULL, esidtype,
dn, attr, value, name, dname, NULL, NULL, sid_type, unixname,
pid, rc);
free(filter);
return (retcode);
}
idmap_retcode
idmap_unixname2sid_batch_add1(idmap_query_state_t *state,
const char *unixname, int is_user, int is_wuser,
char **dn, char **attr, char **value,
char **sid, rid_t *rid, char **name,
char **dname, idmap_id_type *sid_type, idmap_retcode *rc)
{
idmap_retcode retcode;
char *filter, *s_unixname;
const char *attrname;
attrname = (is_user) ?
state->ad_unixuser_attr : state->ad_unixgroup_attr;
if (attrname == NULL)
return (IDMAP_ERR_NOTFOUND);
s_unixname = sanitize_for_ldap_filter(unixname);
if (s_unixname == NULL)
return (IDMAP_ERR_MEMORY);
(void) asprintf(&filter, "(&(objectclass=%s)(%s=%s))",
is_wuser ? "user" : "group", attrname, s_unixname);
if (s_unixname != unixname)
free(s_unixname);
if (filter == NULL) {
return (IDMAP_ERR_MEMORY);
}
retcode = idmap_batch_add1(state, filter, NULL, NULL,
IDMAP_POSIXID, dn, NULL, NULL, name, dname, sid, rid, sid_type,
NULL, NULL, rc);
if (retcode == IDMAP_SUCCESS && attr != NULL) {
if ((*attr = strdup(attrname)) == NULL)
retcode = IDMAP_ERR_MEMORY;
}
if (retcode == IDMAP_SUCCESS && value != NULL) {
if ((*value = strdup(unixname)) == NULL)
retcode = IDMAP_ERR_MEMORY;
}
free(filter);
return (retcode);
}
idmap_retcode
idmap_pid2sid_batch_add1(idmap_query_state_t *state,
posix_id_t pid, int is_user,
char **dn, char **attr, char **value,
char **sid, rid_t *rid, char **name,
char **dname, idmap_id_type *sid_type, idmap_retcode *rc)
{
idmap_retcode retcode;
char *filter;
const char *attrname;
if (is_user) {
(void) asprintf(&filter, UIDNUMBERFILTER, pid);
attrname = UIDNUMBER;
} else {
(void) asprintf(&filter, GIDNUMBERFILTER, pid);
attrname = GIDNUMBER;
}
if (filter == NULL)
return (IDMAP_ERR_MEMORY);
retcode = idmap_batch_add1(state, filter, NULL, NULL,
IDMAP_POSIXID, dn, NULL, NULL, name, dname, sid, rid, sid_type,
NULL, NULL, rc);
if (retcode == IDMAP_SUCCESS && attr != NULL) {
if ((*attr = strdup(attrname)) == NULL)
retcode = IDMAP_ERR_MEMORY;
}
if (retcode == IDMAP_SUCCESS && value != NULL) {
(void) asprintf(value, "%u", pid);
if (*value == NULL)
retcode = IDMAP_ERR_MEMORY;
}
free(filter);
return (retcode);
}