chat-server/main.js

650 lines
14 KiB
JavaScript
Raw Normal View History

2019-03-24 17:57:54 +11:00
#!/usr/bin/node
// Get some libraries
2019-04-10 13:07:31 +10:00
const winston = require("winston");
const random_bytes = require("random-bytes");
const bsplit = require("buffer-split");
const node_rsa = require("node-rsa");
2019-04-10 20:23:01 +10:00
const sqlite3 = require('sqlite3');
2019-04-09 19:19:02 +10:00
const btoa = require("btoa");
const atob = require("atob");
2019-03-24 17:57:54 +11:00
const net = require("net");
const fs = require("fs");
// Load the settings
var settings = require("./settings.json");
2019-04-10 20:23:01 +10:00
// Create a sqlite3 database
var db = new sqlite3.Database("./app.db", function(err)
{
// Was there an error
if(err)
{
// Tell the user the error
2019-04-13 10:07:30 +10:00
console.log("SQlite3 failed to start:", err);
2019-04-10 20:23:01 +10:00
}
else
{
// Tell the user it worked
2019-04-13 10:07:30 +10:00
console.log("SQlite3 ready.");
2019-04-10 20:23:01 +10:00
}
});
function sqlite3_createTable(name, callback)
{
// Run the create table task with the callback
db.run('CREATE TABLE IF NOT EXISTS '+name
+'(id INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT)', callback);
}
function sqlite3_insertData(name, data)
{
// Write data to the table
db.run('INSERT INTO '+name+' (data) VALUES (?)', [JSON.stringify(data)]);
}
function sqlite3_getData(name, callback)
{
// Get the sqlite3 data and send it to the callback
db.all('SELECT rowid AS id, data FROM '+name, callback);
}
function getrows_sqlite3(name, callback)
2019-04-10 20:23:01 +10:00
{
// Create the table
sqlite3_createTable(name, function()
2019-04-10 20:23:01 +10:00
{
// Load the table
sqlite3_getData(name, function(err, rows)
2019-04-10 20:23:01 +10:00
{
// Throw an error if there is one
if(err) throw err;
2019-04-10 20:23:01 +10:00
// Loop over the rows
for(var i=0;i<rows.length;i++)
{
// Parse the data
var data = JSON.parse(rows[i].data);
// Call the callback
callback(data, i);
}
});
2019-04-10 20:23:01 +10:00
});
}
// Load some databases
var users = {};
2019-04-13 10:07:30 +10:00
var chats = [];
// Get the users table
getrows_sqlite3("users", function(data)
{
// Load a user
2019-04-13 10:07:30 +10:00
users[data.username] = {
password: data.password
};
});
// Get the chats table
getrows_sqlite3("chats", function(data)
{
2019-04-13 10:07:30 +10:00
// Push the chat
chats.push({
messages: [],
name: data.name
});
2019-04-10 20:23:01 +10:00
});
// Create a new messages table
getrows_sqlite3("messages", function(data)
{
// Push all the messages to the chats
chats[data.channel].messages.push({
message: data.message,
2019-04-17 16:33:59 +10:00
from: data.from,
date: data.date
});
2019-04-10 20:23:01 +10:00
});
2019-04-13 10:07:30 +10:00
console.log("Server ready.");
2019-04-09 19:19:02 +10:00
function toBuffer(bytes)
{
// String
if(typeof(bytes) == 'string')
{
// Create a buffer
var buffer = new Buffer.alloc(bytes.length);
// Loop over the bytes
for(var i=0;i<bytes.length;i++)
{
// Save to the buffer
buffer[i] = bytes[i].charCodeAt(0);
}
// Return the buffer
return buffer;
}
// Return if this is anything else
return bytes;
}
function toBytes(buffer)
{
// Make sure the buffer is a buffer
buffer = toBuffer(buffer);
// Create some bytes
var bytes = "";
// Loop over the buffer
for(var i=0;i<buffer.length;i++)
{
// Add to the bytes string
bytes += String.fromCharCode(buffer[i]);
}
// Return the bytes
return bytes;
}
function recieve_ordered(data, recieve, callback)
2019-04-09 19:19:02 +10:00
{
// Convert the data into a buffer
data = toBuffer(data);
// Loop over the data
for(var i=0;i<data.length;i++)
{
// Add the data to the buffer
2019-04-19 12:16:37 +10:00
recieve.buffer = new Buffer.concat([recieve.buffer, Buffer([data[i]])]);
2019-04-09 19:19:02 +10:00
// Is the buffer getting data
2019-04-19 12:16:37 +10:00
if(!recieve.get)
2019-04-09 19:19:02 +10:00
{
// Does the buffer contain a number
2019-04-19 12:16:37 +10:00
if(recieve.buffer.length >= 4)
2019-04-09 19:19:02 +10:00
{
// Get the number
2019-04-19 12:16:37 +10:00
recieve.size = recieve.buffer.readUInt32BE(0);
2019-04-09 19:19:02 +10:00
// Reset the buffer
2019-04-19 12:16:37 +10:00
recieve.buffer = new Buffer.alloc(0);
2019-04-09 19:19:02 +10:00
// Set the get data mode to true
2019-04-19 12:16:37 +10:00
recieve.get = true;
2019-04-09 19:19:02 +10:00
}
}
else
{
// Is the recieve buffer as big as the size
2019-04-19 12:16:37 +10:00
if(recieve.buffer.length == recieve.size)
2019-04-09 19:19:02 +10:00
{
// Call the callback
2019-04-19 12:16:37 +10:00
callback(recieve.buffer);
2019-04-09 19:19:02 +10:00
// Reset the buffer and the size
2019-04-19 12:16:37 +10:00
recieve.buffer = new Buffer.alloc(0);
recieve.size = 0;
2019-04-09 19:19:02 +10:00
// Set the get data mode to false
2019-04-19 12:16:37 +10:00
recieve.get = false;
2019-04-09 19:19:02 +10:00
}
}
}
}
function send_ordered(data)
{
// Convert the data into a buffer
data = toBuffer(data);
// Get the size of the data
var size = data.length;
// Turn it into a 32 bit buffer
var bytes = new Buffer.alloc(4);
bytes.writeUInt32BE(size, 0);
// Return the buffer and the data
return new Buffer.concat([bytes, data]);
}
function string_decrypt(key, string)
{
// Convert the string to a buffer
2019-04-09 19:19:02 +10:00
var buff = toBuffer(string);
// Create some new data
var decrypted = new Buffer.alloc(buff.length);
// Iterate over the string
for(var i=0;i<string.length;i++)
{
// Convert the string item to a number
var d = buff[i]-key.str[key.at.rx];
while(d < 0) d += 256;
decrypted[i] = d;
// Add 1 to the key counter
key.at.rx += 1;
// Is the key counter out of range
if(key.at.rx >= key.str.length)
{
// Set to zero
key.at.rx = 0;
}
}
// Return the encrypted data
return decrypted;
}
function string_encrypt(key, string)
{
// Convert the string to a buffer
2019-04-09 19:19:02 +10:00
var buff = toBuffer(string);
// Create some new data
var encrypted = new Buffer.alloc(buff.length);
// Iterate over the string
for(var i=0;i<string.length;i++)
{
// Convert the string item to a number
var e = buff[i]+key.str[key.at.tx];
while(e > 255) e -= 256;
encrypted[i] = e;
// Add 1 to the key counter
key.at.tx += 1;
// Is the key counter out of range
if(key.at.tx >= key.str.length)
{
// Set to zero
key.at.tx = 0;
}
}
// Return the encrypted data
return encrypted;
}
function make_encryption_key(string)
{
// Make a new key
var key = new Object();
// Set the varibles
key.str = toBuffer(string);
key.at = new Object();
key.at.rx = 0;
key.at.tx = 0;
2019-04-10 13:07:31 +10:00
//console.log("make_encryption_key:", key.str);
// Return the key
return key;
}
function socket_write(socket, data)
{
// Send the data encrypted with JSON
2019-04-09 19:19:02 +10:00
socket.sock.write(send_ordered(string_encrypt(socket.key, JSON.stringify(data)+"\n")));
}
2019-04-10 20:23:01 +10:00
function socket_init(socket, ondata)
{
// Set the socket
var sock = new Object();
sock.sock = socket;
sock.new = true;
// Create the recieve ordered memory model
var recieve_ordered_memory = {
buffer: new Buffer.alloc(0),
get: false,
size: 0
}
// Wait for data
sock.sock.on('data', function(data)
{
2019-04-09 19:19:02 +10:00
// Recieve data in order
recieve_ordered(data, recieve_ordered_memory, function(data)
{
2019-04-10 13:07:31 +10:00
//console.log(data);
2019-04-09 19:19:02 +10:00
// Is the socket new
if(sock.new)
{
2019-04-09 19:19:02 +10:00
// Set sock new
sock.new = false;
// Convert from JSON
data = JSON.parse(data);
// Is this the public key
if(data.mode == 'pubkey')
{
// Load the key
var key = new node_rsa();
key.importKey(data.key, 'public');
2019-04-13 10:07:30 +10:00
//console.log("Loaded the RSA key");
2019-04-09 19:19:02 +10:00
// Get some random bytes
random_bytes(settings.encryption_key_size, function(error,string)
{
2019-04-10 13:07:31 +10:00
//console.log("btoa(string):",btoa(string));
2019-04-09 19:19:02 +10:00
// Throw an error if there is one
if(error) throw error;
// Make the key
sock.key = make_encryption_key(string);
2019-04-13 10:07:30 +10:00
//console.log("Created an encryption key")
2019-04-09 19:19:02 +10:00
// Encrypt the key with RSA
var key_encrypted = key.encrypt(string, 'base64');
2019-04-13 10:07:30 +10:00
//console.log("Encrypted the key");
2019-04-09 19:19:02 +10:00
// Send the key to the client
sock.sock.write(send_ordered(JSON.stringify({
key: toBytes(key_encrypted),
mode: "encryption_key"
})));
2019-04-13 10:07:30 +10:00
//console.log("Sent the key to the client");
2019-04-09 19:19:02 +10:00
});
}
}
2019-04-09 19:19:02 +10:00
else
{
2019-04-09 19:19:02 +10:00
// Decrypt the data
data = string_decrypt(sock.key, data);
2019-04-10 13:07:31 +10:00
//console.log(data);
2019-04-09 19:19:02 +10:00
// Parse the json data
var data = JSON.parse(data);
2019-04-10 20:23:01 +10:00
// Send the parsed data to the callback
ondata(data);
}
2019-04-09 19:19:02 +10:00
});
});
// Return the socket
return sock;
}
// Create a connections varible
var connections = [];
2019-03-24 17:57:54 +11:00
// Create a server
var server = net.createServer(function(socket)
{
console.log("Connection from "+socket.remoteAddress);
// Is this a whitelist
if(settings.ips.type == "whitelist")
{
// Is the ip not in the whitelist
if(!settings.ips.list.includes(socket.remoteAddress))
{
// Close the socket
socket.destroy();
// Tell the user
console.log("User at "+socket.remoteAddress+" failed. Reason: whitelist");
// Return
return;
}
}
// Is this a blacklist
if(settings.ips.type == "blacklist")
{
// Is the ip in the blacklist
if(settings.ips.list.includes(socket.remoteAddress))
{
// Close the socket
socket.destroy();
// Tell the user
console.log("User at "+socket.remoteAddress+" failed. Reason: blacklist");
// Return
return;
}
}
2019-04-10 20:23:01 +10:00
// Setup the sock varible
var sock;
2019-03-24 17:57:54 +11:00
// Initialise the socket and wait for data
2019-04-10 20:23:01 +10:00
sock = socket_init(socket, function(data)
{
2019-04-10 20:23:01 +10:00
// Does the user want to login
if(data.mode == "login" && !sock.logged_in)
2019-04-10 20:23:01 +10:00
{
// Is the user registered here
if(users[data.username])
{
// Is the password correct
if(users[data.username].password == data.password)
{
// Log the user in
sock.username = data.username;
console.log("Logged "+sock.username+" in.");
// Set logged in
sock.logged_in = true;
2019-04-10 20:23:01 +10:00
}
}
// Is register on fail set
else if(settings.register_on_fail)
2019-04-10 20:23:01 +10:00
{
// Make a new user
users[data.username] = new Object();
users[data.username].password = data.password;
// Send it to sqlite3
sqlite3_insertData("users", {
username: data.username,
password: data.password
});
// Send the status to all connections
for(var i=0;i<connections.length;i++)
{
// Does the connection exist
if(connections[i])
{
// Send data to the connection
socket_write(connections[i], {
mode: "new_user",
user: data.username,
data: {
}
});
}
}
2019-04-10 20:23:01 +10:00
console.log("Creating account "+data.username+".");
// Set logged in
sock.logged_in = true;
}
// Is the user now logged in
if(sock.logged_in)
{
// Set the users username
sock.username = data.username;
// Add the user to the connections array
sock.connection_id = connections.length;
connections.push(sock);
// Set on disconnect
2019-04-12 18:18:49 +10:00
sock.sock.on('close', function()
{
2019-04-13 10:07:30 +10:00
console.log("Connection from "+data.username+" closed.")
// Delete the connection id
delete connections[sock.connection_id];
});
// Get a list of chat keys
2019-04-13 10:07:30 +10:00
var chats_client = [];
// Get a list of user keys
var user_keys = Object.keys(users);
var users_client = {};
// Loop over the chats
2019-04-13 10:07:30 +10:00
for(var i=0;i<chats.length;i++)
{
2019-04-13 10:07:30 +10:00
// Push it to the temporary varible
chats_client.push({
name: chats[i].name,
messages: chats[i].messages
});
}
// Loop over the users
for(var i=0;i<user_keys.length;i++)
{
// Register the key to the temporary varible
2019-04-13 10:07:30 +10:00
users_client[user_keys[i]] = {
};
}
2019-04-13 10:07:30 +10:00
//console.log("Sending server data");
// Send the server data
socket_write(sock, {
mode: "login",
chats: chats_client,
users: users_client
});
}
else
{
console.log("Authentication failed.");
// Send back an error message
socket_write(sock, {
mode: "error",
error: "auth"
});
2019-04-10 20:23:01 +10:00
}
}
2019-04-13 10:07:30 +10:00
// Is the socket logged in
if(sock.logged_in)
{
// Does the user want to make a new chat
if(data.mode == "new_chat")
{
// Make a new chat
chats.push({
messages: [],
name: data.name
});
// Write it to the chats table
sqlite3_insertData("chats", {
name: data.name
2019-04-13 10:19:44 +10:00
});
// Update all the clients, loop over them
for(var i=0;i<connections.length;i++)
{
// Is this an active connection
if(connections[i])
{
// Send them the chat update
socket_write(connections[i], {
mode: "new_chat",
name: data.name
});
}
}
2019-04-13 10:07:30 +10:00
}
// Does the user want to send a message
if(data.mode == "send_message")
{
// Is the channel real
if(chats[data.channel])
{
// Save the message to sqlite3
sqlite3_insertData("messages", {
channel: data.channel,
message: data.message,
2019-04-17 16:33:59 +10:00
from: sock.username,
date: data.date
});
// Add it to the channel
chats[data.channel].messages.push({
message: data.message,
2019-04-17 16:33:59 +10:00
from: sock.username,
date: data.date
});
// Send it to all the connected clients
for(var i=0;i<connections.length;i++)
{
// Is the client connected
if(connections[i])
{
// Send the client the message
socket_write(connections[i], {
mode: "new_message",
message: data.message,
channel: data.channel,
2019-04-17 16:33:59 +10:00
from: sock.username,
date: data.date
});
}
}
}
}
2019-04-13 10:07:30 +10:00
}
2019-03-24 17:57:54 +11:00
});
// Setup some varibles
sock.logged_in = false;
2019-03-24 17:57:54 +11:00
});
// Set some default settings
settings.port = settings.port || 22068;
settings.ips = settings.ips || {
type: "blacklist",
list: []
};
settings.ips.type = settings.ips.type || "blacklist";
settings.ips.list = settings.ips.list || [];
settings.register_on_fail = settings.register_on_fail || true;
settings.encryption_key_size = settings.encryption_key_size || 16384;
2019-04-12 18:46:18 +10:00
2019-03-24 17:57:54 +11:00
// Listen for data
server.listen(settings.port, '');