/*
Copyright 2001-2018 John Wiseman G8BPQ

This file is part of LinBPQ/BPQ32.

LinBPQ/BPQ32 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 3 of the License, or
(at your option) any later version.

LinBPQ/BPQ32 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 LinBPQ/BPQ32.  If not, see http://www.gnu.org/licenses
*/	

// Mail and Chat Server for BPQ32 Packet Switch
//
// Message Routing Module

// This code decides where to send a message.

// Private messages are routed by TO and AT if possible, if not they follow the Bull Rules

// Bulls are routed on HA where possible

// Bulls should not be distributed outside their designated area.

// Use 4 char continent codes if this is set

int FOURCHARCONT = 0;


#include "bpqmail.h"

char WW[] = "WW";

char * MyElements[20] = {WW};		// My HA in element format

char MyRouteElements[100];

int MyElementCount;

BOOL ReaddressLocal = 0;
BOOL ReaddressReceived = 0;
BOOL WarnNoRoute = TRUE;
BOOL SendPtoMultiple = FALSE;

BOOL Localtime = FALSE;		// Use Local Time for Timebands and forward connect scripts

struct ALIAS * CheckForNTSAlias(struct MsgInfo * Msg, char * FirstDestElement);
struct UserInfo * FindAMPR();
struct UserInfo * FindBBS(char * Name);

struct Continent
{
	char FourCharCode[5];
	char TwoCharCode[5];
};

struct Country
{
	char Country[5];
	char Continent4[5];
	char Continent2[3];
};


struct Continent Continents[] =
{
	"EURO",	"EU", // Europe
	"MEDR",	"EU", // Mediterranean
	"ASIA",	"AS", // The Orient
	"INDI",	"AS", // Indian Ocean including the Indian subcontinent
	"MDLE",	"AS", // Middle East
	"SEAS",	"AS", // South-East Asia
	"NOAM",	"NA", // North America (Canada, USA, Mexico)
	"CEAM",	"NA", // Central America
	"CARB",	"NA", // Caribbean
	"SOAM",	"SA", // South America
	"AUNZ",	"OC", // Australia/New Zealand
	"EPAC",	"OC", // Eastern Pacific
	"NPAC",	"OC", // Northern Pacific
	"SPAC",	"OC", // Southern Pacific
	"WPAC",	"OC", // Western Pacific
	"NAFR",	"AF", // Northern Africa
	"CAFR",	"AF", // Central Africa
	"SAFR",	"AF", // Southern Africa
	"ANTR",	"OC", // Antarctica 
	"MARS",	"MARS", // Special for MARS network
};

