363 lines
16 KiB
C
363 lines
16 KiB
C
#include <stdio.h>
|
|
#include <time.h>
|
|
#include <pthread.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <signal.h>
|
|
#include <toml.h>
|
|
|
|
#include "settings.h"
|
|
#include "ansicolor.h"
|
|
#include "connlist.h"
|
|
#include "utils.h"
|
|
#include "help.h"
|
|
#include "dbutil.h"
|
|
#include "chat.h"
|
|
|
|
// config values
|
|
unsigned short configPort;
|
|
unsigned int configConnlimit;
|
|
char *configCertfile, *configKeyfile, *configPassword, sslInUse;
|
|
|
|
// return value: 0 = succeeded ; 1 = failed
|
|
uint8_t initConfigFromFile(const char* configPath) {
|
|
FILE* configFile = fopen(configPath, "r");
|
|
if (configFile == NULL) {logMessage(error, "Config file does not exist."); return 1;}
|
|
char errbuf[256];
|
|
toml_table_t* conf = toml_parse_file(configFile, errbuf, sizeof(errbuf));
|
|
fclose(configFile);
|
|
if (!conf) {logMessage(error, "Config parsing error: %s", errbuf); return 1;}
|
|
|
|
toml_table_t* server = toml_table_in(conf, "listen");
|
|
if (!server) {logMessage(error, "Config parsing error: missing [listen]"); return 1;}
|
|
|
|
toml_datum_t confPort = toml_int_in(server, "port");
|
|
if (!confPort.ok) {logMessage(error, "Config parsing error: missing 'port' field"); return 1;}
|
|
if (confPort.u.b > USHRT_MAX) {logMessage(error, "Config parsing error: port out of range"); return 1;}
|
|
configPort = confPort.u.b;
|
|
|
|
toml_table_t* ssl = toml_table_in(conf, "ssl");
|
|
if (ssl) {
|
|
toml_datum_t confCertfile = toml_string_in(ssl, "certfile");
|
|
if (!confCertfile.ok) {logMessage(error, "Config parsing error: missing 'certfile' field"); return 1;}
|
|
configCertfile = confCertfile.u.s;
|
|
|
|
toml_datum_t confKeyfile = toml_string_in(ssl, "keyfile");
|
|
if (!confKeyfile.ok) {logMessage(error, "Config parsing error: missing 'keyfile' field"); return 1;}
|
|
configKeyfile = confKeyfile.u.s;
|
|
|
|
toml_datum_t confPassword = toml_string_in(ssl, "password");
|
|
if (!confPassword.ok) {logMessage(error, "Config parsing error: missing 'password' field"); return 1;}
|
|
configPassword = confPassword.u.s;
|
|
}
|
|
|
|
toml_table_t* bbs = toml_table_in(conf, "bbs");
|
|
if (!bbs) {logMessage(error, "Config parsing error: missing [bbs]"); return 1;}
|
|
|
|
toml_array_t* confChannels = toml_array_in(bbs, "channelList");
|
|
if (!confChannels) {logMessage(error, "Config parsing error: missing 'channelList' field"); return 1;}
|
|
unsigned int channelCount = toml_array_nelem(confChannels);
|
|
|
|
if (channelCount == 0) {logMessage(error, "Config parsing error: empty 'channelList' field"); return 1;}
|
|
toml_datum_t confConnlimit = toml_int_in(bbs, "connlimit");
|
|
if (!confConnlimit.ok) {logMessage(error, "Config parsing error: missing 'connlimit' field"); return 1;}
|
|
configConnlimit = confConnlimit.u.b;
|
|
initChat(channelCount);
|
|
|
|
for (int i = 0; i < channelCount; ++i) {
|
|
toml_datum_t channelName = toml_string_at(confChannels, i);
|
|
if (!channelName.ok) {logMessage(error, "Config parsing error: invalid 'channelList' field"); return 1;}
|
|
char* channelNameStr = channelName.u.s;
|
|
trimWhitespace(channelNameStr);
|
|
unsigned int newLength = sanitizeString(channelNameStr, strlen(channelNameStr));
|
|
if (newLength == 0) {
|
|
logMessage(error, "Config parsing error: 'channelList' field contains an empty entry");
|
|
return 1;
|
|
}
|
|
if (newLength >= CHANNEL_NAME_LEN) {
|
|
logMessage(error, "Config parsing error: 'channelList' field contains an entry that is longer than "STR(CHANNEL_NAME_LEN)" characters.");
|
|
return 1;
|
|
}
|
|
setChannelName(i, channelNameStr);
|
|
free(channelNameStr);
|
|
}
|
|
toml_free(conf);
|
|
return 0;
|
|
}
|
|
|
|
int askCredentials(struct LemonClientSocket *socket, char* username, char* password, int bufLength) {
|
|
while (1) {
|
|
lolib_writeSocket(socket, METASTRING(ANSI_YELLOW"\n Username: "ANSI_RESET));
|
|
int read = lolib_readSocket(socket, username, bufLength-1);
|
|
if (read < 1) return -1; // IO error
|
|
if (username[0] == 'q') return 0; // cancelled
|
|
username[read] = 0; // null-terminator
|
|
trimWhitespace(username);
|
|
if (sanitizeString(username, read) == 0) {
|
|
lolib_writeSocket(socket, METASTRING(ANSI_RED"\n Error: Username empty."));
|
|
continue;
|
|
}
|
|
lolib_writeSocket(socket, METASTRING(ANSI_YELLOW" Password: "ANSI_RESET));
|
|
read = lolib_readSocket(socket, password, bufLength-1);
|
|
if (read < 1) return -1; // IO error
|
|
password[read] = 0; // null-terminator
|
|
trimWhitespace(password);
|
|
if (sanitizeString(password, read) == 0) {
|
|
lolib_writeSocket(socket, METASTRING(ANSI_RED"\n Error: Password empty."));
|
|
continue;
|
|
}
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
int channelSelector(struct LemonClientSocket *socket) {
|
|
lolib_writeSocket(socket, METASTRING(ANSI_RED"\n +--------+\n | "ANSI_GREEN"Chat"ANSI_RED" |\n +--------+\n"ANSI_YELLOW" WARNING: Chat feature is EXPERIMENTAL and may not work correctly.\n\n"));
|
|
for (size_t i = 0; i < getChannelCount(); ++i) {
|
|
char line[128];
|
|
int toWrite = snprintf(line, 128, ANSI_RED" %lu. "ANSI_GREEN"#%s "ANSI_CYAN"[%d/%d online]\n", i + 1, getChannelName(i), countUsers(i), USERS_PER_CHANNEL);
|
|
lolib_writeSocket(socket, line, toWrite);
|
|
}
|
|
while (1) {
|
|
char answer[8];
|
|
lolib_writeSocket(socket, METASTRING(ANSI_YELLOW"\n Pick a channel ['q' to cancel]: "ANSI_RESET));
|
|
int read = lolib_readSocket(socket, answer, 7);
|
|
if (read < 1) return -1; // IO error
|
|
if (answer[0] == 'q') return -2; // cancelled
|
|
if (sanitizeString(answer, read) == 0) continue;
|
|
int channelSelected = strtol(answer, NULL, 10);
|
|
if (!inRange(channelSelected, 1, getChannelCount())) {
|
|
lolib_writeSocket(socket, METASTRING(ANSI_YELLOW" No channel with given ID exists."));
|
|
} else {
|
|
return channelSelected-1;
|
|
}
|
|
}
|
|
}
|
|
|
|
int chatWindow(struct Connection* cta, size_t channelId) {
|
|
char inbuf[400], outbuf[500];
|
|
while (1) {
|
|
int read = lolib_readSocket(&cta->socket, inbuf, sizeof(inbuf)-1);
|
|
if (read < 1) return -1; // IO error
|
|
inbuf[read] = 0;
|
|
trimWhitespace(inbuf);
|
|
if (sanitizeString(inbuf, read) == 0) {
|
|
continue;
|
|
}
|
|
if (inbuf[0] == '/') {
|
|
if (strcmp(inbuf+1, "help") == 0) {
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_CYAN"Available commands:\n = /help - displays this help screen\n = /quit - leaves the text channel\n = /info - info about the text channel\n"ANSI_RESET));
|
|
}
|
|
else if (strcmp(inbuf+1, "info") == 0) {
|
|
int toWrite = snprintf(outbuf, sizeof(outbuf), ANSI_GREEN"#%s "ANSI_CYAN"[%d/%d users]\n"ANSI_RESET, getChannelName(channelId), countUsers(channelId), USERS_PER_CHANNEL);
|
|
lolib_writeSocket(&cta->socket, outbuf, toWrite);
|
|
}
|
|
else if (strcmp(inbuf+1, "quit") == 0) {
|
|
return 0;
|
|
}
|
|
} else {
|
|
int toWrite = snprintf(outbuf, sizeof(outbuf), ANSI_YELLOW" <%s>:"ANSI_RESET" %s\n", cta->username, inbuf);
|
|
broadcastString(channelId, outbuf, toWrite);
|
|
}
|
|
}
|
|
}
|
|
|
|
void* connectionHandler(struct Connection* cta) {
|
|
{
|
|
char writebuf[256];
|
|
time_t timeNow = time(0);
|
|
int toWrite = snprintf(writebuf, sizeof(writebuf), ANSI_RED"\n +----------+ +--------------------------+\n | "ANSI_GREEN"LemonBBS"ANSI_RED" |-|"ANSI_GREEN" %.24s "ANSI_RED"|\n +----------+ +--------------------------+\n"ANSI_CYAN" * Made by ./lemon.sh\n * Recommended text mode: 80x25\n", ctime(&timeNow));
|
|
lolib_writeSocket(&cta->socket, writebuf, toWrite);
|
|
}
|
|
cta->userId = -1;
|
|
while (1) {
|
|
char readbuf[256];
|
|
loopStart:
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_YELLOW"\n > "ANSI_RESET));
|
|
int sslRead = lolib_readSocket(&cta->socket, readbuf, sizeof(readbuf)-1);
|
|
if (sslRead < 1) goto end;
|
|
readbuf[sslRead] = 0;
|
|
trimWhitespace(readbuf);
|
|
if (strcmp(readbuf, "chat") == 0) {
|
|
if (cta->userId == -1) {
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_RED" You're not logged in!"));
|
|
goto loopStart;
|
|
}
|
|
int32_t channelSelected = channelSelector(&cta->socket);
|
|
switch (channelSelected) {
|
|
case -1:
|
|
goto end;
|
|
case -2:
|
|
goto loopStart;
|
|
default:
|
|
break;
|
|
}
|
|
if (isUserInChat(channelSelected, cta->userId)) {
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_RED" You're already in this channel on a different session."));
|
|
goto loopStart;
|
|
}
|
|
if (addChatUser(channelSelected, cta) == 0) {
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_YELLOW" This chat room is full. Try again later."));
|
|
goto loopStart;
|
|
}
|
|
char lvenMessage[128];
|
|
int toWrite = snprintf(lvenMessage, sizeof(lvenMessage), "\n %s joined the chat.\n", cta->username);
|
|
broadcastString(channelSelected, lvenMessage, toWrite);
|
|
int chatWindowResult = chatWindow(cta, channelSelected);
|
|
removeChatUser(channelSelected, cta->userId);
|
|
toWrite = snprintf(lvenMessage, sizeof(lvenMessage), "\n %s left the chat.\n", cta->username);
|
|
broadcastString(channelSelected, lvenMessage, toWrite);
|
|
if (chatWindowResult == -1) goto end;
|
|
} else if (strcmp(readbuf, "register") == 0) {
|
|
if (cta->userId != -1) {
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_GREEN" You're already logged in!"));
|
|
goto loopStart;
|
|
}
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_RED"\n +---------------+\n | "ANSI_GREEN"New Account"ANSI_RED" |\n +---------------+\n \n "ANSI_CYAN"Welcome to LemonBBS!\n To register, type your desired username and password below. ['q' to cancel].\n"));
|
|
char username[64], password[64];
|
|
while (1) {
|
|
switch (askCredentials(&cta->socket, username, password, 64)) {
|
|
case -1:
|
|
goto end;
|
|
case 0:
|
|
goto loopStart;
|
|
}
|
|
switch (dbUsernameTaken(username)) {
|
|
case -1:
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_RED"\n An error has occurred while processing your request. Please try again later."));
|
|
goto loopStart;
|
|
case 1:
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_RED"\n Error: Username already taken."));
|
|
continue;
|
|
}
|
|
char writebuf[256];
|
|
int toWrite = snprintf(writebuf, 256, ANSI_CYAN"\n Is this correct?\n "ANSI_RESET"Username: %s\n Password: %s\n "ANSI_YELLOW"[y/n] > ", username, password);
|
|
lolib_writeSocket(&cta->socket, writebuf, toWrite);
|
|
char answer[8];
|
|
if (lolib_readSocket(&cta->socket, answer, 8) < 1) goto end;
|
|
if (answer[0] != 'y') continue;
|
|
switch (dbInsertUser(username, password)) {
|
|
case -1:
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_RED"\n An error has occurred while processing your request. Please try again later."));
|
|
break;
|
|
case 1:
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_GREEN"\n Account successfully created! Use 'login' to log in."));
|
|
}
|
|
goto loopStart;
|
|
}
|
|
} else if (strcmp(readbuf, "login") == 0) {
|
|
if (cta->userId != -1) {
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_YELLOW" You're already logged in!"));
|
|
goto loopStart;
|
|
}
|
|
char username[64], password[64];
|
|
switch (askCredentials(&cta->socket, username, password, 64)) {
|
|
case -1:
|
|
goto end;
|
|
case 0:
|
|
goto loopStart;
|
|
}
|
|
int64_t verifyResult = dbVerifyLogin(username, password);
|
|
switch (verifyResult) {
|
|
case -1:
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_RED"\n An error has occurred while processing your request. Please try again later.")); break;
|
|
case -2:
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_RED"\n No account with this username exists. Create a new account using 'register'.")); break;
|
|
case -3:
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_RED"\n Invalid password.")); break;
|
|
default:
|
|
strcpy(cta->username, username);
|
|
cta->userId = verifyResult;
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_GREEN"\n Logged in successfully!"));
|
|
}
|
|
} else if (strcmp(readbuf, "logout") == 0) {
|
|
if (cta->userId == -1) {
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_YELLOW" You're not logged in!"));
|
|
goto loopStart;
|
|
}
|
|
cta->userId = -1;
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_GREEN" You have been logged out."));
|
|
} else if (strcmp(readbuf, "help") == 0) {
|
|
lolib_writeSocket(&cta->socket, help_txt, sizeof(help_txt));
|
|
} else if (strcmp(readbuf, "mail") == 0) {
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_RED" Error: LemonMail service is not available right now."));
|
|
} else if (strcmp(readbuf, "disconnect") == 0) {
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_RESET"Thanks for using LemonBBS!\n"));
|
|
goto end;
|
|
} else if (strcmp(readbuf, "account") == 0) {
|
|
if (cta->userId == -1) {
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_RED" You're not logged in!"));
|
|
goto loopStart;
|
|
}
|
|
char outbuf[256];
|
|
int toWrite = snprintf(outbuf, sizeof(outbuf), " User ID: %ld | Username: %s", cta->userId, cta->username);
|
|
lolib_writeSocket(&cta->socket, outbuf, toWrite);
|
|
} else {
|
|
lolib_writeSocket(&cta->socket, METASTRING(ANSI_RED" Unknown command!"));
|
|
}
|
|
}
|
|
end:
|
|
lolib_finalize(&cta->socket);
|
|
deallocateConnection(cta);
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
void enableVT() {
|
|
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
if (hOut == INVALID_HANDLE_VALUE) return;
|
|
DWORD dwMode = 0;
|
|
if (!GetConsoleMode(hOut, &dwMode)) return;
|
|
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
|
SetConsoleMode(hOut, dwMode);
|
|
}
|
|
#endif
|
|
|
|
int main(/*int argc, char *argv[]*/)
|
|
{
|
|
#ifdef unix
|
|
signal(SIGPIPE, SIG_IGN);
|
|
#endif
|
|
#ifdef _WIN32
|
|
enableVT();
|
|
#endif
|
|
lolib_init();
|
|
printf(ANSI_CYAN "\n LemonBBS Server v0.3a\n -----------------------\n"ANSI_RESET);
|
|
logMessage(debug, "Initializing...");
|
|
if (initConfigFromFile(CONFIG_PATH) == 1) exit(1);
|
|
switch (dbInit()) {
|
|
case -1:
|
|
logMessage(error, "Couldn't open file '"DB_PATH"'.");
|
|
exit(1);
|
|
case -2:
|
|
logMessage(error, "Couldn't initialize the database.");
|
|
exit(1);
|
|
}
|
|
initConnList(configConnlimit);
|
|
|
|
LemonServerSocket serverSocket;
|
|
if (lolib_createServerSocket("127.0.0.1", 3333, &serverSocket) != 0) {
|
|
logMessage(error, "Couldn't initialize the socket.");
|
|
exit(1);
|
|
}
|
|
|
|
logMessage(debug, "Done! Listening on "ANSI_BLUE"%d"ANSI_RESET".", configPort);
|
|
|
|
while (1) {
|
|
struct LemonClientSocket connSock;
|
|
if (lolib_acceptTcpSocket(serverSocket, &connSock)) {
|
|
logMessage(warning, "A client failed to connect.");
|
|
continue;
|
|
}
|
|
struct Connection* newConnection = allocateConnection();
|
|
if (newConnection == NULL) {
|
|
lolib_writeSocket(&connSock, METASTRING("Sorry, all slots are currently in use. Try again later.\n"));
|
|
lolib_finalize(&connSock);
|
|
continue;
|
|
}
|
|
newConnection->socket = connSock;
|
|
pthread_t connThread;
|
|
pthread_create(&connThread, NULL, (void *(*)(void *)) connectionHandler, newConnection);
|
|
pthread_detach(connThread);
|
|
}
|
|
}
|