Initial commit

This commit is contained in:
lemon-sh 2021-12-15 18:52:53 +01:00
commit e3cd770566
28 changed files with 247560 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
build/
config.toml
.cache/

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"cmake.configureOnOpen": true
}

18
CMakeLists.txt Normal file
View file

@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.15)
project(lemonbbs C)
set(CMAKE_C_STANDARD 11)
find_package(OpenSSL REQUIRED)
aux_source_directory(${CMAKE_SOURCE_DIR}/src LEMONBBS_SOURCE)
aux_source_directory(${CMAKE_SOURCE_DIR}/extsrc LEMONBBS_EXTSOURCE)
add_executable(lemonbbs ${LEMONBBS_SOURCE} ${LEMONBBS_EXTSOURCE})
target_compile_definitions(lemonbbs PUBLIC _WIN32_WINNT=1536)
include_directories(${CMAKE_SOURCE_DIR}/extsrc ${OPENSSL_INCLUDE_DIR})
target_link_libraries(lemonbbs pthread ${OPENSSL_LIBRARIES})
if (WIN32)
# on Windows it's advisable to create a self-contained exe instead
target_link_libraries(lemonbbs ws2_32 -static)
else()
target_link_libraries(lemonbbs dl)
endif()

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# lemonbbs
Lemon's Linux BBS software

1
compile_commands.json Symbolic link
View file

@ -0,0 +1 @@
build/compile_commands.json

35
crypto/cert.crt Normal file
View file

@ -0,0 +1,35 @@
-----BEGIN CERTIFICATE-----
MIIGCzCCA/OgAwIBAgIUaMo5Nqo7FGpI0kNGZjROW10ur8kwDQYJKoZIhvcNAQEL
BQAwgZQxCzAJBgNVBAYTAlBMMQ8wDQYDVQQIDAZQb2xhbmQxDzANBgNVBAcMBktp
ZWxjZTERMA8GA1UECgwITGVtb25EZXYxETAPBgNVBAsMCExlbW9uRGV2MRIwEAYD
VQQDDAlsb2NhbGhvc3QxKTAnBgkqhkiG9w0BCQEWGnBhd2VsLnN0b2xhcnNraTdA
Z21haWwuY29tMB4XDTIwMTIxNjA3MDAzOVoXDTIxMTIxNjA3MDAzOVowgZQxCzAJ
BgNVBAYTAlBMMQ8wDQYDVQQIDAZQb2xhbmQxDzANBgNVBAcMBktpZWxjZTERMA8G
A1UECgwITGVtb25EZXYxETAPBgNVBAsMCExlbW9uRGV2MRIwEAYDVQQDDAlsb2Nh
bGhvc3QxKTAnBgkqhkiG9w0BCQEWGnBhd2VsLnN0b2xhcnNraTdAZ21haWwuY29t
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsYlL64xlwZt6VZtR2p+e
bdeVmYUv54Nn0eJ9ZbdSzAmpgP59i7XE42IT3YxGy1yleHki1tsFD7p9Y7C5/YdE
xR8Lm1MFYGKLmysBlOv0sbKD1/mEhFoLAlxwQnpj6TPSev5lGtDlCs8UsCKKNefH
t4dLgqS5zieF6fZhBFIalXMRFWiKEX+Owq2aZ2dzyBgKgyq47Ntv56l9fKlzf06F
XQPdm/olO/QmaZkYvIJe6WDNS78DyTNbWwmzazf7MYwsnjJCA+eL/A7STudKmLfB
OdE2B7XPFndIWO7joQd+kRELiyhPOQ9aXFqGzq9GUUQFt+eAFnSuWvoO+0vfTrOP
r/37YLJBWIi4c8pR9JkU6HBxSAlCB8cz+G/4SN9J+re/F+b6kwdK1RuXmn8aSFrS
CLtTcvQRpcnYgexcO43mNb5joYt50HHBoIfwkD1FBXkdZAvMscqHI21najPy0ZIl
f9dLt1f/1icV2EQq5SNUz83I0rU4hfJtgi5XboKyQQ8taAe2lQKIxg//cktIpVFh
B06Nwy8PuSf0C470VdIpOSkUc3pzrVRuQviqU/gN1r3IPab4ckNyqT24b3q+q7es
uM/5w+qFb++oTh7s2h7sxomtjkSbvg+Qs+rKPWof5TL1++hUhuZp33vu4Zbo/Tcv
DQqMjt00JugAPDh6fsYJMVcCAwEAAaNTMFEwHQYDVR0OBBYEFEwJ04O/Cj91jvDN
SYwHlvyusdV1MB8GA1UdIwQYMBaAFEwJ04O/Cj91jvDNSYwHlvyusdV1MA8GA1Ud
EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIBACFBBdKiX7zgrF48N/JJtx75
0oj8W8n3REXaetgDEWMw/DvT5/uB16UYPgZssoHykj/e9NIY/rGljfpGmLVSNXIa
RwZ8CXLL8Rxx66xZOPDH37ZDylNf/oz1bVbG3sd8k0wZn3tXJ7+bJXnH7qg95ljf
Zf8K9NdpxmiRtDHiTjoPmiiZB8bkbpekwG+dz3i+bUm2u7s+qcm+kQ58rVLlGhyd
wbpzr9QkIP5xFO47hB4h1P6Ke4lPRf8oCVfZrz8o4iSXtsm9XD6RZ1OIXPC27zll
QjabWpYqzdLDjqF3Brj6csnNrBoaaWCaH4eoImedpIeXnqUDsCXIaGxUawamDm78
UN+zgL0ps9NSIxV/p1TGCkt1l8Zdd0iz9UCrD0kNUrwLPcDQnnbRpAVbv0UbqSGm
Jb5Q/HaBxZT/bdmb+GQdnRMzqV7aWlKXYl/VtG1UgvrrgzS+IAVIlY5Bwylgsknp
VaqBmzLTiV0vl/j0j+hj4O1MWd/sAt2ykUFmaojwW7/G3UzzruvpKx1jj0VewObU
NsdpYsQVyUvQoZiIa9n63zkIRPtMGl8MzrKZcOl8e0ufQF5BYzxvUk3o2JUTKudm
gRhwoOIE4ag11kCoRffBOPnWyClgnuyWs6XcpgCD0AQhcQWCgLHr2QDZK0xODNyB
y4NCVcE4lK6XoNtcqQdH
-----END CERTIFICATE-----