struct Country Countries[] = 
{			
	"AFG", "ASIA", "AS", 		// Afghanistan
	"ALA", "EURO", "EU", 		// Åland Islands
	"ALB", "EURO", "EU", 		// Albania
	"DZA", "NAFR", "AF", 		// Algeria
	"ASM", "ASIA", "AS", 		// American Samoa
	"AND", "EURO", "EU", 		// Andorra
	"AGO", "CAFR", "AF", 		// Angola
	"AIA", "CARB", "NA", 		// Anguilla
	"ATG", "CARB", "NA", 		// Antigua and Barbuda
	"ARG", "SOAM", "SA", 		// Argentina
	"ARM", "ASIA", "AS", 		// Armenia
	"ABW", "CARB", "NA", 		// Aruba
	"AUS", "AUNZ", "OC", 		// Australia
	"AUT", "EURO", "EU", 		// Austria
	"AZE", "ASIA", "AS", 		// Azerbaijan
	"BHS", "CARB", "NA", 		// Bahamas
	"BHR", "MDLE", "AS", 		// Bahrain
	"BGD", "INDE", "AS", 		// Bangladesh
	"BRB", "CARB", "NA", 		// Barbados
	"BLR", "EURO", "EU", 		// Belarus
	"BEL", "EURO", "EU", 		// Belgium
	"BLZ", "CEAM", "NA", 		// Belize
	"BEN", "CAFR", "AF", 		// Benin
	"BMU", "CARB", "NA", 		// Bermuda
	"BTN", "ASIA", "AS", 		// Bhutan
	"BOL", "SOAM", "SA", 		// Bolivia (Plurinational State of)
	"BIH", "EURO", "EU", 		// Bosnia and Herzegovina
	"BWA", "SAFR", "AF", 		// Botswana
	"BRA", "SOAM", "SA", 		// Brazil
	"VGB", "CARB", "NA", 		// British Virgin Islands
	"BRN", "ASIA", "AS", 		// Brunei Darussalam
	"BGR", "EURO", "EU", 		// Bulgaria
	"BFA", "CAFR", "AF", 		// Burkina Faso
	"BDI", "CAFR", "AF", 		// Burundi
	"KHM", "ASIA", "AS", 		// Cambodia
	"CMR", "CAFR", "AF", 		// Cameroon
	"CAN", "NOAM", "NA", 		// Canada
	"CPV", "NAFR", "AF", 		// Cape Verde
	"CYM", "CARB", "NA", 		// Cayman Islands
	"CAF", "CAFR", "AF", 		// Central African Republic
	"TCD", "CAFR", "AF", 		// Chad
	"CHL", "SOAM", "SA", 		// Chile
	"CHN", "ASIA", "AS", 		// China
	"HKG", "ASIA", "AS", 		// Hong Kong Special Administrative Region of China
	"MAC", "ASIA", "AS", 		// Macao Special Administrative Region of China
	"COL", "ASIA", "SA", 		// Colombia
//	"COM", "SAFR", "AF", 		// Comoros
	"COG", "CAFR", "AF", 		// Congo
	"COK", "SPAC", "OC", 		// Cook Islands
	"CRI", "CEAM", "NA", 		// Costa Rica
	"CIV", "CAFR", "AF", 		// Côte d'Ivoire
	"HRV", "EURO", "EU", 		// Croatia
	"CUB", "CARB", "NA", 		// Cuba
	"CYP", "EURO", "EU", 		// Cyprus
	"CZE", "EURO", "EU", 		// Czech Republic
	"PRK", "ASIA", "AS", 		// Democratic People's Republic of Korea
	"COD", "CAFR", "AF", 		// Democratic Republic of the Congo
	"DNK", "EURO", "EU", 		// Denmark
	"DJI", "NAFR", "AF", 		// Djibouti
	"DMA", "CARB", "NA", 		// Dominica
	"DOM", "CARB", "NA", 		// Dominican Republic
	"ECU", "SOAM", "SA", 		// Ecuador
	"EGY", "MDLE", "AF", 		// Egypt
	"SLV", "CEAM", "NA", 		// El Salvador
	"GNQ", "CAFR", "AF", 		// Equatorial Guinea
	"ERI", "NAFR", "AF", 		// Eritrea
	"EST", "EURO", "EU", 		// Estonia
	"ETH", "NAFR", "AF", 		// Ethiopia
	"FRO", "EURO", "EU", 		// Faeroe Islands
	"FLK", "SOAM", "SA", 		// Falkland Islands (Malvinas)
	"FJI", "SPAC", "OC", 		// Fiji
	"FIN", "EURO", "EU", 		// Finland
	"FRA", "EURO", "EU", 		// France
	"GUF", "SOAM", "SA", 		// French Guiana
	"PYF", "SPAC", "OC", 		// French Polynesia
	"GAB", "CAFR", "AF", 		// Gabon
	"GMB", "CAFR", "AF", 		// Gambia
	"GEO", "ASIA", "AS", 		// Georgia
	"DEU", "EURO", "EU", 		// Germany
	"GHA", "CAFR", "AF", 		// Ghana
	"GIB", "EURO", "EU", 		// Gibraltar
	"GRC", "EURO", "EU", 		// Greece
	"GRL", "EURO", "EU", 		// Greenland
	"GRD", "CARB", "NA", 		// Grenada
	"GLP", "CARB", "NA", 		// Guadeloupe
	"GUM", "SPAC", "OC", 		// Guam
	"GTM", "CEAM", "NA", 		// Guatemala
	"GGY", "EURO", "EU", 		// Guernsey
	"GIN", "CAFR", "AF", 		// Guinea
	"GNB", "CAFR", "AF", 		// Guinea-Bissau
	"GUY", "SOAM", "SA", 		// Guyana
	"HTI", "CARB", "NA", 		// Haiti
	"VAT", "EURO", "EU", 		// Holy See
	"HND", "CEAM", "NA", 		// Honduras
	"HUN", "EURO", "EU", 		// Hungary
	"ISL", "EURO", "EU", 		// Iceland
	"IND", "INDI", "AS", 		// India
	"IDN", "ASIA", "AS", 		// Indonesia
	"IRN", "MDLE", "AS", 		// Iran (Islamic Republic of)
	"IRQ", "MDLE", "AS", 		// Iraq
	"IRL", "EURO", "EU", 		// Ireland
	"IMN", "EURO", "EU", 		// Isle of Man
	"ISR", "MDLE", "AS", 		// Israel
	"ITA", "EURO", "EU", 		// Italy
	"JAM", "CEAM", "NA", 		// Jamaica
	"JPN", "ASIA", "AS", 		// Japan
	"JEY", "EURO", "EU", 		// Jersey
	"JOR", "MDLE", "AS", 		// Jordan
	"KAZ", "ASIA", "AS", 		// Kazakhstan
	"KEN", "CAFR", "AF", 		// Kenya
	"KIR", "EPAC", "OC", 		// Kiribati
	"KWT", "MDLE", "AS", 		// Kuwait
	"KGZ", "ASIA", "AS", 		// Kyrgyzstan
	"LAO", "ASIA", "AS", 		// Lao People's Democratic Republic
	"LVA", "EURO", "EU", 		// Latvia
	"LBN", "MDLE", "AS", 		// Lebanon
	"LSO", "SAFR", "AF", 		// Lesotho
	"LBR", "CAFR", "AF", 		// Liberia
	"LBY", "MDLE", "AS", 		// Libyan Arab Jamahiriya
	"LIE", "EURO", "EU", 		// Liechtenstein
	"LTU", "EURO", "EU", 		// Lithuania
	"LUX", "EURO", "EU", 		// Luxembourg
	"MDG", "SAFR", "AF", 		// Madagascar
	"MWI", "SAFR", "AF", 		// Malawi
	"MYS", "ASIA", "AS", 		// Malaysia
	"MDV", "INDI", "AS", 		// Maldives
	"MLI", "CAFR", "AF", 		// Mali
	"MLT", "EURO", "EU", 		// Malta
	"MHL", "WPAC", "OC", 		// Marshall Islands
	"MTQ", "CARB", "NA", 		// Martinique
	"MRT", "NAFR", "AF", 		// Mauritania
	"MUS", "SAFR", "AF", 		// Mauritius
	"MYT", "SAFR", "AF", 		// Mayotte
	"MEX", "NOAM", "NA", 		// Mexico
	"FSM", "WPAC", "OC", 		// Micronesia (Federated States of)
	"MCO", "EURO", "EU", 		// Monaco
	"MNG", "ASIA", "AS", 		// Mongolia
	"MNE", "EURO", "EU", 		// Montenegro
	"MSR", "CARB", "NA", 		// Montserrat
	"MAR", "NAFR", "AF", 		// Morocco
	"MOZ", "SAFR", "AF", 		// Mozambique
	"MMR", "ASIA", "AS", 		// Myanmar
	"NAM", "SAFR", "AF", 		// Namibia
	"NRU", "WPAC", "OC", 		// Nauru
	"NPL", "ASIA", "AS", 		// Nepal
	"NLD", "EURO", "EU", 		// Netherlands
	"ANT", "CARB", "NA", 		// Netherlands Antilles
	"NCL", "SPAC", "OC", 		// New Caledonia
	"NZL", "AUNZ", "OC", 		// New Zealand
	"NIC", "CEAM", "SA", 		// Nicaragua
	"NER", "NAFR", "AF", 		// Niger
	"NGA", "CAFR", "AF", 		// Nigeria
	"NIU", "SPAC", "OC", 		// Niue
	"NFK", "SPAC", "OC", 		// Norfolk Island
	"MNP", "WPAC", "OC", 		// Northern Mariana Islands
	"NOR", "EURO", "EU", 		// Norway
	"PSE", "MDLE", "AS", 		// Occupied Palestinian Territory
	"OMN", "MDLE", "AS", 		// Oman
	"PAK", "INDI", "AS", 		// Pakistan
	"PLW", "SPAC", "OC", 		// Palau
	"PAN", "CEAM", "SA", 		// Panama
	"PNG", "SPAC", "OC", 		// Papua New Guinea
	"PRY", "SOAM", "SA", 		// Paraguay
	"PER", "SOAM", "SA", 		// Peru
	"PHL", "ASIA", "AS", 		// Philippines
	"PCN", "SPAC", "OC", 		// Pitcairn
	"POL", "EURO", "EU", 		// Poland
	"PRT", "EURO", "EU", 		// Portugal
	"PRI", "CARB", "NA", 		// Puerto Rico
	"QAT", "MDLE", "AS", 		// Qatar
	"KOR", "ASIA", "AS", 		// Republic of Korea
	"MDA", "EURO", "EU", 		// Republic of Moldova
	"REU", "SAFR", "AF", 		// Réunion
	"ROU", "EURO", "EU", 		// Romania
	"RUS", "ASIA", "AS", 		// Russian Federation
	"RWA", "CAFR", "AF", 		// Rwanda
	"BLM", "CARB", "NA", 		// Saint-Barthélemy
	"SHN", "SOAM", "SA", 		// Saint Helena
	"KNA", "CARB", "NA", 		// Saint Kitts and Nevis
	"LCA", "CARB", "NA", 		// Saint Lucia
	"MAF", "CARB", "NA", 		// Saint-Martin (French part)
	"SPM", "NOAM", "NA", 		// Saint Pierre and Miquelon
	"VCT", "CARB", "NA", 		// Saint Vincent and the Grenadines
	"WSM", "SPAC", "OC", 		// Samoa
	"SMR", "EURO", "EU", 		// San Marino
	"STP", "CAFR", "AF", 		// Sao Tome and Principe
	"SAU", "MDLE", "AS", 		// Saudi Arabia
	"SEN", "CAFR", "AF", 		// Senegal
	"SRB", "EURO", "EU", 		// Serbia
	"SYC", "SAFR", "AF", 		// Seychelles
	"SLE", "NAFR", "AF", 		// Sierra Leone
	"SGP", "ASIA", "AS", 		// Singapore
	"SVK", "EURO", "EU", 		// Slovakia
	"SVN", "EURO", "EU", 		// Slovenia
	"SLB", "SPAC", "OC", 		// Solomon Islands
	"SOM", "NAFR", "AF", 		// Somalia
	"ZAF", "SAFR", "AF", 		// South Africa
	"ESP", "EURO", "EU", 		// Spain
	"LKA", "INDE", "AS", 		// Sri Lanka
	"SDN", "NAFR", "AF", 		// Sudan
	"SUR", "SOAM", "SA", 		// Suriname
	"SJM", "EURO", "EU", 		// Svalbard and Jan Mayen Islands
	"SWZ", "SAFR", "AF", 		// Swaziland
	"SWE", "EURO", "EU", 		// Sweden
	"CHE", "EURO", "EU", 		// Switzerland
	"SYR", "MDLE", "AS", 		// Syrian Arab Republic
	"TJK", "ASIA", "AS", 		// Tajikistan
	"THA", "ASIA", "AS", 		// Thailand
	"MKD", "EURO", "EU", 		// The former Yugoslav Republic of Macedonia
	"TLS", "ASIA", "AS", 		// Timor-Leste
	"TGO", "CAFR", "AF", 		// Togo
	"TKL", "AUNZ", "OC", 		// Tokelau
	"TON", "SPAC", "OC", 		// Tonga
	"TTO", "CARB", "NA", 		// Trinidad and Tobago
	"TUN", "NAFR", "AF", 		// Tunisia
	"TUR", "EURO", "EU", 		// Turkey
	"TKM", "ASIA", "AS", 		// Turkmenistan
	"TCA", "CARB", "NA", 		// Turks and Caicos Islands
	"TUV", "SPAC", "OC", 		// Tuvalu
	"UGA", "SAFR", "AF", 		// Uganda
	"UKR", "EURO", "EU", 		// Ukraine
	"ARE", "MDLE", "AS", 		// United Arab Emirates
	"GBR", "EURO", "EU", 		// United Kingdom of Great Britain and Northern Ireland
	"TZA", "SAFR", "AF", 		// United Republic of Tanzania
	"USA", "NOAM", "NA", 		// United States of America
	"VIR", "CARB", "NA", 		// United States Virgin Islands
	"URY", "SOAM", "SA", 		// Uruguay
	"UZB", "ASIA", "AS", 		// Uzbekistan
	"VUT", "SPAC", "OC", 		// Vanuatu
	"VEN", "SOAM", "SA", 		// Venezuela (Bolivarian Republic of)
	"VNM", "ASIA", "AS", 		// Viet Nam
	"WLF", "SPAC", "OC", 		// Wallis and Futuna Islands
	"ESH", "CAFR", "AF", 		// Western Sahara
	"YEM", "NAFR", "AF", 		// Yemen
	"ZMB", "SAFR", "AF", 		// Zambia
	"ZWE", "SAFR", "AF"			// Zimbabwe
};
char ** AliasText;
struct ALIAS ** Aliases;

