diff --git a/BBSHTMLConfig.c b/BBSHTMLConfig.c index e4c8248..d328419 100644 --- a/BBSHTMLConfig.c +++ b/BBSHTMLConfig.c @@ -477,6 +477,13 @@ void ProcessMailHTTPMessage(struct HTTPConnectionInfo * Session, char * Method, } + + if (_memicmp(URL, "/Mail/API/v1/", 13) == 0) + { + *RLen = MailAPIProcessHTTPMessage(Session, Reply, Method, URL, input, LOCAL, Context); + return; + } + // There is a problem if Mail is reloaded without reloading the node if (GotFirstMessage == 0) @@ -494,12 +501,6 @@ void ProcessMailHTTPMessage(struct HTTPConnectionInfo * Session, char * Method, return; } - if (_memicmp(URL, "/Mail/API/v1/", 13) == 0) - { - *RLen = MailAPIProcessHTTPMessage(Session, Reply, Method, URL, input, LOCAL, Context); - return; - } - if (strcmp(Method, "POST") == 0) { diff --git a/mailapi.c b/mailapi.c index 5ec43fb..9a19657 100644 --- a/mailapi.c +++ b/mailapi.c @@ -570,9 +570,9 @@ int sendFwdConfig(struct HTTPConnectionInfo * Session, char * response, char * R int i = 0; int Len = 0; - n = sprintf(ptr,"{\"forwardconfig\":[\r\n"); - ptr += n; - + response[n] = 0; + + for (USER = BBSChain; USER; USER = USER->BBSNext) { struct BBSForwardingInfo * FWDInfo = USER->ForwardingInfo; @@ -619,15 +619,10 @@ int sendFwdConfig(struct HTTPConnectionInfo * Session, char * response, char * R ptr += sprintf(ptr, " }\r\n},\r\n"); } - if (response[n] == 0) // No entries - { - response[strlen(response) - 2] = '\0'; // remove \r\n - strcat(response, "]}\r\n"); - } - else + if (response[n]) // No entries { response[strlen(response)-3 ] = '\0'; // remove ,\r\n - strcat(response, "\r\n]}\r\n"); + strcat(response, "\r\n"); } return strlen(response); diff --git a/mailapi.c.bak b/mailapi.c.bak new file mode 100644 index 0000000..fa3d893 --- /dev/null +++ b/mailapi.c.bak @@ -0,0 +1,691 @@ +// basic JASON API to BPQ Node + +// Authentication is via Telnet USER records. + + +#define _CRT_SECURE_NO_DEPRECATE +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +//#include +#include "CHeaders.h" +#include +#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, AuthBBSUser | AuthSysop, + "/mail/api/v1/FwdQLen", 20, sendFwdQueueLen, AuthBBSUser | AuthSysop, + "/mail/api/v1/FwdConfig", 22, sendFwdConfig, AuthBBSUser | 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 int verify_token(const char* token) +{ + // Find the token in the token list + MailToken * existing_token = find_token(token); + + if (existing_token != NULL) + { + // Check if the token has expired + time_t current_time = time(NULL); + if (current_time > existing_token->expiration_time) + { + // Token has expired, remove it from the token list + remove_expired_tokens(); + return 0; + } + // Token is valid + return 1; + } + + // Token doesn't exist in the token list + return 0; +} + +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 * 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; + } + } + 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); + } + + // 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 (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, "%6d %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; + + + 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, "{\r\n"); + 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},\r\n"); + } + + if (response[n]) // No entries + { + 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 + +*/ \ No newline at end of file