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










                      
                       

                        
                                        



                           
                       




                        
                
                         

                              

                     
         








                                                                      








                                      


                                             


                                                                                                   
                                                                                                          
                          

                                                                                                              


                                                                                                  
                                                                                                                                                               

                                                            
        
                                              
 































                                                                         

  



                                                                                                                                                                
 
                                                                                           
                                                                                                                             
 







                                                          
                                                
                              

                             



                               
                            

                              
                          







                                  
                   
                   

                          
                            






                                     
                     
                  

                               







                                  



                                 

                                 


                                                                     


             




                                                                   



















                                                                                                                          









                                                    
                                                                  



                                                                                                                    
                                   
                                                                                                                                       





                                                                                 
                           
                                         
                            
         
                    

 
                                                      


                                             
                                                          
                                                                             

                                                         


                                                                          









                                                             
                                                      

                                                        

 




















                                                                                                      
 
                                                                  



                                                                                                                    


                             
                                                                                                                                            





                                                                                    



                                                                                             
         










                                                                                                        




















                                                                              











                                                            
                                   


                                              



                                                                                       

                                   
                                   








                                                                               
                                                        
                                                                        
                                                                                                                                      
                                                                                                
                                                                                                      
                                                                                                                                         
 



                                                                                           


                                                       













                                                                                                                                        
                                                                                                             



                                            




                     















                                                                        



                                                                                                               




                                                                                                               
   
                                                                                                                                     
 
                            
                                                       


                                       
                                                                                                                                               




                                                                                                                      

                                                                                                                     
                                                                              

                                                                                                                                                         
                                                                       
                                                                     
                                                                                                                       
                                                    







                               


                                           
                                                                                         
 


                                          
                         




                                                                               

                         






















                                                                                                          


                                                   


                               
                                                                                                           

                                           




                                
                                                                                                                            

                                           

                               
                 







                                                     


         
                                                                                                                                             
                                                                                                     


                                                             
                                                                                                  









                                                                  



                                                      
                                                                                     


                                                                                                                             


                                                                                                                   
                                                  



                                          
 
 


                                                
                                                                                                                                        
 
                                         
                                             
                                               
                                                                                                                 
                                               


                                            
                                          











                                                             
                                                      
                                                   
                                                         
                                                        
                                               

                                                                                           
                                                      
                                                   
                                               

                                                                                                                 








                                                                                                     
                 


                    
 

                  









                                                                                                   




                                                                           














                                                                               




                                                                                 





                                                                                                               
                                                                                                                  










                                                                             

                                      

                                                                                                                            
                                                                                                                            
                                                                     
                                                                          

                                                                                                                                         
                                                                                                         

                                                                                                                                 
                                                                                                  

                                       
                                
                              
                                 
                                                                      


                                                    
                                                                         
                              
                                                                          
                                  
                                                                          

                                                                                                                  
                                                            
                                                                                 
                                           
                                                       

                                                        









                                                                                  
                         

                                                        
                          
                                                                         




                                                     
                                                                                  
                                                                      
                                                                         
                                                      
                                                                                                                                    



                                    

                                                                              


                                                                 
                                              
                                        





                                                              
                                                                                                          


                                                                                                     

                                                                 
         

                                         
 
 
                                                                                                                                
 



                                                                
         







                                                             
                                                                         



                                                                                                           
                                                                                                                           
 
                                  

                                            


                                          
                                                      
                                         
                                                                                                      
                                                                                                      














                                                                                                                             



                 


                                                                                 
                                                                    
 
                             
                                                                                                            
















                                                                                                                            


                                                                






                                                                                                            
                                                            

                                            



                                        
                     

 



                                                                                                                                        
                         

                                                                       
                               
                                                           
                     
                                  




                    
                                                                                                                                      

                                 
                                                                                                                  
                                   




                                                                               

                                                               
                                                                                                                                                                    
                             
         
                                                                                                                                                                      
                                                                                                         

                                                



                                                                             
                                                                                   
                                             
                                                                     

                           
                  


                                                               

                                                       
         
                                                                                              

                                                              
                                                                                                                     
                                             
                             
         







                                                                                                       
                                     

                                                                      
                                                                                    
                 
                                                                  
         
                                                          


                                                                 
                                                       

                                               
                                             




                                                                                                       

                                                        
                                                                                     



                                       

                                                  

 
                                                                                                                                                               




                                                                   
                                                                                                                     
                            
         





                                                   
                                                     
                                                                      



                                                                                                                              
                                           



                                                        
                                                                                         


                                                        

                                                                
                                                                                                            
                                                         
                                     

                                          
                                                                                                     
                                                         
                                     


                                                            






                                                                                                              







                                                                                                
                                                                                
                    

 
                                                                                                                                    


                                      

                                    
                       
                                                                                                                                    
                                                             

                                                                   
                                                                                     
                                                                                                      
                
                                  
                               

                                                                                                                                                                               

                                                                   
                                                                                

                                                                                                       
                                               
                                                                     


                                                                                                                    

                                                                                                                                
                                                                                                                                                

                                                                                                                          
                                                                                                                   
                                                                                                    
                                                                                      

                                                                                                              


                                                                                    
                                                                                                                                     


                                                                                                                
                                                                                                                                                

                                                                             

                                                                  
                                                    


                                 
         
                                                                                         
                                                                   



                                                                                   
                                                                                                                                     



                                                                                                                                             
                                                             
                                                                                                                                                                                                                          

                                            
                                                                                       
                
                                                                                 
         
                                        

                                                                   
                                          



                                                                                                    
                                                                              
                               
                                                                                                   

 

                           













                                                                                                                                                                        
                                                                                       




















                                                                                                 
                                                                                                                            
 

                            
                                 
                   
                                                                                
                                                            
                                                                 


                                                                                                                 
                                                          




                                                       
                                                                                                  


                                                                                            
                                                     
                                                      


                                                                                                       






                                                                                                              
                        
                                                                                                         
                 
         







                                                                                                 
 
