// basic JASON API to BPQ Node

// Authentication is via Telnet USER records or bbs records


#define _CRT_SECURE_NO_DEPRECATE
#define WIN32_LEAN_AND_MEAN		// Exclude rarely-used stuff from Windows headers

//#include <windows.h>
#include "CHeaders.h"
#include <stdlib.h>
#include "bpqmail.h"
#include "httpconnectioninfo.h"

struct MsgInfo * GetMsgFromNumber(int msgno);
BOOL CheckUserMsg(struct MsgInfo * Msg, char * Call, BOOL SYSOP);
char * doXMLTransparency(char * string);


// Constants
#define TOKEN_SIZE 32 // Length of the authentication token
#define TOKEN_EXPIRATION 7200 // Token expiration time in seconds (2 hours)

// Token data structure
typedef struct MailToken {
	char token[TOKEN_SIZE + 1];
	time_t expiration_time;
	struct UserInfo * User;
	char Call[10];
	int Auth;			// Security level of user

	struct MailToken* next;
} MailToken;

static MailToken * token_list = NULL;

typedef struct MailAPI
{
	char *URL;
	int URLLen;
	int (* APIRoutine)();
	int Auth;
} MailAPI;

// Auth defines

#define AuthNone 0
#define AuthUser 1
#define AuthBBSUser 2
#define AuthSysop 4


static int verify_token(const char* token);
static void remove_expired_tokens();
static int  request_token(char * response);
static void add_token_to_list(MailToken* token);
static MailToken * find_token(const char* token); 

int sendMsgList(struct HTTPConnectionInfo * Session, char * response, char * Rest, int Auth);
int sendFwdQueueLen(struct HTTPConnectionInfo * Session, char * response, char * Rest, int Auth);
int sendFwdConfig(struct HTTPConnectionInfo * Session, char * response, char * Rest, int Auth);

static struct MailAPI APIList[] =
{
	"/mail/api/v1/msgs", 17, sendMsgList, 0,
	"/mail/api/v1/FwdQLen", 20, sendFwdQueueLen, AuthSysop,
	"/mail/api/v1/FwdConfig", 22, sendFwdConfig, AuthSysop,
};

static int APICount = sizeof(APIList) / sizeof(struct MailAPI);

#ifndef WIN32
iconv_t * icu = NULL;
#endif

void APIConvertTitletoUTF8(char * Title, char * UTF8Title, int Len)
{
	if (WebIsUTF8(Title, (int)strlen(Title)) == FALSE)
	{
		// With Windows it is simple - convert using current codepage
		// I think the only reliable way is to convert to unicode and back

		int origlen = (int)strlen(Title) + 1;
#ifdef WIN32
		WCHAR BufferW[128];
		int wlen;
		int len = origlen;

		wlen = MultiByteToWideChar(CP_ACP, 0, Title, len, BufferW, origlen * 2); 
		len = WideCharToMultiByte(CP_UTF8, 0, BufferW, wlen, UTF8Title, origlen * 2, NULL, NULL); 
#else
		size_t left = Len - 1;
		size_t len = origlen;

		if (icu == NULL)
			icu = iconv_open("UTF-8//IGNORE", "CP1252");

		if (icu == (iconv_t)-1)
		{
			strcpy(UTF8Title, Title);
			icu = NULL;
			return;
		}

		char * orig = UTF8Title;

		iconv(icu, NULL, NULL, NULL, NULL);		// Reset State Machine
		iconv(icu, &Title, &len, (char ** __restrict__)&UTF8Title, &left);

#endif
	}
	else
		strcpy(UTF8Title, Title);
}

static MailToken * generate_token() 
{
	// Generate a random authentication token
	int i;

	MailToken * token = malloc(sizeof(MailToken));

	srand(time(NULL));

	for (i = 0; i < TOKEN_SIZE; i++)
	{
		token->token[i] = 'A' + rand() % 26; // Random uppercase alphabet character
	}
	token->token[TOKEN_SIZE] = '\0'; // Null-terminate the token
	token->expiration_time = time(NULL) + TOKEN_EXPIRATION; // Set token expiration time
	add_token_to_list(token);
	return token;
}