54
crypto/key.pem Normal file
View file

@ -0,0 +1,54 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
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
1sNnlUSsq/ORkXmjsE/XSw==
-----END ENCRYPTED PRIVATE KEY-----

21
docs/building.txt Normal file
View file

@ -0,0 +1,21 @@
1. Dependencies
a. OpenSSL
OpenSSL has to be installed on your system (possibly through a package manager).
b. tomlc99 and SQlite3
Download these libraries in their single-file form (one .c and one .h file) and put them in the `extsrc` folder.
CMake will automatically include all source files in that directory, so you don't have to do anything else.
2. Building
a. Create a `build` directory inside the lemonbbs root directory.
b. Open `src/settings.h` and adjust compile-time settings if you need to.
c. Run `cmake .. -DCMAKE_BUILD_TYPE="<Release or Debug>"` in that `build` directory.
d. Build the project using `make`.
3. Setup
a. Copy the `sample_config.toml` file as `config.toml`
b. Edit the `config.toml` file and adjust the settings.
c. Run the lemonbbs binary (config.toml has to be in the working directory).

12
docs/help.txt Normal file
View file

@ -0,0 +1,12 @@
------------------------------------------------------------------------------
LemonBBS 0.1 Alpha | ./lemon.sh (C) 2021
------------------------------------------------------------------------------
LemonBBS is a fast BBS server written in C. It currently allows for chat
and e-mail, but I have lots of other features planned.
Available commands:
* help - display this help screen * login
* chat - start chatting * disconnect
* mail - run the LemonMail program * logout
* register - create a new account * account - display account info
------------------------------------------------------------------------------

231756
extsrc/sqlite3.c Normal file

File diff suppressed because it is too large Load diff

12237
extsrc/sqlite3.h Normal file

File diff suppressed because it is too large Load diff

2249
extsrc/toml.c Normal file

File diff suppressed because it is too large Load diff

175
extsrc/toml.h Normal file
View file