#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>
#include <sys/socket.h>

#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;
	BOOL bogusFieldName42;
	BOOL bogusFieldName43;
	// Internal
	BOOL hasUser;
} attr_t;
/*
 * The bogus fields can be used to map two arbitrary additional fields
 * to the uid when searching for a user. It's currently used to handle
 * sssd being smart and doing lookups for krbPrincipalName and mail
 * when the authenticating user's name contains '@'
 * For this reason the sssd.conf contains (in the ldap section):
 * ldap_user_email = bogusFieldName42
 * ldap_user_principal = bogusFieldName43
 */

typedef struct
{
	unsigned long clientMessageId;
	unsigned long serverMessageId;
	epoll_client_t *client;
	attr_t attr;
} pending_t;

static pending_t _pendingRequest[MAXPENDING];
static int _pendingCount = 0;

// 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;
// Some again in lowercase
static struct string s_samaccountname, s_objectsid, s_homemount, s_memberuid, s_realaccount, s_objectclass;
static struct string s_homedirectory, s_gidnumber, s_uidnumber, s_memberof, s_distinguishedname, s_loginshell;
// 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;
static struct string s_bogusFieldName42, s_bogusFieldName43;
static struct string s_bogusfieldname42, s_bogusfieldname43;
// Other
static struct string str_ADUSER, str_ADUSERDN;

// 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);
	SETSTR(samaccountname);
	SETSTR(objectsid);
	SETSTR(homemount);
	SETSTR(memberuid);
	SETSTR(realaccount);
	SETSTR(homedirectory);
	SETSTR(gidnumber);
	SETSTR(uidnumber);
	SETSTR(memberof);
	SETSTR(distinguishedname);
	SETSTR(bogusFieldName42);
	SETSTR(bogusFieldName43);
	SETSTR(bogusfieldname42);
	SETSTR(bogusfieldname43);
	// TODO: configurable
	str_ADUSER.s = "ad_user";
	str_ADUSER.l = strlen(str_ADUSER.s);
	str_ADUSERDN.s = "cn=ad_user,ou=groups,dc=sausageface,dc=de";
	str_ADUSERDN.l = strlen(str_ADUSERDN.s);
}
#undef SETSTR