// Function to add the token to the token_list
static void add_token_to_list(MailToken * token)
{
	if (token_list == NULL)
	{
		token_list = token;
		token->next = NULL;
	}
	else
	{
		MailToken * current = token_list;
		
		while (current->next != NULL) 
			current = current->next;

		current->next = token;
		token->next = NULL;
	}
}

static void remove_expired_tokens()
{
	time_t current_time = time(NULL);
	MailToken* current_token = token_list;
	MailToken* prev_token = NULL;
	MailToken* next_token;

	while (current_token != NULL)
	{
		if (current_time > current_token->expiration_time) 
		{
			// Token has expired, remove it from the token list
			if (prev_token == NULL)
			{
				token_list = current_token->next;
			} else {
				prev_token->next = current_token->next;
			}
			next_token = current_token->next;
			free(current_token);
			current_token = next_token;
		} else {
			prev_token = current_token;
			current_token = current_token->next;
		}
	}
}

static MailToken * find_token(const char* token) 
{
	MailToken * current_token = token_list;

	while (current_token != NULL) 
	{
		if (strcmp(current_token->token, token) == 0) 
		{
			return current_token;
		}
		current_token = current_token->next;
	}
	return NULL;
}

static int send_http_response(char * response, const char* msg)
{
	return sprintf(response, "HTTP/1.1 %s\r\nContent-Length: 0\r\nConnection: close\r\n\r\n", msg);
}


int MailAPIProcessHTTPMessage(struct HTTPConnectionInfo * Session, char * response, char * Method, char * URL, char * request, BOOL LOCAL, char  *Params, char * TokenString)
{
	char * pass = strlop(Params, '&');
	int Flags = 0, n;
	MailToken * Token;
	char Msg[64];
	struct UserInfo * User;
	int Auth = 0;

	if (LOCAL)
		Auth = AuthSysop;

	// Check if the request is for token generation

	if (strcmp(Method, "GET") != 0)
		return send_http_response(response, "403 (Bad Method)");

	if (_stricmp(URL, "/mail/api/v1/login") == 0)
	{
		// Key is in Session->Key
		
		// Signon may have been validated in Node. If Session->Callsign is set
		
		if (Session->Callsign[0] == 0)
		{
			// Try BBS logon
			
			User = LookupCall(Params);
			
			if (User)
			{
				// Check Password

				if (pass[0] == 0 || strcmp(User->pass, pass) != 0 || User->flags & F_Excluded)
					return send_http_response(response, "403 (Login Failed)");		
			
				strcpy(Session->Callsign, User->Call);
				Auth = AuthBBSUser;
				if (User->flags & F_SYSOP)
					Auth |= AuthSysop;



			}
		}
		else
		{
			User = LookupCall(Session->Callsign);

			if (User)
			{
				Auth = AuthUser;
				if (User->flags & F_SYSOP)
					Auth |= AuthSysop;
			}
		}

		n = sprintf_s(Msg, sizeof(Msg), "API Connect from %s", _strupr(Params));
		WriteLogLine(NULL, '|',Msg, n, LOG_BBS);

		Token = zalloc(sizeof(MailToken));

		strcpy(Token->token, Session->Key);
		strcpy(Token->Call, Session->Callsign);
		Token->Auth = Auth;

		Token->expiration_time = time(NULL) + TOKEN_EXPIRATION; // Set token expiration time
		add_token_to_list(Token);

		// Return Token

		sprintf(response, "{\"access_token\":\"%s\", \"expires_at\":%d, \"scope\":\"create\"}\r\n",
			Token->token, Token->expiration_time);

		return strlen(response);
	}

	// Find Token

	if (TokenString[0])					// Token form Auth Header
		Token = find_token(TokenString);
	else
		Token = find_token(Params);		// Token form URL

	if (Token != NULL)
	{
		// Check if the token has expired
	
		time_t current_time = time(NULL);
		if (current_time > Token->expiration_time)
		{
			// Token has expired, remove it from the token list
			remove_expired_tokens();
			Token = NULL;
		}
	}

	if (Token)
		Auth |= Token->Auth;

	// Determine the requested API endpoint

	for (n = 0; n < APICount; n++)
	{
		struct MailAPI * APIEntry;
		char * rest;
		
		APIEntry = &APIList[n];

		if (_memicmp(URL, APIEntry->URL, APIEntry->URLLen) == 0)
		{
			rest = &request[4 + APIEntry->URLLen];	// Anything following?

			if (rest[0] =='?')
			{
				//Key

				strlop(rest, ' ');
				strlop(rest, '&');

				Token = find_token(&rest[1]);

				if (Token)
				{
					strcpy(Session->Callsign, Token->Call);
					strcpy(Session->Key, Token->token);
				}
				else
					return send_http_response(response, "403 (Invalid Security Token)");
			}
			
			if (APIEntry->Auth)
			{
				// Check Level 

				if ((Auth & APIEntry->Auth) == 0)
					return send_http_response(response, "403 (Not Authorized)");
			}

			if (rest[0] == ' ' || rest[0] == '/' || rest[0] == '?')
			{
				return APIEntry->APIRoutine(Session, response, rest, Auth);
			}
		}

	}

	return send_http_response(response, "401 Invalid API Call");


	return 0;
}