struct ALIAS ** NTSAliases = NULL;

/*struct ALIAS Aliases[] =
{
"AMSAT",	"WW",
"USBBS",	"USA",
"ALLUS",	"USA"};
*/

int NumberofContinents = sizeof(Continents)/sizeof(Continents[1]);
int NumberofCountries = sizeof(Countries)/sizeof(Countries[1]);

struct Continent * FindContinent(char * Name)
{
	int i;
	struct Continent * Cont;

	for(i=0; i< NumberofContinents; i++)
	{
		Cont = &Continents[i];

		if ((_stricmp(Name, Cont->FourCharCode) == 0) || (_stricmp(Name, Cont->TwoCharCode) == 0))
			return Cont;
	}

	return NULL;
}

struct Country * FindCountry(char * Name)
{
	int i;
	struct Country * Cont;

	for(i=0; i< NumberofCountries; i++)
	{
		Cont = &Countries[i];

		if (_stricmp(Name, Cont->Country) == 0)
			return Cont;
	}

	return NULL;
}

struct ALIAS * FindAlias(char * Name)
{
	struct ALIAS ** Alias;

	Alias =  Aliases;

	while(Alias[0])
	{
		if (_stricmp(Name, Alias[0]->Dest) == 0)
			return Alias[0];

		Alias++;
	}

	return NULL;
}

VOID SetupMyHA()
{
	int Elements = 1;					// Implied WW on front
	char * ptr2;
	struct Continent * Continent;

	strcpy(MyRouteElements, HRoute);

	// Split it up

	ptr2 = MyRouteElements + strlen(MyRouteElements) - 1;

	if (HRoute[0])
	{
		do 
		{
			while ((*ptr2 != '.') && (ptr2 > MyRouteElements))
				ptr2 --;

			if (ptr2 == MyRouteElements)
			{
				// End

				MyElements[Elements++] = _strdup(ptr2);
				break;
			}

			MyElements[Elements++] = _strdup(ptr2+1);
			*ptr2 = 0;

		} while(Elements < 19);		// Just in case!
	}

	MyElements[Elements++] = _strdup(BBSName);

	MyElementCount = Elements;

	if (MyElements[1])
	{
		if (FOURCHARCONT == 0)
		{
			if (strlen(MyElements[1]) == 4)
			{
				// Convert to 2 char Continent;
				Continent = FindContinent(MyElements[1]);
				if (Continent)
				{
					free(MyElements[1]);
					MyElements[1] = _strdup(Continent->TwoCharCode);
				}
			}
		}
		else
		{
			if (strlen(MyElements[1]) == 2)
			{
				// Convert to 4 char Continent;
				Continent = FindContinent(MyElements[1]);
				if (Continent)
				{
					free(MyElements[1]);
					MyElements[1] = _strdup(Continent->FourCharCode);
				}
			}
		}
	}
}

VOID SetupNTSAliases(char * FN)
{
	FILE *in;
	char Buffer[2048];
	char *buf = Buffer;
	int Count = 0;
	char seps[] = " ,:/\t";
	char * Dest, * Alias, * Context;

	NTSAliases = zalloc(sizeof(struct ALIAS));

	in = fopen(FN, "r");

	if (in)
	{
		while(fgets(buf, 128, in))
		{
			strlop(buf, '\n');
			strlop(buf, '\r');			// in case Windows Format

			Dest = _strdup(buf);		// strtok changes it

			Dest = strtok_s(Dest, seps, &Context);
			if (Dest == NULL)
				continue;

			Alias = strtok_s(NULL, seps, &Context);
			if (Alias == NULL)
				continue;

			if (strlen(Dest) < 3 && (strchr(Dest, '*') == 0))
				continue;

			NTSAliases = realloc(NTSAliases, (Count+2)* sizeof(struct ALIAS));
			NTSAliases[Count] = zalloc(sizeof(struct ALIAS));

			NTSAliases[Count]->Dest =  Dest; 
			NTSAliases[Count]->Alias = Alias;

			Count++;
		}

		NTSAliases[Count] = NULL;
		fclose(in);
	}
}

VOID SetupFwdAliases()
{
	char ** Text = AliasText;
	char * Dest, * Alias;
	int Count = 0;
	char seps[] = " ,:/";

	Aliases = zalloc(sizeof(struct ALIAS));

	if (Text)
	{
		while(Text[0])
		{
			Aliases = realloc(Aliases, (Count+2)* sizeof(struct ALIAS));
			Aliases[Count] = zalloc(sizeof(struct ALIAS));

			Dest = _strdup(Text[0]);		// strtok changes it

			Dest = strtok_s(Dest, seps, &Alias);

			Aliases[Count]->Dest =  Dest; 
			Aliases[Count]->Alias = Alias;

			Count++;
			Text++;
		}
		Aliases[Count] = NULL;
	}
}

VOID SetupHAElements(struct BBSForwardingInfo * ForwardingInfo)
{
	char * HText = ForwardingInfo->BBSHA;
	char * SaveHText, * ptr2;
	struct Continent * Continent;
	int Elements = 1;

	ForwardingInfo->BBSHAElements = zalloc(8);				// always NULL entry on end even if no values

	SaveHText = _strdup(HText);

	ptr2 = SaveHText + strlen(SaveHText) -1;

	ForwardingInfo->BBSHAElements[0] = _strdup(WW);

	do 
	{
		ForwardingInfo->BBSHAElements = realloc(ForwardingInfo->BBSHAElements, (Elements+2) * sizeof(void *));

		while ((*ptr2 != '.') && (ptr2 > SaveHText))
		{
			ptr2 --;
		}

		if (ptr2 == SaveHText)
		{
			// End

			ForwardingInfo->BBSHAElements[Elements++] = _strdup(ptr2);
			break;
		}

		ForwardingInfo->BBSHAElements[Elements++] = _strdup(ptr2+1);
		*ptr2 = 0;

	} while(TRUE);

	ForwardingInfo->BBSHAElements[Elements++] = NULL;

	if (ForwardingInfo->BBSHAElements[1])
	{
		if (FOURCHARCONT == 0)
		{
			if (strlen(ForwardingInfo->BBSHAElements[1]) == 4)
			{
				// Convert to 2 char Continent;
				Continent = FindContinent(ForwardingInfo->BBSHAElements[1]);
				if (Continent)
				{
					free(ForwardingInfo->BBSHAElements[1]);
					ForwardingInfo->BBSHAElements[1] = _strdup(Continent->TwoCharCode);
				}
			}
		}
		else
		{
			if (strlen(ForwardingInfo->BBSHAElements[1]) == 2)
			{
				// Convert to 4 char Continent;
				Continent = FindContinent(ForwardingInfo->BBSHAElements[1]);
				if (Continent)
				{
					free(ForwardingInfo->BBSHAElements[1]);
					ForwardingInfo->BBSHAElements[1] = _strdup(Continent->FourCharCode);
				}
			}
		}
	}

	free(SaveHText);

}