@ -0,0 +1,175 @@
/*
MIT License
Copyright (c) 2017 - 2019 CK Tan
https://github.com/cktan/tomlc99
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef TOML_H
#define TOML_H
#include <stdio.h>
#include <stdint.h>
#ifdef __cplusplus
#define TOML_EXTERN extern "C"
#else
#define TOML_EXTERN extern
#endif
typedef struct toml_timestamp_t toml_timestamp_t;
typedef struct toml_table_t toml_table_t;
typedef struct toml_array_t toml_array_t;
typedef struct toml_datum_t toml_datum_t;
/* Parse a file. Return a table on success, or 0 otherwise.
* Caller must toml_free(the-return-value) after use.
*/
TOML_EXTERN toml_table_t* toml_parse_file(FILE* fp,
char* errbuf,
int errbufsz);
/* Parse a string containing the full config.
* Return a table on success, or 0 otherwise.
* Caller must toml_free(the-return-value) after use.
*/
TOML_EXTERN toml_table_t* toml_parse(char* conf, /* NUL terminated, please. */
char* errbuf,
int errbufsz);
/* Free the table returned by toml_parse() or toml_parse_file(). Once
* this function is called, any handles accessed through this tab
* directly or indirectly are no longer valid.
*/
TOML_EXTERN void toml_free(toml_table_t* tab);
/* Timestamp types. The year, month, day, hour, minute, second, z
* fields may be NULL if they are not relevant. e.g. In a DATE
* type, the hour, minute, second and z fields will be NULLs.
*/
struct toml_timestamp_t {
struct { /* internal. do not use. */
int year, month, day;
int hour, minute, second, millisec;
char z[10];
} __buffer;
int *year, *month, *day;
int *hour, *minute, *second, *millisec;
char* z;
};
/*-----------------------------------------------------------------
* Enhanced access methods
*/
struct toml_datum_t {
int ok;
union {
toml_timestamp_t* ts; /* ts must be freed after use */
char* s; /* string value. s must be freed after use */
int b; /* bool value */
int64_t i; /* int value */
double d; /* double value */
} u;
};
/* on arrays: */
/* ... retrieve size of array. */
TOML_EXTERN int toml_array_nelem(const toml_array_t* arr);
/* ... retrieve values using index. */
TOML_EXTERN toml_datum_t toml_string_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_bool_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_int_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_double_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_datum_t toml_timestamp_at(const toml_array_t* arr, int idx);
/* ... retrieve array or table using index. */
TOML_EXTERN toml_array_t* toml_array_at(const toml_array_t* arr, int idx);
TOML_EXTERN toml_table_t* toml_table_at(const toml_array_t* arr, int idx);
/* on tables: */
/* ... retrieve the key in table at keyidx. Return 0 if out of range. */
TOML_EXTERN const char* toml_key_in(const toml_table_t* tab, int keyidx);
/* ... retrieve values using key. */
TOML_EXTERN toml_datum_t toml_string_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_bool_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_int_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_double_in(const toml_table_t* arr, const char* key);
TOML_EXTERN toml_datum_t toml_timestamp_in(const toml_table_t* arr, const char* key);
/* .. retrieve array or table using key. */
TOML_EXTERN toml_array_t* toml_array_in(const toml_table_t* tab,
const char* key);
TOML_EXTERN toml_table_t* toml_table_in(const toml_table_t* tab,
const char* key);
/*-----------------------------------------------------------------
* lesser used
*/
/* Return the array kind: 't'able, 'a'rray, 'v'alue */
TOML_EXTERN char toml_array_kind(const toml_array_t* arr);
/* For array kind 'v'alue, return the type of values
i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp
0 if unknown
*/
TOML_EXTERN char toml_array_type(const toml_array_t* arr);
/* Return the key of an array */
TOML_EXTERN const char* toml_array_key(const toml_array_t* arr);
/* Return the number of key-values in a table */
TOML_EXTERN int toml_table_nkval(const toml_table_t* tab);
/* Return the number of arrays in a table */
TOML_EXTERN int toml_table_narr(const toml_table_t* tab);
/* Return the number of sub-tables in a table */
TOML_EXTERN int toml_table_ntab(const toml_table_t* tab);
/* Return the key of a table*/
TOML_EXTERN const char* toml_table_key(const toml_table_t* tab);
/*--------------------------------------------------------------
* misc
*/
TOML_EXTERN int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret);
TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]);
TOML_EXTERN void toml_set_memutil(void* (*xxmalloc)(size_t),
void (*xxfree)(void*));
/*--------------------------------------------------------------
* deprecated
*/
/* A raw value, must be processed by toml_rto* before using. */
typedef const char* toml_raw_t;
TOML_EXTERN toml_raw_t toml_raw_in(const toml_table_t* tab, const char* key);
TOML_EXTERN toml_raw_t toml_raw_at(const toml_array_t* arr, int idx);
TOML_EXTERN int toml_rtos(toml_raw_t s, char** ret);
TOML_EXTERN int toml_rtob(toml_raw_t s, int* ret);
TOML_EXTERN int toml_rtoi(toml_raw_t s, int64_t* ret);
TOML_EXTERN int toml_rtod(toml_raw_t s, double* ret);
TOML_EXTERN int toml_rtod_ex(toml_raw_t s, double* ret, char* buf, int buflen);
TOML_EXTERN int toml_rtots(toml_raw_t s, toml_timestamp_t* ret);
#endif /* TOML_H */

16
sample_config.toml Normal file
View file

@ -0,0 +1,16 @@
[listen]
port = 3333
[ssl]
# certificate file
certfile = "crypto/cert.crt"
# private key file
keyfile = "crypto/key.pem"
# private key password
password = "r8YvRVZqLPPiZDhT7QLmRyoTDo44bE8jp/jGVRMAtRU="
[bbs]
# text channels
channelList = [ "Linux", "BSD" ]
# how many users can be connected to the bbs at once
connlimit = 100

19
src/ansicolor.h Normal file
View file