int WebMailAPIProcessHTTPMessage(char * response, char * Method, char * URL, char * request, BOOL LOCAL, char  *Params)
{
	char * pass = strlop(Params, '&');
	int Flags = 0;
	MailToken * Token;


	// Check if the request is for token generation

	if (strcmp(Method, "GET") != 0)
		return send_http_response(response, "403 (Bad Method)");

	if (_stricmp(URL, "/mail/api/login") == 0)
	{
		// user is in Params and Password in pass

		struct UserInfo * User;
		char Msg[256];
		int n;

		User = LookupCall(Params);

		if (User)
		{
			// Check Password

			if (pass[0] == 0 || strcmp(User->pass, pass) != 0 || User->flags & F_Excluded)
				return send_http_response(response, "403 (Login Failed)");		
			
			n=sprintf_s(Msg, sizeof(Msg), "API Connect from %s", _strupr(Params));
			WriteLogLine(NULL, '|',Msg, n, LOG_BBS);

			Token = generate_token();
			add_token_to_list(Token);

			Token->User = User;

			strcpy(Token->Call, Params);

			// Return Token

			sprintf(response, "{\"access_token\":\"%s\", \"expires_in\":%d, \"scope\":\"create\"}\r\n",
					Token->token, Token->expiration_time);

			return strlen(response);

		}
	}

	return 0;
}

//	Unauthorised users can only get bulls.
//	Autothorised may read only users message or all messages depending on sysop status

int sendMsgList(struct HTTPConnectionInfo * Session, char * response, char * Rest, int Auth)
{
 	struct UserInfo * User = LookupCall(Session->Callsign);
	int m;
	struct MsgInfo * Msg;
	char * ptr = response;
	int n = NumberofMessages; //LineCount;
	char Via[64];
	int Count = 0;
	struct UserInfo DummyUser = {""};
	ptr[0] = 0;

	if (User == 0)
		User=&DummyUser;

	n = sprintf(ptr,"{\"msgs\":[\r\n");
	ptr += n;

	for (m = LatestMsg; m >= 1; m--)
	{
		if (ptr > &response[244000])
			break;						// protect buffer

		Msg = GetMsgFromNumber(m);

		if (Msg == 0 || Msg->type == 0 || Msg->status == 0)
			continue;					// Protect against corrupt messages
		
		if (Msg && CheckUserMsg(Msg, User->Call, Auth & AuthSysop))
		{
			char UTF8Title[4096];
			char  * EncodedTitle;
			
			// List if it is the right type and in the page range we want

	
			if (Count++ < Session->WebMailSkip)
				continue;

			ptr += sprintf(ptr, "{\r\n");


			strcpy(Via, Msg->via);
			strlop(Via, '.');

			// make sure title is HTML safe (no < > etc) and UTF 8 encoded

			EncodedTitle = doXMLTransparency(Msg->title);

			memset(UTF8Title, 0, 4096);		// In case convert fails part way through
			APIConvertTitletoUTF8(EncodedTitle, UTF8Title, 4095);

			ptr += sprintf(ptr, "\"id\": \"%d\",\r\n", Msg->number);
			ptr += sprintf(ptr, "\"mid\": \"%s\",\r\n", Msg->bid);
			ptr += sprintf(ptr, "\"rcvd\": \"%d\",\r\n", Msg->datecreated);
			ptr += sprintf(ptr, "\"type\": \"%c\",\r\n", Msg->type);
			ptr += sprintf(ptr, "\"status\": \"%c\",\r\n", Msg->status);
			ptr += sprintf(ptr, "\"to\": \"%s\",\r\n", Msg->to);
			ptr += sprintf(ptr, "\"from\": \"%s\",\r\n", Msg->from);
			ptr += sprintf(ptr, "\"size\": \"%d\",\r\n", Msg->length);
			ptr += sprintf(ptr, "\"subject\": \"%s\"\r\n", UTF8Title);

			free(EncodedTitle);
			
	//		ptr += sprintf(ptr, "<a href=/WebMail/WM?%s&%d>%6d</a> %s %c%c %5d %-8s%-8s%-8s%s\r\n",
	//			Session->Key, Msg->number, Msg->number,
	//			FormatDateAndTime((time_t)Msg->datecreated, TRUE), Msg->type,
	//			Msg->status, Msg->length, Msg->to, Via,
	//			Msg->from, UTF8Title);

			ptr += sprintf(ptr, "},\r\n"); 

			n--;

			if (n == 0)
				break;
		}
	}

	if (response[n] == 0)		// No entries
	{
		response[strlen(response) - 2] = '\0';          // remove \r\n
		strcat(response, "]}\r\n");
	}
	else
	{
		response[strlen(response)-3 ] = '\0';          // remove ,\r\n
		strcat(response, "\r\n]}\r\n");
	}
	return strlen(response);
}

