using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Runtime;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Security.Cryptography.X509Certificates;
using System.Security.Authentication;
using System.IO.Compression;
namespace SharpNet
{
public class Server
{
///
/// Delegate for recieving data
///
/// Always two bytes
///
///
///
public delegate void DataRecieved(byte[] header, MemoryStream message, string name, Connection sender);
public delegate void ClientEvent(string name, Connection connection);
public delegate void NewBPS(long uncompressed);
public event DataRecieved MessageRecieved;
public event ClientEvent ClientLoggedIn;
public event ClientEvent ClientLeft;
public event NewBPS NewBytesPerSecond;
private long compressedBPS = 0;
private long uncompressedBPS = 0;
public string password = "";
public Dictionary connections = new Dictionary();
private TcpListener tcpListener;
private List tasks = new List();
public IPAddress address
{
get
{
return ((IPEndPoint)tcpListener.LocalEndpoint).Address;
}
}
public int port
{
get
{
return ((IPEndPoint)tcpListener.LocalEndpoint).Port;
}
}
public Connection this[int i]
{
get
{
return connections.ElementAt(i).Value;
}
protected set { }
}
///
/// Initiates the server, runs threads etc.
///
/// IP adress on which the server will be listening
/// IP adress on which the server will be listening
/// Password required from client, else the server will not even reply to the client. Password can be maximaly 100 characters long.
/// Key, which will be used to encrypt the communication
public Server(IPAddress address, int port, string password)
{
try
{
this.password = password;
tcpListener = new TcpListener(address, port);
Thread listenThread = new Thread(ListenThread); // we don't want to block main thread
listenThread.Name = "listenThread";
listenThread.IsBackground = true; // it will not block exiting the application
listenThread.Start();
Thread sendThread = new Thread(SendThread); // we don't want to block main thread
sendThread.Name = "sendThread";
sendThread.IsBackground = true; // it will not block exiting the application
sendThread.Start();
// bytes per second counter
System.Timers.Timer timer = new System.Timers.Timer();
timer.Elapsed += timer_Elapsed;
timer.Interval = 1000;
timer.Enabled = true;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (NewBytesPerSecond != null)
NewBytesPerSecond(uncompressedBPS);
uncompressedBPS = 0;
}
private void ListenThread()
{
try
{
this.tcpListener.Start();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.WriteLine("Listening...");
while (true)
{
try
{
TcpClient connection = tcpListener.AcceptTcpClient(); // thread will sleep until something connects
using (FileStream cert = File.OpenRead(@"cert-public.pem")) // load public key certificate
{
BinaryWriter writer = new BinaryWriter(connection.GetStream()); // send the certificate containing the public key
writer.Write(cert.Length);
cert.Position = 0;
cert.CopyTo(connection.GetStream());
}
SslStream sslStream = new SslStream(connection.GetStream());
sslStream.AuthenticateAsServer(new X509Certificate2(@"cert.pfx", password), false, SslProtocols.Default, false);
Thread t = new Thread(ClientThread);
t.IsBackground = true; // it will not block exiting the application
t.Start(new Connection(sslStream, connection));
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
private void SendThread()
{
while (true)
{
if (tasks.Count != 0)
{
lock (tasks)
{
foreach(Action task in tasks)
{
task();
}
tasks.Clear();
Collect();
}
}
}
}
private void ClientThread(object data)
{
Connection connection = (Connection)data; // I swear it's a Connection :-)
string name = "";
//Console.WriteLine("Client connected from" + ((IPEndPoint)connection.Client.RemoteEndPoint).ToString());
while (connection.client.Connected)
{
if (!connections.Values.Contains(connection)) // if current connection is not in the collection, it hasn't logged in
{
try
{
if (password != "") // if the password is set
{
if (ReadString(connection.securedStream) == password) // first thing the client must send is the password
{
name = ReadString(connection.securedStream); // then it sends the name
connections.Add(name, connection);
Send(connection.securedStream, new MemoryStream(), new byte[2] { OwnerID.SERVER_INTERNAL, TypeID.AUTH_OK });
}
else
{
Send(connection.securedStream, new MemoryStream(), new byte[2] { OwnerID.SERVER_INTERNAL, TypeID.AUTH_ERR });
break;
}
}
else
{
name = ReadString(connection.securedStream);
connections.Add(name, connection);
Send(connection.securedStream, new MemoryStream(), new byte[2] { OwnerID.SERVER_INTERNAL, TypeID.AUTH_OK });
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
if (ClientLoggedIn != null)
ClientLoggedIn(name, connection); // alright, stuff set up, ready to communicate
//Console.WriteLine("Client from " + ((IPEndPoint)connection.Client.RemoteEndPoint).ToString() + " logged in as " + name);
}
else
{
try
{
byte[] header = new byte[2];
MemoryStream stream = Read(connection.securedStream, out header);
if (MessageRecieved != null)
MessageRecieved(header, stream, name, connection);
}
catch (Exception e) // when client disconnects, exception is thrown and catch breaks main loop
{
Console.WriteLine(e.Message);
connections.Remove(name);
break;
}
}
}
if (ClientLeft != null)
ClientLeft(name, connection);
//Console.WriteLine("Client from " + ((IPEndPoint)connection.Client.RemoteEndPoint).ToString() + " disconnected");
}
///
/// Overload for Send() accepting a string as 2nd parameter instead of a MemoryStream.
/// Decodes the string to a byte array using UTF-8 encoding and sends it to a client.
///
/// TcpClient whom to send the message
/// UTF-8 encoded string to send
/// Header
public void Send(Stream securedStream, string data, byte[] header)
{
Send(securedStream, new MemoryStream(GetBytes(data)), header);
}
///
/// Sends all the bytes from provided Stream to secified TcpClient
///
/// TcpClient whom to send these bytes
/// Stream to send the data from
/// Header
public void Send(Stream securedStream, Stream data, byte[] header)
{
lock (tasks)
{
tasks.Add(delegate
{
try
{
if (data.Length > 0)
{
BinaryWriter writer = new BinaryWriter(securedStream);
writer.Write(data.Length);
writer.Write(header);
data.Position = 0;
data.CopyTo(securedStream);
}
else
{
BinaryWriter writer = new BinaryWriter(securedStream);
writer.Write((long)0);
writer.Write(header);
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
});
}
}
///
/// Reads message from client byte by byte and puts it to a MemoryStream (to avoid memory exceptions)
///
/// MemoryStream containing read bytes
private MemoryStream Read(Stream securedStream, out byte[] header)
{
MemoryStream result = new MemoryStream();
BinaryReader networkReader = new BinaryReader(securedStream);
BinaryWriter memoryWriter = new BinaryWriter(result);
long bufferSize = networkReader.ReadInt64(); // first read the buffer size (64-bit int)
header = new byte[2];
header[0] = networkReader.ReadByte(); // first byte - package owner
header[1] = networkReader.ReadByte(); // second byte - type of package
if (header[0] == OwnerID.SERVER_INTERNAL && header[1] == TypeID.BYE)
throw new Exception("Client said: \"bye!\"");
if (bufferSize == 0)
return new MemoryStream();
Collect();
while (bufferSize != 0) // loop and read byte by byte
{
memoryWriter.Write(networkReader.ReadByte());
bufferSize--;
}
uncompressedBPS += result.Length;
result.Position = 0; // reset
return result; // and return the stream
}
///
/// Calls Read() method and decodes output bytes as UTF-8 string. VERY UNSAFE because of working with potentially big arrays!
///
/// Bytes decoded as UTF-8 string
private string ReadString(Stream securedStream)
{
//Collect();
byte[] header = new byte[2];
return GetString(Read(securedStream, out header).ToArray());
}
public static string GetString(byte[] message)
{
return Encoding.UTF8.GetString(message);
}
public static byte[] GetBytes(string message)
{
return Encoding.UTF8.GetBytes(message);
}
///
/// Intiates Garbage Collection to avoid OutOfMemoryException. Should be called in each method working with big data.
///
private void Collect()
{
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
}
private byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
{
byte[] encryptedBytes = null;
// Set your salt here, change it to meet your flavor:
// The salt bytes must be at least 8 bytes.
byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
using (MemoryStream ms = new MemoryStream())
{
using (RijndaelManaged AES = new RijndaelManaged())
{
AES.KeySize = 256;
AES.BlockSize = 128;
var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);
AES.Mode = CipherMode.CBC;
using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
cs.Close();
}
encryptedBytes = ms.ToArray();
}
}
return encryptedBytes;
}
private byte[] AES_Decrypt(byte[] bytesToBeDecrypted, byte[] passwordBytes)
{
byte[] decryptedBytes = null;
// Set your salt here, change it to meet your flavor:
// The salt bytes must be at least 8 bytes.
byte[] saltBytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
using (MemoryStream ms = new MemoryStream())
{
using (RijndaelManaged AES = new RijndaelManaged())
{
AES.KeySize = 256;
AES.BlockSize = 128;
var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);
AES.Mode = CipherMode.CBC;
using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
cs.Close();
}
decryptedBytes = ms.ToArray();
}
}
return decryptedBytes;
}
private string MD5Hash(Stream input)
{
// step 1, calculate MD5 hash from input
MD5 md5 = System.Security.Cryptography.MD5.Create();
byte[] hash = md5.ComputeHash(input);
// step 2, convert byte array to hex string
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
sb.Append(hash[i].ToString("X2"));
}
return sb.ToString();
}
}
}