Remove NP server code from file tree.

feature-npv2
Icedream 2015-03-21 19:55:50 +01:00
parent 89d9a3f2c1
commit 58f34660d7
17 changed files with 0 additions and 1452 deletions

View File

@ -18,8 +18,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{F8
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPSharp.Client", "src\client\NPSharp.Client.csproj", "{C6F941A5-82AF-456A-9B3A-752E5B001035}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NPSharp.Server", "src\server\NPSharp.Server.csproj", "{1A5AC63A-250E-4BC8-B81A-822AC31F5E37}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU

View File

@ -1,137 +0,0 @@
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using log4net;
using uhttpsharp;
using uhttpsharp.Handlers;
using uhttpsharp.Headers;
using uhttpsharp.Listeners;
using uhttpsharp.RequestProviders;
namespace NPSharp.Authentication
{
/// <summary>
/// Represents a session authentication server which uses the HTTP protocol to send out session tokens to
/// authenticating NP clients.
/// </summary>
public class SessionAuthenticationServer
{
private readonly ILog _log;
private HttpServer _http;
/// <summary>
/// Constructs a new session authentication server.
/// </summary>
public SessionAuthenticationServer()
{
SupportOldAuthentication = true;
_log = LogManager.GetLogger("Auth");
}
/// <summary>
/// Support oldskool "user&amp;&amp;pass" authentication format.
/// </summary>
public bool SupportOldAuthentication { get; set; }
/// <summary>
/// Will be triggered whenever a client tries to authenticate via this server.
/// </summary>
public event Func<string, string, SessionAuthenticationResult> Authenticating;
protected virtual SessionAuthenticationResult OnAuthenticating(string username, string password)
{
var handler = Authenticating;
return handler != null
? handler(username, password)
: new SessionAuthenticationResult {Reason = "Login currently disabled"};
}
/// <summary>
/// Starts the authentication server.
/// </summary>
/// <param name="port">The port on which the authentication server should listen on.</param>
public void Start(ushort port = 12003)
{
if (_http != null)
{
throw new InvalidOperationException("This server is already running");
}
_log.Debug("Starting authentication server...");
_http = new HttpServer(new HttpRequestProvider());
_http.Use(new TcpListenerAdapter(new TcpListener(IPAddress.Any, port)));
_http.Use(new TcpListenerAdapter(new TcpListener(IPAddress.IPv6Any, port)));
_http.Use(new HttpRouter().With("authenticate", new AuthenticateHandler(this)));
_http.Use(new AnonymousHttpRequestHandler((ctx, task) =>
{
ctx.Response = HttpResponse.CreateWithMessage(HttpResponseCode.NotFound, "Not found",
ctx.Request.Headers.KeepAliveConnection());
return Task.Factory.GetCompleted();
}));
_http.Start();
_log.Debug("Done starting authentication server.");
}
/// <summary>
/// Stops the authentication server.
/// </summary>
public void Stop()
{
_http.Dispose();
}
protected class AuthenticateHandler : IHttpRequestHandler
{
private readonly SessionAuthenticationServer _authServer;
public AuthenticateHandler(SessionAuthenticationServer sessionAuthenticationServer)
{
_authServer = sessionAuthenticationServer;
}
public Task Handle(IHttpContext context, Func<Task> next)
{
SessionAuthenticationResult sar;
string username;
string password;
if (!context.Request.Post.Parsed.TryGetByName("user", out username) ||
!context.Request.Post.Parsed.TryGetByName("pass", out password))
{
var login = Encoding.UTF8.GetString(context.Request.Post.Raw)
.Split(new[] {"&&"}, StringSplitOptions.None);
if (login.Length != 2)
{
sar = new SessionAuthenticationResult {Reason = @"Invalid login data"};
context.Response = new HttpResponse(HttpResponseCode.Ok, sar.ToString(),
context.Request.Headers.KeepAliveConnection());
return Task.Factory.GetCompleted();
}
username = login[0];
password = login[1];
}
try
{
sar = _authServer.OnAuthenticating(username, password);
}
catch (Exception error)
{
_authServer._log.Error(@"Authentication handler error", error);
sar = new SessionAuthenticationResult {Reason = @"Internal server error"};
}
_authServer._log.DebugFormat("/authenticate reply is {0}", sar);
context.Response = new HttpResponse(HttpResponseCode.Ok, sar.ToString(),
!sar.Success && context.Request.Headers.KeepAliveConnection());
return Task.Factory.GetCompleted();
}
}
}
}

View File

@ -1,15 +0,0 @@
using System;
using NPSharp.NP;
namespace NPSharp.Events
{
public class ClientEventArgs : EventArgs
{
internal ClientEventArgs(NPServerClient client)
{
Client = client;
}
public NPServerClient Client { get; private set; }
}
}

View File

@ -1,11 +0,0 @@
using NPSharp.NP;
namespace NPSharp.Events
{
/// <summary>
/// A delegate for all general client-related events.
/// </summary>
/// <param name="sender">The instance of the NP server</param>
/// <param name="args">All related arguments to this event</param>
public delegate void ClientEventHandler(NPServer sender, ClientEventArgs args);
}

View File

@ -1,44 +0,0 @@
using NPSharp.NP;
using NPSharp.RPC.Messages.Data;
namespace NPSharp.Handlers
{
/// <summary>
/// Represents a handler for all authentication-related requests.
/// </summary>
public interface IAuthenticationHandler
{
/// <summary>
/// Authenticates a user based on username and password.
/// </summary>
/// <param name="client">The NP server client to authenticate</param>
/// <param name="username">The username to use for authentication</param>
/// <param name="password">The password to use for authentication</param>
/// <returns>An instance of <seealso cref="NPAuthenticationResult" /></returns>
NPAuthenticationResult AuthenticateUser(NPServerClient client, string username, string password);
/// <summary>
/// Authenticates a user based on a session token.
/// </summary>
/// <param name="client">The NP server client to authenticate</param>
/// <param name="token">The session token to use for authentication</param>
/// <returns>An instance of <seealso cref="NPAuthenticationResult" /></returns>
NPAuthenticationResult AuthenticateUser(NPServerClient client, string token);
/// <summary>
/// Authenticates a dedicated server based on its license key.
/// </summary>
/// <param name="client">The NP server client of the dedicated server to authenticate</param>
/// <param name="licenseKey">The license key to use for authentication</param>
/// <returns>An instance of <see cref="NPAuthenticationResult" /></returns>
NPAuthenticationResult AuthenticateServer(NPServerClient client, string licenseKey);
/// <summary>
/// Validates a ticket.
/// </summary>
/// <param name="client">The NP server client of the user who is trying to get the ticket validated</param>
/// <param name="server">The server that the user wants to connect to using this ticket</param>
/// <returns>A <see cref="TicketValidationResult" /> determining if the ticket is valid</returns>
TicketValidationResult ValidateTicket(NPServerClient client, NPServerClient server);
}
}

View File

@ -1,34 +0,0 @@
using NPSharp.NP;
namespace NPSharp.Handlers
{
/// <summary>
/// Represents a handler for all file-related requests.
/// </summary>
public interface IFileServingHandler
{
/// <summary>
/// Reads a file assigned to the user.
/// </summary>
/// <returns>The file contents as byte array or null if the file could not be read, opened or generated</returns>
/// <param name="client">NP server client of the user</param>
/// <param name="file">The file name</param>
byte[] ReadUserFile(NPServerClient client, string file);
/// <summary>
/// Reads a publisher file.
/// </summary>
/// <returns>The file contents as byte array or null if the file could not be read, opened or generated</returns>
/// <param name="client">NP server client of the user</param>
/// <param name="file">The file name</param>
byte[] ReadPublisherFile(NPServerClient client, string file);
/// <summary>
/// Writes a file and assigns it to the client user.
/// </summary>
/// <param name="client">NP server client of the user</param>
/// <param name="file">The file name</param>
/// <param name="data">The file contents as byte array</param>
void WriteUserFile(NPServerClient client, string file, byte[] data);
}
}

View File

@ -1,19 +0,0 @@
using System.Collections.Generic;
using NPSharp.NP;
using NPSharp.RPC.Messages.Data;
namespace NPSharp.Handlers
{
/// <summary>
/// Represents a handler for all friends-related requests.
/// </summary>
public interface IFriendsHandler
{
/// <summary>
/// Fetches all friends of the connected user.
/// </summary>
/// <param name="client">The NP server client of the user</param>
/// <returns>All friend details found for the user</returns>
IEnumerable<FriendDetails> GetFriends(NPServerClient client);
}
}

View File

@ -1,14 +0,0 @@
using NPSharp.Steam;
namespace NPSharp.Handlers
{
/// <summary>
/// Represents a handler for all user avatar-related requests.
/// </summary>
public interface IUserAvatarHandler
{
byte[] GetUserAvatar(CSteamID id);
byte[] GetDefaultAvatar();
}
}

View File

@ -1,117 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using log4net;
using NPSharp.Master.Messages;
using NPSharp.Master.Messages.Client;
namespace NPSharp.Master
{
public class MasterServer
{
// TODO: !! Avoid socket fail if stopping then restarting
private readonly List<KeyValuePair<Type, Action<MasterClientMessage>>> _callbacks =
new List<KeyValuePair<Type, Action<MasterClientMessage>>>();
private readonly ILog _log;
private readonly ushort _port;
// TODO: Use the same kind of interfaces as in NP server to handle server addition and deletion
private readonly List<DedicatedServerEntry> _registeredServers = new List<DedicatedServerEntry>();
private readonly Socket _socket4 = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
private readonly Socket _socket6 = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
public MasterServer(ushort port = 20810)
{
_port = port;
_log = LogManager.GetLogger("MasterServer");
// Internal callbacks
AddCallback<MasterGetServersMessage>(messages => { });
}
internal void AddCallback<T>(Action<T> callback) where T : MasterClientMessage
{
_callbacks.Add(
new KeyValuePair<Type, Action<MasterClientMessage>>(
typeof (T),
msg => callback.Invoke((T) msg)));
}
/// <summary>
/// Starts up the NP server.
/// </summary>
public void Start()
{
if (_socket4.IsBound || _socket6.IsBound)
throw new InvalidOperationException("This server is already running");
try
{
// ReSharper disable once ObjectCreationAsStatement
// TODO: fix this shit permission code
new SocketPermission(NetworkAccess.Accept, TransportType.Udp, "", _port);
}
catch
{
_log.Error("Socket permission request failed, can't start server.");
throw new SocketException(10013 /* Permission denied */);
}
_socket4.Bind(new IPEndPoint(IPAddress.Any, _port));
_socket4.Listen(100);
_socket6.Bind(new IPEndPoint(IPAddress.IPv6Any, _port));
_socket6.Listen(100);
// TODO: Implement IPv4 handling
Task.Factory.StartNew(() =>
{
_log.Debug("Listener loop (IPv6) started");
while (_socket6 != null && _socket6.IsBound)
{
var mergedBuffer = new List<byte>();
while (true)
{
var buffer = new byte[1400];
var clientEndPoint = (EndPoint) new IPEndPoint(IPAddress.IPv6Any, 0);
var recvLength = _socket6.ReceiveFrom(buffer, ref clientEndPoint);
if (recvLength <= buffer.Length)
mergedBuffer.AddRange(buffer);
if (recvLength < 1400)
break;
_handleClient(buffer, clientEndPoint);
}
}
_log.Debug("Listener loop (IPv6) shut down");
});
}
private void _handleClient(byte[] buffer, EndPoint ep)
{
_log.DebugFormat("Handle client {0}", ep);
var message = MasterClientMessage.Deserialize(buffer);
if (message == null)
{
_log.WarnFormat("Received invalid or empty request from {0}", ep);
return;
}
// Invoke (internal) callbacks for fitting message types
foreach (var callback in _callbacks.Where(i => i.Key == message.GetType()).Select(i => i.Value))
{
callback.Invoke(message);
}
_log.DebugFormat("Not handling client {0} anymore", ep);
}
}
}

View File

@ -1,97 +0,0 @@
# Master API source code
This folder is supposed to contain the source code that will provide both, a master server and client.
alterIWnet and fourdeltaone made use of the open-sourced DP master server code to serve registration of dedicated servers
and the dedicated server list for the game client.
This server is supposed to be replaced by a full implementation of the protocol, both as server and client, in C# and is
to be integrated into libnpsharp.
## Protocol
The master server protocol is based on Quake 3's (http://src.gnu-darwin.org/ports/games/masterserver/work/masterserver-0.4.1/docs/PROTOCOLS):
Request message:
AA AA AA AA|BB .. .. BB| A=Header, B=Body ending with line break or \x00
Response message:
AA .. .. AA|0a|BB .. .. A=Message name, B=Body
BB
Important: The response is supposed to be one or multiple specific-length plus a less-than-specific-length
UDP packet(s).
TODO: Look up the specific length in the DP master source code. It's somewhere around 1400/1500 bytes.
The master server itself has to only serve for the "getservers" request and has to respond with a getServersResponse message.
This getServersResponse message's body is serialized as following:
XX AA AA AA AA BB BB[CC X=ascii("\\"), A=IP (uint, network byte order), B=Port 1 (ushort, network byte order), C=Optional port 2 (ushort, network byte order)
CC[..] ]XX .. .. .. ..
XX YY YY YY 00 00 00[00 Y=ascii("EOT")
00]
In short, serialized ip-port pairs/triples separated by ASCII backslashes and ended by an entry which decodes to "EOT".
## Protocol limits
(see https://github.com/kphillisjr/dpmaster/blob/master/src/messages.c for reference)
```c
// Timeout after a valid infoResponse (in secondes)
#define TIMEOUT_INFORESPONSE (15 * 60)
// Period of validity for a challenge string (in secondes)
#define TIMEOUT_CHALLENGE 2
// Maximum size of a reponse packet
#define MAX_PACKET_SIZE_OUT 1400
```
## Internal management
Both the server and the client should be able to handle a variable amount of ports. For this, we plan following things:
1) Assume "\" at the beginning of an entry. If not true, stream out of sync -> throw exception/disconnect.
2) Assume an IP address exists (4 bytes minimum).
a) If IP address is ASCII "EOT" with \x00 bytes at the end, stop reading the message.
3) Do not assume a fixed amount of ports. Instead
a) ...if rest > 0 bytes, read 2 bytes and append to a ushort list for ports.
b) ...if rest == 0 bytes, prepare for reading next entry.
This should be compatible with both, messages with 1 IP address + 1 port (game) and 1 IP address + 2 ports (game/query).
## Messages
(see https://github.com/kphillisjr/dpmaster/blob/master/src/messages.c for reference)
// Types of messages (with samples):
// Q3: "heartbeat QuakeArena-1\x0A"
// DP: "heartbeat DarkPlaces\x0A"
#define S2M_HEARTBEAT "heartbeat "
// Q3 & DP & QFusion: "getinfo A_Challenge"
#define M2S_GETINFO "getinfo"
// Q3 & DP & QFusion: "infoResponse\x0A\\pure\\1\\..."
#define S2M_INFORESPONSE "infoResponse\x0A"
// Q3: "getservers 67 ffa empty full"
// DP: "getservers DarkPlaces-Quake 3 empty full"
// DP: "getservers Transfusion 3 empty full"
// QFusion: "getservers qfusion 39 empty full"
#define C2M_GETSERVERS "getservers "
// DP: "getserversExt DarkPlaces-Quake 3 empty full ipv4 ipv6"
// IOQuake3: "getserversExt 68 empty ipv6"
#define C2M_GETSERVERSEXT "getserversExt "
// Q3 & DP & QFusion:
// "getserversResponse\\...(6 bytes)...\\...(6 bytes)...\\EOT\0\0\0"
#define M2C_GETSERVERSREPONSE "getserversResponse"
// DP & IOQuake3:
// "getserversExtResponse\\...(6 bytes)...//...(18 bytes)...\\EOT\0\0\0"
#define M2C_GETSERVERSEXTREPONSE "getserversExtResponse"

View File

@ -1,732 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using log4net;
using NPSharp.Events;
using NPSharp.Handlers;
using NPSharp.RPC;
using NPSharp.RPC.Messages.Client;
using NPSharp.RPC.Messages.Data;
using NPSharp.RPC.Messages.Server;
using NPSharp.Steam;
namespace NPSharp.NP
{
public class NPServer
{
private readonly List<NPServerClient> _clients;
private readonly ILog _log;
private readonly ushort _port;
#if MONO_INCOMPATIBLE
private readonly Socket _socket;
#else
private readonly Socket _socket4;
private readonly Socket _socket6;
#endif
/// <summary>
/// Constructs a new NP server.
/// </summary>
public NPServer(ushort port = 3025)
{
_log = LogManager.GetLogger("NPServer");
_clients = new List<NPServerClient>();
#if MONO_INCOMPATIBLE
// Mono can't compile this since the constructor is proprietary to Windows' .NET library
_socket = new Socket(SocketType.Stream, ProtocolType.IP);
// Mono can't compile this either since the IPv6Only socket option is completely missing.
//_socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
//_socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, 0);
#else
// So as much as this hurts me, I have to go with TWO sockets.
// Guys, this is why I hate network programming sometimes. SOMETIMES.
_socket4 = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_socket6 = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
#endif
_port = port;
}
/// <summary>
/// The handler to use for file requests to this NP server.
/// </summary>
public IFileServingHandler FileServingHandler { get; set; }
/// <summary>
/// The handler to use for user avatar requests to this NP server.
/// </summary>
public IUserAvatarHandler UserAvatarHandler { get; set; }
/// <summary>
/// Returns all currently connected clients
/// </summary>
public NPServerClient[] Clients
{
get { return _clients.ToArray(); } // Avoid race condition by IEnum changes
}
/// <summary>
/// The handler to use for authentication requests to this NP server.
/// </summary>
public IAuthenticationHandler AuthenticationHandler { get; set; }
/// <summary>
/// The handler to use for friends-related requests to this NP server.
/// </summary>
public IFriendsHandler FriendsHandler { get; set; }
/// <summary>
/// Starts up the NP server.
/// </summary>
public void Start()
{
if (
#if MONO_INCOMPATIBLE
_socket.IsBound
#else
_socket4.IsBound || _socket6.IsBound
#endif
)
throw new InvalidOperationException("This server is already running");
try
{
// ReSharper disable once ObjectCreationAsStatement
// TODO: fix this shit permission code
new SocketPermission(NetworkAccess.Accept, TransportType.Tcp, "", _port);
}
catch
{
_log.Error("Socket permission request failed, can't start server.");
throw new SocketException(10013 /* Permission denied */);
}
#if MONO_INCOMPATIBLE
_socket.Bind(new IPEndPoint(IPAddress.IPv6Any, _port));
_socket.Listen(100);
#else
_socket4.Bind(new IPEndPoint(IPAddress.Any, _port));
_socket4.Listen(100);
_socket6.Bind(new IPEndPoint(IPAddress.IPv6Any, _port));
_socket6.Listen(100);
#endif
Task.Factory.StartNew(() =>
{
_log.Debug("Listener loop started");
#if MONO_INCOMPATIBLE
var socket = _socket;
#else
var socket = _socket4;
#endif
var allDone = new ManualResetEvent(false);
while (
#if MONO_INCOMPATIBLE
_socket != null && _socket.IsBound
#else
_socket4 != null && _socket4.IsBound
#endif
)
{
allDone.Reset();
socket.BeginAccept(ar =>
{
_log.Debug("Async accept client start");
allDone.Set();
var serverSocket = (Socket) ar.AsyncState;
var clientSocket = serverSocket.EndAccept(ar);
var npsc = new NPServerClient(this, new RPCServerStream(clientSocket));
_log.Debug("Async accept client end");
_handleClient(npsc);
}, socket);
allDone.WaitOne();
}
_log.Debug("Listener loop shut down");
});
#if !MONO_INCOMPATIBLE
Task.Factory.StartNew(() =>
{
_log.Debug("Listener loop (IPv6) started");
var allDone = new ManualResetEvent(false);
while (_socket6 != null && _socket6.IsBound)
{
allDone.Reset();
_socket6.BeginAccept(ar =>
{
_log.Debug("Async accept (IPv6) client start");
allDone.Set();
var serverSocket = (Socket) ar.AsyncState;
var clientSocket = serverSocket.EndAccept(ar);
var npsc = new NPServerClient(this, new RPCServerStream(clientSocket));
_log.Debug("Async accept (IPv6) client end");
_handleClient(npsc);
}, _socket6);
allDone.WaitOne();
}
_log.Debug("Listener loop (IPv6) shut down");
});
}
#endif
/// <summary>
/// Shuts down all connections and stops the NP server.
/// </summary>
public void Stop()
{
// TODO: Wait for sockets to COMPLETELY shut down
#if MONO_INCOMPATIBLE
_socket.Shutdown(SocketShutdown.Both);
#else
_socket4.Shutdown(SocketShutdown.Both);
_socket6.Shutdown(SocketShutdown.Both);
#endif
}
internal void _handleClient(NPServerClient client)
{
_log.Debug("Client now being handled");
#region RPC authentication message handlers
client.RPC.AttachHandlerForMessageType<AuthenticateWithKeyMessage>(msg =>
{
var result = new NPAuthenticationResult();
if (AuthenticationHandler != null)
{
try
{
result = AuthenticationHandler.AuthenticateServer(client, msg.LicenseKey);
}
catch (Exception error)
{
_log.Error("Error occurred in authentication handler", error);
}
}
// Send authentication result directly to client
client.RPC.Send(new AuthenticateResultMessage
{
NPID = result.UserID,
Result = result.Result ? 0 : 1,
SessionToken = string.Empty
});
// Authentication failed => close connection
if (!result.Result)
{
client.RPC.Close();
return;
}
// Assign login ID
client.UserID = result.UserID;
client.IsServer = true;
OnClientAuthenticated(client);
});
client.RPC.AttachHandlerForMessageType<AuthenticateWithDetailsMessage>(msg =>
{
var result = new NPAuthenticationResult();
if (AuthenticationHandler != null)
{
try
{
result = AuthenticationHandler.AuthenticateUser(client, msg.Username, msg.Password);
}
catch (Exception error)
{
_log.Error("Error occurred in authentication handler", error);
result = new NPAuthenticationResult();
}
}
// Send authentication result directly to client
client.RPC.Send(new AuthenticateResultMessage
{
NPID = result.UserID,
Result = result.Result ? 0 : 1,
SessionToken = string.Empty
});
// Authentication failed => close connection
if (!result.Result)
{
client.RPC.Close();
return;
}
// Assign login ID
client.UserID = result.UserID;
// Send "online" notification to all friends of this player
foreach (var fconn in client.FriendConnections)
{
fconn.RPC.Send(new FriendsPresenceMessage
{
CurrentServer = client.DedicatedServer == null ? 0 : client.DedicatedServer.UserID,
Friend = client.UserID,
Presence = client.PresenceData,
PresenceState = client.DedicatedServer == null ? 1 : 2
});
}
// Send friends roster to player
client.RPC.Send(new FriendsRosterMessage
{
Friends = client.Friends.ToArray()
});
OnClientAuthenticated(client);
});
client.RPC.AttachHandlerForMessageType<AuthenticateWithTokenMessage>(msg =>
{
var result = new NPAuthenticationResult();
if (AuthenticationHandler != null)
{
try
{
result = AuthenticationHandler.AuthenticateUser(client, msg.Token);
}
catch (Exception error)
{
_log.Error("Error occurred in authentication handler", error);
}
}
// Send authentication result directly to client
client.RPC.Send(new AuthenticateResultMessage
{
NPID = result.UserID == null ? 0 : result.UserID.ConvertToUint64(),
Result = result.Result ? 0 : 1,
SessionToken = msg.Token
});
// Authentication failed => close connection
if (!result.Result)
{
client.RPC.Close();
return;
}
// Assign login ID
client.UserID = result.UserID;
// Send "online" notification to all friends of this player
foreach (var fconn in client.FriendConnections)
{
fconn.RPC.Send(new FriendsPresenceMessage
{
CurrentServer = client.DedicatedServer == null ? 0 : client.DedicatedServer.UserID,
Friend = client.UserID,
Presence = client.PresenceData,
PresenceState = client.DedicatedServer == null ? 1 : 2
});
}
// Send friends roster to player
client.RPC.Send(new FriendsRosterMessage
{
Friends = client.Friends.ToArray()
});
OnClientAuthenticated(client);
});
client.RPC.AttachHandlerForMessageType<AuthenticateValidateTicketMessage>(msg =>
{
var validTicket = false;
if (!client.IsDirty)
{
Ticket ticketData = null;
try
{
ticketData = new Ticket(msg.Ticket);
_log.DebugFormat("Ticket[Version={0},ServerID={1},Time={2}]", ticketData.Version,
ticketData.ServerID, ticketData.Time);
}
catch (ArgumentException error)
{
_log.Warn("Got some weird-length ticket data", error);
}
if (ticketData != null)
{
if (ticketData.Version == 1) // Version 1 enforcement
{
if (ticketData.ClientID == client.UserID) // NPID enforcement
{
var s =
_clients.Where(c => c.IsServer && !c.IsDirty && c.UserID == ticketData.ServerID)
.ToArray();
if (s.Any())
{
// TODO: Time validation. Problem is some clocks go wrong by minutes!
client.DedicatedServer = s.First();
validTicket = true;
_log.Debug("Ticket validated");
}
else
{
_log.Warn("Ticket invalid, could not find any sane servers with requested server ID");
}
}
else
{
_log.Warn("Ticket invalid, found NPID spoofing attempt");
}
}
else
{
_log.Warn("Ticket invalid, found invalid version");
}
}
}
else
{
_log.Warn("Ticket invalid, client is marked as dirty");
}
// Invalid data buffer
client.RPC.Send(new AuthenticateValidateTicketResultMessage
{
GroupID = client.GroupID,
NPID = client.UserID,
Result = validTicket ? 0 : 1
});
});
#endregion
#region RPC friend message handlers
client.RPC.AttachHandlerForMessageType<FriendsSetPresenceMessage>(msg =>
{
foreach (var pdata in msg.Presence)
{
client.SetPresence(pdata.Key, pdata.Value);
_log.DebugFormat("Client says presence \"{0}\" is \"{1}\"", pdata.Key, pdata.Value);
}
});
client.RPC.AttachHandlerForMessageType<FriendsGetUserAvatarMessage>(msg =>
{
// TODO: Not compatible with non-public accounts
var npid = new CSteamID((uint) msg.Guid, EUniverse.Public,
EAccountType.Individual).ConvertToUint64();
var avatar = UserAvatarHandler.GetUserAvatar(npid) ?? UserAvatarHandler.GetDefaultAvatar();
client.RPC.Send(new FriendsGetUserAvatarResultMessage
{
FileData = avatar,
Guid = msg.Guid,
Result = avatar != null ? 0 : 1
});
});
client.RPC.AttachHandlerForMessageType<FriendsGetProfileDataMessage>(msg =>
{
// TODO
});
#endregion
#region RPC storage message handlers
client.RPC.AttachHandlerForMessageType<StorageGetPublisherFileMessage>(msg =>
{
if (FileServingHandler == null)
{
client.RPC.Send(new StoragePublisherFileMessage
{
MessageId = msg.MessageId,
Result = 2,
FileName = msg.FileName
});
return;
}
try
{
if (client.UserID == null)
{
client.RPC.Send(new StoragePublisherFileMessage
{
MessageId = msg.MessageId,
Result = 2,
FileName = msg.FileName
});
_log.WarnFormat("Client tried to read publisher file {0} while not logged in", msg.FileName);
return;
}
var data = FileServingHandler.ReadPublisherFile(client, msg.FileName);
if (data == null)
{
client.RPC.Send(new StoragePublisherFileMessage
{
MessageId = msg.MessageId,
Result = 1,
FileName = msg.FileName
});
_log.DebugFormat("Could not open publisher file {0}", msg.FileName);
return;
}
client.RPC.Send(new StoragePublisherFileMessage
{
MessageId = msg.MessageId,
Result = 0,
FileName = msg.FileName,
FileData = data
});
_log.DebugFormat("Sent publisher file {0}", msg.FileName);
}
catch (Exception error)
{
_log.Warn("GetPublisherFile handler error", error);
client.RPC.Send(new StoragePublisherFileMessage
{
MessageId = msg.MessageId,
Result = 2,
FileName = msg.FileName
});
}
});
client.RPC.AttachHandlerForMessageType<StorageGetUserFileMessage>(msg =>
{
if (FileServingHandler == null)
{
client.RPC.Send(new StorageUserFileMessage
{
MessageId = msg.MessageId,
Result = 2,
FileName = msg.FileName
});
return;
}
try
{
if (client.UserID == null)
{
client.RPC.Send(new StorageUserFileMessage
{
MessageId = msg.MessageId,
Result = 2,
FileName = msg.FileName,
NPID = client.UserID
});
_log.WarnFormat("Client tried to read user file {0} while not logged in", msg.FileName);
return;
}
var data = FileServingHandler.ReadUserFile(client, msg.FileName);
if (data == null)
{
client.RPC.Send(new StorageUserFileMessage
{
MessageId = msg.MessageId,
Result = 1,
FileName = msg.FileName,
NPID = client.UserID
});
_log.DebugFormat("Could not open user file {0}", msg.FileName);
return;
}
client.RPC.Send(new StorageUserFileMessage
{
MessageId = msg.MessageId,
Result = 0,
FileName = msg.FileName,
FileData = data,
NPID = client.UserID
});
_log.DebugFormat("Sent user file {0}", msg.FileName);
}
catch (Exception error)
{
_log.Warn("GetUserFile handler error", error);
client.RPC.Send(new StorageUserFileMessage
{
MessageId = msg.MessageId,
Result = 2,
FileName = msg.FileName
});
}
});
client.RPC.AttachHandlerForMessageType<StorageSendRandomStringMessage>(msg =>
{
// TODO: Handle "random string" messages
});
client.RPC.AttachHandlerForMessageType<StorageWriteUserFileMessage>(msg =>
{
if (FileServingHandler == null)
{
client.RPC.Send(new StorageWriteUserFileResultMessage
{
MessageId = msg.MessageId,
Result = 2,
FileName = msg.FileName
});
return;
}
try
{
if (client.UserID == null)
{
client.RPC.Send(new StorageWriteUserFileResultMessage
{
MessageId = msg.MessageId,
Result = 2,
FileName = msg.FileName,
NPID = client.UserID
});
_log.WarnFormat("Client tried to write user file {0} while not logged in", msg.FileName);
return;
}
FileServingHandler.WriteUserFile(client, msg.FileName, msg.FileData);
client.RPC.Send(new StorageWriteUserFileResultMessage
{
MessageId = msg.MessageId,
Result = 0,
FileName = msg.FileName,
NPID = client.UserID
});
_log.DebugFormat("Received and wrote user file {0}", msg.FileName);
}
catch (Exception error)
{
_log.Warn("WriteUserFile handler error", error);
client.RPC.Send(new StorageWriteUserFileResultMessage
{
MessageId = msg.MessageId,
Result = 2,
FileName = msg.FileName
});
}
});
// TODO: RPC message handling for MessagingSendData
#endregion
#region RPC server session message handler
#endregion
_clients.Add(client);
#if !DEBUG
try
{
#endif
_log.Debug("Client connected");
OnClientConnected(client);
#if !DEBUG
try
#endif
{
while (true)
{
var msg = client.RPC.Read();
if (msg == null)
break;
}
}
#if !DEBUG
catch (Exception error)
{
_log.Error("Error in RPC read loop", error);
}
#endif
_log.Debug("Client disconnected");
OnClientDisconnected(client);
#if !DEBUG
}
catch (Exception error)
{
_log.Error("Error in client handling loop", error);
client.RPC.Send(new CloseAppMessage {Reason = "Server-side error occurred, try again later."});
client.RPC.Close();
}
#endif
_clients.Remove(client);
}
/// <summary>
/// Triggered when a client has connected but is not authenticating yet.
/// </summary>
public event ClientEventHandler ClientConnected;
/// <summary>
/// Invokes the <see cref="ClientConnected" /> event.
/// </summary>
/// <param name="client">The client</param>
protected virtual void OnClientConnected(NPServerClient client)
{
var handler = ClientConnected;
var args = new ClientEventArgs(client);
if (handler != null) handler(this, args);
}
/// <summary>
/// Triggered when a client has disconnected.
/// </summary>
public event ClientEventHandler ClientDisconnected;
/// <summary>
/// Invokes the <see cref="ClientDisconnected" /> event.
/// </summary>
/// <param name="client">The client</param>
protected virtual void OnClientDisconnected(NPServerClient client)
{
var handler = ClientDisconnected;
var args = new ClientEventArgs(client);
if (handler != null) handler(this, args);
}
/// <summary>
/// Triggered when a client has authenticated successfully.
/// </summary>
public event ClientEventHandler ClientAuthenticated;
/// <summary>
/// Invokes the <see cref="ClientAuthenticated" /> event.
/// </summary>
/// <param name="client">The client</param>
protected virtual void OnClientAuthenticated(NPServerClient client)
{
var handler = ClientAuthenticated;
var args = new ClientEventArgs(client);
if (handler != null) handler(this, args);
}
}
}

View File

@ -1,59 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using NPSharp.RPC;
using NPSharp.RPC.Messages.Data;
using NPSharp.Steam;
namespace NPSharp.NP
{
/// <summary>
/// Represents a remote client connection to an NP server.
/// </summary>
public class NPServerClient
{
internal readonly NPServer NP;
internal readonly RPCServerStream RPC;
private readonly Dictionary<string, string> _presence = new Dictionary<string, string>();
internal NPServerClient DedicatedServer;
internal NPServerClient(NPServer np, RPCServerStream rpcclient)
{
NP = np;
RPC = rpcclient;
}
public CSteamID UserID { get; internal set; }
public IEnumerable<FriendDetails> Friends
{
get
{
return NP.FriendsHandler == null
? new FriendDetails[0]
: NP.FriendsHandler.GetFriends(this).ToArray();
}
}
public IEnumerable<NPServerClient> FriendConnections
{
get { return NP.Clients.Where(c => Friends.Any(f => f.NPID == c.UserID)); }
}
public FriendsPresence[] PresenceData
{
get { return _presence.Select(i => new FriendsPresence {Key = i.Key, Value = i.Value}).ToArray(); }
}
public bool IsServer { get; set; }
public bool IsDirty { get; set; }
public int GroupID { get; set; }
internal void SetPresence(string key, string value)
{
if (!_presence.ContainsKey(key))
_presence.Add(key, value);
else
_presence[key] = value;
}
}
}

View File

@ -1,93 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{1A5AC63A-250E-4BC8-B81A-822AC31F5E37}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>NPSharp</RootNamespace>
<AssemblyName>npsharp_server</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<ProductVersion>12.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<DefineConstants>TRACE;DEBUG;COMPILE_RPC,COMPILE_NP,COMPILE_AUTH</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<DefineConstants>TRACE;COMPILE_RPC,COMPILE_NP,COMPILE_AUTH</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<OutDir>$(SolutionDir)\bin\$(Configuration)\$(Platform)\</OutDir>
<IntDir>$(SolutionDir)\obj\$(TargetName)\$(Configuration)\$(Platform)\</IntDir>
<IntermediateOutputPath>$(SolutionDir)\obj\$(TargetName)\$(Configuration)\$(Platform)\</IntermediateOutputPath>
<BaseIntermediateOutputPath>$(SolutionDir)\obj\$(TargetName)\$(Configuration)\$(Platform)\</BaseIntermediateOutputPath>
<OutputPath>$(SolutionDir)\bin\$(Configuration)\$(Platform)\</OutputPath>
</PropertyGroup>
<ItemGroup>
<Reference Include="log4net, Version=1.2.13.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\log4net.2.0.3\lib\net40-full\log4net.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\Newtonsoft.Json.6.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="uhttpsharp, Version=0.1.5307.27114, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>$(SolutionDir)\packages\uHttpSharp.0.1.5.4\lib\net40\uhttpsharp.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Master\MasterServer.cs" />
<Compile Include="Authentication\SessionAuthenticationServer.cs" />
<Compile Include="Events\ClientEventArgs.cs" />
<Compile Include="Events\ClientEventHandler.cs" />
<Compile Include="Handlers\IAuthenticationHandler.cs" />
<Compile Include="Handlers\IFileServingHandler.cs" />
<Compile Include="Handlers\IFriendsHandler.cs" />
<Compile Include="Handlers\IUserAvatarHandler.cs" />
<Compile Include="NP\NPServer.cs" />
<Compile Include="NP\NPServerClient.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RPC\RPCServerStream.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Master\README.txt" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\client\NPSharp.Client.csproj">
<Project>{c6f941a5-82af-456a-9b3a-752e5b001035}</Project>
<Name>NPSharp.Client</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</Project>

View File

@ -1,39 +0,0 @@
using System.Reflection;
using System.Runtime.InteropServices;
// Allgemeine Informationen über eine Assembly werden über die folgenden
// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern,
// die mit einer Assembly verknüpft sind.
[assembly: AssemblyTitle("NPSharp server library")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Carl Kittelberger")]
[assembly: AssemblyProduct("NPSharp")]
[assembly: AssemblyCopyright("© 2014 Carl Kittelberger")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar
// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von
// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest.
[assembly: ComVisible(false)]
// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird
[assembly: Guid("0efdd0b5-fd69-48fd-b714-f4f0e032f417")]
// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten:
//
// Hauptversion
// Nebenversion
// Buildnummer
// Revision
//
// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern
// übernehmen, indem Sie "*" eingeben:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("1.0")]

View File

@ -1,19 +0,0 @@
#if COMPILE_RPC||COMPILE_NP
using System.Net.Sockets;
using NPSharp.RPC.Messages;
namespace NPSharp.RPC
{
/// <summary>
/// Represents a low-level stream which communicates with an NP client using RPC messages.
/// </summary>
public class RPCServerStream : RPCStream<RPCServerMessage, RPCClientMessage>
{
public RPCServerStream(Socket sock) : base(sock)
{
}
}
}
#endif

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.5.0.0" newVersion="4.5.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="log4net" version="2.0.3" targetFramework="net45" />
<package id="Newtonsoft.Json" version="6.0.3" targetFramework="net45" />
<package id="protobuf-net" version="2.0.0.668" targetFramework="net45" />
<package id="uHttpSharp" version="0.1.5.4" targetFramework="net45" />
</packages>