int sendFwdQueueLen(struct HTTPConnectionInfo * Session, char * response, char * Rest, int Auth)
{
	struct UserInfo * USER;
	char * ptr = response;
	int n;
	int i = 0;
	int Len = 0;

	n = sprintf(ptr,"{\"forwardqueuelength\":[\r\n");
	ptr += n;
	
	for (USER = BBSChain; USER; USER = USER->BBSNext)
	{
		int Count = CountMessagestoForward (USER);

		ptr += sprintf(ptr, "{");
		ptr += sprintf(ptr, "\"call\": \"%s\",", USER->Call);
		ptr += sprintf(ptr, "\"queueLength\": \"%d\"", Count);
		ptr += sprintf(ptr, "},\r\n");
	}

	if (response[n] == 0)		// No entries
	{
		response[strlen(response) - 2] = '\0';          // remove \r\n
		strcat(response, "]}\r\n");
	}
	else
	{
		response[strlen(response)-3 ] = '\0';          // remove ,\r\n
		strcat(response, "\r\n]}\r\n");
	}
	return strlen(response);
}

VOID APIMultiStringValue(char ** values, char * Multi)
{
	char ** Calls;
	char * ptr = &Multi[0];

	*ptr = 0;

	if (values)
	{
		Calls = values;

		while(Calls[0])
		{
			ptr += sprintf(ptr, "\"%s\",", Calls[0]);
			Calls++;
		}
		if (ptr != &Multi[0])
			*(--ptr) = 0;
	}
}

char * APIConvTime(int ss)
{
	int hh, mm;
	static char timebuf[64];

	hh = ss / 3600;
	mm = (ss - (hh * 3600)) / 60;
	ss = ss % 60;

	sprintf(timebuf, "\"%02d:%02d:%02d\"", hh, mm, ss);

	return timebuf;
}


