Initial commit
This commit is contained in:
commit
e3cd770566
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
build/
|
||||||
|
config.toml
|
||||||
|
.cache/
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"cmake.configureOnOpen": true
|
||||||
|
}
|
18
CMakeLists.txt
Normal file
18
CMakeLists.txt
Normal 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()
|
1
compile_commands.json
Symbolic link
1
compile_commands.json
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
build/compile_commands.json
|
35
crypto/cert.crt
Normal file
35
crypto/cert.crt
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
y4NCVcE4lK6XoNtcqQdH
|
||||||
|
-----END CERTIFICATE-----
|
54
crypto/key.pem
Normal file
54
crypto/key.pem
Normal 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
21
docs/building.txt
Normal 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
12
docs/help.txt
Normal 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
231756
extsrc/sqlite3.c
Normal file
File diff suppressed because it is too large
Load diff
12237
extsrc/sqlite3.h
Normal file
12237
extsrc/sqlite3.h
Normal file
File diff suppressed because it is too large
Load diff
2249
extsrc/toml.c
Normal file
2249
extsrc/toml.c
Normal file
File diff suppressed because it is too large
Load diff
175
extsrc/toml.h
Normal file
175
extsrc/toml.h
Normal 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
16
sample_config.toml
Normal 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
19
src/ansicolor.h
Normal 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
108
src/chat.c
Normal 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
22
src/chat.h
Normal 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
43
src/connlist.c
Normal 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
19
src/connlist.h
Normal 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
92
src/dbutil.c
Normal 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
11
src/dbutil.h
Normal 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
46
src/help.h
Normal 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
362
src/main.c
Normal 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
118
src/oslib.c
Normal 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
40
src/oslib.h
Normal 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
17
src/settings.h
Normal 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
63
src/utils.c
Normal 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
17
src/utils.h
Normal 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
|
Reference in a new issue