@ -0,0 +1,19 @@
// ANSI color escape code definitions.
#ifdef BBS_COLOR
#define ANSI_RED "\x1b[91m"
#define ANSI_GREEN "\x1b[92m"
#define ANSI_YELLOW "\x1b[93m"
#define ANSI_BLUE "\x1b[94m"
#define ANSI_MAGENTA "\x1b[95m"
#define ANSI_CYAN "\x1b[96m"
#define ANSI_RESET "\x1b[0m"
#else
#define ANSI_RED
#define ANSI_GREEN
#define ANSI_YELLOW
#define ANSI_BLUE
#define ANSI_MAGENTA
#define ANSI_CYAN
#define ANSI_RESET
#endif

108
src/chat.c Normal file
View file

@ -0,0 +1,108 @@
// LEMONBBS MODULES: CHAT
// to enable the text chat module, first initialize the module using the initChat() method.
// then, set a name for every channel using the setChannelName() method.
#include <memory.h>
#include <pthread.h>
#include <stdlib.h>
#include "chat.h"
struct Channel* channelList;
unsigned int channelListLength;
unsigned int getChannelCount() {
return channelListLength;
}
void initChat(unsigned int channelCount) {
unsigned int allocSize = channelCount*sizeof(struct Channel);
channelList = malloc(allocSize);
memset(channelList, 0, allocSize);
for (int i = 0; i < channelCount; ++i) {
pthread_mutex_init(&channelList[i].clientsLock, NULL);
}
channelListLength = channelCount;
}
// warning: memory unsafe. check string length first.
void setChannelName(int channelId, const char* name) {
strcpy(channelList[channelId].name, name);
}
const char* getChannelName(size_t channelId) {
return channelList[channelId].name;
}
int isUserInChat(size_t channelId, int64_t userId) {
pthread_mutex_lock(&channelList[channelId].clientsLock);
int result = 0;
for (int i = 0; i < USERS_PER_CHANNEL; ++i) {
struct Connection* currIter = channelList[channelId].clients[i];
if (currIter == NULL) {
continue;
}
if (currIter->userId == userId) {
result = 1;
break;
}
}
pthread_mutex_unlock(&channelList[channelId].clientsLock);
return result;
}
int addChatUser(size_t channelId, struct Connection* userConnection) {
pthread_mutex_lock(&channelList[channelId].clientsLock);
for (int i = 0; i < USERS_PER_CHANNEL; ++i) {
struct Connection** currIter = channelList[channelId].clients + i;
if (*currIter == NULL) {
*currIter = userConnection;
pthread_mutex_unlock(&channelList[channelId].clientsLock);
return 1;
}
}
pthread_mutex_unlock(&channelList[channelId].clientsLock);
return 0;
}
int removeChatUser(size_t channelId, int64_t userId) {
pthread_mutex_lock(&channelList[channelId].clientsLock);
for (int i = 0; i < USERS_PER_CHANNEL; ++i) {
struct Connection** currIter = channelList[channelId].clients + i;
if (*currIter == NULL) {
continue;
}
if ((*currIter)->userId == userId) {
*currIter = NULL;
pthread_mutex_unlock(&channelList[channelId].clientsLock);
return 1;
}
}
pthread_mutex_unlock(&channelList[channelId].clientsLock);
return 0;
}
int countUsers(size_t channelId) {
pthread_mutex_lock(&channelList[channelId].clientsLock);
int result = 0;
for (int i = 0; i < USERS_PER_CHANNEL; ++i) {
struct Connection* currIter = channelList[channelId].clients[i];
if (currIter == NULL) {
continue;
} else {
result++;
}
}
pthread_mutex_unlock(&channelList[channelId].clientsLock);
return result;
}
void broadcastString(size_t channelId, const char* message, size_t messageLength) {
pthread_mutex_lock(&channelList[channelId].clientsLock);
for (int i = 0; i < USERS_PER_CHANNEL; ++i) {
struct Connection* currIter = channelList[channelId].clients[i];
if (currIter != NULL) {
lolib_writeSocket(&currIter->socket, message, messageLength);
}
}
pthread_mutex_unlock(&channelList[channelId].clientsLock);
}

22
src/chat.h Normal file
View file

@ -0,0 +1,22 @@
#ifndef LEMONBBS_CHAT_H
#define LEMONBBS_CHAT_H
#include "settings.h"
#include "connlist.h"
struct Channel {
char name[CHANNEL_NAME_LEN]; // null-terminated
struct Connection* clients[USERS_PER_CHANNEL];
pthread_mutex_t clientsLock;
};
unsigned int getChannelCount();
void setChannelName(int channelId, const char* name);
const char* getChannelName(size_t channelId);
void initChat(unsigned int channelCount);
int removeChatUser(size_t channelId, int64_t userId);
int addChatUser(size_t channelId, struct Connection* userConnection);
void broadcastString(size_t channelId, const char* message, size_t messageLength);
int isUserInChat(size_t channelId, int64_t userId);
int countUsers(size_t channelId);
#endif