int sendFwdConfig(struct HTTPConnectionInfo * Session, char * response, char * Rest, int Auth)
{
	struct UserInfo * USER;
	char * ptr = response;
	int n = 0;
	int i = 0;
	int Len = 0;

	response[n] = 0;

	n = sprintf(ptr, "{\r\n");
	ptr += n;

	for (USER = BBSChain; USER; USER = USER->BBSNext)
	{
		struct BBSForwardingInfo * FWDInfo = USER->ForwardingInfo;

		int Count = CountMessagestoForward (USER);

		char TO[2048] = "";
		char AT[2048] = "";
		char TIMES[2048] = "";
		char FWD[100000] = "";
		char HRB[2048] = "";
		char HRP[2048] = "";

		APIMultiStringValue(FWDInfo->TOCalls, TO);
		APIMultiStringValue(FWDInfo->ATCalls, AT);
		APIMultiStringValue(FWDInfo->FWDTimes, TIMES);
		APIMultiStringValue(FWDInfo->ConnectScript, FWD);
		APIMultiStringValue(FWDInfo->Haddresses, HRB);
		APIMultiStringValue(FWDInfo->HaddressesP, HRP);



		ptr += sprintf(ptr, " \"%s\": {\r\n", USER->Call);
		ptr += sprintf(ptr, "  \"queueLength\": %d,\r\n", Count);
		ptr += sprintf(ptr, "  \"to\": [%s],\r\n", TO);
		ptr += sprintf(ptr, "  \"at\": [%s],\r\n", AT);
		ptr += sprintf(ptr, "  \"hrp\": [%s],\r\n",HRP);
		ptr += sprintf(ptr, "  \"hrb\": [%s],\r\n",HRB);
		ptr += sprintf(ptr, "  \"times\": [%s],\r\n",TIMES);
		ptr += sprintf(ptr, "  \"connectScript\": [%s],\r\n",FWD);
		ptr += sprintf(ptr, "  \"bbsHa\": \"%s\",\r\n", (FWDInfo->BBSHA)?FWDInfo->BBSHA:"");
		ptr += sprintf(ptr, "  \"enableForwarding\": %s,\r\n", (FWDInfo->Enabled)?"true":"false");
		ptr += sprintf(ptr, "  \"forwardingInterval\": %s,\r\n", APIConvTime(FWDInfo->FwdInterval));
		ptr += sprintf(ptr, "  \"requestReverse\": %s,\r\n", (FWDInfo->ReverseFlag)?"true":"false");
		ptr += sprintf(ptr, "  \"reverseInterval\": %s,\r\n", APIConvTime(FWDInfo->RevFwdInterval));
		ptr += sprintf(ptr, "  \"sendNewMessagesWithoutWaiting\": %s,\r\n", (FWDInfo->SendNew)?"true":"false");
		ptr += sprintf(ptr, "  \"fbbBlocked\": %s,\r\n", (FWDInfo->AllowBlocked)?"true":"false");
		ptr += sprintf(ptr, "  \"maxBlock\": %d,\r\n", FWDInfo->MaxFBBBlockSize);
		ptr += sprintf(ptr, "  \"sendPersonalMailOnly\": %s,\r\n", (FWDInfo->PersonalOnly)?"true":"false");
		ptr += sprintf(ptr, "  \"allowBinary\": %s,\r\n", (FWDInfo->AllowCompressed)?"true":"false");
		ptr += sprintf(ptr, "  \"useB1Protocol\": %s,\r\n", (FWDInfo->AllowB1)?"true":"false");
		ptr += sprintf(ptr, "  \"useB2Protocol\": %s,\r\n", (FWDInfo->AllowB2)?"true":"false");
		ptr += sprintf(ptr, "  \"incomingConnectTimeout\": %s\r\n", APIConvTime(FWDInfo->ConTimeout));
		ptr += sprintf(ptr, " },\r\n");
	}

	if (response[n] == 0)		// No entries
	{
		strcpy(response, "{}\r\n");
	}
	else
	{
		response[strlen(response)-3 ] = '\0';          // remove ,\r\n
		strcat(response, "\r\n}\r\n");
	}

	return strlen(response);
}



