/* ARISA - Access List Functions
 * Copyright (C) 2004 Carl Ritson
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include "arisa.h"

static int test_entry(access_entry_t *ent, network_t *net,
		const char *host, const char *hostmask, const char *nick) {
	int ret,mode,accept = 1;
	
	assert(ent != NULL);
	
	if(ent->network != NULL)
		accept &= (ent->network == net);
	if(ent->host != NULL) {
		if(hostmask != NULL &&
				(strchr(ent->host,'!') != NULL || 
				 strchr(ent->host,'@') != NULL)) {
			accept &= (irc_hostmask_match(hostmask,ent->host));
		} else if(host != NULL) {
			accept &= (irc_hostmask_match(host,ent->host));
		} else {
			return 0;
		}
	}
	if(ent->channel != NULL) {
		if(net == NULL || nick == NULL)
			return 0;
		ret = 0;
		LOCK(net);
		if(net->info != NULL) {
			LOCK(net->info);
			ret = nickhash_present_mode(net->info->nick_tracker,
					ent->channel,nick,&mode);
			UNLOCK(net->info);
		}
		UNLOCK(net);
		accept &= (ret != 0);
		if(ret != 0 && ent->mode != 0)
			accept &= ((mode & ent->mode) != 0);
	} else if(ent->mode != 0) {
		mode = 0;
		LOCK(net);
		if(net->info != NULL) {
			LOCK(net->info);
			mode = nickhash_highest_mode(
					net->info->nick_tracker,nick);
			UNLOCK(net->info);
		}
		UNLOCK(net);
		accept &= ((mode & ent->mode) != 0);
	}

	return (accept != 0 ? 1 : 0);
}

int access_check(access_t *acc, network_t *net, 
		const char *host, const char *hostmask, const char *nick) {
	int i,accept;
	
	if(acc == NULL)
		return 0;
	
	LOCK(acc);
	if((acc->flags & ACCESS_SMART) == ACCESS_SMART) {
		accept = 1;
		for(i = 0; i < acc->no_entries && accept; ++i) {
			if(acc->entries[i]->flags & ACCESS_ALLOW)
				accept = 0;
		}
	} else if(acc->flags & ACCESS_ALLOW) {
		accept = 1;
	} else {
		accept = 0;
	}
	for(i = 0; i < acc->no_entries; ++i) {
		if(test_entry(acc->entries[i],net,host,hostmask,nick)) {
			if(acc->entries[i]->flags & ACCESS_ALLOW) {
				if(acc->flags & ACCESS_ALLOW) {
					accept = 1;
					break;
				} else
					accept = 1;
			} else {
				if(acc->flags & ACCESS_DENY) {
					accept = 0;
					break;
				} else
					accept = 0;
			}
		}
	}
	UNLOCK(acc);
	
	return accept;
}

int access_check_channel(access_t *acc, network_t *net, channel_t *chan) {
	int i,accept;
	
	if(acc == NULL)
		return 0;
	
	LOCK(acc);
	if((acc->flags & ACCESS_SMART) == ACCESS_SMART) {
		accept = 1;
		for(i = 0; i < acc->no_entries && accept; ++i) {
			if(acc->entries[i]->flags & ACCESS_ALLOW)
				accept = 0;
		}
	} else if(acc->flags & ACCESS_ALLOW) {
		accept = 1;
	} else {
		accept = 0;
	}
	for(i = 0; i < acc->no_entries; ++i) {
		if(acc->entries[i]->network == net && 
				acc->entries[i]->channel == chan) {
			if(acc->entries[i]->flags & ACCESS_ALLOW) {
				if(acc->flags & ACCESS_ALLOW) {
					accept = 1;
					break;
				} else
					accept = 1;
			} else {
				if(acc->flags & ACCESS_DENY) {
					accept = 0;
					break;
				} else
					accept = 0;
			}
		}
	}
	UNLOCK(acc);

	return accept;
}

int access_add_entry(access_t *acc, int flags, network_t *net, 
		const char *host, channel_t *chan, int mode) {
	access_entry_t *ent = alloc_access_entry();
	int tmp,ret = 0;
	
	ent->flags	= flags;
	ent->network	= net;
	if(host != NULL)
		ent->host = xstrdup(host);
	ent->channel	= chan;
	ent->mode	= mode;

	LOCK(acc);
	PTRARR_ADD(&(acc->entries),&(acc->no_entries),ent);
	UNLOCK(acc);

	/* The following code deals with the race condition, we added an
	 * entry for a network or channel which was deleted before we added
	 * the entry.  We do it after we add the entry, incase one of the
	 * purge functions gets ahead of us and does its job.
	 * FIXME: more clear description?
	 */
	if(net != NULL) {
		LOCK(net);
		if(net->deleted != 0)
			ret = -1;
		UNLOCK(net);
	}
	if(chan != NULL) {
		LOCK(chan);
		if(chan->deleted != 0)
			ret = -1;
		UNLOCK(chan);
	}	
	if(ret != 0) {
		LOCK(acc);
		tmp = acc->no_entries;
		PTRARR_DEL(&(acc->entries),&(acc->no_entries),ent);
		if(tmp != acc->no_entries)
			free_access_entry(ent);
		// else it was already removed by a purge function
		UNLOCK(acc);
	}

	return ret;
}