43
src/connlist.c Normal file
View file

@ -0,0 +1,43 @@
// LEMONBBS MODULES: CONNLIST
// This module allows for storing connection information statically instead of
// allocating new blocks on every connection and freeing them afterwards.
#include <pthread.h>
#include <memory.h>
#include <stdlib.h>
#include "connlist.h"
pthread_mutex_t connListLock = PTHREAD_MUTEX_INITIALIZER;
struct Connection* connList;
unsigned int connListLength;
void initConnList(unsigned int maxConnCount) {
size_t allocNeeded = maxConnCount*sizeof(struct Connection);
connList = malloc(allocNeeded);
memset(connList, 0, allocNeeded);
connListLength = maxConnCount;
}
// allocates a new Connection and activates it
struct Connection* allocateConnection() {
pthread_mutex_lock(&connListLock);
struct Connection* result = NULL;
for (int i = 0; i < connListLength; ++i) {
if (connList[i].state == 0x00) {
result = connList+i;
break;
}
}
if (result != NULL) {
result->state = 0xFF;
}
pthread_mutex_unlock(&connListLock);
return result;
}
// deactivates and deallocates a Connection
void deallocateConnection(struct Connection* connection) {
pthread_mutex_lock(&connListLock);
memset(connection, 0, sizeof(struct Connection));
pthread_mutex_unlock(&connListLock);
}

19
src/connlist.h Normal file
View file

@ -0,0 +1,19 @@
#ifndef CHATSERVER_CONNLIST_H
#define CHATSERVER_CONNLIST_H
#include <stdint.h>
#include "oslib.h"
struct Connection {
uint8_t state;
struct LemonClientSocket socket;
pthread_t connThread;
int64_t userId; // -1 if not logged in
char username[64]; // to reduce db calls
};
struct Connection* allocateConnection();
void deallocateConnection(struct Connection* connection);
void initConnList(unsigned int maxConnCount);
#endif

92
src/dbutil.c Normal file
View file

@ -0,0 +1,92 @@
// LEMONBBS MODULES: DATABASE
// this is a simple sqlite3 database module. To enable it, just call the dbInit() method.
#include <sqlite3.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <memory.h>
#include "dbutil.h"
#include "settings.h"
sqlite3* db;
int dbInsertUser(const char* username, const char* secret) {
uint8_t key[32], salt[32];
RAND_bytes(salt, sizeof(salt));
PKCS5_PBKDF2_HMAC_SHA1(secret, -1, salt, sizeof(salt), 1000, sizeof(key), key);
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(db, "insert into users(username, secret, salt) values(?1, ?2, ?3)", 60, &stmt, NULL) != SQLITE_OK) goto panic;
if (sqlite3_bind_text(stmt, 1, username, -1, SQLITE_TRANSIENT) != SQLITE_OK) goto panic;
if (sqlite3_bind_blob(stmt, 2, key, 32, SQLITE_TRANSIENT) != SQLITE_OK) goto panic;
if (sqlite3_bind_blob(stmt, 3, salt, 32, SQLITE_TRANSIENT) != SQLITE_OK) goto panic;
if (sqlite3_step(stmt) != SQLITE_DONE) goto panic;
sqlite3_finalize(stmt);
return 1;
panic:
sqlite3_finalize(stmt);
return -1;
}
int dbUsernameTaken(const char* username) {
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(db, "select 1 from users where username=?1", 37, &stmt, NULL) != SQLITE_OK) goto panic;
if (sqlite3_bind_text(stmt, 1, username, -1, SQLITE_TRANSIENT) != SQLITE_OK) goto panic;
int stepResult = sqlite3_step(stmt);
sqlite3_finalize(stmt);
switch (stepResult) {
case SQLITE_DONE:
return 0;
case SQLITE_ROW:
return 1;
default:
return -1;
}
panic:
sqlite3_finalize(stmt);
return -1;
}
/*
* -1 = Database error
* -2 = User not found
* -3 = Secret doesn't match
*/
int64_t dbVerifyLogin(const char* username, const char* secret) {
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(db, "select user_id, secret, salt from users where username=?1", 57, &stmt, NULL) != SQLITE_OK) goto panic;
if (sqlite3_bind_text(stmt, 1, username, -1, SQLITE_TRANSIENT) != SQLITE_OK) goto panic;
int stepResult = sqlite3_step(stmt);
int64_t result;
switch (stepResult) {
case SQLITE_DONE:
result = -2; break;
case SQLITE_ROW:
{
const uint8_t *salt, *key;
uint8_t generated[32];
key = sqlite3_column_blob(stmt, 1);
salt = sqlite3_column_blob(stmt, 2);
PKCS5_PBKDF2_HMAC_SHA1(secret, -1, salt, 32, 1000, 32, generated);
result = memcmp(generated, key, 32) == 0 ? sqlite3_column_int64(stmt, 0) : -3;
break;
}
default:
result = -1; break;
}
sqlite3_finalize(stmt);
return result;
panic:
sqlite3_finalize(stmt);
return -1;
}
int dbInit() {
int dbresult = sqlite3_open(DB_PATH, &db);
if (dbresult != SQLITE_OK) {
return -1;
}
if (sqlite3_exec(db, "create table if not exists users(user_id integer primary key, username text not null unique, secret blob not null, salt blob not null)", NULL, NULL, NULL) != SQLITE_OK) {
return -2;
}
return 0;
}