/*
{
  "GB7BEX": {
    "queueLength": 0,
    "forwardingConfig": {
      "to": [],
      "at": [
        "OARC",
        "GBR",
        "WW"
      ],
      "times": [],
      "connectScript": [
        "PAUSE 3",
        "INTERLOCK 3",
        "NC 3 !GB7BEX"
      ],
      "hierarchicalRoutes": [],
      "hr": [
        "#38.GBR.EURO"
      ],
      "bbsHa": "GB7BEX.#38.GBR.EURO",
      "enableForwarding": true,
      "forwardingInterval": "00:56:40",
      "requestReverse": false,
      "reverseInterval": "00:56:40",
      "sendNewMessagesWithoutWaiting": true,
      "fbbBlocked": true,
      "maxBlock": 1000,
      "sendPersonalMailOnly": false,
      "allowBinary": true,
      "useB1Protocol": false,
      "useB2Protocol": true,
      "sendCtrlZInsteadOfEx": false,
      "incomingConnectTimeout": "00:02:00"
    }
  },
  "GB7RDG": {
    "queueLength": 0,
    "forwardingConfig": {
      "to": [],
...
      "incomingConnectTimeout": "00:02:00"
    }
  }
}



# HELP packetmail_queue_length The number of messages in the packetmail queue
# TYPE packetmail_queue_length gauge
packetmail_queue_length{partner="DM4RW"} 0 1729090716916
packetmail_queue_length{partner="G8BPQ"} 3 1729090716916
packetmail_queue_length{partner="GB7BEX"} 0 1729090716916
packetmail_queue_length{partner="GB7BPQ"} 1 1729090716916
packetmail_queue_length{partner="GB7MNS"} 0 1729090716916
packetmail_queue_length{partner="GB7NOT"} 0 1729090716916
packetmail_queue_length{partner="GB7NWL"} 0 1729090716916
packetmail_queue_length{partner="GM8BPQ"} 0 1729090716916

*/


// Stuff send to  packetnodes.spots.radio/api/bbsdata/{bbsCall}
//https://nodes.ukpacketradio.network/swagger/index.html


/*
BbsData{
callsign*	[...]
time*	[...]
hroute*	[...]
peers	[...]
software*	[...]
version*	[...]
mailQueues	[...]
messages	[...]
latitude	[...]
longitude	[...]
locator	[...]
location	[...]
unroutable	[...]
}

[

{
    "callsign": "GE8PZT",
    "time": "2024-11-25T10:07:41+00:00",
    "hroute": ".#24.GBR.EU",
    "peers": [
      "GB7BBS",
      "VE2PKT",
      "GB7NXT",
      "VA2OM"
    ],
    "software": "XrLin",
    "version": "504a",
    "mailQueues": [],
    "messages": [
      {
        "to": "TECH@WW",
        "mid": "20539_GB7CIP",
        "rcvd": "2024-11-24T09:27:59+00:00",
        "routing": [
          "R:241124/0927Z @:GE8PZT.#24.GBR.EU [Lamanva] #:2315 XrLin504a",
 

      {
        "to": "TNC@WW",
        "mid": "37_PA2SNK",
        "rcvd": "2024-11-18T21:56:55+00:00",
        "routing": [
          "R:241118/2156Z @:GE8PZT.#24.GBR.EU [] #:2215 XrLin504a",
          "R:241118/2156Z 12456@VE2PKT.#TRV.QC.CAN.NOAM BPQ6.0.24",
          "R:241118/2130Z 51539@VE3KPG.#ECON.ON.CAN.NOAM BPQK6.0.23",
          "R:241118/2130Z 26087@VE3CGR.#SCON.ON.CAN.NOAM LinBPQ6.0.24",
          "R:241118/2130Z 37521@PA8F.#ZH1.NLD.EURO LinBPQ6.0.24",
          "R:241118/2129Z 48377@PI8LAP.#ZLD.NLD.EURO LinBPQ6.0.24",
          "R:241118/2129Z @:PD0LPM.FRL.EURO.NLD #:33044 [Joure] $:37_PA2SNK"
        ]
      }
    ],
    "latitude": 50.145832,
    "longitude": -5.125,
    "locator": "IO70KD",
    "location": "Lamanva",
    "unroutable": [
      {
        "type": "P",
        "at": "WW"
      },
      {
        "type": "P",
        "at": "G8PZT-2"
      },
      {
        "type": "P",
        "at": "g8pzt._24.gbr.eu"
      },
      {
        "type": "P",
        "at": "G8PZT.#24.GBR.EU"
      },
      {
        "type": "P",
        "at": "GE8PZT.#24.GBR.EU"
      },
      {
        "type": "P",
        "at": "G8PZT.#24.GBR.EURO"
      }
    ]
  },

*/


//	https://packetnodes.spots.radio/swagger/index.html

// 	   "unroutable": [{"type": "P","at": "WW"}, {"type": "P", "at": "G8PZT.#24.GBR.EURO"}]

char * ViaList[100000];			// Pointers to the Message Header field
char TypeList[100000];

int unroutableCount = 0;


