/*
 *  charybdis: an advanced internet relay chat daemon (ircd).
 *  hostmask.c: Code to efficiently find IP & hostmask based configs.
 *
 *  Copyright (C) 1990 Jarkko Oikarinen and University of Oulu, Co Center
 *  Copyright (C) 1996-2002 Hybrid Development Team
 *  Copyright (C) 2002-2005 ircd-ratbox development team
 *  Copyright (C) 2005-2008 charybdis development team
 *
 *  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
 */

namespace ircd {

#ifdef RB_IPV6
static unsigned long hash_ipv6(struct sockaddr *, int);
#endif
static unsigned long hash_ipv4(struct sockaddr *, int);


/* int parse_netmask(const char *, struct rb_sockaddr_storage *, int *);
 * Input: A hostmask, or an IPV4/6 address.
 * Output: An integer describing whether it is an IPV4, IPV6 address or a
 *         hostmask, an address(if it is an IP mask),
 *         a bitlength(if it is IP mask).
 * Side effects: None
 */
int
parse_netmask(const char *text, struct rb_sockaddr_storage *naddr, int *nb)
{
	char *ip = LOCAL_COPY(text);
	char *ptr;
	struct rb_sockaddr_storage *addr, xaddr;
	int *b, xb;
	if(nb == NULL)
		b = &xb;
	else
		b = nb;

	if(naddr == NULL)
		addr = &xaddr;
	else
		addr = naddr;

	if(strpbrk(ip, "*?") != NULL)
	{
		return HM_HOST;
	}
#ifdef RB_IPV6
	if(strchr(ip, ':'))
	{
		if((ptr = strchr(ip, '/')))
		{
			*ptr = '\0';
			ptr++;
			*b = atoi(ptr);
			if(*b > 128)
				*b = 128;
			else if(*b < 0)
				return HM_HOST;
		} else
			*b = 128;
		if(rb_inet_pton_sock(ip, (struct sockaddr *)addr) > 0)
			return HM_IPV6;
		else
			return HM_HOST;
	} else
#endif
	if(strchr(text, '.'))
	{
		if((ptr = strchr(ip, '/')))
		{
			*ptr = '\0';
			ptr++;
			*b = atoi(ptr);
			if(*b > 32)
				*b = 32;
			else if(*b < 0)
				return HM_HOST;
		} else
			*b = 32;
		if(rb_inet_pton_sock(ip, (struct sockaddr *)addr) > 0)
			return HM_IPV4;
		else
			return HM_HOST;
	}
	return HM_HOST;
}

/* Hashtable stuff...now external as its used in m_stats.c */
struct AddressRec *atable[ATABLE_SIZE];

void
init_host_hash(void)
{
	memset(&atable, 0, sizeof(atable));
}

/* unsigned long hash_ipv4(struct rb_sockaddr_storage*)
 * Input: An IP address.
 * Output: A hash value of the IP address.
 * Side effects: None
 */
static unsigned long
hash_ipv4(struct sockaddr *saddr, int bits)
{
	struct sockaddr_in *addr = (struct sockaddr_in *)(void *)saddr;

	if(bits != 0)
	{
		unsigned long av = ntohl(addr->sin_addr.s_addr) & ~((1 << (32 - bits)) - 1);
		return (av ^ (av >> 12) ^ (av >> 24)) & (ATABLE_SIZE - 1);
	}

	return 0;
}

/* unsigned long hash_ipv6(struct rb_sockaddr_storage*)
 * Input: An IP address.
 * Output: A hash value of the IP address.
 * Side effects: None
 */
#ifdef RB_IPV6
static unsigned long
hash_ipv6(struct sockaddr *saddr, int bits)
{
	struct sockaddr_in6 *addr = (struct sockaddr_in6 *)(void *)saddr;
	unsigned long v = 0, n;
	for (n = 0; n < 16; n++)
	{
		if(bits >= 8)
		{
			v ^= addr->sin6_addr.s6_addr[n];
			bits -= 8;
		}
		else if(bits)
		{
			v ^= addr->sin6_addr.s6_addr[n] & ~((1 << (8 - bits)) - 1);
			return v & (ATABLE_SIZE - 1);
		}
		else
			return v & (ATABLE_SIZE - 1);
	}
	return v & (ATABLE_SIZE - 1);
}
#endif

/* int hash_text(const char *start)
 * Input: The start of the text to hash.
 * Output: The hash of the string between 1 and (TH_MAX-1)
 * Side-effects: None.
 */
static int
hash_text(const char *start)
{
	const char *p = start;
	unsigned long h = 0;

	while(*p)
	{
		h = (h << 4) - (h + (unsigned char) rfc1459::tolower(*p++));
	}

	return (h & (ATABLE_SIZE - 1));
}

/* unsigned long get_hash_mask(const char *)
 * Input: The text to hash.
 * Output: The hash of the string right of the first '.' past the last
 *         wildcard in the string.
 * Side-effects: None.
 */
static unsigned long
get_mask_hash(const char *text)
{
	const char *hp = "", *p;

	for (p = text + strlen(text) - 1; p >= text; p--)
		if(*p == '*' || *p == '?')
			return hash_text(hp);
		else if(*p == '.')
			hp = p + 1;
	return hash_text(text);
}

/* struct ConfItem* find_conf_by_address(const char*, struct rb_sockaddr_storage*,
 *         int type, int fam, const char *username)
 * Input: The hostname, the address, the type of mask to find, the address
 *        family, the username.
 * Output: The matching value with the highest precedence.
 * Side-effects: None
 * Note: Setting bit 0 of the type means that the username is ignored.
 */
struct ConfItem *
find_conf_by_address(const char *name, const char *sockhost,
			const char *orighost,
			struct sockaddr *addr, int type, int fam,
			const char *username, const char *auth_user)
{
	unsigned long hprecv = 0;
	struct ConfItem *hprec = NULL;
	struct AddressRec *arec;
	int b;

	if(username == NULL)
		username = "";

	if(addr)
	{
		/* Check for IPV6 matches... */
#ifdef RB_IPV6
		if(fam == AF_INET6)
		{

			for (b = 128; b >= 0; b -= 16)
			{
				for (arec = atable[hash_ipv6(addr, b)]; arec; arec = arec->next)
					if(arec->type == (type & ~0x1) &&
					   arec->masktype == HM_IPV6 &&
					   comp_with_mask_sock(addr, (struct sockaddr *)&arec->Mask.ipa.addr,
						arec->Mask.ipa.bits) &&
						(type & 0x1 || match(arec-> username, username)) &&
						(type != CONF_CLIENT || !arec->auth_user ||
						(auth_user && match(arec->auth_user, auth_user))) &&
						arec->precedence > hprecv)
					{
						hprecv = arec->precedence;
						hprec = arec->aconf;
					}
			}
		}
		else
#endif
		if(fam == AF_INET)
		{
			for (b = 32; b >= 0; b -= 8)
			{
				for (arec = atable[hash_ipv4(addr, b)]; arec; arec = arec->next)
					if(arec->type == (type & ~0x1) &&
					   arec->masktype == HM_IPV4 &&
					   comp_with_mask_sock(addr, (struct sockaddr *)&arec->Mask.ipa.addr,
							       arec->Mask.ipa.bits) &&
						(type & 0x1 || match(arec->username, username)) &&
						(type != CONF_CLIENT || !arec->auth_user ||
						(auth_user && match(arec->auth_user, auth_user))) &&
						arec->precedence > hprecv)
					{
						hprecv = arec->precedence;
						hprec = arec->aconf;
					}
			}
		}
	}

	if(orighost != NULL)
	{
		const char *p;

		for (p = orighost; p != NULL;)
		{
			for (arec = atable[hash_text(p)]; arec; arec = arec->next)

				if((arec->type == (type & ~0x1)) &&
				   (arec->masktype == HM_HOST) &&
				   arec->precedence > hprecv &&
				   match(arec->Mask.hostname, orighost) &&
				   (type != CONF_CLIENT || !arec->auth_user ||
				   (auth_user && match(arec->auth_user, auth_user))) &&
				   (type & 0x1 || match(arec->username, username)))
				{
					hprecv = arec->precedence;
					hprec = arec->aconf;
				}
			p = strchr(p, '.');
			if(p != NULL)
				p++;
			else
				break;
		}
		for (arec = atable[0]; arec; arec = arec->next)
		{
			if(arec->type == (type & ~0x1) &&
			   arec->masktype == HM_HOST &&
			   arec->precedence > hprecv &&
			   (match(arec->Mask.hostname, orighost) ||
			    (sockhost && match(arec->Mask.hostname, sockhost))) &&
			    (type != CONF_CLIENT || !arec->auth_user ||
			    (auth_user && match(arec->auth_user, auth_user))) &&
			   (type & 0x1 || match(arec->username, username)))
			{
				hprecv = arec->precedence;
				hprec = arec->aconf;
			}
		}
	}

	if(name != NULL)
	{
		const char *p;
		/* And yes - we have to check p after strchr and p after increment for
		 * NULL -kre */
		for (p = name; p != NULL;)
		{
			for (arec = atable[hash_text(p)]; arec; arec = arec->next)
				if((arec->type == (type & ~0x1)) &&
				   (arec->masktype == HM_HOST) &&
				   arec->precedence > hprecv &&
				   match(arec->Mask.hostname, name) &&
				   (type != CONF_CLIENT || !arec->auth_user ||
				   (auth_user && match(arec->auth_user, auth_user))) &&
				   (type & 0x1 || match(arec->username, username)))
				{
					hprecv = arec->precedence;
					hprec = arec->aconf;
				}
			p = strchr(p, '.');
			if(p != NULL)
				p++;
			else
				break;
		}
		for (arec = atable[0]; arec; arec = arec->next)
		{
			if(arec->type == (type & ~0x1) &&
			   arec->masktype == HM_HOST &&
			   arec->precedence > hprecv &&
			   (match(arec->Mask.hostname, name) ||
			    (sockhost && match(arec->Mask.hostname, sockhost))) &&
			    (type != CONF_CLIENT || !arec->auth_user ||
			    (auth_user && match(arec->auth_user, auth_user))) &&
			   (type & 0x1 || match(arec->username, username)))
			{
				hprecv = arec->precedence;
				hprec = arec->aconf;
			}
		}
	}
	return hprec;
}

/* struct ConfItem* find_address_conf(const char*, const char*,
 * 	                               struct rb_sockaddr_storage*, int);
 * Input: The hostname, username, address, address family.
 * Output: The applicable ConfItem.
 * Side-effects: None
 */
struct ConfItem *
find_address_conf(const char *host, const char *sockhost, const char *user,
		const char *notildeuser, struct sockaddr *ip, int aftype, char *auth_user)
{
	struct ConfItem *iconf, *kconf;
	const char *vuser;
#ifdef RB_IPV6
	struct sockaddr_in ip4;
#endif

	/* Find the best I-line... If none, return NULL -A1kmm */
	if(!(iconf = find_conf_by_address(host, sockhost, NULL, ip, CONF_CLIENT, aftype, user, auth_user)))
		return NULL;
	/* Find what their visible username will be.
	 * Note that the username without tilde may contain one char more.
	 * -- jilles */
	vuser = IsNoTilde(iconf) ? notildeuser : user;

	/* If they are exempt from K-lines, return the best I-line. -A1kmm */
	if(IsConfExemptKline(iconf))
		return iconf;

	/* Find the best K-line... -A1kmm */
	kconf = find_conf_by_address(host, sockhost, NULL, ip, CONF_KILL, aftype, user, NULL);

	/* If they are K-lined, return the K-line */
	if(kconf)
		return kconf;

	/* if theres a spoof, check it against klines.. */
	if(IsConfDoSpoofIp(iconf))
	{
		char *p = strchr(iconf->info.name, '@');

		/* note, we dont need to pass sockhost here, as its
		 * guaranteed to not match by whats above.. --anfl
		 */
		if(p)
		{
			*p = '\0';
			kconf = find_conf_by_address(p+1, NULL, NULL, ip, CONF_KILL, aftype, iconf->info.name, NULL);
			*p = '@';
		}
		else
			kconf = find_conf_by_address(iconf->info.name, NULL, NULL, ip, CONF_KILL, aftype, vuser, NULL);

		if(kconf)
			return kconf;
	}

	/* if no_tilde, check the username without tilde against klines too
	 * -- jilles */
	if(user != vuser)
	{
		kconf = find_conf_by_address(host, sockhost, NULL, ip, CONF_KILL, aftype, vuser, NULL);
		if(kconf)
			return kconf;
	}

#ifdef RB_IPV6
	if(ip != NULL && ip->sa_family == AF_INET6 &&
			rb_ipv4_from_ipv6((const struct sockaddr_in6 *)(const void *)ip, &ip4))
	{
		kconf = find_conf_by_address(NULL, NULL, NULL, (struct sockaddr *)&ip4, CONF_KILL, AF_INET, vuser, NULL);
		if(kconf)
			return kconf;
	}
#endif /* RB_IPV6 */

	return iconf;
}

/* struct ConfItem* find_dline(struct rb_sockaddr_storage*, int)
 * Input: An address, an address family.
 * Output: The best matching D-line or exempt line.
 * Side effects: None.
 */
struct ConfItem *
find_dline(struct sockaddr *addr, int aftype)
{
	struct ConfItem *aconf;
#ifdef RB_IPV6
	struct sockaddr_in addr2;
#endif

	aconf = find_conf_by_address(NULL, NULL, NULL, addr, CONF_EXEMPTDLINE | 1, aftype, NULL, NULL);
	if(aconf)
		return aconf;
	aconf = find_conf_by_address(NULL, NULL, NULL, addr, CONF_DLINE | 1, aftype, NULL, NULL);
	if(aconf)
		return aconf;
#ifdef RB_IPV6
	if(addr->sa_family == AF_INET6 &&
			rb_ipv4_from_ipv6((const struct sockaddr_in6 *)(const void *)addr, &addr2))
	{
		aconf = find_conf_by_address(NULL, NULL, NULL, (struct sockaddr *)&addr2, CONF_DLINE | 1, AF_INET, NULL, NULL);
		if(aconf)
			return aconf;
	}
#endif
	return NULL;
}

/* void find_exact_conf_by_address(const char*, int, const char *)
 * Input:
 * Output: ConfItem if found
 * Side-effects: None
 */
struct ConfItem *
find_exact_conf_by_address(const char *address, int type, const char *username)
{
	int masktype, bits;
	unsigned long hv;
	struct AddressRec *arec;
	struct rb_sockaddr_storage addr;

	if(address == NULL)
		address = "/NOMATCH!/";
	masktype = parse_netmask(address, &addr, &bits);
#ifdef RB_IPV6
	if(masktype == HM_IPV6)
	{
		/* We have to do this, since we do not re-hash for every bit -A1kmm. */
		hv = hash_ipv6((struct sockaddr *)&addr, bits - bits % 16);
	}
	else
#endif
	if(masktype == HM_IPV4)
	{
		/* We have to do this, since we do not re-hash for every bit -A1kmm. */
		hv = hash_ipv4((struct sockaddr *)&addr, bits - bits % 8);
	}
	else
	{
		hv = get_mask_hash(address);
	}
	for (arec = atable[hv]; arec; arec = arec->next)
	{
		if (arec->type == type &&
				arec->masktype == masktype &&
				(arec->username == NULL || username == NULL ? arec->username == username : !irccmp(arec->username, username)))
		{
			if (masktype == HM_HOST)
			{
				if (!irccmp(arec->Mask.hostname, address))
					return arec->aconf;
			}
			else
			{
				if (arec->Mask.ipa.bits == bits &&
					comp_with_mask_sock((struct sockaddr *)&arec->Mask.ipa.addr, (struct sockaddr *)&addr, bits))
					return arec->aconf;
			}
		}
	}
	return NULL;
}

/* void add_conf_by_address(const char*, int, const char *,
 *         struct ConfItem *aconf)
 * Input:
 * Output: None
 * Side-effects: Adds this entry to the hash table.
 */
void
add_conf_by_address(const char *address, int type, const char *username, const char *auth_user, struct ConfItem *aconf)
{
	static unsigned long prec_value = 0xFFFFFFFF;
	int masktype, bits;
	unsigned long hv;
	struct AddressRec *arec;

	if(address == NULL)
		address = "/NOMATCH!/";
	arec = (AddressRec *)rb_malloc(sizeof(struct AddressRec));
	masktype = parse_netmask(address, &arec->Mask.ipa.addr, &bits);
	arec->Mask.ipa.bits = bits;
	arec->masktype = masktype;
#ifdef RB_IPV6
	if(masktype == HM_IPV6)
	{
		/* We have to do this, since we do not re-hash for every bit -A1kmm. */
		bits -= bits % 16;
		arec->next = atable[(hv = hash_ipv6((struct sockaddr *)&arec->Mask.ipa.addr, bits))];
		atable[hv] = arec;
	}
	else
#endif
	if(masktype == HM_IPV4)
	{
		/* We have to do this, since we do not re-hash for every bit -A1kmm. */
		bits -= bits % 8;
		arec->next = atable[(hv = hash_ipv4((struct sockaddr *)&arec->Mask.ipa.addr, bits))];
		atable[hv] = arec;
	}
	else
	{
		arec->Mask.hostname = address;
		arec->next = atable[(hv = get_mask_hash(address))];
		atable[hv] = arec;
	}
	arec->username = username;
	arec->auth_user = auth_user;
	arec->aconf = aconf;
	arec->precedence = prec_value--;
	arec->type = type;
}

/* void delete_one_address(const char*, struct ConfItem*)
 * Input: An address string, the associated ConfItem.
 * Output: None
 * Side effects: Deletes an address record. Frees the ConfItem if there
 *               is nothing referencing it, sets it as illegal otherwise.
 */
void
delete_one_address_conf(const char *address, struct ConfItem *aconf)
{
	int masktype, bits;
	unsigned long hv;
	struct AddressRec *arec, *arecl = NULL;
	struct rb_sockaddr_storage addr;
	masktype = parse_netmask(address, &addr, &bits);
#ifdef RB_IPV6
	if(masktype == HM_IPV6)
	{
		/* We have to do this, since we do not re-hash for every bit -A1kmm. */
		bits -= bits % 16;
		hv = hash_ipv6((struct sockaddr *)&addr, bits);
	}
	else
#endif
	if(masktype == HM_IPV4)
	{
		/* We have to do this, since we do not re-hash for every bit -A1kmm. */
		bits -= bits % 8;
		hv = hash_ipv4((struct sockaddr *)&addr, bits);
	}
	else
		hv = get_mask_hash(address);
	for (arec = atable[hv]; arec; arec = arec->next)
	{
		if(arec->aconf == aconf)
		{
			if(arecl)
				arecl->next = arec->next;
			else
				atable[hv] = arec->next;
			aconf->status |= CONF_ILLEGAL;
			if(!aconf->clients)
				free_conf(aconf);
			rb_free(arec);
			return;
		}
		arecl = arec;
	}
}

/* void clear_out_address_conf(void)
 * Input: None
 * Output: None
 * Side effects: Clears out all address records in the hash table,
 *               frees them, and frees the ConfItems if nothing references
 *               them, otherwise sets them as illegal.
 */
void
clear_out_address_conf(void)
{
	int i;
	struct AddressRec **store_next;
	struct AddressRec *arec, *arecn;

	for (i = 0; i < ATABLE_SIZE; i++)
	{
		store_next = &atable[i];
		for (arec = atable[i]; arec; arec = arecn)
		{
			arecn = arec->next;
			/* We keep the temporary K-lines and destroy the
			 * permanent ones, just to be confusing :) -A1kmm */
			if(arec->aconf->flags & CONF_FLAGS_TEMPORARY ||
			   (arec->type != CONF_CLIENT && arec->type != CONF_EXEMPTDLINE))
			{
				*store_next = arec;
				store_next = &arec->next;
			}
			else
			{
				arec->aconf->status |= CONF_ILLEGAL;
				if(!arec->aconf->clients)
					free_conf(arec->aconf);
				rb_free(arec);
			}
		}
		*store_next = NULL;
	}
}

void
clear_out_address_conf_bans(void)
{
	int i;
	struct AddressRec **store_next;
	struct AddressRec *arec, *arecn;

	for (i = 0; i < ATABLE_SIZE; i++)
	{
		store_next = &atable[i];
		for (arec = atable[i]; arec; arec = arecn)
		{
			arecn = arec->next;
			/* We keep the temporary K-lines and destroy the
			 * permanent ones, just to be confusing :) -A1kmm */
			if(arec->aconf->flags & CONF_FLAGS_TEMPORARY ||
			   (arec->type == CONF_CLIENT || arec->type == CONF_EXEMPTDLINE))
			{
				*store_next = arec;
				store_next = &arec->next;
			}
			else
			{
				arec->aconf->status |= CONF_ILLEGAL;
				if(!arec->aconf->clients)
					free_conf(arec->aconf);
				rb_free(arec);
			}
		}
		*store_next = NULL;
	}
}


/*
 * show_iline_prefix()
 *
 * inputs       - pointer to client::client requesting output
 *              - pointer to struct ConfItem
 *              - name to which iline prefix will be prefixed to
 * output       - pointer to static string with prefixes listed in ascii form
 * side effects - NONE
 */
char *
show_iline_prefix(client::client *sptr, struct ConfItem *aconf, char *name)
{
	static char prefix_of_host[USERLEN + 15];
	char *prefix_ptr;

	prefix_ptr = prefix_of_host;
	if(IsNoTilde(aconf))
		*prefix_ptr++ = '-';
	if(IsNeedIdentd(aconf))
		*prefix_ptr++ = '+';
	if(IsConfDoSpoofIp(aconf))
		*prefix_ptr++ = '=';
	if(is(*sptr, umode::OPER) && IsConfExemptFlood(aconf))
		*prefix_ptr++ = '|';
	if(is(*sptr, umode::OPER) && IsConfExemptDNSBL(aconf) && !IsConfExemptKline(aconf))
		*prefix_ptr++ = '$';
	if(is(*sptr, umode::OPER) && IsConfExemptKline(aconf))
		*prefix_ptr++ = '^';
	if(is(*sptr, umode::OPER) && IsConfExemptLimits(aconf))
		*prefix_ptr++ = '>';
	*prefix_ptr = '\0';
	strncpy(prefix_ptr, name, USERLEN);
	return (prefix_of_host);
}

/* report_auth()
 *
 * Inputs: pointer to client to report to
 * Output: None
 * Side effects: Reports configured auth{} blocks to client_p
 */
void
report_auth(client::client *client_p)
{
	char *name, *host, *user, *classname;
	const char *pass;
	struct AddressRec *arec;
	struct ConfItem *aconf;
	int i, port;

	for (i = 0; i < ATABLE_SIZE; i++)
		for (arec = atable[i]; arec; arec = arec->next)
			if(arec->type == CONF_CLIENT)
			{
				aconf = arec->aconf;

				if(!is(*client_p, umode::OPER) && IsConfDoSpoofIp(aconf))
					continue;

				get_printable_conf(aconf, &name, &host, &pass, &user, &port,
						   &classname);

				if(!EmptyString(aconf->spasswd))
					pass = aconf->spasswd;

				sendto_one_numeric(client_p, RPL_STATSILINE,
						   form_str(RPL_STATSILINE),
						   name, pass, show_iline_prefix(client_p, aconf, user),
						   show_ip_conf(aconf, client_p) ? host : "255.255.255.255",
						   port, classname);
			}
}

} // namespace ircd