11
src/dbutil.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef CHATSERVER_DBUTIL_H
#define CHATSERVER_DBUTIL_H
#include <stdint.h>
int dbInit();
int64_t dbVerifyLogin(const char* username, const char* secret);
int dbUsernameTaken(const char* username);
int dbInsertUser(const char* username, const char* secret);
#endif

46
src/help.h Normal file
View file

@ -0,0 +1,46 @@
char help_txt[] = {
0x20,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
0x2D,0x2D,0x2D,0x2D,0x0A,0x20,0x20,0x20,0x20,0x20,0x4C,0x65,0x6D,0x6F,0x6E,
0x42,0x42,0x53,0x20,0x30,0x2E,0x31,0x20,0x41,0x6C,0x70,0x68,0x61,0x20,0x7C,
0x20,0x2E,0x2F,0x6C,0x65,0x6D,0x6F,0x6E,0x2E,0x73,0x68,0x20,0x28,0x43,0x29,
0x20,0x32,0x30,0x32,0x31,0x0A,0x20,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x0A,0x20,0x20,0x4C,0x65,
0x6D,0x6F,0x6E,0x42,0x42,0x53,0x20,0x69,0x73,0x20,0x61,0x20,0x66,0x61,0x73,
0x74,0x20,0x42,0x42,0x53,0x20,0x73,0x65,0x72,0x76,0x65,0x72,0x20,0x77,0x72,
0x69,0x74,0x74,0x65,0x6E,0x20,0x69,0x6E,0x20,0x43,0x2E,0x20,0x49,0x74,0x20,
0x63,0x75,0x72,0x72,0x65,0x6E,0x74,0x6C,0x79,0x20,0x61,0x6C,0x6C,0x6F,0x77,
0x73,0x20,0x66,0x6F,0x72,0x20,0x63,0x68,0x61,0x74,0x0A,0x20,0x20,0x61,0x6E,
0x64,0x20,0x65,0x2D,0x6D,0x61,0x69,0x6C,0x2C,0x20,0x62,0x75,0x74,0x20,0x49,
0x20,0x68,0x61,0x76,0x65,0x20,0x6C,0x6F,0x74,0x73,0x20,0x6F,0x66,0x20,0x6F,
0x74,0x68,0x65,0x72,0x20,0x66,0x65,0x61,0x74,0x75,0x72,0x65,0x73,0x20,0x70,
0x6C,0x61,0x6E,0x6E,0x65,0x64,0x2E,0x0A,0x0A,0x20,0x20,0x41,0x76,0x61,0x69,
0x6C,0x61,0x62,0x6C,0x65,0x20,0x63,0x6F,0x6D,0x6D,0x61,0x6E,0x64,0x73,0x3A,
0x0A,0x20,0x20,0x2A,0x20,0x68,0x65,0x6C,0x70,0x20,0x2D,0x20,0x64,0x69,0x73,
0x70,0x6C,0x61,0x79,0x20,0x74,0x68,0x69,0x73,0x20,0x68,0x65,0x6C,0x70,0x20,
0x73,0x63,0x72,0x65,0x65,0x6E,0x20,0x20,0x20,0x20,0x20,0x2A,0x20,0x6C,0x6F,
0x67,0x69,0x6E,0x0A,0x20,0x20,0x2A,0x20,0x63,0x68,0x61,0x74,0x20,0x2D,0x20,
0x73,0x74,0x61,0x72,0x74,0x20,0x63,0x68,0x61,0x74,0x74,0x69,0x6E,0x67,0x20,
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x2A,
0x20,0x64,0x69,0x73,0x63,0x6F,0x6E,0x6E,0x65,0x63,0x74,0x0A,0x20,0x20,0x2A,
0x20,0x6D,0x61,0x69,0x6C,0x20,0x2D,0x20,0x72,0x75,0x6E,0x20,0x74,0x68,0x65,
0x20,0x4C,0x65,0x6D,0x6F,0x6E,0x4D,0x61,0x69,0x6C,0x20,0x70,0x72,0x6F,0x67,
0x72,0x61,0x6D,0x20,0x20,0x20,0x20,0x2A,0x20,0x6C,0x6F,0x67,0x6F,0x75,0x74,
0x0A,0x20,0x20,0x2A,0x20,0x72,0x65,0x67,0x69,0x73,0x74,0x65,0x72,0x20,0x2D,
0x20,0x63,0x72,0x65,0x61,0x74,0x65,0x20,0x61,0x20,0x6E,0x65,0x77,0x20,0x61,
0x63,0x63,0x6F,0x75,0x6E,0x74,0x20,0x20,0x20,0x20,0x20,0x2A,0x20,0x61,0x63,
0x63,0x6F,0x75,0x6E,0x74,0x20,0x2D,0x20,0x64,0x69,0x73,0x70,0x6C,0x61,0x79,
0x20,0x61,0x63,0x63,0x6F,0x75,0x6E,0x74,0x20,0x69,0x6E,0x66,0x6F,0x0A,0x20,
0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,0x2D,
0x2D,0x2D,0x2D,};