VOID SetupHAddreses(struct BBSForwardingInfo * ForwardingInfo)
{
	int Count=0;
	char ** HText = ForwardingInfo->Haddresses;
	char * SaveHText, * ptr2;
	//	char * TopElement;
	char * Num;
	struct Continent * Continent;

	ForwardingInfo->HADDRS = zalloc(sizeof(void *));				// always NULL entry on end even if no values
	ForwardingInfo->HADDROffet = zalloc(sizeof(void *));			// always NULL entry on end even if no values

	while(HText[0])
	{
		int Elements = 1;
		ForwardingInfo->HADDRS = realloc(ForwardingInfo->HADDRS, (Count+2) * sizeof(void *));
		ForwardingInfo->HADDROffet = realloc(ForwardingInfo->HADDROffet, (Count+2) * sizeof(void *));

		ForwardingInfo->HADDRS[Count] = zalloc(8);	// Always at lesat WWW and NULL

		SaveHText = _strdup(HText[0]);
		Num = strlop(SaveHText, ',');

		ptr2 = SaveHText + strlen(SaveHText) -1;

		ForwardingInfo->HADDRS[Count][0] = _strdup(WW);

		if (strcmp(HText[0], "WW") != 0)
		{
			do 
			{
				ForwardingInfo->HADDRS[Count] = realloc(ForwardingInfo->HADDRS[Count], (Elements+2) * sizeof(void *));

				while ((*ptr2 != '.') && (ptr2 > SaveHText))
				{
					ptr2 --;
				}

				if (ptr2 == SaveHText)
				{
					// End

					ForwardingInfo->HADDRS[Count][Elements++] = _strdup(ptr2);
					break;
				}

				ForwardingInfo->HADDRS[Count][Elements++] = _strdup(ptr2+1);
				*ptr2 = 0;

			} while(TRUE);
		}

		ForwardingInfo->HADDRS[Count][Elements++] = NULL;

		// If the route is not a complete HR (ie starting with a continent)
		// Add elemets from BBS HA to complete it.
		// (How do we know how many??
		// ?? Still ont sure about this ??
		// What i was trying to do was end up with a full HR, but knowing how many elements to use.
		// Far simpler to config it, but can users cope??
		//	Will config for testing. HA, N

		/*
		TopElement=ForwardingInfo->HADDRS[Count][0];

		if (strcmp(TopElement, MyElements[0]) == 0)
		goto FullHR;

		if (FindContinent(TopElement))
		goto FullHR;

		// Need to add stuff from our HR

		Elements--;

		if (Elements < MyElementCount)
		break;

		FullHR:
		*/

		ForwardingInfo->HADDROffet[Count] = (Num)? atoi(Num): 0;

		if (ForwardingInfo->HADDRS[Count][1])
		{
			if (FOURCHARCONT == 0)
			{

				if (strlen(ForwardingInfo->HADDRS[Count][1]) == 4)
				{
					// Convert to 2 char Continent;
					Continent = FindContinent(ForwardingInfo->HADDRS[Count][1]);
					if (Continent)
					{
						free(ForwardingInfo->HADDRS[Count][1]);
						ForwardingInfo->HADDRS[Count][1] = _strdup(Continent->TwoCharCode);
					}
				}
			}
			else
			{
				if (strlen(ForwardingInfo->HADDRS[Count][1]) == 2)
				{
					// Convert to 4 char Continent;
					Continent = FindContinent(ForwardingInfo->HADDRS[Count][1]);
					if (Continent)
					{
						free(ForwardingInfo->HADDRS[Count][1]);
						ForwardingInfo->HADDRS[Count][1] = _strdup(Continent->FourCharCode);
					}
				}
			}
		}
		free(SaveHText);
		HText++;
		Count++;
	}

	ForwardingInfo->HADDRS[Count] = NULL;
}

VOID SetupHAddresesP(struct BBSForwardingInfo * ForwardingInfo)
{
	int Count=0;
	char ** HText = ForwardingInfo->HaddressesP;
	char * SaveHText, * ptr2;
	//	char * TopElement;
	char * Num;
	struct Continent * Continent;

	ForwardingInfo->HADDRSP = zalloc(sizeof(void *));				// always NULL entry on end even if no values

	while(HText[0])
	{
		int Elements = 1;
		ForwardingInfo->HADDRSP = realloc(ForwardingInfo->HADDRSP, (Count+2) * sizeof(void *));

		ForwardingInfo->HADDRSP[Count] = zalloc(2 * sizeof(void *));	// Always at lesat WWW and NULL

		SaveHText = _strdup(HText[0]);
		Num = strlop(SaveHText, ',');

		ptr2 = SaveHText + strlen(SaveHText) -1;

		ForwardingInfo->HADDRSP[Count][0] = _strdup(WW);

		if (strcmp(HText[0], "WW") != 0)
		{
			do 
			{
				ForwardingInfo->HADDRSP[Count] = realloc(ForwardingInfo->HADDRSP[Count], (Elements+2) * sizeof(void *));

				while ((*ptr2 != '.') && (ptr2 > SaveHText))
				{
					ptr2 --;
				}

				if (ptr2 == SaveHText)
				{
					// End

					ForwardingInfo->HADDRSP[Count][Elements++] = _strdup(ptr2);
					break;
				}

				ForwardingInfo->HADDRSP[Count][Elements++] = _strdup(ptr2+1);
				*ptr2 = 0;

			} while(TRUE);
		}

		ForwardingInfo->HADDRSP[Count][Elements++] = NULL;

		if (ForwardingInfo->HADDRSP[Count][1])
		{
			if (FOURCHARCONT == 0)
			{

				if (strlen(ForwardingInfo->HADDRSP[Count][1]) == 4)
				{
					// Convert to 2 char Continent;
					Continent = FindContinent(ForwardingInfo->HADDRSP[Count][1]);
					if (Continent)
					{
						free(ForwardingInfo->HADDRSP[Count][1]);
						ForwardingInfo->HADDRSP[Count][1] = _strdup(Continent->TwoCharCode);
					}
				}
			}
			else
			{
				if (strlen(ForwardingInfo->HADDRSP[Count][1]) == 2)
				{
					// Convert to 4 char Continent;
					Continent = FindContinent(ForwardingInfo->HADDRSP[Count][1]);
					if (Continent)
					{
						free(ForwardingInfo->HADDRSP[Count][1]);
						ForwardingInfo->HADDRSP[Count][1] = _strdup(Continent->FourCharCode);
					}
				}
			}
		}
		free(SaveHText);
		HText++;
		Count++;
	}

	ForwardingInfo->HADDRSP[Count] = NULL;
}

VOID CheckAndSend(struct MsgInfo * Msg, CIRCUIT * conn, struct UserInfo * bbs) 
{
	struct BBSForwardingInfo * ForwardingInfo = bbs->ForwardingInfo;

	if (ForwardToMe || _stricmp(bbs->Call, BBSName) != 0) // Don't forward to ourself - already here! (unless ForwardToMe set)
	{
		if ((conn == NULL) || (!(conn->BBSFlags & BBS) || (_stricmp(conn->UserPointer->Call, bbs->Call) != 0))) // Dont send back
		{
			set_fwd_bit(Msg->fbbs, bbs->BBSNumber);
			ForwardingInfo->MsgCount++;
			if (ForwardingInfo->SendNew)
				ForwardingInfo->FwdTimer = ForwardingInfo->FwdInterval - (2 + (rand() % 30)); //Short delay to prevent all starting at once
		}
	}
	else
	{
		// if User has Redirect to RMS set do it.

		struct UserInfo * user = LookupCall(Msg->to);

		if (user && user->flags & F_RMSREDIRECT)
		{
			user = FindBBS("RMS");

			if (user)
			{
				CheckAndSend(Msg, conn, user);
			}

			Logprintf(LOG_BBS, conn, '?', "Message matches this BBS and RMS Redirect set - fwd to RMS");

		}
		else
			Logprintf(LOG_BBS, conn, '?', "Message matches this BBS and ForwardToMe not set - not queuing message");
	}
}