void CheckifRoutable(struct MsgInfo * Msg)
{
	char NextBBS[64];
	int n;

	if (Msg->status == 'K')
		return;

	if (Msg->via[0] == 0)		// No routing
		return;

	strcpy(NextBBS, Msg->via);
	strlop(NextBBS, '.');

	if (strcmp(NextBBS, BBSName) == 0)	// via this BBS
		return;

	if ((memcmp(Msg->fbbs, zeros, NBMASK) != 0) || (memcmp(Msg->forw, zeros, NBMASK) != 0))	// Has Forwarding Info
		return;

	// See if we already have it

	for (n = 0; n < unroutableCount; n++)
	{
		if ((TypeList[n] == Msg->type) && strcmp(ViaList[n], Msg->via) == 0)
			return;

	}

	// Add to list

	TypeList[unroutableCount] = Msg->type;
	ViaList[unroutableCount] = Msg->via;

	unroutableCount++;
}


extern char LOC[7];


DllExport VOID WINAPI SendWebRequest(char * Host, char * Request, char * Params, char * Return);

#ifdef LINBPQ
extern double LatFromLOC;
extern double LonFromLOC;
#else
typedef int (WINAPI FAR *FARPROCX)();
extern FARPROCX pSendWebRequest;
extern FARPROCX pGetLatLon;
double LatFromLOC = 0;
double LonFromLOC = 0;
#endif

