summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2015-10-15 11:36:12 +0200
committerSimon Rettberg2015-10-15 11:36:12 +0200
commitf95591d9e905ceab0e44109ab1970fb6d2ac9742 (patch)
treef7459979d2836e947f86230981c344923b8bc1fe
parentBail out if config file is not readable (diff)
downloadldadp-f95591d9e905ceab0e44109ab1970fb6d2ac9742.tar.gz
ldadp-f95591d9e905ceab0e44109ab1970fb6d2ac9742.tar.xz
ldadp-f95591d9e905ceab0e44109ab1970fb6d2ac9742.zip
Only relay whitelisted fields to client on anonymous bind connections
-rw-r--r--proxy.c176
1 files changed, 137 insertions, 39 deletions
diff --git a/proxy.c b/proxy.c
index e4423ad..8c3fee6 100644
--- a/proxy.c
+++ b/proxy.c
@@ -40,11 +40,18 @@ typedef struct
static pending_t _pendingRequest[MAXPENDING];
static int _pendingCount = 0;
-static struct string s_shadowAccount, s_posixAccount, s_user, s_uid, s_sAMAccountName, s_objectSid;
-static struct string s_objectClass, s_objectclass, s_homeDirectory, s_gidNumber, s_gecos, s_cn, s_dn, s_posixGroup;
+// Attributes
+static struct string s_uid, s_sAMAccountName, s_objectSid, s_homeMount, s_memberUid, s_realAccount;
+static struct string s_objectClass, s_homeDirectory, s_gidNumber, s_gecos, s_cn, s_dn;
static struct string s_loginShell, s_uidNumber, s_mail, s_objectCategory, s_memberOf, s_distinguishedName;
-static struct string s_3, s_1001, s_homeMount, s_member, s_memberUid, s_realAccount;
+// Some again in lowercase
+static struct string s_homemount, s_memberuid, s_realaccount, s_objectclass, s_homedirectory, s_gidnumber;
+static struct string s_uidnumber, s_memberof, s_distinguishedname;
+// Values
+static struct string s_shadowAccount, s_posixAccount, s_posixGroup, s_3, s_1001, s_user, s_member;
+// Feature query
static struct string s_namingContexts, s_supportedControl, s_supportedExtension, s_supportedFeatures, s_supportedLDAPVersion, s_lastUSN, s_highestCommittedUSN;
+// Other
static struct string str_ADUSER, str_ADUSERDN;
// HACK
@@ -131,6 +138,14 @@ void proxy_init()
SETSTR(highestCommittedUSN);
SETSTR(1001);
SETSTR(3);
+ SETSTR(homemount);
+ SETSTR(memberuid);
+ SETSTR(realaccount);
+ SETSTR(homedirectory);
+ SETSTR(gidnumber);
+ SETSTR(uidnumber);
+ SETSTR(memberof);
+ SETSTR(distinguishedname);
// TODO: configurable
str_ADUSER.s = "ad_user";
str_ADUSER.l = strlen(str_ADUSER.s);
@@ -248,7 +263,10 @@ static inline int equals(struct string *a, struct string *b)
return strncmp(a->s, b->s, a->l) == 0;
}
-static inline int iequals(struct string *a, struct string *b)
+/**
+ * b MUST be in lowercase already.
+ */
+static inline int iequals(const struct string * const a, const struct string * const b)
{
if (a->l != b->l) return 0;
for (size_t i = 0; i < a->l; ++i) {
@@ -286,7 +304,7 @@ static BOOL request_isUserFilter(struct Filter *filter)
(equals(&filter->ava.value, &s_posixAccount) || equals(&filter->ava.value, &s_shadowAccount))) {
return TRUE;
}
- if (equals(&filter->ava.desc, &s_uid) || equals(&filter->ava.desc, &s_uidNumber)) {
+ if (iequals(&filter->ava.desc, &s_uid) || iequals(&filter->ava.desc, &s_uidnumber)) {
return TRUE;
}
break;
@@ -337,12 +355,12 @@ static BOOL request_getGroupFilter(struct Filter *filter, struct string *wantedG
retval = TRUE;
} else if (iequals(&filter->ava.desc, &s_dn) && iequals(&filter->ava.value, &str_ADUSERDN)) {
*wantedGroupName = str_ADUSER;
- } else if (equals(&filter->ava.desc, &s_gidNumber)) {
+ } else if (iequals(&filter->ava.desc, &s_gidnumber)) {
*wantedGroupId = 0; // Should we check for a valid number? I don't see how it would hurt not doing so...
for (size_t i = 0; i < filter->ava.value.l; ++i) *wantedGroupId = (*wantedGroupId * 10) + (filter->ava.value.s[i] - '0');
- } else if (equals(&filter->ava.desc, &s_cn)) {
+ } else if (iequals(&filter->ava.desc, &s_cn)) {
*wantedGroupName = filter->ava.value;
- } else if (equals(&filter->ava.desc, &s_member) || equals(&filter->ava.desc, &s_memberUid)) {
+ } else if (iequals(&filter->ava.desc, &s_member) || iequals(&filter->ava.desc, &s_memberuid)) {
*wantsMember = TRUE;
}
break;
@@ -396,7 +414,8 @@ static void request_replaceFilter(server_t *server, struct Filter **filter)
static void request_replaceAdl(server_t *server, struct AttributeDescriptionList **adl, attr_t *attr)
{
if (server->plainLdap) {
- return request_replaceAdlLdap(server, adl, attr);
+ request_replaceAdlLdap(server, adl, attr);
+ return;
}
while (*adl != NULL) {
struct AttributeDescriptionList *next = NULL;
@@ -425,21 +444,21 @@ static BOOL request_replaceAttribute(server_t *server, struct string *attribute,
if (server->plainLdap) {
return request_replaceAttributeLdap(server, attribute, value, attr);
}
- if (equals(attribute, &s_uid)) {
+ if (iequals(attribute, &s_uid)) {
*attribute = s_sAMAccountName;
if (attr) attr->hasUser = TRUE;
// If uid is of format s[0-9]+, we assume that it's a numeric account name in AD, as a workaround
if (value == NULL) return FALSE;
fixUnNumeric(value);
////// ###################
- } else if (equals(attribute, &s_homeMount)) {
+ } else if (iequals(attribute, &s_homemount)) {
*attribute = s_homeDirectory;
if (attr != NULL) attr->homeMount = TRUE;
} else if (iequals(attribute, &s_objectclass)) {
if (value == NULL) return TRUE;
if (equals(value, &s_shadowAccount)) *value = s_user;
else if (equals(value, &s_posixAccount)) *value = s_user;
- } else if (equals(attribute, &s_uidNumber)) {
+ } else if (iequals(attribute, &s_uidnumber)) {
*attribute = s_objectSid;
if (value == NULL) return TRUE;
if (value != NULL && value->l == 1 && value->s[0] == '0') return FALSE;
@@ -459,7 +478,7 @@ static void request_replaceAdlLdap(server_t *server, struct AttributeDescription
while (*adl != NULL) {
struct AttributeDescriptionList *next = NULL;
if (attr == NULL) { }
- elifSETATTR(homeDirectory);
+ else if (iequals(&(*adl)->a, &s_homedirectory)) attr->homeDirectory = TRUE;
elifSETATTR(gidNumber);
elifSETATTR(gecos);
elifSETATTR(realAccount);
@@ -468,12 +487,21 @@ static void request_replaceAdlLdap(server_t *server, struct AttributeDescription
if (*adl == NULL) break;
if (next == NULL) adl = &(*adl)->next; // If next is not NULL, we removed an entry, so we don't need to shift
}
+ if (!attr->hasUser) {
+ if (attr->homeDirectory || attr->gecos || attr->homeMount) {
+ struct AttributeDescriptionList *user = calloc(1, sizeof(struct AttributeDescriptionList));
+ user->a = s_uid;
+ user->next = *adl;
+ *adl = user;
+ }
+ }
}
static BOOL request_replaceAttributeLdap(server_t *server, struct string *attribute, struct string *value, attr_t *attr)
{
- if (equals(attribute, &s_uid)) {
- // If uid is of format s[0-9]+, we assume that it's a numeric account name in AD, as a workaround
+ if (iequals(attribute, &s_uid)) {
+ if (attr) attr->hasUser = TRUE;
+ // If uid is of format s[0-9]+, we assume that it's a numeric account name, as a workaround
if (value == NULL) return FALSE;
fixUnNumeric(value);
}
@@ -481,6 +509,49 @@ static BOOL request_replaceAttributeLdap(server_t *server, struct string *attrib
}
#undef elifSETATTR
+// ----- Sanitize requested attributes by whitelist
+
+static void prependAdl(struct AttributeDescriptionList **adl, struct string *str)
+{
+ struct AttributeDescriptionList *item = calloc(1, sizeof(struct AttributeDescriptionList));
+ item->a = *str;
+ item->next = *adl;
+ *adl = item;
+}
+
+static void request_addDefaultAttributes(struct AttributeDescriptionList **adl)
+{
+ prependAdl(adl, &s_uid);
+ prependAdl(adl, &s_homemount);
+ prependAdl(adl, &s_memberuid);
+ prependAdl(adl, &s_realaccount);
+ prependAdl(adl, &s_objectclass);
+ prependAdl(adl, &s_homedirectory);
+ prependAdl(adl, &s_gidnumber);
+ prependAdl(adl, &s_uidnumber);
+ prependAdl(adl, &s_memberof);
+ prependAdl(adl, &s_distinguishedname);
+}
+
+#define S_EQ(wat) iequals(str, &s_ ## wat)
+static void request_filterRequestedAttributes(struct AttributeDescriptionList **adl)
+{
+ while (*adl != NULL) {
+ const struct string * const str = &(*adl)->a;
+ if (!(S_EQ(uid) || S_EQ(homemount) || S_EQ(memberuid) || S_EQ(realaccount) || S_EQ(objectclass)
+ || S_EQ(homedirectory) || S_EQ(gidnumber) || S_EQ(uidnumber) || S_EQ(memberof)
+ || S_EQ(distinguishedname))) {
+ // Delete
+ struct AttributeDescriptionList *next = (*adl)->next;
+ free(*adl);
+ *adl = next;
+ } else {
+ adl = &(*adl)->next;
+ }
+ }
+}
+#undef S_EQ
+
// --------- AD to client replacements
static void response_replacePal(server_t *server, struct PartialAttributeList **pal, attr_t *attr);
@@ -490,6 +561,7 @@ static void response_replacePalLdap(server_t *server, struct PartialAttributeLis
static void response_replaceAdlLdap(server_t *server, struct string *type, struct AttributeDescriptionList **adl, attr_t *attr);
static void response_replaceAttributeLdap(server_t *server, struct string *attribute, struct string *value);
static BOOL response_filterHomeDir(struct PartialAttributeList *pal);
+static BOOL response_filterLocalHomeDir(struct PartialAttributeList *pal);
static struct PartialAttributeList* response_addPal(struct PartialAttributeList *pal, struct string *attribute, const char *format, ...);
#define ADDATTR(x,...) do { if (attr->x) *pal = response_addPal(*pal, &s_ ## x, __VA_ARGS__); } while (0)
@@ -534,7 +606,7 @@ static void response_replacePal(server_t *server, struct PartialAttributeList **
}
response_replaceAdl(server, &(*pal)->type, &(*pal)->values, attr);
// Fetch user name so we can add our fake fields later
- if (username == NULL && equals(&(*pal)->type, &s_uid)) {
+ if (username == NULL && iequals(&(*pal)->type, &s_uid)) {
username = &(*pal)->values->a;
if (username->l > 1 && username->s[0] == 's' && isInt(username, 1)) wasNumeric = TRUE;
}
@@ -617,12 +689,32 @@ static void response_replaceAttribute(server_t *server, struct string *attribute
static BOOL response_filterHomeDir(struct PartialAttributeList *pal)
{
for (struct AttributeDescriptionList *adl = pal->values; adl != NULL; adl = pal->values /* sic */) {
- if (adl->a.l > 2 && adl->a.s[0] == '\\' && adl->a.s[1] == '\\') {
- for (size_t i = 0; i < adl->a.l; ++i) if (adl->a.s[i] == '\\') *((char*)adl->a.s + i) = '/';
- free_ldapadl(adl->next);
- adl->next = NULL;
- pal->type = s_homeMount;
- return TRUE;
+ if (adl->a.s != NULL) {
+ if (adl->a.l > 0 && adl->a.s[0] == '\\') {
+ for (size_t i = 0; i < adl->a.l; ++i) if (adl->a.s[i] == '\\') *((char*)adl->a.s + i) = '/';
+ }
+ if (adl->a.l > 2 && adl->a.s[0] == '/' && adl->a.s[1] == '/') {
+ free_ldapadl(adl->next);
+ adl->next = NULL;
+ pal->type = s_homeMount;
+ return TRUE;
+ }
+ }
+ pal->values = adl->next;
+ free(adl);
+ }
+ return FALSE;
+}
+
+static BOOL response_filterLocalHomeDir(struct PartialAttributeList *pal)
+{
+ for (struct AttributeDescriptionList *adl = pal->values; adl != NULL; adl = pal->values /* sic */) {
+ if (adl->a.s != NULL) {
+ if (adl->a.l > 0 && adl->a.s[0] == '/') {
+ free_ldapadl(adl->next);
+ adl->next = NULL;
+ return TRUE;
+ }
}
pal->values = adl->next;
free(adl);
@@ -660,15 +752,14 @@ static void response_replacePalLdap(server_t *server, struct PartialAttributeLis
elifDEL(mail);
elifDELATTR(cn);
elifDEL(memberOf);
- else if (equals(&(*pal)->type, &s_homeDirectory)) {
+ else if (iequals(&(*pal)->type, &s_homedirectory)) {
// homeDirectory is set in AD - it can either be a local path (in which case it's useless)
// or a UNC path, which we can easily mount via mount.cifs
- if (!response_filterHomeDir(*pal)) {
- del = TRUE;
- attr->homeMount = TRUE;
- next = (*pal)->next;
- } else {
+ if (response_filterHomeDir(*pal)) {
attr->homeMount = FALSE;
+ attr->homeDirectory = TRUE;
+ } else if(response_filterLocalHomeDir(*pal)) {
+ attr->homeDirectory = FALSE;
}
}
// Entry should be removed, free structs
@@ -680,7 +771,7 @@ static void response_replacePalLdap(server_t *server, struct PartialAttributeLis
}
response_replaceAdlLdap(server, &(*pal)->type, &(*pal)->values, attr);
// Fetch user name so we can add our fake fields later
- if (username == NULL && equals(&(*pal)->type, &s_uid)) {
+ if (username == NULL && iequals(&(*pal)->type, &s_uid)) {
username = &(*pal)->values->a;
if (username->l > 1 && username->s[0] == 's' && isInt(username, 1)) wasNumeric = TRUE;
}
@@ -689,10 +780,12 @@ static void response_replacePalLdap(server_t *server, struct PartialAttributeLis
if (username != NULL) {
char *user = tmpbuffer_get();
snprintf(user, TMPLEN, "%.*s", (int)username->l, username->s);
- ADDATTR(homeDirectory, "/home/%s", user);
+ if (attr->homeDirectory) {
+ ADDATTR(homeDirectory, "/home/%s", user);
+ }
ADDATTR(gecos, "%s,,,", user);
ADDATTR(cn, "%s", user);
- if (wasNumeric) user++; // From here on, user is the real AD username, no leading 's'
+ if (wasNumeric) user++; // From here on, user is the real ldap username, no leading 's'
if (attr->homeMount && server->homeTemplate[0] != '\0') {
ADDATTR(homeMount, server->homeTemplate, user, user, user, user, user, user);
}
@@ -722,7 +815,7 @@ static void response_replaceAdlLdap(server_t *server, struct string *type, struc
static void response_replaceAttributeLdap(server_t *server, struct string *attribute, struct string *value)
{
- if (equals(attribute, &s_uid)) {
+ if (iequals(attribute, &s_uid)) {
if (value != NULL) fixNumeric(value);
}
}
@@ -764,14 +857,21 @@ static BOOL proxy_clientSearchRequest(epoll_client_t *client, const unsigned lon
free_ldapsearchrequest(&req);
return FALSE;
}
+ // In case of anonymous bind: Narrow down list of what the user can see to protect private data
+ if (client->fixedServer == NULL) {
+ request_filterRequestedAttributes(&req.attributes);
+ }
+ if (req.attributes != NULL) {
+ request_replaceAdl(server, &req.attributes, &pending->attr);
+ }
+ // Might have become NULL in the meantime
if (req.attributes == NULL) {
+ if (client->fixedServer == NULL) {
+ request_addDefaultAttributes(&req.attributes);
+ }
memset(&pending->attr, -1, sizeof(pending->attr));
- } else {
- request_replaceAdl(server, &req.attributes, &pending->attr);
}
request_replaceFilter(server, &req.filter);
- //helper_printfilter(req.filter);
- //helper_printal(req.attributes);
pending->clientMessageId = messageId;
if (client->fixedServer == NULL) {
pending->serverMessageId = server_searchRequest(server, &req);
@@ -783,7 +883,6 @@ static BOOL proxy_clientSearchRequest(epoll_client_t *client, const unsigned lon
printf("Failed to forward search request.\n");
pending->client = NULL;
}
- printf("C->S: %lu -> %lu\n", pending->clientMessageId, pending->serverMessageId);
free_ldapsearchrequest(&req);
//
if (pending->client == NULL) return FALSE;
@@ -799,7 +898,6 @@ static BOOL proxy_serverSearchResult(epoll_server_t *server, const unsigned long
printf("[AD] Received message with unknown messageId %lu, ignoring\n", messageId);
return TRUE;
}
- printf("S->C: %lu -> %lu\n", messageId, pending->clientMessageId);
const char *body;
size_t bodyLen;
if (type == SearchResultDone) {
@@ -972,7 +1070,7 @@ static BOOL proxy_localSearchRequest(epoll_client_t *client, const unsigned long
// posixGroup requested, but neither gidNumber nor cn requested, so it must be "list all"
number = 1001;
name.l = 1;
- } else if (!equals(&name, &str_ADUSER)) {
+ } else if (!iequals(&name, &str_ADUSER)) {
// We know only one group...
name.l = 0;
}