VOID UpdateB2Dest(struct MsgInfo * Msg, char * Alias)
{
	int FileSize;
	char MsgFile[MAX_PATH];
	FILE * hFile;
	char * MsgBytes;
	struct stat STAT;

	sprintf_s(MsgFile, sizeof(MsgFile), "%s/m_%06d.mes", MailDir, Msg->number);

	if (stat(MsgFile, &STAT) == -1)
		return;

	FileSize = STAT.st_size;

	hFile = fopen(MsgFile, "rb");

	if (hFile == NULL)
		return;

	MsgBytes=malloc(FileSize + 100);	// A bit of space for alias substitution on B2

	fread(MsgBytes, 1, FileSize, hFile); 

	fclose(hFile);

	if (MsgBytes)
	{
		char * To, * AT, * ATEND;
		To = strstr(MsgBytes, "To:");

		if (To)
		{
			ATEND = strchr(To, '\r');
			if (ATEND)
			{
				size_t i = ATEND - To;
				AT = memchr(To, '@', i);
				if (AT)
				{
					// Move Message up/down to make room for substitution

					size_t Diff = (ATEND - AT - 1) - strlen(Alias);
					size_t BeforeAT = AT - MsgBytes + 1;
					int j = 0;

					memmove(AT + 1, AT + Diff + 1, Msg->length - BeforeAT + 10);
					memcpy(AT + 1, Alias, strlen(Alias));

					j = 1;
					Msg->length -= (int)Diff;

					// Write Back

					hFile = fopen(MsgFile, "wb");

					if (hFile == NULL)
						return;


					fwrite(MsgBytes, 1, Msg->length, hFile);
					fclose(hFile);
				}
			}
		}
	}
}

