#include #include #include #include #include #include #include #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); } }