void access_copy(access_t *dst, access_t *src) {
	int i;
	
	// two irregular locks, serialise on the global lock
	LOCK(global);
	
	LOCK(src);
	LOCK(dst);
	
	for(i = 0; i < dst->no_entries; ++i)
		free_access_entry(dst->entries[i]);
	if(dst->entries != NULL)
		xfree(dst->entries);

	dst->flags = src->flags;
	
	dst->no_entries = src->no_entries;
	dst->entries = xalloc(sizeof(access_entry_t *) * dst->no_entries);
	for(i = 0; i < src->no_entries; ++i) {
		dst->entries[i] = alloc_access_entry();
		dst->entries[i]->flags	= src->entries[i]->flags;
		dst->entries[i]->network = src->entries[i]->network;
		dst->entries[i]->channel = src->entries[i]->channel;
		dst->entries[i]->mode = src->entries[i]->mode;
		if(src->entries[i]->host != NULL)
			dst->entries[i]->host = xstrdup(src->entries[i]->host);
	}

	UNLOCK(dst);
	UNLOCK(src);

	UNLOCK(global);
}

int access_del_entry(access_t *acc, access_entry_t *ent) {
	int tmp, ret = -1;

	LOCK(acc);
	tmp = acc->no_entries;
	PTRARR_DEL(&(acc->entries),&(acc->no_entries),ent);
	if(acc->no_entries != tmp) {
		free_access_entry(ent);
		ret = 0;
	}
	UNLOCK(acc);

	return ret;
}		

int access_del_entry_no(access_t *acc, int no) {
	int ret = -1;
	
	LOCK(acc);
	if(no >= 0 && no < acc->no_entries) {
		access_entry_t *ent = acc->entries[no];
		PTRARR_DEL(&(acc->entries),&(acc->no_entries),ent);
		free_access_entry(ent);
		ret = 0;
	}
	UNLOCK(acc);

	return ret;
}

static void purge_netchan(access_t *acc, network_t *net, channel_t *chan) {
	int i;
	
	LOCK(acc);
	for(i = 0; i < acc->no_entries; ++i) {
		if(	(chan == NULL && net != NULL && 
			 acc->entries[i]->network == net) ||
			(net == NULL && chan != NULL &&
			 acc->entries[i]->channel == chan) ||
			(acc->entries[i]->network == net &&
			 acc->entries[i]->channel == chan)) {
			
			free_access_entry(acc->entries[i]);
			acc->entries[i] = NULL;
		}
	}
	PTRARR_DEL(&(acc->entries),&(acc->no_entries),NULL);
	UNLOCK(acc);
}

static void access_purge_netchan(network_t *net, channel_t *chan) {
	int i;

	LOCK(global);
	for(i = 0; i < global->no_interfaces; ++i) {
		LOCK(global->interfaces[i]);
		if(global->interfaces[i]->type == INTERFACE_RECV) {
			purge_netchan(global->interfaces[i]->recv.access,
					net,chan);
		}
		UNLOCK(global->interfaces[i]);
	}
	for(i = 0; i < global->no_packlists; ++i) {
		LOCK(global->packlists[i]);
		purge_netchan(global->packlists[i]->access,net,chan);
		UNLOCK(global->packlists[i]);
	}
	UNLOCK(global);
}

void access_purge_channel(channel_t *chan) {
	access_purge_netchan(NULL,chan);
}

void access_purge_network(network_t *net) {
	access_purge_netchan(net,NULL);
}

void access_verify_entries(access_t *acc) {
	int i;

	assert(acc != NULL);
	
	LOCK(acc);
	for(i = 0; i < acc->no_entries; ++i) {
		if(acc->entries[i]->network != NULL) {
			if(!irc_network_valid(acc->entries[i]->network)) {
				free_access_entry(acc->entries[i]);
				acc->entries[i] = NULL;
				continue;
			}
		}
		if(acc->entries[i]->network != NULL &&
			acc->entries[i]->channel != NULL) {
			if(!irc_channel_valid(acc->entries[i]->network,
						acc->entries[i]->channel)) {
				free_access_entry(acc->entries[i]);
				acc->entries[i] = NULL;
				continue;
			}
		}
	}
	PTRARR_DEL(&(acc->entries),&(acc->no_entries),NULL);
	UNLOCK(acc);
}