int MatchMessagetoBBSList(struct MsgInfo * Msg, CIRCUIT * conn)
{
	struct UserInfo * bbs;
	struct UserInfo * user;
	struct BBSForwardingInfo * ForwardingInfo;
	char ATBBS[41]="";
	char RouteElements[41];
	int Count = 0;
	char * HElements[20] = {NULL};
	int Elements = 0;
	char * ptr2;
	int MyElement = 0;
	BOOL Flood = FALSE;
	char FullRoute[100];
	struct Continent * Continent;
	struct Country * Country;
	struct ALIAS * Alias;
	struct UserInfo * RMS;
	int toLen;

	if (Msg->status == 'K')
		return 1;				// No point,  but don't want no route warning

	strcpy(RouteElements, Msg->via);

	Logprintf(LOG_BBS, conn, '?', "Msg %d Routing Trace To %s Via %s", Msg->number, Msg->to, RouteElements);

	//	"NTS" Alias substitution is now done on P and T before any other processing

	//	No, Not a good idea to use same aliss file for NTS and other messages
	//	(What about OT 2*?)
	//	If we need to alias other types, add a new file

	if (strchr(RouteElements, '!'))		// Source Route ("Bang Routing")
	{
		char * bang = &RouteElements[strlen(RouteElements)];

		while (*(--bang) != '!');		// Find last !

		*(bang) = 0;					// remove it;
		_strupr(++bang);

		strcpy(Msg->via, RouteElements);	// Remove from via

		for (bbs = BBSChain; bbs; bbs = bbs->BBSNext)
		{
			if (_stricmp(bbs->Call, bang) == 0)
			{
				Logprintf(LOG_BBS, conn, '?', "Routing Trace Source Route via %s", bbs->Call);
				CheckAndSend(Msg, conn, bbs);
				return 1;
			}
		}

		if (conn && !(conn->BBSFlags & BBS))
			nodeprintf(conn, "Routing via %s requested but %s not known to this BBS\r", bang, bang);

		Logprintf(LOG_BBS, conn, '?', "Routing via %s requested but %s not known to this BBS", bang, bang);
	}

	if (Msg->type == 'T')
	{
		strlop(RouteElements, '.');

		Alias = CheckForNTSAlias(Msg, RouteElements);

		if (Alias)
		{
			// Replace the AT in the message with the Alias

			Logprintf(LOG_BBS, conn, '?', "Routing Trace @%s taken from Alias File", Alias->Alias);
			strcpy(Msg->via, Alias->Alias);
			if (Msg->B2Flags & B2Msg)
				UpdateB2Dest(Msg, Alias->Alias);

			SaveMessageDatabase();
		}

		strcpy(RouteElements, Msg->via);		// May have changed
	}

	// See if AMPR.ORG Mail

	// Note message is probably already queued to SMTP or RMS

	// If for our domain leave here

	toLen = (int)strlen(Msg->via);

	if (_memicmp(&Msg->via[toLen - 8], "ampr.org", 8) == 0)
	{
		// message is for ampr.org

		char toCall[48];
		char * via;

		strcpy(toCall, _strupr(Msg->via));

		via = strlop(toCall, '@');

		if (via == NULL)
		{
			// To and VIA already set up. This should only happen if message is for us

			Logprintf(LOG_BBS, conn, '?', "Routing Trace at %s is for us. Leave Here", AMPRDomain);
			return 1;
		}

		if (_stricmp(via, AMPRDomain) == 0)
		{
			// message is for us. Leave Here

			if (strlen(toCall) > 6)
				toCall[6] = 0;

			strcpy(Msg->to, toCall);
			strcpy(Msg->via, via);
			Logprintf(LOG_BBS, conn, '?', "Routing Trace at %s is for us. Leave Here", AMPRDomain);
			return 1;
		}

		if (SendAMPRDirect)
		{
			// We want to send ampr mail direct to host. Queue to BBS AMPR

			bbs = FindAMPR();

			if (bbs)
			{
				// We have bbs AMPR

				if (_stricmp(Msg->to, "RMS") == 0 || Msg->to[0] == 0)
				{
					// Was set to go via RMS or ISP - change it

					strcpy(Msg->to, "AMPR");
				}		

				Logprintf(LOG_BBS, conn, '?', "Routing Trace to ampr.org Matches BBS AMPR");

				set_fwd_bit(Msg->fbbs, bbs->BBSNumber);
				bbs->ForwardingInfo->MsgCount++;
				if (bbs->ForwardingInfo->SendNew)
					bbs->ForwardingInfo->FwdTimer = bbs->ForwardingInfo->FwdInterval;

				return 1;
			}

			// To AMPR, but don't have a BBS called AMPR - dropthrough in case we are forwarding AMPR to another BBS
		}
	}

	//	See if sending @ winlink.org

	if (_stricmp(Msg->to, "RMS") == 0)
	{
		// If a user of this bbs with Poll RMS set, leave it here - no point in sending to winlink

		// To = RMS could come from RMS:EMAIL Address. If so, we only check user if @winlink.org, or
		// we will hold g8bpq@g8bpq.org.uk

		char * Call;
		char * AT;

		Call = _strupr(_strdup(Msg->via));
		AT = strlop(Call, '@');

		if (AT)
		{
			if (_stricmp(AT, "WINLINK.ORG") == 0)
			{
				user = LookupCall(Call);

				if (user)
				{
					if (user->flags & F_POLLRMS)
					{
						Logprintf(LOG_BBS, conn, '?', "Message @ winlink.org, but local RMS user - leave here");
						strcpy(Msg->to, Call);
						strcpy(Msg->via, AT);

						free(Call);

						if (user->flags & F_BBS)	// User is also a BBS, so set FWD bit so he can get it
							set_fwd_bit(Msg->fbbs, user->BBSNumber);

						return 1;
					}
				}
			}
		}
		free(Call);

		// To = RMS, but not winlink.org, or not local user.

		RMS = FindRMS();

		if (RMS)
		{
			Logprintf(LOG_BBS, conn, '?', "Routing Trace to RMS Matches BBS RMS");

			set_fwd_bit(Msg->fbbs, RMS->BBSNumber);
			RMS->ForwardingInfo->MsgCount++;
			if (RMS->ForwardingInfo->SendNew)
				RMS->ForwardingInfo->FwdTimer = RMS->ForwardingInfo->FwdInterval;

			return 1;
		}

		// To RMS, but don't have a BBS called RMS - dropthrough in case we are forwarding RMS to another BBS

	}

	if (_stricmp(RouteElements, "WINLINK.ORG") == 0)
	{
		user = LookupCall(Msg->to);

		if (user)
		{
			if (user->flags & F_POLLRMS)
			{
				Logprintf(LOG_BBS, conn, '?', "Routing Trace @ winlink.org, but local RMS user - leave here");

				if (user->flags & F_BBS)	// User is also a BBS, so set FWD bit so he can get it
					set_fwd_bit(Msg->fbbs, user->BBSNumber);

				return 1;					// Route found
			}
		}

		RMS = FindRMS();

		if (RMS)
		{
			Logprintf(LOG_BBS, conn, '?', "Routing Trace @ winlink.org Matches BBS RMS");

			set_fwd_bit(Msg->fbbs, RMS->BBSNumber);
			RMS->ForwardingInfo->MsgCount++;
			if (RMS->ForwardingInfo->SendNew)
				RMS->ForwardingInfo->FwdTimer = RMS->ForwardingInfo->FwdInterval;

			return 1;
		}
		Logprintf(LOG_BBS, conn, '?', "Routing Trace - @ winlink.org but no BBS RMS");
		return 0;
	}


	// See if a well-known alias

	Alias = FindAlias(RouteElements);

	if (Alias)
	{
		Logprintf(LOG_BBS, conn, '?', "Routing Trace Alias Substitution %s > %s",
			RouteElements, Alias->Alias); 

		strcpy(RouteElements, Alias->Alias);

		if (conn)
			if ((ReaddressReceived && (conn->BBSFlags & BBS)) || (ReaddressLocal && ((conn->BBSFlags & BBS) == 0)))
				strcpy(Msg->via, Alias->Alias);
	}

	// Make sure HA is complete (starting at WW)

	if (RouteElements[0] == 0)
	{
		// Just a TO. If a Bull, set flood

		if (Msg->type == 'B')
			Flood = TRUE;

		goto NOHA;
	}

	ptr2 = RouteElements + strlen(RouteElements) - 1;

	while ((*ptr2 != '.') && (ptr2 > RouteElements))
	{
		ptr2 --;
	}

	if (ptr2 != RouteElements)
		*ptr2++;

	if ((strcmp(ptr2, "WW") == 0) || (strcmp(ptr2, "WWW") == 0))
	{
		strcpy(FullRoute, RouteElements);
		goto FULLHA;
	}

	if (FindContinent(ptr2))
	{
		// Just need to add WW

		sprintf_s(FullRoute, sizeof(FullRoute),"%s.WW", RouteElements);
		goto FULLHA;
	}

	Country = FindCountry(ptr2);

	if (Country)
	{
		// Just need to add Continent and WW
		if (FOURCHARCONT == 0)
			sprintf_s(FullRoute, sizeof(FullRoute),"%s.%s.WW", RouteElements, Country->Continent2);
		else
			sprintf_s(FullRoute, sizeof(FullRoute),"%s.%s.WW", RouteElements, Country->Continent4);

		goto FULLHA;
	}

	// Don't know

	// Assume a local dis list, and set Flood.

	strcpy(FullRoute, RouteElements);

	if (Msg->type == 'B')
		Flood = TRUE;

FULLHA:

	strcpy(ATBBS, FullRoute);

	strlop(ATBBS, '.');

	if (FullRoute)
	{
		// Split it up

		ptr2 = FullRoute + strlen(FullRoute) - 1;

		do 
		{
			while ((*ptr2 != '.') && (ptr2 > FullRoute))
			{
				ptr2 --;
			}

			if (ptr2 != FullRoute)
				*ptr2++ = 0;

			HElements[Elements++] = ptr2;


		} while(Elements < 20 && ptr2 != FullRoute);		// Just in case!
	}

	if (HElements[1])
	{
		if (FOURCHARCONT == 0)
		{
			if (strlen(HElements[1]) == 4)
			{
				// Convert to 2 char Continent;
				Continent = FindContinent(HElements[1]);
				if (Continent)
				{
					//				free(MyElements[1]);
					HElements[1] = _strdup(Continent->TwoCharCode);
				}
			}
		}
		else
		{
			if (strlen(HElements[1]) == 2)
			{
				// Convert to 4 char Continent;
				Continent = FindContinent(HElements[1]);
				if (Continent)
				{
					//				free(MyElements[1]);
					HElements[1] = _strdup(Continent->FourCharCode);
				}
			}
		}
	}


	// if Bull, see if reached right area

	if (Msg->type == 'B')
	{
		int i = 0;

		// Log My HA for debugging

		Logprintf(LOG_BBS, conn, '?', "Routing Trace. Check if reached correct area My HA is %s", HRoute);

		// All elements of Helements must match Myelements

		while (MyElements[i] && HElements[i]) // Until one set runs out
		{
			if (strcmp(MyElements[i], HElements[i]) != 0)
				break;
			i++;
		}

		// if HElements[i+1] = NULL, have matched all

		if (HElements[i] == NULL)
			Flood = TRUE;
	}

NOHA:

	Logprintf(LOG_BBS, conn, '?', "Routing Trace Type %c %sTO %s VIA %s Route On %s %s %s %s %s",
		Msg->type, (Flood) ? "(Flood) ":"", Msg->to, Msg->via, HElements[0],
		HElements[1], HElements[2], HElements[3], HElements[4]);


	if (Msg->type == 'T')
	{
		int depth = 0;
		int bestmatch = -1;
		struct UserInfo * bestbbs = NULL;

		//	if NTS Traffic. Route on Wildcarded TO (best match).

		//  If no match, route on AT (Should be NTSxx)

		//  If no match, send to any BBS with routing to state XX and NTS flag set (no we dont!)

		for (bbs = BBSChain; bbs; bbs = bbs->BBSNext)
		{		
			ForwardingInfo = bbs->ForwardingInfo;

			depth = CheckBBSToForNTS(Msg, ForwardingInfo);

			if (depth > -1)
			{
				Logprintf(LOG_BBS, conn, '?', "Routing Trace NTS Matches TO BBS %s Length %d", bbs->Call, depth);

				if (depth > bestmatch)
				{
					bestmatch = depth;
					bestbbs = bbs;
				}
			}
		}
		if (bestbbs)
		{
			if (bestbbs->flags  & F_NTSMPS) 
				Logprintf(LOG_BBS, conn, '?', "Routing Trace NTS Best Match is %s, but NTS MPS Set so not queued", bestbbs->Call);
			else
			{
				Logprintf(LOG_BBS, conn, '?', "Routing Trace NTS Best Match is %s", bestbbs->Call);
				CheckAndSend(Msg, conn, bestbbs);
			}
			return 1;
		}

		// Check AT 

		for (bbs = BBSChain; bbs; bbs = bbs->BBSNext)
		{		
			ForwardingInfo = bbs->ForwardingInfo;

			if (CheckBBSAtList(Msg, ForwardingInfo, ATBBS))
			{
				if (bbs->flags  & F_NTSMPS) 
					Logprintf(LOG_BBS, conn, '?', "Routing Trace NTS %s Matches AT %s, but NTS MPS Set so not queued", ATBBS, bbs->Call);
				else
				{
					Logprintf(LOG_BBS, conn, '?', "Routing Trace NTS %s Matches AT %s", ATBBS, bbs->Call);
					CheckAndSend(Msg, conn, bbs);
				}
				return 1;
			}
		}
		goto CheckWildCardedAT;
	}

	// First check P message to multiple destinations

	// We should choose the BBS with most matching elements (ie match on #23.GBR better that GBR)
	// If SendPtoMultiple is set I think we send to any with same match level

	// So if SendPtoMultiple is set I think I need to find the best depth then send to all with the same depth
	// If none are found on HA match drop through.

	// But this means a message at the @BBS call will be forwarded without checking the Implied AT. So do that first


	if (SendPtoMultiple && Msg->type == 'P')
	{
		struct UserInfo * bestbbs = NULL;
		int bestmatch = 0;
		int depth;
		int Matched = 0;
		int MultiPDepth = 0;

		Count = 0;

		Logprintf(LOG_BBS, conn, '?', "SendPtoMultiple is set. Checking for best match level");

		for (bbs = BBSChain; bbs; bbs = bbs->BBSNext)
		{		
			ForwardingInfo = bbs->ForwardingInfo;

			// Check Implied AT

			if ((strcmp(ATBBS, bbs->Call) == 0))			// @BBS = BBS		
			{
				Logprintf(LOG_BBS, conn, '?', "Routing Trace %s Matches implied AT %s", ATBBS, bbs->Call);

				CheckAndSend(Msg, conn, bbs);
				return 1;
			}

			depth = CheckBBSHElements(Msg, bbs, ForwardingInfo, ATBBS, &HElements[0]);

			if (depth)
			{
				if (depth > MultiPDepth)
				{
					MultiPDepth = depth;
					bestbbs = bbs;
				}
			}
		}

		if (MultiPDepth)
		{
			for (bbs = BBSChain; bbs; bbs = bbs->BBSNext)
			{		
				ForwardingInfo = bbs->ForwardingInfo;

				depth = CheckBBSHElements(Msg, bbs, ForwardingInfo, ATBBS, &HElements[0]);

				if (depth == MultiPDepth)
				{
					Logprintf(LOG_BBS, conn, '?', "Routing Trace HR Matches BBS %s Depth %d", bbs->Call, depth);	
					CheckAndSend(Msg, conn, bbs);
					Count++;
				}	
			}
			return	Count;		
		}
		else
			Logprintf(LOG_BBS, conn, '?', "SendPtoMultiple is set but no match on HA");
	}
	
	if (Msg->type == 'P' || Flood == 0)
	{
		// P messages are only sent to one BBS, but check the TO and AT of all BBSs before routing on HA,
		// and choose the best match on HA

		// now has option to send P messages to more than one BBS

		struct UserInfo * bestbbs = NULL;
		int bestmatch = 0;
		int depth;
		int Matched = 0;
		int MultiPDepth = 0;

		for (bbs = BBSChain; bbs; bbs = bbs->BBSNext)
		{		
			ForwardingInfo = bbs->ForwardingInfo;

			if (ForwardingInfo->PersonalOnly && (Msg->type != 'P') && (Msg->type != 'T'))
				continue;

			if (CheckBBSToList(Msg, bbs, ForwardingInfo))
			{
				Logprintf(LOG_BBS, conn, '?', "Routing Trace TO %s Matches BBS %s", Msg->to, bbs->Call);

				CheckAndSend(Msg, conn, bbs);

				Matched++;
				if (SendPtoMultiple && Msg->type == 'P')
					continue;

				return 1;
			}
		}

		for (bbs = BBSChain; bbs; bbs = bbs->BBSNext)
		{		
			ForwardingInfo = bbs->ForwardingInfo;

			if (ForwardingInfo->PersonalOnly && (Msg->type != 'P'))
				continue;

			// Check implied AT 

			if ((strcmp(ATBBS, bbs->Call) == 0))			// @BBS = BBS		
			{
				Logprintf(LOG_BBS, conn, '?', "Routing Trace %s Matches implied AT %s", ATBBS, bbs->Call);

				CheckAndSend(Msg, conn, bbs);

				Matched++;
				if (SendPtoMultiple && Msg->type == 'P')
					continue;

				return 1;
			}

			// Check AT 

			if (CheckBBSAtList(Msg, ForwardingInfo, ATBBS))
			{
				Logprintf(LOG_BBS, conn, '?', "Routing Trace %s Matches AT %s", ATBBS, bbs->Call);

				CheckAndSend(Msg, conn, bbs);

				Matched++;
				if (SendPtoMultiple && Msg->type == 'P')
					continue;

				return 1;
			}
		}

		if (Matched)
		{
			// Should only get here if Sending P to Multiples is enabled
			// and we have at least one forward.

			return 1;
		}

		// Normal HA match (not SendPToMultiple)

		for (bbs = BBSChain; bbs; bbs = bbs->BBSNext)
		{		
			ForwardingInfo = bbs->ForwardingInfo;

			if (ForwardingInfo->PersonalOnly && (Msg->type != 'P'))
				continue;

			depth = CheckBBSHElements(Msg, bbs, ForwardingInfo, ATBBS, &HElements[0]);

			if (depth)
			{
				Logprintf(LOG_BBS, conn, '?', "Routing Trace HR Matches BBS %s Depth %d", bbs->Call, depth);

				if (depth > bestmatch)
				{
					bestmatch = depth;
					bestbbs = bbs;
				}
			}
		}
		if (bestbbs)
		{
			Logprintf(LOG_BBS, conn, '?', "Routing Trace HR Best Match is %s", bestbbs->Call);

			CheckAndSend(Msg, conn, bestbbs);

			return 1;
		}

		// Check for wildcarded AT address

		//		if (ATBBS[0] == 0)
		//			return FALSE;			// no AT

CheckWildCardedAT:

		depth = 0;
		bestmatch = -1;
		bestbbs = NULL;

		for (bbs = BBSChain; bbs; bbs = bbs->BBSNext)
		{		
			ForwardingInfo = bbs->ForwardingInfo;

			if (ForwardingInfo->PersonalOnly && (Msg->type != 'P'))
				continue;

			depth = CheckBBSATListWildCarded(Msg, ForwardingInfo, ATBBS);

			if (depth > -1)
			{
				Logprintf(LOG_BBS, conn, '?', "Routing Trace Wildcarded AT Matches  %s Length %d", bbs->Call, depth);

				if (depth > bestmatch)
				{
					bestmatch = depth;
					bestbbs = bbs;
				}
			}
		}
		if (bestbbs)
		{
			if (Msg->type == 'T' && (bestbbs->flags  & F_NTSMPS)) 
				Logprintf(LOG_BBS, conn, '?', "Routing Trace Wildcarded AT Best Match is %s, but NTS Msg and MPS Set so not queued", bestbbs->Call);
			else
			{
				Logprintf(LOG_BBS, conn, '?', "Routing Trace Wildcarded AT Best Match is %s", bestbbs->Call);
				CheckAndSend(Msg, conn, bestbbs);
			}
			return 1;
		}

		Logprintf(LOG_BBS, conn, '?', "Routing Trace - No Match");
		return FALSE;	// No match
	}

	// Flood Bulls go to all matching BBSs in the flood area, so the order of checking doesn't matter

	// For now I will only route on AT (for non-hierarchical addresses) and HA

	// Ver 1.0.4.2 - Try including TO

	for (bbs = BBSChain; bbs; bbs = bbs->BBSNext)
	{		
		ForwardingInfo = bbs->ForwardingInfo;

		if (ForwardingInfo->PersonalOnly)
			continue;

		if (CheckBBSToList(Msg, bbs, ForwardingInfo))
		{
			Logprintf(LOG_BBS, conn, '?', "Routing Trace TO %s Matches BBS %s", Msg->to, bbs->Call);

			if (ForwardToMe || _stricmp(bbs->Call, BBSName) != 0) // Dont forward to ourself - already here! (unless ForwardToMe set)
			{
				set_fwd_bit(Msg->fbbs, bbs->BBSNumber);
				ForwardingInfo->MsgCount++;
			}
			Count++;
			continue;
		}

		if ((strcmp(ATBBS, bbs->Call) == 0) ||			// @BBS = BBS		
			CheckBBSAtList(Msg, ForwardingInfo, ATBBS))
		{
			Logprintf(LOG_BBS, conn, '?', "Routing Trace AT %s Matches BBS %s", Msg->to, bbs->Call);
			CheckAndSend(Msg, conn, bbs);

			Count++;
			continue;
		}


		if (CheckBBSHElementsFlood(Msg, bbs, ForwardingInfo, Msg->via, &HElements[0]))
		{
			Logprintf(LOG_BBS, conn, '?', "Routing Trace HR %s %s %s %s %s Matches BBS %s",
				HElements[0], HElements[1], HElements[2], 
				HElements[3], HElements[4], bbs->Call);

			CheckAndSend(Msg, conn, bbs);

			Count++;
		}

	}

	if (Count == 0)
		goto CheckWildCardedAT;

	Logprintf(LOG_BBS, conn, '?', "Routing Trace - No Match");

	return Count;
}