void SendBBSDataToPktMap()
{
	char Return[4096];
	char Request[64];
	char Params[50000];
	char * ptr = Params;
	struct MsgInfo * Msg;

	struct UserInfo * ourBBSRec = LookupCall(BBSName);
	struct UserInfo * USER;
	char Time[64];
	struct tm * tm;
	time_t Date = time(NULL);
	char Peers[2048] = "[]";
	char MsgQueues[16000] = "[]";
	char * Messages = malloc(1000000);	
	char * Unroutables;
	int m;
	char * MsgBytes;
	char * Rlineptr;
	char * Rlineend;
	char * RLines;
	char * ptr1, * ptr2;
	int n;

#ifndef LINBPQ
	if (pSendWebRequest == 0)
		return;						// Old Version of bpq32.dll

	pGetLatLon(&LatFromLOC, &LonFromLOC);

#endif
	if (ourBBSRec == 0)
		return;		// Wot!!

	// Get peers and Mail Queues

	ptr = &Peers[1];
	ptr1 = &MsgQueues[1];

	for (USER = BBSChain; USER; USER = USER->BBSNext)
	{
		if (strcmp(USER->Call, BBSName) != 0)
		{
			int Bytes;
			
			int Count = CountMessagestoForward(USER);	
			
			ptr += sprintf(ptr, "\"%s\",", USER->Call);

			if (Count)
			{
				Bytes = CountBytestoForward(USER);

				ptr1 += sprintf(ptr1, "{\"peerCall\": \"%s\", \"numQueued\": %d, \"bytesQueued\": %d},",
					USER->Call, Count, Bytes);
			}
      }
	}

	if ((*ptr) != ']')		// Have some entries
	{
		ptr--;				// over trailing comms
		*(ptr++) = ']';
		*(ptr) = 0;
	}

	if ((*ptr1) != ']')		// Have some entries
	{
		ptr1--;				// over trailing comms
		*(ptr1++) = ']';
		*(ptr1) = 0;
	}

	// Get Messages

	strcpy(Messages, "[]");
	ptr = &Messages[1];

	for (m = LatestMsg; m >= 1; m--)
	{
		if (ptr > &Messages[999000])
			break;						// protect buffer

		Msg = GetMsgFromNumber(m);

		if (Msg == 0 || Msg->type == 0 || Msg->status == 0)
			continue;					// Protect against corrupt messages

		// Paula suggests including H and K but limit it to the last 30 days or the last 100 messages, whichever is the smaller.

//		if (Msg->status == 'K' || Msg->status == 'H')
//			continue;

		if ((Date - Msg->datereceived) > 30 * 86400)		// Too old
			continue;

		CheckifRoutable(Msg);

		tm = gmtime(&Msg->datereceived);

		sprintf(Time, "%04d-%02d-%02dT%02d:%02d:%02d+00:00", 
			tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);

		// Get Routing

		MsgBytes = ReadMessageFile(Msg->number);
		RLines = malloc(Msg->length * 2);				// Very unlikely to need so much but better safe..

		strcpy(RLines, "[]");

		ptr2 = &RLines[1];
		
		// Need to skip B2 header if B2 Message

		Rlineptr = MsgBytes;

		// If it is a B2 Message, Must Skip B2 Header

		if (Msg->B2Flags & B2Msg)
		{
			Rlineptr = strstr(Rlineptr, "\r\n\r\n");
			if (Rlineptr)
				Rlineptr += 4;
			else
				Rlineptr = MsgBytes;
		}

		// We have to process R: lines one at a time as we need to send each one as a separate string

		while (memcmp(Rlineptr, "R:", 2) == 0)
		{
			// Have R Lines

			Rlineend = strstr(Rlineptr, "\r\n");
			Rlineend[0] = 0;
			ptr2 += sprintf(ptr2, "\"%s\",", Rlineptr);

			Rlineptr = Rlineend + 2;		// over crlf
		}

		if ((*ptr2) == ']')		// no entries
			continue;

		ptr2--;				// over trailing comms
		*(ptr2++) = ']';
		*(ptr2) = 0;
	
		ptr += sprintf(ptr, "{\"to\": \"%s\", \"mid\": \"%s\", \"rcvd\": \"%s\", \"routing\": %s},",
			Msg->to, Msg->bid, Time, RLines);

		free(MsgBytes);
		free(RLines);

	}

	if ((*ptr) != ']')	// Have some entries?
	{
		ptr--;				// over trailing comms
		*(ptr++) = ']';
		*(ptr) = 0;
	}

	// Get unroutables

	Unroutables = malloc((unroutableCount + 1) * 100);

	strcpy(Unroutables, "[]");
	ptr = &Unroutables[1];


	for (n = 0; n < unroutableCount; n++)
	{
		ptr += sprintf(ptr, "{\"type\": \"%c\",\"at\": \"%s\"},", TypeList[n], ViaList[n]);
	}

	if ((*ptr) != ']')	// Have some entries?
	{
		ptr--;				// over trailing comms
		*(ptr++) = ']';
		*(ptr) = 0;
	}



	/*
char * ViaList[100000];			// Pointers to the Message Header field
char TypeList[100000];

int unroutableCount = 0;
	   "unroutable": [{"type": "P","at": "WW"}, {"type": "P", "at": "G8PZT.#24.GBR.EURO"}]
	*/


	tm = gmtime(&Date);

	sprintf(Time, "%04d-%02d-%02dT%02d:%02d:%02d+00:00", 
		tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);


	ptr = Params;

	sprintf(Request, "/api/bbsdata/%s", BBSName);

	ptr += sprintf(ptr, "{\"callsign\": \"%s\",\r\n", BBSName);
	ptr += sprintf(ptr, "\"time\": \"%s\",\r\n", Time);
	ptr += sprintf(ptr, "\"hroute\": \"%s\",\r\n", HRoute);
	ptr += sprintf(ptr, "\"peers\": %s,\r\n", Peers);
#ifdef LINBPQ
	ptr += sprintf(ptr, "\"software\": \"%s\",\r\n", "linbpq");
#else
	ptr += sprintf(ptr, "\"software\": \"%s\",\r\n", "BPQMail");
#endif
	ptr += sprintf(ptr, "\"version\": \"%s\",\r\n", VersionString);
	ptr += sprintf(ptr, "\"mailQueues\": %s,\r\n", MsgQueues);
	ptr += sprintf(ptr, "\"messages\": %s,\r\n", Messages);
	ptr += sprintf(ptr, "\"latitude\": %1.6f,\r\n", LatFromLOC);
	ptr += sprintf(ptr, "\"longitude\": %.6f,\r\n", LonFromLOC);
	ptr += sprintf(ptr, "\"locator\": \"%s\",\r\n", LOC);
	ptr += sprintf(ptr, "\"location\": \"%s\",\r\n", ourBBSRec->Address);
	ptr += sprintf(ptr, "\"unroutable\": %s\r\n}\r\n", Unroutables);

	SendWebRequest("packetnodes.spots.radio", Request, Params, Return);
	free(Messages);
	free(Unroutables);
}