2021-12-15 11:52:53 -06:00
# 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 ;
2021-12-16 15:16:13 -06:00
char * configCertfile , * configKeyfile , * configPassword , sslInUse ;
2021-12-15 11:52:53 -06:00
// 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 " ) ;
2021-12-16 15:16:13 -06:00
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 ;
}
2021-12-15 11:52:53 -06:00
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 ) ;
}
}