BOOL CheckBBSToList(struct MsgInfo * Msg, struct UserInfo * bbs, struct	BBSForwardingInfo * ForwardingInfo)
{
	char ** Calls;

	// Check TO distributions

	if (ForwardingInfo->TOCalls)
	{
		Calls = ForwardingInfo->TOCalls;

		while(Calls[0])
		{
			if (strcmp(Calls[0], Msg->to) == 0)	
				return TRUE;

			Calls++;
		}
	}
	return FALSE;
}

BOOL CheckBBSAtList(struct MsgInfo * Msg, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS)
{
	char ** Calls;

	// Check AT distributions

	//	if (strcmp(ATBBS, bbs->Call) == 0)			// @BBS = BBS
	//		return TRUE;

	if (ForwardingInfo->ATCalls)
	{
		Calls = ForwardingInfo->ATCalls;

		while(Calls[0])
		{
			if (strcmp(Calls[0], ATBBS) == 0)	
				return TRUE;

			Calls++;
		}
	}
	return FALSE;
}

int CheckBBSHElements(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char ** HElements)
{
	// Used for Personal Messages, and Bulls not yot at their target area

	char *** HRoutes;
	int i = 0, j, k = 0;
	int bestmatch = 0;

	if (ForwardingInfo->HADDRSP)
	{
		// Match on Routes

		HRoutes = ForwardingInfo->HADDRSP;
		k=0;

		while(HRoutes[k])
		{
			i = j = 0;

			while (HRoutes[k][i] && HElements[j]) // Until one set runs out
			{
				if (strcmp(HRoutes[k][i], HElements[j]) != 0)
					break;
				i++;
				j++;
			}

			// Only send if all BBS elements match

			if (HRoutes[k][i] == 0)
			{
				if (i > bestmatch)
					bestmatch = i;
			}
			k++;
		}
	}
	return bestmatch;
}


