From f0b46f7a343a79d1eeb29c0d45942df646e9cc35 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 18 Mar 2014 19:32:40 +0100 Subject: First working version with user and group support, login relaying --- proxy.c | 288 ++++++++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 225 insertions(+), 63 deletions(-) (limited to 'proxy.c') diff --git a/proxy.c b/proxy.c index ca4dfc1..c396cc7 100644 --- a/proxy.c +++ b/proxy.c @@ -11,7 +11,6 @@ #include #define MAXPENDING 200 -#define BINDLEN 500 typedef struct { @@ -41,11 +40,10 @@ 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_homeDirectory, s_gidNumber, s_gecos, s_dn, s_posixGroup; +static struct string s_objectClass, s_objectclass, s_homeDirectory, s_gidNumber, s_gecos, s_cn, s_dn, s_posixGroup; static struct string s_loginShell, s_uidNumber, s_mail, s_objectCategory, s_memberOf, s_distinguishedName; - -static char osHack[200], osHackNum[12]; -static int osHackLen = 0; +static struct string s_1001; +static struct string str_ADUSER; // @@ -64,6 +62,7 @@ void proxy_init() static int done = 0; if (done) return; done = 1; + // Set up some strings we frequently use SETSTR(shadowAccount); SETSTR(posixAccount); SETSTR(posixGroup); @@ -71,6 +70,7 @@ void proxy_init() SETSTR(uid); SETSTR(sAMAccountName); SETSTR(objectSid); + SETSTR(objectClass); SETSTR(objectclass); SETSTR(homeDirectory); SETSTR(gidNumber); @@ -81,7 +81,12 @@ void proxy_init() SETSTR(objectCategory); SETSTR(memberOf); SETSTR(distinguishedName); + SETSTR(cn); SETSTR(dn); + SETSTR(1001); + // TODO: configurable + str_ADUSER.s = "ad_user"; + str_ADUSER.l = strlen("ad_user"); } #undef SETSTR @@ -98,6 +103,8 @@ int proxy_fromClient(epoll_client_t *client, const size_t maxLen) return proxy_clientBindRequest(client, messageId, res, maxLen); case SearchRequest: return proxy_clientSearchRequest(client, messageId, res, maxLen); + case UnbindRequest: + return 0; } return 0; } @@ -186,40 +193,95 @@ static inline int iequals(struct string *a, struct string *b) //#define PREF(...) do { pref(spaces, prefix); printf(__VA_ARGS__); } while (0) static BOOL request_isUserFilter(struct Filter *filter); -static void request_replaceFilter(struct Filter *filter); -static void request_replaceAdl(struct AttributeDescriptionList **adl, attr_t *attr); -static void request_replaceAttribute(struct string *attribute, struct string *value); +static BOOL request_getGroupFilter(struct Filter *filter, struct string *wantedGroupName, uint32_t *wantedGroupId); +static void request_replaceFilter(server_t *server, struct Filter *filter); +static void request_replaceAdl(server_t *server, struct AttributeDescriptionList **adl, attr_t *attr); +static void request_replaceAttribute(server_t *server, struct string *attribute, struct string *value); static BOOL request_isUserFilter(struct Filter *filter) { for (; filter != NULL; filter = filter->next) { - if (filter->x != NULL && request_isUserFilter(filter->x)) return TRUE; - if (filter->type != EQUAL) continue; - if (iequals(&filter->ava.desc, &s_objectclass) && - (equals(&filter->ava.value, &s_posixAccount) || equals(&filter->ava.value, &s_shadowAccount))) { - return TRUE; - } else if (equals(&filter->ava.desc, &s_uid) || equals(&filter->ava.desc, &s_uidNumber)) { - return TRUE; + switch (filter->type) { + case NOT: + case AND: + case OR: + if (filter->x != NULL && request_isUserFilter(filter->x)) return TRUE; + break; + case EQUAL: + case GREATEQUAL: + case LESSEQUAL: + case APPROX: + if (iequals(&filter->ava.desc, &s_objectclass) && + (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)) { + return TRUE; + } + break; + default: break; } } return FALSE; } -static void request_replaceFilter(struct Filter *filter) +/** + * This is REALLY cheap. It doesn't really look at the logic operators in the filter as we assume that pam_ldap + * or nss_ldap etc. won't do anything fancy like "!(objectClass=groupAccount)", just simple AND and OR combined + * with EQUAL. + */ +static BOOL request_getGroupFilter(struct Filter *filter, struct string *wantedGroupName, uint32_t *wantedGroupId) { + BOOL retval = FALSE; for (; filter != NULL; filter = filter->next) { - if (filter->type == PRESENT || filter->type == SUBSTRING) { - request_replaceAttribute(&filter->ava.desc, NULL); - } else if (filter->type == EQUAL) { - request_replaceAttribute(&filter->ava.desc, &filter->ava.value); + switch (filter->type) { + case AND: + case OR: + if (filter->x != NULL && request_getGroupFilter(filter->x, wantedGroupName, wantedGroupId)) retval = TRUE; + break; + case EQUAL: + case APPROX: + if (iequals(&filter->ava.desc, &s_objectclass) && equals(&filter->ava.value, &s_posixGroup)) { + retval = TRUE; + } else if (equals(&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)) { + *wantedGroupName = filter->ava.value; + } + break; + default: break; + } + } + return retval; +} + +static void request_replaceFilter(server_t *server, struct Filter *filter) +{ + for (; filter != NULL; filter = filter->next) { + switch (filter->type) { + case NOT: + case AND: + case OR: + request_replaceFilter(server, filter->x); + break; + case PRESENT: + case SUBSTRING: + request_replaceAttribute(server, &filter->ava.desc, NULL); + break; + case EQUAL: + case GREATEQUAL: + case LESSEQUAL: + case APPROX: + request_replaceAttribute(server, &filter->ava.desc, &filter->ava.value); + break; + default: break; } - //request_replaceAdl(&filter->a, NULL, spaces + 2, prefix); - request_replaceFilter(filter->x); } } #define elifSETATTR(x) else if (equals(&(*adl)->a, &s_ ## x)) attr->x = TRUE, next = (*adl)->next, free(*adl), *adl = next -static void request_replaceAdl(struct AttributeDescriptionList **adl, attr_t *attr) +static void request_replaceAdl(server_t *server, struct AttributeDescriptionList **adl, attr_t *attr) { while (*adl != NULL) { struct AttributeDescriptionList *next = NULL; @@ -228,14 +290,14 @@ static void request_replaceAdl(struct AttributeDescriptionList **adl, attr_t *at elifSETATTR(gidNumber); elifSETATTR(gecos); elifSETATTR(loginShell); - else request_replaceAttribute(&(*adl)->a, NULL); + else request_replaceAttribute(server, &(*adl)->a, NULL); 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 } } #undef elifSETATTR -static void request_replaceAttribute(struct string *attribute, struct string *value) +static void request_replaceAttribute(server_t *server, struct string *attribute, struct string *value) { if (equals(attribute, &s_uid)) { *attribute = s_sAMAccountName; @@ -247,23 +309,23 @@ static void request_replaceAttribute(struct string *attribute, struct string *va *attribute = s_objectSid; if (value == NULL) return; uint32_t tmp = 0; - for (size_t i = 0; i < value->l; ++i) tmp = tmp * 10 + (value->s[i] - '0'); - memcpy(osHack + osHackLen, &tmp, 4); - value->s = osHack; - value->l = osHackLen + 4; + for (size_t i = 0; i < value->l; ++i) tmp = (tmp * 10) + (value->s[i] - '0'); + memcpy(server->sid + (SIDLEN - 4), &tmp, 4); + value->s = server->sid; + value->l = SIDLEN; } } // --------- AD to client replacements -static void response_replacePal(struct PartialAttributeList **pal, attr_t *attr); -static void response_replaceAdl(struct string *type, struct AttributeDescriptionList **adl, attr_t *attr); -static void response_replaceAttribute(struct string *attribute, struct string *value); +static void response_replacePal(server_t *server, struct PartialAttributeList **pal, attr_t *attr); +static void response_replaceAdl(server_t *server, struct string *type, struct AttributeDescriptionList **adl, attr_t *attr); +static void response_replaceAttribute(server_t *server, struct string *attribute, struct string *value); static struct PartialAttributeList* response_addPal(struct PartialAttributeList *pal, struct string *attribute, const char *format, ...); #define ADDATTR(x,...) do { if (attr->x) last = response_addPal(last, &s_ ## x, __VA_ARGS__); } while (0) #define elifDELATTR(x) else if (equals(&(*pal)->type, &s_ ## x)) next = (*pal)->next -static void response_replacePal(struct PartialAttributeList **pal, attr_t *attr) +static void response_replacePal(server_t *server, struct PartialAttributeList **pal, attr_t *attr) { struct string *username = NULL; struct PartialAttributeList *last = NULL; @@ -284,7 +346,7 @@ static void response_replacePal(struct PartialAttributeList **pal, attr_t *attr) *pal = next; continue; } - response_replaceAdl(&(*pal)->type, &(*pal)->values, attr); + 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)) { username = &(*pal)->values->a; @@ -316,7 +378,7 @@ static void response_replacePal(struct PartialAttributeList **pal, attr_t *attr) #undef ADDATTR #undef elifDELATTR -static void response_replaceAdl(struct string *type, struct AttributeDescriptionList **adl, attr_t *attr) +static void response_replaceAdl(server_t *server, struct string *type, struct AttributeDescriptionList **adl, attr_t *attr) { while (*adl != NULL) { struct AttributeDescriptionList *next = NULL; @@ -326,26 +388,27 @@ static void response_replaceAdl(struct string *type, struct AttributeDescription *adl = next; continue; } - response_replaceAttribute(type, &(*adl)->a); + response_replaceAttribute(server, type, &(*adl)->a); adl = &(*adl)->next; // If next is not NULL, we removed an entry, so we don't need to shift } } -static void response_replaceAttribute(struct string *attribute, struct string *value) +static void response_replaceAttribute(server_t *server, struct string *attribute, struct string *value) { if (equals(attribute, &s_sAMAccountName)) { *attribute = s_uid; } else if (equals(attribute, &s_objectSid)) { *attribute = s_uidNumber; if (value == NULL) return; - if (osHackLen == 0 && value->l > 4) { - osHackLen = value->l - 4; - memcpy(osHack, value->s, osHackLen); + if (value->l != SIDLEN) return; + // It we don't have the servers SID base yet and we see a valid one, store it + if (server->sid[0] == 0 && value->s[0] == 1 && value->s[1] == 5 && value->s[7] == 5) { + memcpy(server->sid, value->s, SIDLEN - 4); } int tmp; - memcpy(&tmp, value->s + value->l - 4, 4); - value->l = snprintf(osHackNum, 10, "%u", tmp); - value->s = osHackNum; + memcpy(&tmp, value->s + (value->l - 4), 4); + // We know value->s is in our receive buffer and there are 28 bytes available, so we reuse the buffer + value->l = snprintf((char*)value->s, value->l, "%u", tmp); } } @@ -371,15 +434,17 @@ static int proxy_clientSearchRequest(epoll_client_t *client, const unsigned long struct SearchRequest req; const size_t res = scan_ldapsearchrequest(client->readBuffer + offset, client->readBuffer + maxLen, &req); if (res == 0) return -1; - const int server = server_getFromBase(&req.baseObject); - if (server == -1) { + server_t *server = server_getFromBase(&req.baseObject); + if (server == NULL) { printf("scan_ldapsearchrequest: baseObj '%.*s' unknown.\n", (int)req.baseObject.l, req.baseObject.s); return -1; } printf("scan_ldapsearchrequest: baseObj: %.*s, scope: %d, derefAliases: %d\n", (int)req.baseObject.l, req.baseObject.s, req.scope, req.derefAliases); - // + // Try to figure out if this is a lookup for a user/multiple users, or something else (eg. group) if (!request_isUserFilter(req.filter)) { // Handle locally + printf("Handling local:\n"); + helper_printfilter(req.filter); const int ret = proxy_localSearchRequest(client, messageId, &req); free_ldapsearchrequest(&req); return ret; @@ -394,14 +459,14 @@ static int proxy_clientSearchRequest(epoll_client_t *client, const unsigned long if (req.attributes == NULL) { memset(&pending->attr, -1, sizeof(pending->attr)); } else { - request_replaceAdl(&req.attributes, &pending->attr); + request_replaceAdl(server, &req.attributes, &pending->attr); } - request_replaceFilter(req.filter); + request_replaceFilter(server, req.filter); helper_printfilter(req.filter); helper_printal(req.attributes); - printf("Attrs: "); - for (size_t i = 0; i < sizeof(attr_t); ++i) putchar(((char*)&pending->attr)[i] == 0 ? '0' : '1'); - putchar('\n'); + //printf("Attrs: "); + //for (size_t i = 0; i < sizeof(attr_t); ++i) putchar(((char*)&pending->attr)[i] == 0 ? '0' : '1'); + //putchar('\n'); pending->clientMessageId = messageId; pending->serverMessageId = server_searchRequest(server, &req); if (pending->serverMessageId == 0) { @@ -435,7 +500,7 @@ static int proxy_serverSearchResult(epoll_server_t *server, const unsigned long struct SearchResultEntry sre; const size_t res = scan_ldapsearchresultentry(server->readBuffer + offset, server->readBuffer + maxLen, &sre); if (res == 0) return -1; - response_replacePal(&sre.attributes, &pending->attr); + response_replacePal(server->serverData, &sre.attributes, &pending->attr); bodyLen = fmt_ldapsearchresultentry(NULL, &sre); if (bodyLen == 0) { printf("Error formatting ldapsearchresultentry after transformation\n"); @@ -465,20 +530,46 @@ static int proxy_clientBindRequest(epoll_client_t *client, const unsigned long m { unsigned long version, method; struct string name, password; - const size_t res = scan_ldapbindrequest(client->readBuffer + offset, client->readBuffer + maxLen, &version, &name, &method); - if (res == 0) return -1; // Parsing request failed - const size_t res2 = scan_ldapstring(client->readBuffer + offset + res, client->readBuffer + maxLen, &password); - printf("scan_ldapbindrequest: Consumed %d, version %lu, method %lu, name '%.*s', bindpw: '%.*s'\n", (int)(res + res2), version, method, (int)name.l, name.s, (int)password.l, password.s); - //for (int i = 0; i < password.l; ++i) printf("%c(%x)\n", password.s[i], (int)password.s[i]); char buffer[800]; char *bufoff = buffer + 100; size_t bodyLen; - if (strncmp(password.s, "\x08\x0a\x0d\x7fINCORRECT", 13) != 0) { // FIXME: Drexhack! - printf("Password OK\n"); - bodyLen = fmt_ldapbindresponse(bufoff, 0, "", "main screen turn on", ""); + const size_t res = scan_ldapbindrequest(client->readBuffer + offset, client->readBuffer + maxLen, &version, &name, &method); + if (res == 0) return -1; // Parsing request failed + if (method != 0) { + // Other than simple bind - currently not supported + printf("Unsupported bind method: %lu\n", method); + bodyLen = fmt_ldapbindresponse(bufoff, authMethodNotSupported, "", "SIMPLE only", ""); } else { - printf("Password WRONG\n"); - bodyLen = fmt_ldapbindresponse(bufoff, 49, "", "nix da", ""); + // Simple bind :-) + const size_t res2 = scan_ldapstring(client->readBuffer + offset + res, client->readBuffer + maxLen, &password); + printf("scan_ldapbindrequest: Consumed %d, version %lu, method %lu, name '%.*s'\n", (int)(res + res2), version, method, (int)name.l, name.s); + if (name.l == 0 && password.l == 0) { + // Anonymous bind used for "normal" lookups + printf("Anonymous bind ok\n"); + bodyLen = fmt_ldapbindresponse(bufoff, success, "", "main screen turn on", ""); + } else { + server_t *server = server_getFromBase(&name); + if (server == NULL || strncmp(password.s, "\x08\x0a\x0d\x7fINCORRECT", 13) == 0) { + // The INCORRECT part is some weird thing I saw pam_ldap do - save the round trip to AD and deny + printf("Password INCORRECT or binddn unknown\n"); + bodyLen = fmt_ldapbindresponse(bufoff, invalidCredentials, "", "invalid credentials", ""); + } else { + // Seems to be an actual bind - forward to AD - TODO: SASL (DIGEST-MD5?) + pending_t *pending = proxy_getFreePendingSlot(client); + const unsigned long smid = server_tryUserBind(server, &name, &password); + if (pending == NULL || smid == 0) { + // Busy + if (pending != NULL) pending->client = NULL; + printf("Too many pending requests, or cannot connect to AD\n"); + bodyLen = fmt_ldapbindresponse(bufoff, busy, "", "can't handle it", ""); + } else { + // Request queued, client needs to wait + pending->clientMessageId = messageId; + pending->serverMessageId = smid; + return 0; + } + } + } } const size_t headerLen = fmt_ldapmessage(NULL, messageId, BindResponse, bodyLen); if (headerLen > 100) return -1; // Too long - don't care @@ -494,13 +585,84 @@ static int proxy_serverBindResponse(epoll_server_t *server, const unsigned long if (res == 0) return -1; // Parsing request failed printf("scan_ldapbindresponse: Consumed %d, result: %lu, binddn: %.*s, error: %.*s, referral: %.*s\n", (int)res, result, (int)binddn.l, binddn.s, (int)error.l, error.s, (int)refer.l, refer.s); if (result == success) server->bound = TRUE; - return 0; + if (messageId <= 1) return 0; + // Was a forwarded auth + pending_t *pending = proxy_getPendingFromServer(messageId); + if (pending == NULL) return 0; + const size_t headerLen = fmt_ldapmessage(NULL, pending->clientMessageId, BindResponse, res); + char buffer[headerLen]; + fmt_ldapmessage(buffer, pending->clientMessageId, BindResponse, res); + client_send(pending->client, buffer, headerLen, TRUE); + client_send(pending->client, server->readBuffer + offset, maxLen, FALSE); + pending->client = NULL; + return -1; } // ---- Local handling ---- static int proxy_localSearchRequest(epoll_client_t *client, const unsigned long messageId, const struct SearchRequest *req) { + struct string name; + uint32_t number = 2; + name.l = 0; + if (request_getGroupFilter(req->filter, &name, &number)) { + // Request for group (by number or by name)? + if (number == 2 && name.l == 0) { + // posixGroup requested, but neither gidNumber nor cn requested, so it must be "list all" + number = 1001; + name.l = 1; + } else if (name.l != 0 && strncmp(name.s, "ad_user", name.l) != 0) { + // We know only one group... + name.l = 0; + } + if (number == 1001 || name.l != 0) { + // At least one of them was set + // TODO: Helper for setting this stuff up + struct SearchResultEntry sre; + struct PartialAttributeList gidNumber, cn, objectClass; + struct AttributeDescriptionList gidNumberVal, cnVal, objectClassVal; + //sre.objectName.l = snprintf(dnbuffer, BASELEN, "cn=ad_user,%.*s", (int)req->baseObject.l, req->baseObject.s); + //sre.objectName.s = dnbuffer; + //if (sre.objectName.l > BASELEN) sre.objectName.l = BASELEN; + sre.objectName.l = 0; + memset(&sre, 0, sizeof(sre)); + memset(&gidNumber, 0, sizeof(gidNumber)); + memset(&cn, 0, sizeof(cn)); + memset(&cnVal, 0, sizeof(cnVal)); + cn.next = sre.attributes; + sre.attributes = &cn; + cn.type = s_cn; + cn.values = &cnVal; + cnVal.a = str_ADUSER; + memset(&gidNumber, 0, sizeof(gidNumber)); + memset(&gidNumberVal, 0, sizeof(gidNumberVal)); + gidNumber.next = sre.attributes; + sre.attributes = &gidNumber; + gidNumber.type = s_gidNumber; + gidNumber.values = &gidNumberVal; + gidNumberVal.a = s_1001; + memset(&objectClass, 0, sizeof(objectClass)); + memset(&objectClassVal, 0, sizeof(objectClassVal)); + objectClass.next = sre.attributes; + sre.attributes = &objectClass; + objectClass.type = s_objectClass; + objectClass.values = &objectClassVal; + objectClassVal.a = s_posixGroup; + // Build reply + const size_t bodyLen = fmt_ldapsearchresultentry(NULL, &sre); + const size_t headerLen = fmt_ldapmessage(NULL, messageId, SearchResultEntry, bodyLen); + char buffer[headerLen + bodyLen]; + fmt_ldapmessage(buffer, messageId, SearchResultEntry, bodyLen); + fmt_ldapsearchresultentry(buffer + headerLen, &sre); + client_send(client, buffer, headerLen + bodyLen, TRUE); + } + const size_t doneLen = fmt_ldapsearchresultdone(NULL, success, "", "", ""); + const size_t doneHeaderLen = fmt_ldapmessage(NULL, messageId, SearchResultDone, doneLen); + char buffer[doneLen + doneHeaderLen]; + fmt_ldapsearchresultdone(buffer + doneHeaderLen, success, "", "", ""); + fmt_ldapmessage(buffer, messageId, SearchResultDone, doneLen); + return client_send(client, buffer, doneHeaderLen + doneLen, FALSE); + } return -1; } -- cgit v1.2.3-55-g7522