summaryrefslogblamecommitdiffstats
path: root/proxy.c
blob: 5bd9541f0751e3bc1246be34883aefc6f72557cc (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13












                        



                           
                       




                        
                

                     


















                                             
                                                                                                   
                                                                                                                   
                                                                                                          
                                                                
                                
 






                                                                                                                                                               

                                                                                                                            







                                                          
                                                
                              

                             



                               
                            

                              
                          







                                  
                   
                   

                          



                                         















                                                                                                                              

                           






















































































                                                                                                                               
                                                        
                                                                                                                                      

                                                                                                      
                                                                                                                     
 


                                                       


















                                                                                                                                        




                     



                                                                                                               




                                                                                                               
   
                                                                                                                                     
 
                            
                                                       


                                       
                                                                                                                                               









                                                                                                                                                         

                                                                                                                     


















                                                                          
                                                                                        




                                
                                                                                                      

                               
                 



                                                                                                                          
                                                                                                     







                                                             
                                                                              


                                                                                                                             







                                                                                                                   


                  
                                                                                                                    


                                              



                                                         


                                                                     




                                                                         



                                                                                             




                                      


                                                                                                                            
                                                                     

                                                                                                                                         
                                                                                                         

                                                                                                                
                                                                                                  

                                       
                                                                
                                                 
                              
                                 
                                                                 



                                        


                                  


                                                                                                                  
                                                            
                                           
                                                       
                                                    

                                                        
                         

                                                        
                          




                                                     
                                                                                  



                                                                        







                                                                                                                   


                                    


                                                                              
                                              
                                        


                                                                                                     
         
                                      


                                                 





                                                                                           



                  
                                                                                                                           








                                                             
                                                                    



                                                                                                           
                                                                                                       


                                                   

                                                     
                                          



                                                                                                      
                 
                        


                                                                                                                     


         
                                                                    






                                                                                                                    
                                    



                                        
                     

 




                                                                                                                                        
                         












                                                                                                                                     

                                                                                                                  

                                                               
                                                                                                                     

                          
                                                                                                                                                             
                                                                                                         

                                                

                                               



                                                                                  



                                                               
                                             




                                                                  
                                                                            
         
                                                  

                                       


                                                                                                           
































                                                                                                                                                              
                                                                                         




























                                                                                                                                   

                                    
                       





                                                                                                                                    
                





























                                                                                                                                                             
         













                                                                                                                                                                                                        










                                                                                                    

 



                                                                                                                           

                            
                                 
                   
                                                                                
                                                            
                                                                 








                                                                                                                 
                                                                  





































                                                                                                                                       

                                                                            







                                                                                                         


                  
#include "proxy.h"
#include "client.h"
#include "server.h"
#include "helper.h"
#include "tmpbuffer.h"
#include "ldap.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdarg.h>

#define MAXPENDING   200

typedef struct
{
	BOOL homeDirectory;
	BOOL homeMount;
	BOOL gidNumber;
	BOOL gecos;
	BOOL loginShell;
	BOOL uid;
	BOOL uidNumber;
	BOOL cn;
	// 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_1001, s_homeMount, s_member, s_memberUid;
static struct string str_ADUSER;

//

static int proxy_clientBindRequest(epoll_client_t *client, const unsigned long messageId, const size_t offset, const size_t maxLen);
static int proxy_serverBindResponse(epoll_server_t *server, const unsigned long messageId, const size_t offset, const size_t maxLen);
static int proxy_clientSearchRequest(epoll_client_t *client, const unsigned long messageId, const size_t offset, const size_t maxLen);
static int proxy_serverSearchResult(epoll_server_t *server, const unsigned long messageId, const unsigned long type, const size_t offset, const size_t maxLen);

static int 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(1001);
	// TODO: configurable
	str_ADUSER.s = "ad_user";
	str_ADUSER.l = strlen("ad_user");
}
#undef SETSTR

int 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 -1;
	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 0;
	}
	return 0;
}

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;
}

int 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 -1;
	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 -1;
}

//

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_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 void 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;
}

/**
 * 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)
{
	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, NULL);
			break;
		case EQUAL:
		case GREATEQUAL:
		case LESSEQUAL:
		case APPROX:
			request_replaceAttribute(server, &filter->ava.desc, &filter->ava.value, NULL);
			break;
		default: break;
		}
	}
}

#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(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 void 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;
	} 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;
		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;
		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;
	}
}

// --------- 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;
	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;
		}
		// 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') {
			ADDATTR(homeMount, server->homeTemplate, user, user, user, user, user, 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;
	} 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 int 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 -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;
	}
	//
	pending_t *pending = proxy_getFreePendingSlot(client);
	if (pending == NULL) {
		printf("No more slots for pending requests\n");
		free_ldapsearchrequest(&req);
		return -1;
	}
	if (req.attributes == NULL) {
		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);
	//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
		pending->client = NULL;
	}
	free_ldapsearchrequest(&req);
	//
	if (pending->client == NULL) return -1;
	return 0;
}

static int 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 0;
	}
	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 -1;
		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 -1;
		}
		if (bodyLen > MAXMSGLEN) {
			printf("ldapsearchresultentry too large after transformation\n");
			free_ldapsearchresultentry(&sre);
			return -1;
		}
		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 0;
}

static int 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 -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 {
		// 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
	fmt_ldapmessage(bufoff - headerLen, messageId, BindResponse, bodyLen);
	return client_send(client, bufoff - headerLen, bodyLen + headerLen, FALSE);
}

static int 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 -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;
	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;
	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 (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
			printf("Sending fake group membership\n");
			// 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);
		} 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 -1;
}