int CheckBBSHElementsFlood(struct MsgInfo * Msg, struct UserInfo * bbs, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS, char ** HElements)
{
	char *** HRoutes;
	char ** BBSHA;

	int i = 0, j, k = 0;
	int bestmatch = 0;

	if (ForwardingInfo->HADDRS)
	{
		// Match on Routes

		// Message must be in right area (all elements of message match BBS Location HA)

		BBSHA = ForwardingInfo->BBSHAElements;

		if (BBSHA == NULL)
			return 0;				// Not safe to flood

		i = j = 0;

		while (BBSHA[i] && HElements[j]) // Until one set runs out
		{
			if (strcmp(BBSHA[i], HElements[j]) != 0)
				break;
			i++;
			j++;
		}

		if (HElements[j] != 0)
			return 0;				// Message is not for BBS's area 

		HRoutes = ForwardingInfo->HADDRS;
		k=0;

		while(HRoutes[k])
		{
			i = j = 0;

			while (HRoutes[k][i] && HElements[j]) // Until one set runs out
			{
				if (strcmp(HRoutes[k][i], HElements[j]) != 0)
					break;
				i++;
				j++;
			}

			if (i > bestmatch)
			{
				// As Flooding, only match if all elements match, and elements matching > offset

				// As Flooding, only match if all elements from BBS are matched
				// ie if BBS has #23.gbr.eu, and msg gbr.eu, don't match
				// if BBS has gbr.eu, and msg #23.gbr.eu, ok (so long as bbs in in #23, checked above.


				if (HRoutes[k][i] == 0)
					bestmatch = i;
			}
			k++;
		}
	}
	return bestmatch;
}


int CheckBBSToForNTS(struct MsgInfo * Msg, struct BBSForwardingInfo * ForwardingInfo)
{
	char ** Calls;
	char * Call;
	char * ptr;
	int bestmatch = -1;
	int MatchLen = 0;

	// Look for Matches on TO using Wildcarded Addresses. Intended for use with NTS traffic, with TO = ZIPCode

	// We forward to the BBS with the most specific match - ie minimum *'s in match

	if (ForwardingInfo->TOCalls)
	{
		Calls = ForwardingInfo->TOCalls;

		while(Calls[0])
		{
			BOOL Invert = FALSE;		// !Match

			Call = Calls[0];

			if (Call[0] == '!' || Call[0] == '-')
			{
				Call++;
				Invert = TRUE;
			}

			ptr = strchr(Call, '*');

			if (ptr)
			{
				MatchLen = ptr - Call;

				if (memcmp(Msg->to, Call, MatchLen) == 0)
				{
					// Match - de we have a better one?

					// if it is a !Match, return without checking any more

					if (Invert)
						return -1;

					if (MatchLen > bestmatch)
						bestmatch = MatchLen;
				}
			}
			else
			{
				//no star - just do a normal compare

				if (strcmp(Msg->to, Call) == 0)
				{
					if (Invert)
						return -1;

					MatchLen = (int)strlen(Call);
					if (MatchLen > bestmatch)
						bestmatch = MatchLen;
				}
			}

			Calls++;
		}
	}
	return (int)bestmatch;
}


int CheckBBSATListWildCarded(struct MsgInfo * Msg, struct BBSForwardingInfo * ForwardingInfo, char * ATBBS)
{
	char ** Calls;
	char * Call;
	char * ptr;
	int bestmatch = -1;				// must be signed!
	int MatchLen = 0;

	// Look for Matches on AT using Wildcarded Addresses. Only applied after all other checks fail. Intended mainly
	// for setting a default route, but could have other uses

	// We forward to the BBS with the most specific match - ie minimum *'s in match

	if (ForwardingInfo->ATCalls)
	{
		Calls = ForwardingInfo->ATCalls;

		while(Calls[0])
		{
			Call = Calls[0];
			ptr = strchr(Call, '*');

			// only look if * present - we have already tried routing on the full AT

			if (ptr)
			{
				MatchLen = ptr - Call;

				if (memcmp(ATBBS, Call, MatchLen) == 0)
				{
					// Match - de we have a better one?

					if (MatchLen > bestmatch)
						bestmatch = MatchLen;
				}
			}

			Calls++;
		}
	}
	return (int)bestmatch;
}

struct ALIAS * CheckForNTSAlias(struct MsgInfo * Msg, char * ATFirstElement)
{
	struct ALIAS ** Alias = NTSAliases;
	char * Call;
	char * ptr;
	size_t MatchLen = 0;

	//	We have a list of wildcarded TO (ie Zip Codes) and corresponding replacement NTSXX

	//	It seems the NTS people want to match on either TO or DEST, and replace the AT
	//	and to use only the first matching

	while(Alias[0])
	{
		Call = Alias[0]->Dest;

		if (Call == NULL)
			break;

		ptr = strchr(Call, '*');

		if (ptr)
		{
			MatchLen = ptr - Call;

			if (memcmp(Msg->to, Call, MatchLen) == 0)
				return(Alias[0]);
		}
		else
		{
			//no star - just do a normal compare

			if (strcmp(Msg->to, Call) == 0)
				return(Alias[0]);
		}

		// Try AT

		if (ATFirstElement && ATFirstElement[0])
		{
			if (ptr)
			{
				if (memcmp(ATFirstElement, Call, MatchLen) == 0)
					return(Alias[0]);
			}
			else
			{
				if (strcmp(ATFirstElement, Call) == 0)
					return(Alias[0]);
			}
		}

		Alias++;
	}

	return NULL;
}


VOID ReRouteMessages()
{
	// Pass all messages to the Mail Routing routine.

	// Used if a new BBS is set up, or to readdress messages from a failed BBS

	struct MsgInfo * Msg;
	int n;
	char SaveStatus;
	char Savefbbs[NBMASK];

	for (n = 1; n <= NumberofMessages; n++)
	{
		Msg = MsgHddrPtr[n];

		// if killed, or forwarded and not a bull, ignore

		// If status was F and we add anther BBS, set back to N

		if (Msg->status == 'K' || (Msg->status == 'F' &&  Msg->type != 'B'))
			continue;


		if (Msg->type == 'B')
			SaveStatus = Msg->status;

		memcpy(Savefbbs, Msg->fbbs, NBMASK);

		memset(Msg->fbbs, 0, NBMASK);

		MatchMessagetoBBSList(Msg, NULL);

		if (memcmp(Savefbbs, Msg->fbbs, NBMASK) != 0)
		{
			// Have changed Message Routing

			// Clear fwd bits on any BBS it has been sent to

			if (memcmp(Msg->fbbs, zeros, NBMASK) != 0)
			{	
				struct UserInfo * user;

				for (user = BBSChain; user; user = user->BBSNext)
				{
					if (check_fwd_bit(Msg->fbbs, user->BBSNumber))		// for this BBS?	
					{
						if (check_fwd_bit(Msg->forw, user->BBSNumber))	// Already sent?
							clear_fwd_bit(Msg->fbbs, user->BBSNumber);

					}
				}

				// If still some bits set, change to $

				if (memcmp(Msg->fbbs, zeros, NBMASK) != 0)
				{
					if (FirstMessageIndextoForward > n)
						FirstMessageIndextoForward = n;

					if (Msg->type == 'B')
						Msg->status = '$';
				}
				else
				{
					// all clear - set to N or F

					if (Msg->type == 'B')
					{
						if (memcmp(Msg->forw, zeros, NBMASK) == 0)
							Msg->status = 'N';						// Not sent anywhere, and nowhere to send, so N
						else
							Msg->status = 'F';
					}
				}
			}
		}
	}
	SaveMessageDatabase();
}