362
src/main.c Normal file
View file

@ -0,0 +1,362 @@
#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;
// 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) {logMessage(error, "Config parsing error: missing [ssl]"); return 1;}
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);
}
}

118
src/oslib.c Normal file
View file

@ -0,0 +1,118 @@
#include "oslib.h"
#define SOCKET_TCP 0
#define SOCKET_SSL 1
// SSL section
SSL_CTX* sslContext;
int lolib_enableSsl(const char* certfilePath, const char* keyfilePath, char* keyfilePassword) {
SSL_library_init();
const SSL_METHOD *method = TLS_server_method();
sslContext = SSL_CTX_new(method);
if (sslContext == NULL) return 1;
if (SSL_CTX_use_certificate_file(sslContext, certfilePath, SSL_FILETYPE_PEM) <= 0) return 2;
SSL_CTX_set_default_passwd_cb_userdata(sslContext, keyfilePassword);
if (SSL_CTX_use_PrivateKey_file(sslContext, keyfilePath, SSL_FILETYPE_PEM) <= 0) return 3;
if (!SSL_CTX_check_private_key(sslContext)) return 4;
return 0;
}
int lolib_acceptSslSocket(LemonServerSocket serverSocket, struct LemonClientSocket *dst) {
SOCKET connFd = accept(serverSocket, 0, 0);
if (connFd == -1) return 1;
SSL* newSSL = SSL_new(sslContext);
SSL_set_fd(newSSL, connFd);
if (SSL_accept(newSSL) != 1) {
SSL_shutdown(newSSL);
SSL_free(newSSL);
return 2;
}
dst->type = SOCKET_SSL;
dst->descriptor.sslDescriptor = newSSL;
return 0;
}
// OS Abstraction section
int lolib_init()
{
#ifdef _WIN32
WSADATA wsa_data;
return WSAStartup(MAKEWORD(1,1), &wsa_data);
#else
return 0;
#endif
}
int lolib_finalize(struct LemonClientSocket *socket)
{
int status;
switch (socket->type) {
case SOCKET_TCP:
#ifdef _WIN32
status = shutdown(socket->descriptor.tcpDescriptor, SD_BOTH);
if (status == 0) { status = closesocket(socket->descriptor.tcpDescriptor); }
#else
status = shutdown(socket->descriptor.tcpDescriptor, SHUT_RDWR);
if (status == 0) { status = close(socket->descriptor.tcpDescriptor); }
#endif
break;
case SOCKET_SSL:
SSL_shutdown(socket->descriptor.sslDescriptor);
SSL_free(socket->descriptor.sslDescriptor);
}
return status;
}
int lolib_quit()
{
if (sslContext != NULL) SSL_CTX_free(sslContext);
#ifdef _WIN32
return WSACleanup();
#else
return 0;
#endif
}
// Wrapper section
int lolib_createServerSocket(const char* bindAddress, unsigned short port, LemonServerSocket *dst) {
struct sockaddr_in serverAddr;
SOCKET serverFd;
if ((serverFd = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) return 1;
serverAddr.sin_family = AF_INET;
inet_pton(AF_INET, bindAddress, &serverAddr.sin_addr);
serverAddr.sin_port = htons(port);
if (bind(serverFd, (const struct sockaddr *) &serverAddr, sizeof(serverAddr)) != 0) return 2;
if (listen(serverFd, 5) != 0) return 3;
*dst = serverFd;
return 0;
}
int lolib_acceptTcpSocket(LemonServerSocket serverSocket, struct LemonClientSocket *dst) {
SOCKET connFd = accept(serverSocket, 0, 0);
if (connFd == -1) return 1;
dst->type = SOCKET_TCP;
dst->descriptor.tcpDescriptor = connFd;
return 0;
}
int lolib_writeSocket(struct LemonClientSocket *socket, const char* data, unsigned int dataLength) {
switch (socket->type) {
case SOCKET_TCP:
return send(socket->descriptor.tcpDescriptor, data, dataLength, 0);
default:
return -2;
}
}
int lolib_readSocket(struct LemonClientSocket *socket, char *data, unsigned int bytesToRead) {
switch (socket->type) {
case SOCKET_TCP:
return recv(socket->descriptor.tcpDescriptor, data, bytesToRead, 0);
default:
return -2;
}
}

40
src/oslib.h Normal file
View file

@ -0,0 +1,40 @@
// LemonOSLib (lolib): simple system abstractions made specifically for LemonBBS
#ifndef LEMONBBS_OSLIB_H
#define LEMONBBS_OSLIB_H
#include <openssl/ssl.h>
#ifdef _WIN32
#include <Ws2tcpip.h>
#elif defined(unix)
typedef int SOCKET;
#define INVALID_SOCKET -1
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#else
#error Platform not supported.
#endif
struct LemonClientSocket {
int type;
union Descriptor {
SSL* sslDescriptor;
SOCKET tcpDescriptor;
} descriptor;
};
typedef SOCKET LemonServerSocket;
int lolib_init();
int lolib_quit();
int lolib_finalize(struct LemonClientSocket *socket);
int lolib_enableSsl(const char* certfilePath, const char* keyfilePath, char* keyfilePassword);
int lolib_createServerSocket(const char* bindAddress, unsigned short port, LemonServerSocket *dst);
int lolib_acceptTcpSocket(LemonServerSocket serverSocket, struct LemonClientSocket *dst);
int lolib_acceptSslSocket(LemonServerSocket serverSocket, struct LemonClientSocket *dst);
int lolib_writeSocket(struct LemonClientSocket *socket, const char* data, unsigned int dataLength);
int lolib_readSocket(struct LemonClientSocket *socket, char *data, unsigned int bytesToRead);
#endif

17
src/settings.h Normal file
View file

@ -0,0 +1,17 @@
#ifndef CHATSERVER_SETTINGS_H
#define CHATSERVER_SETTINGS_H
// comment this line to build without ANSI color
#define BBS_COLOR
// how many users can be in a text channel at once
#define USERS_PER_CHANNEL 10
// max length of a text channel name
#define CHANNEL_NAME_LEN 64
// filenames
#define DB_PATH "lemonbbs.db3"
#define CONFIG_PATH "config.toml"
#endif

63
src/utils.c Normal file
View file

@ -0,0 +1,63 @@
#include <pthread.h>
#include <stdarg.h>
#include <stdio.h>
#include <ctype.h>
#include <time.h>
#include <string.h>
#include "utils.h"
#include "settings.h"
#include "ansicolor.h"
pthread_mutex_t logLock = PTHREAD_MUTEX_INITIALIZER;
void logMessage(enum severity messageSeverity, const char *format, ...) {
pthread_mutex_lock(&logLock);
time_t timeNow = time(0);
printf(" [%.24s]", ctime(&timeNow));
switch (messageSeverity) {
case warning:
printf(ANSI_YELLOW " [WARN] ");
break;
case error:
printf(ANSI_RED " [ERROR] ");
break;
default:
printf(ANSI_GREEN " [INFO] ");
break;
}
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
printf(ANSI_RESET"\n");
pthread_mutex_unlock(&logLock);
}
// WARNING: this method may increment the 'str' pointer.
void trimWhitespace(char* str)
{
char *end;
while(isspace((unsigned char)*str)) str++;
if(*str == 0) return;
end = str + strlen(str) - 1;
while(end > str && isspace((unsigned char)*end)) end--;
end[1] = '\0';
}
int inRange(int number, int rangeFrom, int rangeTo) {
return number >= rangeFrom && number <= rangeTo;
}
int sanitizeString(char* str, size_t stringLength) {
if (stringLength == 0) return 0;
char newString[stringLength+1];
int newStringCurrent = 0;
for (int i = 0; i < stringLength; ++i) {
if (inRange(str[i], 32, 126)) {
newString[newStringCurrent] = str[i]; // copy (allowed character)
newStringCurrent++;
}
}
newString[newStringCurrent] = 0;
memcpy(str, newString, newStringCurrent+1);
return newStringCurrent;
}

17
src/utils.h Normal file
View file

@ -0,0 +1,17 @@
#ifndef CHATSERVER_UTILS_H
#define CHATSERVER_UTILS_H
#define METASTRING(x) x,sizeof(x)-1
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
enum severity {
debug, warning, error
};
void logMessage(enum severity messageSeverity, const char *format, ...);
void trimWhitespace(char* str);
int inRange(int number, int rangeFrom, int rangeTo);
int sanitizeString(char* str, size_t stringLength);
#endif //CHATSERVER_UTILS_H