/**
 * Initialize default mapping of attributes for those which weren't
 * set in the config file. For LDAP, this is mostly a 1:1 mapping,
 * for AD, we pull some stunts.
 */
void proxy_initDefaultMap(server_t *server)
{
	attr_map_t *m = &server->map;
	if (server->plainLdap) {
		if (m->homemount.l == 0) m->homemount = s_homemount;
		if (m->localhome.l == 0) m->localhome = s_homedirectory;
		if (m->posixAccount.l == 0) m->posixAccount = s_posixAccount;
		if (m->shadowAccount.l == 0) m->shadowAccount = s_shadowAccount;
		if (m->uid.l == 0) m->uid = s_uid;
		if (m->uidnumber.l == 0) m->uidnumber = s_uidnumber;
	} else {
		if (m->homemount.l == 0) m->homemount = s_homedirectory;
		if (m->localhome.l == 0) m->localhome = s_supportedLDAPVersion; // Unused, use something long and unlikely
		if (m->posixAccount.l == 0) m->posixAccount = s_user;
		if (m->shadowAccount.l == 0) m->shadowAccount = s_user;
		if (m->uid.l == 0) m->uid = s_samaccountname;
		if (m->uidnumber.l == 0) m->uidnumber = s_objectsid;
	}
}

static void proxy_killClient(epoll_client_t *client)
{
	client->kill = TRUE;
	if (client->ssl) {
		SSL_shutdown(client->ssl);
	} else {
		shutdown(client->fd, SHUT_RDWR);
	}
}

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;
	plog(DEBUG_TRACE, "[C] scan_ldapmessage: Consumed %d, remaining length %d, id %lu, op %lu", (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:
		proxy_killClient(client);
		return TRUE;
	}
	return TRUE;
}

void proxy_removeClient(epoll_client_t * const client)
{
	int i, lastValid = -1;
	for (i = 0; i < _pendingCount; ++i) {
		if (_pendingRequest[i].client == client) {
			plog(DEBUG_TRACE, "RemoveClient success %p", client);
			_pendingRequest[i].client = NULL;
		}
		else if (_pendingRequest[i].client != NULL) lastValid = i;
	}
	_pendingCount = lastValid + 1;
	if (client->fixedServer != NULL) {
		client->fixedServer->kill = TRUE;
		shutdown(client->fixedServer->fd, SHUT_RDWR);
		client->fixedServer->fixedClient = NULL;
	}
}

void proxy_removeServer(epoll_server_t * const server)
{
	if (server->fixedClient != NULL) {
		proxy_killClient(server->fixedClient);
		server->fixedClient->fixedServer = NULL;
	}
}

static void hexdump(epoll_server_t *server, const size_t start, const size_t maxLen)
{
	for (size_t i = start; i < maxLen; ++i) {
		const uint8_t c = server->readBuffer[i];
		if (c >= 32 && c <= 126) {
			putchar(c);
		} else {
			printf("[%X]", (int)c);
		}
	}
	putchar('\n');
}

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

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;
	}
	plog(DEBUG_TRACE, "[Server] scan_ldapmessage: Consumed %d, remaining length %d, id %lu, op %lu", (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);
	case SearchResultReference:
		// Just ignore these for now
		//scan_ldapstring(server->readBuffer + res,const char* max,struct string* s);
		return TRUE;
	}
	pending_t *pending = proxy_getPendingFromServer(messageId);
	plog(DEBUG_WARNING, "[Server] Unsupported op in reply: %lu; dropping pending %p.", op, pending);
	if (_debugLevel >= DEBUG_VERBOSE) {
		printf("[Server] Message content was: ");
		hexdump(server, 0, maxLen);
	}
	if (pending != NULL && pending->client != NULL) {
		proxy_killClient(pending->client);
		pending->client = NULL;
	}
	return TRUE; // See if we can recover by skipping this message...
}