/* check_string()
 *
 * input	- string to check
 * output	- pointer to 'fixed' string, or "*" if empty
 * side effects - any white space found becomes \0
 */
static char *
check_string(char *s)
{
	using namespace ircd;

	char *str = s;
	static char splat[] = "*";
	if(!(s && *s))
		return splat;

	for(; *s; ++s)
	{
		if(rfc1459::is_space(*s))
		{
			*s = '\0';
			break;
		}
	}
	return str;
}

/* pretty_mask()
 *
 * inputs	- mask to pretty
 * outputs	- better version of the mask
 * side effects - mask is chopped to limits, and transformed:
 *                x!y@z => x!y@z
 *                y@z   => *!y@z
 *                x!y   => x!y@*
 *                x     => x!*@*
 *                z.d   => *!*@z.d
 */
char *
ircd::pretty_mask(const char *idmask)
{
	static char mask_buf[BUFSIZE];
	int old_mask_pos;
	const char *nick, *user, *host, *forward = NULL;
	char *t, *at, *ex;
	int nl, ul, hl, fl;
	char *mask;
	size_t masklen;
	int mask_pos;

	mask = LOCAL_COPY(idmask);
	mask = check_string(mask);
	collapse(mask);
	masklen = strlen(mask);

	nick = user = host = "*";
	nl = ul = hl = 1;
	fl = 0;

	if((size_t) BUFSIZE - mask_pos < masklen + 5)
		return NULL;

	old_mask_pos = mask_pos;

	if (*mask == '$')
	{
		memcpy(mask_buf + mask_pos, mask, masklen + 1);
		mask_pos += masklen + 1;
		t = mask_buf + old_mask_pos + 1;
		if (*t == '!')
			*t = '~';
		if (*t == '~')
			t++;
		*t = rfc1459::tolower(*t);
		return mask_buf + old_mask_pos;
	}

	at = ex = NULL;
	if((t = (char *)memchr(mask, '@', masklen)) != NULL)
	{
		at = t;
		t++;
		if(*t != '\0')
			host = t, hl = strlen(t);

		if((t = (char *)memchr(mask, '!', at - mask)) != NULL)
		{
			ex = t;
			t++;
			if(at != t)
				user = t, ul = at - t;
			if(ex != mask)
				nick = mask, nl = ex - mask;
		}
		else
		{
			if(at != mask)
				user = mask, ul = at - mask;
		}

		if((t = (char *)memchr(host, '!', hl)) != NULL ||
				(t = (char *)memchr(host, '$', hl)) != NULL)
		{
			t++;
			if (host + hl != t)
				forward = t, fl = host + hl - t;
			hl = t - 1 - host;
		}
	}
	else if((t = (char *)memchr(mask, '!', masklen)) != NULL)
	{
		ex = t;
		t++;
		if(ex != mask)
			nick = mask, nl = ex - mask;
		if(*t != '\0')
			user = t, ul = strlen(t);
	}
	else if(memchr(mask, '.', masklen) != NULL ||
			memchr(mask, ':', masklen) != NULL)
	{
		host = mask, hl = masklen;
	}
	else
	{
		if(masklen > 0)
			nick = mask, nl = masklen;
	}

	/* truncate values to max lengths */
	if(nl > NICKLEN - 1)
		nl = NICKLEN - 1;
	if(ul > USERLEN)
		ul = USERLEN;
	if(hl > HOSTLEN)
		hl = HOSTLEN;
	if(fl > CHANNELLEN)
		fl = CHANNELLEN;

	memcpy(mask_buf + mask_pos, nick, nl), mask_pos += nl;
	mask_buf[mask_pos++] = '!';
	memcpy(mask_buf + mask_pos, user, ul), mask_pos += ul;
	mask_buf[mask_pos++] = '@';
	memcpy(mask_buf + mask_pos, host, hl), mask_pos += hl;
	if (forward) {
		mask_buf[mask_pos++] = '$';
		memcpy(mask_buf + mask_pos, forward, fl), mask_pos += fl;
	}
	mask_buf[mask_pos++] = '\0';

	return mask_buf + old_mask_pos;
}