#include "proxy.h" #include "client.h" #include "server.h" #include "helper.h" #include "tmpbuffer.h" #include "ldap.h" #include #include #include #include #include #define MAXPENDING 200 #define MAX(a,b) ((a) > (b) ? (a) : (b)) typedef struct { BOOL homeDirectory; BOOL homeMount; BOOL gidNumber; BOOL gecos; BOOL loginShell; BOOL uid; BOOL uidNumber; BOOL cn; BOOL realAccount; // Internal BOOL hasUser; } attr_t; typedef struct { unsigned long clientMessageId; unsigned long serverMessageId; epoll_client_t *client; attr_t attr; } pending_t; typedef struct { const char *search; struct string replace; } replace_t; 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; 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; static struct string s_namingContexts, s_supportedControl, s_supportedExtension, s_supportedFeatures, s_supportedLDAPVersion, s_lastUSN, s_highestCommittedUSN; static struct string str_ADUSER; // HACK static BOOL isInt(struct string *value, int start) { size_t i; for (i = start; i < value->l; ++i) { if (value->s[i] < '0' || value->s[i] > '9') return FALSE; } return TRUE; } static void fixUnNumeric(struct string *value) { if (value == NULL || value->l < 2) return; if (value->s[0] != 's') return; if (!isInt(value, 1)) return; value->s++; value->l--; } static void fixNumeric(struct string *value) { size_t i; if (value == NULL || value->l < 1 || value->l > 18) return; if (!isInt(value, 0)) return; char *buf = tmpbuffer_get(); buf[0] = 's'; for (i = 0; i < value->l; ++i) { buf[i+1] = value->s[i]; } value->s = buf; value->l++; } // END HACK // static BOOL proxy_clientBindRequest(epoll_client_t *client, const unsigned long messageId, const size_t offset, const size_t maxLen); static BOOL proxy_serverBindResponse(epoll_server_t *server, const unsigned long messageId, const size_t offset, const size_t maxLen); static BOOL proxy_clientSearchRequest(epoll_client_t *client, const unsigned long messageId, const size_t offset, const size_t maxLen); static BOOL proxy_serverSearchResult(epoll_server_t *server, const unsigned long messageId, const unsigned long type, const size_t offset, const size_t maxLen); static BOOL proxy_localFeatureReply(epoll_client_t *client, const unsigned long messageId); static BOOL proxy_localSearchRequest(epoll_client_t *client, const unsigned long messageId, const struct SearchRequest *req); // #define SETSTR(x) s_ ## x.s = #x; s_ ## x.l = strlen( #x ) 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); SETSTR(user); SETSTR(uid); SETSTR(sAMAccountName); SETSTR(objectSid); SETSTR(objectClass); SETSTR(objectclass); SETSTR(homeDirectory); SETSTR(homeMount); SETSTR(gidNumber); SETSTR(gecos); SETSTR(loginShell); SETSTR(uidNumber); SETSTR(mail); SETSTR(objectCategory); SETSTR(memberOf); SETSTR(distinguishedName); SETSTR(cn); SETSTR(dn); SETSTR(member); SETSTR(memberUid); SETSTR(realAccount); SETSTR(namingContexts); SETSTR(supportedControl); SETSTR(supportedExtension); SETSTR(supportedFeatures); SETSTR(supportedLDAPVersion); SETSTR(lastUSN); SETSTR(highestCommittedUSN); SETSTR(1001); SETSTR(3); // TODO: configurable str_ADUSER.s = "ad_user"; str_ADUSER.l = strlen("ad_user"); } #undef SETSTR BOOL proxy_fromClient(epoll_client_t *client, const size_t maxLen) { unsigned long messageId, op; size_t len; const size_t res = scan_ldapmessage(client->readBuffer, client->readBuffer + maxLen, &messageId, &op, &len); if (res == 0) return FALSE; printf("[C] scan_ldapmessage: Consumed %d, remaining length %d, id %lu, op %lu\n", (int)res, (int)len, messageId, op); // TODO: Caching switch (op) { case BindRequest: return proxy_clientBindRequest(client, messageId, res, maxLen); case SearchRequest: return proxy_clientSearchRequest(client, messageId, res, maxLen); case UnbindRequest: return TRUE; } return TRUE; } void proxy_removeClient(const epoll_client_t *client) { int i, lastValid = -1; for (i = 0; i < _pendingCount; ++i) { if (_pendingRequest[i].client == client) _pendingRequest[i].client = NULL; else if (_pendingRequest[i].client != NULL) lastValid = i; } _pendingCount = lastValid + 1; } BOOL proxy_fromServer(epoll_server_t *server, const size_t maxLen) { unsigned long messageId, op; size_t len; const size_t res = scan_ldapmessage(server->readBuffer, server->readBuffer + maxLen, &messageId, &op, &len); if (res == 0) return FALSE; printf("[AD] scan_ldapmessage: Consumed %d, remaining length %d, id %lu, op %lu\n", (int)res, (int)len, messageId, op); switch (op) { case BindResponse: return proxy_serverBindResponse(server, messageId, res, maxLen); case SearchResultEntry: case SearchResultDone: return proxy_serverSearchResult(server, messageId, op, res, maxLen); } printf("Unsupported op: %lu\n", op); return FALSE; } // static pending_t* proxy_getFreePendingSlot(epoll_client_t *client) { for (int i = 0; i < _pendingCount; ++i) { if (_pendingRequest[i].client == NULL) { memset(&_pendingRequest[i], 0, sizeof(pending_t)); _pendingRequest[i].client = client; return &_pendingRequest[i]; } } if (_pendingCount < MAXPENDING) { memset(&_pendingRequest[_pendingCount], 0, sizeof(pending_t)); _pendingRequest[_pendingCount].client = client; return &_pendingRequest[_pendingCount++]; } return NULL; } static pending_t* proxy_getPendingFromServer(unsigned long serverMessageId) { for (int i = 0; i < _pendingCount; ++i) { if (_pendingRequest[i].client == NULL) continue; if (_pendingRequest[i].serverMessageId == serverMessageId) return &_pendingRequest[i]; } return NULL; } /* static void pref(int spaces, char prefix) { for (int i = 0; i < spaces; ++i) putchar(' '); putchar(prefix); putchar(' '); } */ static inline int equals(struct string *a, struct string *b) { if (a->l != b->l) return 0; return strncmp(a->s, b->s, a->l) == 0; } static inline int iequals(struct string *a, struct string *b) { if (a->l != b->l) return 0; for (size_t i = 0; i < a->l; ++i) { if (tolower(a->s[i]) != b->s[i]) return 0; } return 1; } // ---- client to AD replacements //#define PREF(...) do { pref(spaces, prefix); printf(__VA_ARGS__); } while (0) static BOOL request_isUserFilter(struct Filter *filter); static BOOL request_isServerCheck(struct AttributeDescriptionList* adl); static BOOL request_getGroupFilter(struct Filter *filter, struct string *wantedGroupName, uint32_t *wantedGroupId, BOOL *wantsMember); static void request_replaceFilter(server_t *server, struct Filter **filter); static void request_replaceAdl(server_t *server, struct AttributeDescriptionList **adl, attr_t *attr); static BOOL request_replaceAttribute(server_t *server, struct string *attribute, struct string *value, attr_t *attr); static BOOL request_isUserFilter(struct Filter *filter) { for (; filter != NULL; filter = filter->next) { 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 BOOL request_isServerCheck(struct AttributeDescriptionList* adl) { int counter = 0; for (; adl != NULL; adl = adl->next) { if (equals(&adl->a, &s_namingContexts)) counter++; if (equals(&adl->a, &s_supportedControl)) counter++; if (equals(&adl->a, &s_supportedExtension)) counter++; if (equals(&adl->a, &s_supportedLDAPVersion)) counter++; if (equals(&adl->a, &s_supportedFeatures)) counter++; if (equals(&adl->a, &s_lastUSN)) counter++; if (equals(&adl->a, &s_highestCommittedUSN)) counter++; if (counter > 3) return TRUE; } return FALSE; } /** * 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. * You could actually get real group memberships using the memberOf attributes of the user, and do additional * queries for these groups, but for that to make any sense you'd also have to implement useful permission * handling.... So as all we really want is authentication and optionally mounting a home directory, we pretend * there is just one group with id 1001 and name ad_user. * This function will try to figure out if the given filter is a lookup for a group name or group id. */ static BOOL request_getGroupFilter(struct Filter *filter, struct string *wantedGroupName, uint32_t *wantedGroupId, BOOL *wantsMember) { BOOL retval = FALSE; for (; filter != NULL; filter = filter->next) { switch (filter->type) { case AND: case OR: if (filter->x != NULL && request_getGroupFilter(filter->x, wantedGroupName, wantedGroupId, wantsMember)) 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; } else if (equals(&filter->ava.desc, &s_member) || equals(&filter->ava.desc, &s_memberUid)) { *wantsMember = TRUE; } break; default: break; } } return retval; } static void request_replaceFilter(server_t *server, struct Filter **filter) { while (*filter != NULL) { BOOL del = FALSE; switch ((*filter)->type) { case NOT: case AND: case OR: request_replaceFilter(server, &(*filter)->x); if ((*filter)->x == NULL) { del = TRUE; } break; case PRESENT: case SUBSTRING: if (!request_replaceAttribute(server, &(*filter)->ava.desc, NULL, NULL)) { del = TRUE; } break; case EQUAL: case GREATEQUAL: case LESSEQUAL: case APPROX: if (!request_replaceAttribute(server, &(*filter)->ava.desc, &(*filter)->ava.value, NULL)) { del = TRUE; } break; default: break; } if (del) { struct Filter *old = *filter; *filter = (*filter)->next; old->next = NULL; free_ldapsearchfilter(old); } else { filter = &(*filter)->next; } } } #define elifSETATTR(x) else if (equals(&(*adl)->a, &s_ ## x)) attr->x = TRUE, next = (*adl)->next, free(*adl), *adl = next static void request_replaceAdl(server_t *server, struct AttributeDescriptionList **adl, attr_t *attr) { while (*adl != NULL) { struct AttributeDescriptionList *next = NULL; if (attr == NULL) { } elifSETATTR(homeDirectory); elifSETATTR(gidNumber); elifSETATTR(gecos); elifSETATTR(realAccount); elifSETATTR(loginShell); else request_replaceAttribute(server, &(*adl)->a, NULL, attr); 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_sAMAccountName; user->next = *adl; *adl = user; } } } #undef elifSETATTR static BOOL request_replaceAttribute(server_t *server, struct string *attribute, struct string *value, attr_t *attr) { if (equals(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)) { *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)) { *attribute = s_objectSid; if (value == NULL) return TRUE; if (value != NULL && value->l == 1 && value->s[0] == '0') return FALSE; uint32_t tmp = 0; 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; } return TRUE; } // --------- AD to client replacements 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 BOOL response_filterHomeDir(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) #define elifDELATTR(x) else if (equals(&(*pal)->type, &s_ ## x)) next = (*pal)->next, del = TRUE, attr->x = TRUE #define elifDEL(x) else if (equals(&(*pal)->type, &s_ ## x)) next = (*pal)->next, del = TRUE static void response_replacePal(server_t *server, struct PartialAttributeList **pal, attr_t *attr) { struct string *username = NULL; struct AttributeDescriptionList *lastObjectClass = NULL; struct PartialAttributeList *next = NULL; BOOL wasNumeric = FALSE; while (*pal != NULL) { BOOL del = FALSE; if (0) { } // Remove fields we don't want from AD elifDELATTR(gidNumber); elifDELATTR(gecos); elifDELATTR(loginShell); elifDELATTR(uidNumber); elifDEL(mail); elifDELATTR(cn); elifDEL(memberOf); else if (equals(&(*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 { attr->homeMount = FALSE; } } // Entry should be removed, free structs if (del) { free_ldapadl((*pal)->values); free(*pal); *pal = next; continue; } 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; if (username->l > 1 && username->s[0] == 's' && isInt(username, 1)) wasNumeric = TRUE; } // Map objectClass user back to posixAccount and shadowAccount if (lastObjectClass == NULL && iequals(&(*pal)->type, &s_objectclass)) { BOOL hasUser = FALSE; for (struct AttributeDescriptionList *adl = (*pal)->values; adl != NULL; adl = adl->next) { if (!hasUser && iequals(&adl->a, &s_user)) hasUser = TRUE; if (hasUser && adl->next == NULL) lastObjectClass = adl; } } pal = &(*pal)->next; } if (username != NULL) { char *user = tmpbuffer_get(); snprintf(user, TMPLEN, "%.*s", (int)username->l, username->s); ADDATTR(homeDirectory, "/home/%s", user); ADDATTR(gecos, "%s,,,", user); ADDATTR(cn, "%s", user); if (attr->homeMount && server->homeTemplate[0] != '\0') { if (wasNumeric) user++; ADDATTR(homeMount, server->homeTemplate, user, user, user, user, user, user); } // Do this here so user++ will have been executed ADDATTR(realAccount, "%s", user); } if (lastObjectClass != NULL) { ADDATTR(loginShell, "/bin/bash"); ADDATTR(gidNumber, "1001"); // TODO: Nicer lastObjectClass->next = calloc(1, sizeof(struct AttributeDescriptionList)); lastObjectClass->next->a = s_posixAccount; lastObjectClass = lastObjectClass->next; lastObjectClass->next = calloc(1, sizeof(struct AttributeDescriptionList)); lastObjectClass->next->a = s_shadowAccount; } } #undef ADDATTR #undef elifDELATTR static void response_replaceAdl(server_t *server, struct string *type, struct AttributeDescriptionList **adl, attr_t *attr) { while (*adl != NULL) { struct AttributeDescriptionList *next = NULL; // Maybe delete entries here later if (next != NULL) { free(*adl); *adl = next; continue; } 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(server_t *server, struct string *attribute, struct string *value) { if (equals(attribute, &s_sAMAccountName)) { *attribute = s_uid; if (value != NULL) fixNumeric(value); } else if (equals(attribute, &s_objectSid)) { *attribute = s_uidNumber; if (value == NULL) return; 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); // 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); } } 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; } pal->values = adl->next; free(adl); } return FALSE; } static struct PartialAttributeList* response_addPal(struct PartialAttributeList *pal, struct string *attribute, const char *format, ...) { struct PartialAttributeList *next = malloc(sizeof(struct PartialAttributeList)); va_list args; va_start(args, format); next->next = pal; next->type = *attribute; next->values = malloc(sizeof(struct AttributeDescriptionList)); tmpbuffer_formatva(&next->values->a, format, args); next->values->next = NULL; va_end(args); return next; } // ----- static BOOL proxy_clientSearchRequest(epoll_client_t *client, const unsigned long messageId, const size_t offset, const size_t maxLen) { struct SearchRequest req; const size_t res = scan_ldapsearchrequest(client->readBuffer + offset, client->readBuffer + maxLen, &req); if (res == 0) return FALSE; if (req.scope == baseObject && request_isServerCheck(req.attributes)) { const BOOL ret = proxy_localFeatureReply(client, messageId); free_ldapsearchrequest(&req); return ret; } server_t *server = server_getFromBase(&req.baseObject); if (server == NULL) { printf("scan_ldapsearchrequest: baseObj '%.*s' unknown (scope %d).\n", (int)req.baseObject.l, req.baseObject.s, (int)req.scope); return FALSE; } 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 BOOL ret = proxy_localSearchRequest(client, messageId, &req); free_ldapsearchrequest(&req); return ret; } // Forward if (req.sizeLimit == 0 || req.sizeLimit > 20) req.sizeLimit = 20; // TODO: Magic value pending_t *pending = proxy_getFreePendingSlot(client); if (pending == NULL) { printf("No more slots for pending requests\n"); free_ldapsearchrequest(&req); return FALSE; } if (req.attributes == NULL) { memset(&pending->attr, -1, sizeof(pending->attr)); } else { request_replaceAdl(server, &req.attributes, &pending->attr); } helper_printfilter(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'); pending->clientMessageId = messageId; pending->serverMessageId = server_searchRequest(server, &req); if (pending->serverMessageId == 0) { // Failed to forward.. TODO: Fail client printf("Failed to forward search request.\n"); pending->client = NULL; } free_ldapsearchrequest(&req); // if (pending->client == NULL) return FALSE; return TRUE; } static BOOL proxy_serverSearchResult(epoll_server_t *server, const unsigned long messageId, const unsigned long type, const size_t offset, const size_t maxLen) { static char *bodyBuffer = NULL; if (bodyBuffer == NULL) bodyBuffer = malloc(MAXMSGLEN); pending_t *pending = proxy_getPendingFromServer(messageId); if (pending == NULL) { printf("No client matching server message id %lu\n", messageId); return TRUE; } printf("ServerID %lu -> ClientID %lu\n", messageId, pending->clientMessageId); const char *body; size_t bodyLen; if (type == SearchResultDone) { // Just forward with new header bodyLen = maxLen - offset; body = server->readBuffer + offset; } else { // Transform reply struct SearchResultEntry sre; const size_t res = scan_ldapsearchresultentry(server->readBuffer + offset, server->readBuffer + maxLen, &sre); if (res == 0) return FALSE; response_replacePal(server->serverData, &sre.attributes, &pending->attr); bodyLen = fmt_ldapsearchresultentry(NULL, &sre); if (bodyLen == 0) { printf("Error formatting ldapsearchresultentry after transformation\n"); free_ldapsearchresultentry(&sre); return FALSE; } if (bodyLen > MAXMSGLEN) { printf("ldapsearchresultentry too large after transformation\n"); free_ldapsearchresultentry(&sre); return FALSE; } fmt_ldapsearchresultentry(bodyBuffer, &sre); free_ldapsearchresultentry(&sre); body = bodyBuffer; } // Build header and fire away const size_t headerLen = fmt_ldapmessage(NULL, pending->clientMessageId, type, bodyLen); char buffer[headerLen]; fmt_ldapmessage(buffer, pending->clientMessageId, type, bodyLen); client_send(pending->client, buffer, headerLen, TRUE); client_send(pending->client, body, bodyLen, FALSE); if (type == SearchResultDone) pending->client = NULL; return TRUE; } static BOOL proxy_clientBindRequest(epoll_client_t *client, const unsigned long messageId, const size_t offset, const size_t maxLen) { unsigned long version, method; struct string name, password; char buffer[800]; char *bufoff = buffer + 100; size_t bodyLen; const size_t res = scan_ldapbindrequest(client->readBuffer + offset, client->readBuffer + maxLen, &version, &name, &method); if (res == 0) return FALSE; // 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 { // 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 || isInt(&name, 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 or numeric username\n"); bodyLen = fmt_ldapbindresponse(bufoff, invalidCredentials, "", "invalid credentials", ""); } else { // Seems to be an actual bind - forward to AD - TODO: SASL (DIGEST-MD5?) fixUnNumeric(&name); 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 TRUE; } } } } const size_t headerLen = fmt_ldapmessage(NULL, messageId, BindResponse, bodyLen); if (headerLen > 100) return FALSE; // Too long - don't care fmt_ldapmessage(bufoff - headerLen, messageId, BindResponse, bodyLen); return client_send(client, bufoff - headerLen, bodyLen + headerLen, FALSE); } static BOOL proxy_serverBindResponse(epoll_server_t *server, const unsigned long messageId, const size_t offset, const size_t maxLen) { unsigned long result; struct string binddn, error, refer; const size_t res = scan_ldapbindresponse(server->readBuffer + offset, server->readBuffer + maxLen, &result, &binddn, &error, &refer); if (res == 0) return FALSE; // 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; if (messageId <= 1) return TRUE; // Was a forwarded auth pending_t *pending = proxy_getPendingFromServer(messageId); if (pending == NULL) return FALSE; 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, res, FALSE); pending->client = NULL; return FALSE; // Return FALSE here so server.c will kill off this server connection } // ---- Local handling ---- static void prependPal(struct SearchResultEntry *dest, struct PartialAttributeList *pal, struct AttributeDescriptionList *adl, struct string *key, struct string *value) { memset(pal, 0, sizeof *pal); memset(adl, 0, sizeof *adl); pal->next = dest->attributes; dest->attributes = pal; pal->type = *key; pal->values = adl; adl->a = *value; } static BOOL proxy_localFeatureReply(epoll_client_t *client, const unsigned long messageId) { printf("Sending fake feature request reply\n"); struct SearchResultEntry sre; struct PartialAttributeList vers; struct AttributeDescriptionList versVal; memset(&sre, 0, sizeof(sre)); sre.objectName.l = 0; prependPal(&sre, &vers, &versVal, &s_supportedLDAPVersion, &s_3); // Build reply const size_t bodyLen = fmt_ldapsearchresultentry(NULL, &sre); const size_t headerLen = fmt_ldapmessage(NULL, messageId, SearchResultEntry, bodyLen); const size_t doneLen = fmt_ldapsearchresultdone(NULL, success, "", "", ""); const size_t doneHeaderLen = fmt_ldapmessage(NULL, messageId, SearchResultDone, doneLen); size_t len = MAX(bodyLen + headerLen, doneLen + doneHeaderLen); char buffer[len]; fmt_ldapmessage(buffer, messageId, SearchResultEntry, bodyLen); fmt_ldapsearchresultentry(buffer + headerLen, &sre); client_send(client, buffer, headerLen + bodyLen, TRUE); fmt_ldapmessage(buffer, messageId, SearchResultDone, doneLen); fmt_ldapsearchresultdone(buffer + doneHeaderLen, success, "", "", ""); return client_send(client, buffer, doneHeaderLen + doneLen, FALSE); } static BOOL proxy_localSearchRequest(epoll_client_t *client, const unsigned long messageId, const struct SearchRequest *req) { struct string name; uint32_t number = 2; BOOL wantsMember = FALSE; name.l = 0; if (request_getGroupFilter(req->filter, &name, &number, &wantsMember)) { // Request for group (by number or by name)? if (number == 2 && name.l == 0 && !wantsMember) { // 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)) { // We know only one group... name.l = 0; } if (number == 1001 || name.l != 0) { // At least one of them was set printf("Sending fake group membership\n"); struct SearchResultEntry sre; struct PartialAttributeList gidNumber, cn, objectClass; struct AttributeDescriptionList gidNumberVal, cnVal, objectClassVal; memset(&sre, 0, sizeof(sre)); sre.objectName.l = 0; prependPal(&sre, &cn, &cnVal, &s_cn, &str_ADUSER); prependPal(&sre, &gidNumber, &gidNumberVal, &s_gidNumber, &s_1001); prependPal(&sre, &objectClass, &objectClassVal, &s_objectClass, &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); } else { printf("Sending empty posixGroup search result.\n"); } 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 FALSE; }