//

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 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;
	if (a->s == b->s) return 1;
	return strncmp(a->s, b->s, a->l) == 0;
}

/**
 * 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;
	if (a->s == b->s) return 1;
	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, const BOOL negated);
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, const BOOL negated);

/**
 * Checks whether the filter contains attributes that indicate this is a search for a user.
 * Expects pre-replacement attribute names.
 */
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 (iequals(&filter->ava.desc, &s_uid) || iequals(&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 (iequals(&filter->ava.desc, &s_dn) && iequals(&filter->ava.value, &str_ADUSERDN)) {
				*wantedGroupName = str_ADUSER;
			} 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 (iequals(&filter->ava.desc, &s_cn)) {
				*wantedGroupName = filter->ava.value;
			} else if (iequals(&filter->ava.desc, &s_member) || iequals(&filter->ava.desc, &s_memberuid)) {
				*wantsMember = TRUE;
			}
			break;
		default: break;
		}
	}
	return retval;
}

/**
 * Gets passed original filter from client.
 */
static void request_replaceFilter(server_t *server, struct Filter **filter, BOOL negated)
{
	while (*filter != NULL) {
		BOOL del = FALSE;
		switch ((*filter)->type) {
		case NOT:
			request_replaceFilter(server, &(*filter)->x, !negated);
			if ((*filter)->x == NULL) {
				del = TRUE;
			}
			break;
		case AND:
		case OR:
#ifdef FILTER_COLLAPSING
			// This collapses nested filters of same type, which was suspected to cause issues
			// with certain LDAP servers (which was not the case in the end)
			{
				struct Filter *f = (*filter)->x, **p = &(*filter)->x;
				while (f) {
					if (f->type == (*filter)->type) {
						struct Filter **l = &f->x;
						while (*l != NULL) l = &(*l)->next;
						*l = f->next;
						*p = f->x;
						f->x = NULL;
						f->next = NULL;
						free_ldapsearchfilter(f);
						f = *p;
					} else {
						p = &f->next;
						f = f->next;
					}
				}
			}
#endif
			request_replaceFilter(server, &(*filter)->x, negated);
			if ((*filter)->x == NULL) {
				del = TRUE;
			}
			break;
		case PRESENT:
		case SUBSTRING:
			if (!request_replaceAttribute(server, &(*filter)->ava.desc, NULL, NULL, negated)) {
				del = TRUE;
			}
			break;
		case EQUAL:
		case GREATEQUAL:
		case LESSEQUAL:
		case APPROX:
			if (!request_replaceAttribute(server, &(*filter)->ava.desc, &(*filter)->ava.value, NULL, negated)) {
				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(MATCH,TOSET) else if (iequals(&(*adl)->a, &s_ ## MATCH)) attr->TOSET = 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) request_replaceAttribute(server, &(*adl)->a, NULL, NULL, FALSE);
		else if (iequals(&(*adl)->a, &s_homedirectory)) {
			attr->homeDirectory = TRUE;
			if (server->plainLdap) {
				(*adl)->a = server->map.localhome;
			} else {
				next = (*adl)->next;
				free(*adl);
				*adl = next;
			}
		}
		elifSETATTR(gidnumber, gidNumber);
		elifSETATTR(gecos, gecos);
		elifSETATTR(realaccount, realAccount);
		elifSETATTR(loginshell, loginShell);
		else request_replaceAttribute(server, &(*adl)->a, NULL, attr, FALSE);
		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 = server->map.uid;
			user->next = *adl;
			*adl = user;
		}
	}
}

/**
 * @return FALSE = delete attribute, TRUE = keep
 */
static BOOL request_replaceAttribute(server_t *server, struct string *attribute, struct string *value, attr_t *attr, const BOOL negated)
{
	if (iequals(attribute, &s_uid)) {
		*attribute = server->map.uid;
		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 TRUE;
		if (server->fixNumeric) {
			fixUnNumeric(value);
		}
		////// ###################
	} else if (iequals(attribute, &s_bogusfieldname42)) {
		if (attr) {
			attr->bogusFieldName42 = TRUE;
			return FALSE;
		}
		*attribute = server->map.uid;
	} else if (iequals(attribute, &s_bogusfieldname43)) {
		if (attr) {
			attr->bogusFieldName43 = TRUE;
			return FALSE;
		}
		*attribute = server->map.uid;
	} else if (iequals(attribute, &s_homemount)) {
		*attribute = server->map.homemount;
		if (attr != NULL) attr->homeMount = TRUE;
	} else if (iequals(attribute, &s_objectclass)) {
		if (value == NULL) return TRUE;
		if (equals(value, &s_shadowAccount)) *value = server->map.shadowAccount;
		else if (equals(value, &s_posixAccount)) *value = server->map.posixAccount;
	} else if (iequals(attribute, &s_uidnumber)) {
		*attribute = server->map.uidnumber;
		if (value == NULL) return TRUE;
		if (value != NULL && !negated && value->l == 1 && value->s[0] == '0') {
			// Saftey measure: Query for user with uidNumber == 0 - root; replace with something that
			// should never return anything
			*value = s_uid;
		}
		if (!server->plainLdap) {
			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;
}

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

/**
 * Add all the default attributes we're interested in in our context to the
 * given ADL from a client. Pre-relace/map, with names corresponding to
 * our expected names.
 */
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)
/**
 * Filter all requested attributes which are not on our whitelist.
 * Attribute names are pre-replacement, directly what the client sends us, before
 * mapping to scheme expected by server.
 */
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) || S_EQ(bogusfieldname42) || S_EQ(bogusfieldname43))) {
			// 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);
static void response_replaceAdl(server_t *server, struct string *type, struct AttributeDescriptionList **adl, attr_t *attr);
static void response_replaceAttribute(server_t *server, const struct string * const attribute, struct string * const 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)
#define elifDELATTR(MATCH,FIELD,...) else if (__VA_ARGS__ (iequals(&(*pal)->type, &s_ ## MATCH)) ) del = TRUE, attr->FIELD = TRUE
#define elifDEL(MATCH) else if (iequals(&(*pal)->type, &s_ ## MATCH)) del = TRUE
static void response_replacePal(server_t *server, struct PartialAttributeList **pal, attr_t *attr)
{
	struct string *username = NULL;
	BOOL wasNumeric = FALSE;
	while (*pal != NULL) {
		BOOL del = FALSE;
		if (0) { } // Remove fields we don't want from AD/LDAP
		elifDELATTR(gidnumber, gidNumber);
		elifDELATTR(gecos, gecos);
		elifDELATTR(loginshell, loginShell);
		elifDELATTR(uidnumber, uidNumber, !server->plainLdap &&);
		elifDEL(mail);
		elifDELATTR(cn, cn, !iequals(&server->map.uid, &s_cn) &&);
		elifDEL(memberof);
		else if (iequals(&(*pal)->type, &server->map.homemount)) {
			// 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)) {
				// Not UNC, ignore and generate later if possible
				del = TRUE;
				attr->homeMount = TRUE;
			} else {
				attr->homeMount = FALSE;
				(*pal)->type = s_homeMount;
			}
		}
		else if (iequals(&(*pal)->type, &server->map.localhome)) {
			// homeDirectory is set in LDAP - use if it's a local path
			if(response_filterLocalHomeDir(*pal)) {
				attr->homeDirectory = FALSE;
				(*pal)->type = s_homeDirectory;
			} else {
				del = TRUE;
			}
		}
		// Entry should be removed, free structs
		if (del) {
			struct PartialAttributeList *next = (*pal)->next;
			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 && iequals(&(*pal)->type, &s_uid)) {
			username = &(*pal)->values->a;
			if (server->fixNumeric && username->l > 1 && username->s[0] == 's' && isInt(username, 1)) wasNumeric = TRUE;
		}
		pal = &(*pal)->next;
	}
	if (username != NULL) {
		char *user = tmpbuffer_get();
		snprintf(user, TMPLEN, "%.*s", (int)username->l, username->s);
		if (attr->homeDirectory) {
			ADDATTR(homeDirectory, "/home/%s", user);
		}
		ADDATTR(gecos, "%s,,,", user);
		ADDATTR(cn, "%s", user);
		if (attr->bogusFieldName42) {
			ADDATTR(bogusFieldName42, "%s", user);
		}
		if (attr->bogusFieldName43) {
			ADDATTR(bogusFieldName43, "%s", user);
		}
		if (wasNumeric) user++; // From here on, user is the real AD/ldap username, no leading 's'
		if (attr->homeMount && server->homeTemplate[0] != '\0') {
			ADDATTR(homeMount, server->homeTemplate, user, user, user, user, user, user);
		}
		// Do this here so user++ will have been executed
		ADDATTR(realAccount, "%s", user);
	}
	ADDATTR(loginShell, "/bin/bash");
	ADDATTR(gidNumber, "1001");
}

static void response_replaceAdl(server_t *server, struct string *attribute, struct AttributeDescriptionList **adl, attr_t *attr)
{
	if (iequals(attribute, &server->map.uid)) {
		*attribute = s_uid;
	} else if (iequals(attribute, &server->map.uidnumber)) {
		*attribute = s_uidNumber;
	}
	while (*adl != NULL) {
		struct AttributeDescriptionList *next = NULL;
		// Maybe delete entries here later
		if (next != NULL) {
			free(*adl);
			*adl = next;
			continue;
		}
		response_replaceAttribute(server, attribute, &(*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, const struct string * const attribute, struct string * const value)
{
	if (value == NULL) return;
	// Attributes already remapped here!
	if (iequals(attribute, &s_uid)) {
		if (server->fixNumeric) {
			fixNumeric(value);
		}
	} else if (iequals(attribute, &s_uidnumber)) {
		if (!server->plainLdap) {
			plog(DEBUG_TRACE, "Replacing uidnumber from objectsid len=%d", (int)value->l);
			// If this is AD, this must have been objectSid before - convert SID to number
			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);
		}
	} else if (iequals(attribute, &s_objectclass)) {
		if (equals(value, &server->map.posixAccount)) {
			*value = s_posixAccount;
		} else if (equals(value, &server->map.shadowAccount)) {
			*value = s_shadowAccount;
		}
	}
}

/**
 * Remove every attribute except one that looks like a local home directory path.
 */
static BOOL response_filterHomeDir(struct PartialAttributeList *pal)
{
	helper_printpal(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] == '\\') {
				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;
}

/**
 * Remove every attribute except one that looks like a UNC path.
 */
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;
				pal->type = s_homeDirectory;
				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;
	next->next = pal;
	next->type = *attribute;
	next->values = malloc(sizeof(struct AttributeDescriptionList));
	va_start(args, format);
	tmpbuffer_formatva(&next->values->a, format, args);
	va_end(args);
	next->values->next = NULL;
	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) {
		plog(DEBUG_WARNING, "[Client] Invalid search request: baseObj '%.*s' unknown (scope %d).", (int)req.baseObject.l, req.baseObject.s, (int)req.scope);
		return FALSE;
	}
	plog(DEBUG_TRACE, "scan_ldapsearchrequest: baseObj: %.*s, scope: %d, derefAliases: %d", (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
		if (_debugLevel >= DEBUG_VERBOSE) {
			printf("[Client] Search request (handling local): ");
			helper_printfilter(req.filter);
		}
		const BOOL ret = proxy_localSearchRequest(client, messageId, &req);
		free_ldapsearchrequest(&req);
		plog(DEBUG_TRACE, "[Client] Result: %d\n", (int)ret);
		return ret;
	}
	// Forward
	if (_debugLevel >= DEBUG_TRACE) {
		printf("[Client] Search request (original): ");
		helper_printfilter(req.filter);
		printf("[Client] Wanted attributes: ");
		helper_printal(req.attributes);
	}
	if (req.sizeLimit == 0 || req.sizeLimit > 20) req.sizeLimit = 20; // TODO: Magic value
	pending_t *pending = proxy_getFreePendingSlot(client);
	if (pending == NULL) {
		plog(DEBUG_WARNING, "Cannot handle incoming client request; too many pending requests on the wire.");
		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);
			request_replaceAdl(server, &req.attributes, &pending->attr);
		}
		memset(&pending->attr, -1, sizeof(pending->attr));
	}
	request_replaceFilter(server, &req.filter, FALSE);
	if (_debugLevel >= DEBUG_TRACE) {
		printf("[Client] Search request (translated): ");
		helper_printfilter(req.filter);
		printf("[Client] Wanted attributes: ");
		helper_printal(req.attributes);
	}
	pending->clientMessageId = messageId;
	if (client->fixedServer == NULL) {
		pending->serverMessageId = server_searchRequest(server, &req);
	} else {
		pending->serverMessageId = server_searchRequestOnConnection(client->fixedServer, &req);
	}
	if (pending->serverMessageId == 0) {
		// Failed to forward.. TODO: Fail client
		plog(DEBUG_WARNING, "Failed to forward a search request to server.");
		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) {
		plog(DEBUG_WARNING, "[Server] Received response with unknown messageId %lu, ignoring...", messageId);
		return TRUE;
	}
	const char *body;
	size_t bodyLen;
	if (type == SearchResultDone) {
		// Just forward with new header
		bodyLen = maxLen - offset;
		body = server->readBuffer + offset;
		plog(DEBUG_TRACE, "[Server] SRDONE");
		// pending->client will be released at end of function
	} else {
		// Transform reply
		struct SearchResultEntry sre;
		const size_t res = scan_ldapsearchresultentry(server->readBuffer + offset, server->readBuffer + maxLen, &sre);
		if (res == 0) return FALSE;
		if (_debugLevel >= DEBUG_TRACE) {
			printf("SearchResultEntry:\n");
			helper_printpal(sre.attributes);
		}
		response_replacePal(server->serverData, &sre.attributes, &pending->attr);
		if (_debugLevel >= DEBUG_TRACE) {
			helper_printpal(sre.attributes);
		}
		bodyLen = fmt_ldapsearchresultentry(NULL, &sre);
		if (bodyLen == 0) {
			plog(DEBUG_WARNING, "Error formatting ldapsearchresultentry after transformation.");
			free_ldapsearchresultentry(&sre);
			return FALSE;
		}
		if (bodyLen > MAXMSGLEN) {
			plog(DEBUG_WARNING, "ldapsearchresultentry too large after transformation.");
			free_ldapsearchresultentry(&sre);
			return FALSE;
		}
		fmt_ldapsearchresultentry(bodyBuffer, &sre);
		free_ldapsearchresultentry(&sre);
		if (_debugLevel >= DEBUG_TRACE) {
			const size_t res = scan_ldapsearchresultentry(bodyBuffer, bodyBuffer + bodyLen, &sre);
			if (res != 0) {
				helper_printpal(sre.attributes);
				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; // Release pending
	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
		plog(DEBUG_WARNING, "[Client] Unsupported bind method: %lu", method);
		bodyLen = fmt_ldapbindresponse(bufoff, authMethodNotSupported, "", "SIMPLE only", "");
	} else {
		// Simple bind :-)
		password.l = 0;
		const size_t res2 = scan_ldapstring(client->readBuffer + offset + res, client->readBuffer + maxLen, &password);
		plog(DEBUG_TRACE, "[Client] scan_ldapbindrequest: Consumed %d, version %lu, method %lu, name '%.*s'", (int)(res + res2), version, method, (int)name.l, name.s);
		if (name.l == 0 && password.l == 0) {
			// Anonymous bind used for "normal" lookups
			plog(DEBUG_VERBOSE, "[Client] Anonymous bind accepted");
			bodyLen = fmt_ldapbindresponse(bufoff, success, "", "main screen turn on", "");
		} else {
			BOOL incorrect = FALSE;
			server_t *server = server_getFromBase(&name);
			if (server == NULL
					|| (incorrect = (strncmp(password.s, "\x08\x0a\x0d\x7fINCORRECT", 13) == 0))
					|| (server->fixNumeric && isInt(&name, 0))) {
				// The INCORRECT part is some weird thing I saw pam_ldap do - probably to identify misconfigured
				// LDAP servers/accounts that will accept any password - save the round trip to AD and deny
				if (!incorrect) plog(DEBUG_WARNING, "[Client] Numeric account or invalid binddn for %.*s", (int)name.l, name.s);
				bodyLen = fmt_ldapbindresponse(bufoff, invalidCredentials, "", "invalid credentials", "");
			} else {
				// Seems to be an actual bind - forward to AD - TODO: SASL (DIGEST-MD5? Something?)
				// TODO: Handle DN, but should not be needed... fixUnNumeric(&name);
				pending_t *pending = proxy_getFreePendingSlot(client);
				epoll_server_t *con;
				const unsigned long smid = server_tryUserBind(server, &name, &password, &con);
				if (pending == NULL || smid == 0) {
					// Busy
					if (pending != NULL) pending->client = NULL;
					plog(DEBUG_WARNING, "[Client] Too many pending requests, or cannot connect to AD for bind.");
					bodyLen = fmt_ldapbindresponse(bufoff, busy, "", "can't handle it", "");
				} else {
					// Request queued, client needs to wait
					plog(DEBUG_INFO, "[Client] Forwarding bind to AD/LDAP for user %.*s (#%lu)", (int)name.l, name.s, smid);
					pending->clientMessageId = messageId;
					pending->serverMessageId = smid;
					con->fixedClient = client;
					client->fixedServer = con;
					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
	plog(DEBUG_TRACE, "[Server] scan_ldapbindresponse: Consumed %d, result: %lu, binddn: %.*s, error: %.*s, referral: %.*s", (int)res, result, (int)binddn.l, binddn.s, (int)error.l, error.s, (int)refer.l, refer.s);
	server->bound = (result == success);
	if (server->bound) {
		plog(DEBUG_VERBOSE, "[Server] Accepted credentials (#%lu)", messageId);
	} else {
		plog(DEBUG_INFO, "[Server] Wrong credentials (#%lu)", messageId);
	}
	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 server->bound; // 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)
{
	plog(DEBUG_VERBOSE, "[Proxy] Sending static feature request reply to client.");
	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 (!iequals(&name, &str_ADUSER)) {
			// We know only one group...
			name.l = 0;
		}
		if (number == 1001 || name.l != 0) {
			// At least one of them was set
			plog(DEBUG_VERBOSE, "[Proxy] Sending static group membership to client.");
			struct SearchResultEntry sre;
			struct PartialAttributeList gidNumber, cn, objectClass;
			struct AttributeDescriptionList gidNumberVal, cnVal, objectClassVal;
			memset(&sre, 0, sizeof(sre));
			sre.objectName = str_ADUSERDN;
			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 {
			plog(DEBUG_VERBOSE, "[Proxy] Sending empty posixGroup search result to client.");
		}
	}
	// Always send SearchResultDone here, so if we have no match above it results in
	// an empty reply.
	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);
}