From f95591d9e905ceab0e44109ab1970fb6d2ac9742 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Thu, 15 Oct 2015 11:36:12 +0200 Subject: Only relay whitelisted fields to client on anonymous bind connections --- proxy.c | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 137 insertions(+), 39 deletions(-) (limited to 'proxy.c') 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; } -- cgit v1.2.3-55-g7522