From 1297afc0ee27e0423d3b3dcdb8c28ac2e913b138 Mon Sep 17 00:00:00 2001 From: icedream Date: Fri, 9 May 2014 14:33:10 +0200 Subject: [PATCH] Documentation and HTTP keep-alive tweak. --- .../SessionAuthenticationClient.cs | 118 ++++++++++++++++-- .../SessionAuthenticationServer.cs | 115 +++++++++++++---- src/libnpsharp/NPServer.cs | 19 ++- 3 files changed, 210 insertions(+), 42 deletions(-) diff --git a/src/libnpsharp/Authentication/SessionAuthenticationClient.cs b/src/libnpsharp/Authentication/SessionAuthenticationClient.cs index 3838bfa..22a7176 100644 --- a/src/libnpsharp/Authentication/SessionAuthenticationClient.cs +++ b/src/libnpsharp/Authentication/SessionAuthenticationClient.cs @@ -1,8 +1,10 @@ using System; using System.IO; using System.Net; +using System.Security.Authentication; using System.Text; using System.Text.RegularExpressions; +using log4net; namespace NPSharp.Authentication { @@ -15,6 +17,7 @@ namespace NPSharp.Authentication private readonly string _host; private readonly string _path; private readonly ushort _port; + private readonly ILog _log; /// /// Initializes a new instance of the class. @@ -27,6 +30,7 @@ namespace NPSharp.Authentication _host = host; _port = port; _path = path; + _log = LogManager.GetLogger("AuthClient"); } /// @@ -60,9 +64,24 @@ namespace NPSharp.Authentication /// The password to use for authentication. public void Authenticate(string username, string password) { - string post = string.Format("{0}&&{1}", username, password); + _log.Debug("Authentication running..."); + try + { + AuthenticateUsingNewFormat(username, password); + } + catch (InvalidCredentialException) + { + _log.Debug("Trying login again using old format"); + AuthenticateUsingOldFormat(username, password); + } + _log.Debug("Authentication successful."); + } - Uri uri = new UriBuilder + protected void AuthenticateUsingNewFormat(string username, string password) + { + var post = string.Format("user={0}&pass={1}", Uri.EscapeDataString(username), Uri.EscapeDataString(password)); + + var uri = new UriBuilder { Scheme = "http", Port = _port, @@ -70,14 +89,14 @@ namespace NPSharp.Authentication Path = _path }.Uri; - var req = (HttpWebRequest) WebRequest.Create(uri); + var req = (HttpWebRequest)WebRequest.Create(uri); req.Method = "POST"; req.ContentType = "application/x-www-form-urlencoded"; req.AllowAutoRedirect = true; - req.KeepAlive = false; - using (Stream reqStream = req.GetRequestStream()) + req.KeepAlive = true; + using (var reqStream = req.GetRequestStream()) { - byte[] buffer = Encoding.UTF8.GetBytes(post); + var buffer = Encoding.UTF8.GetBytes(post); reqStream.Write(buffer, 0, post.Length); reqStream.Flush(); } @@ -87,8 +106,8 @@ namespace NPSharp.Authentication var rx = new Regex( "^(?ok|fail)#(?.+)#(?[0-9]+)#(?.+)#(?.+)#(?[^#]+)[#]*$"); - var resp = (HttpWebResponse) req.GetResponse(); - using (Stream respStream = resp.GetResponseStream()) + var resp = (HttpWebResponse)req.GetResponse(); + using (var respStream = resp.GetResponseStream()) { if (respStream == null) throw new Exception(@"No answer from server"); @@ -96,7 +115,7 @@ namespace NPSharp.Authentication { while (!respReader.EndOfStream) { - string line = respReader.ReadLine(); + var line = respReader.ReadLine(); // No answer? if (string.IsNullOrEmpty(line)) @@ -107,20 +126,97 @@ namespace NPSharp.Authentication continue; // This is a DW response line, analyze - Match rxm = rx.Match(line); + var rxm = rx.Match(line); // Login succeeded? if (rxm.Groups["status"].Value != "ok") - throw new Exception(rxm.Groups["text"].Value); + { + throw new InvalidCredentialException(rxm.Groups["text"].Value); + } // Store all data Username = rxm.Groups["username"].Value; UserEMail = rxm.Groups["usermail"].Value; SessionToken = rxm.Groups["sessiontoken"].Value; UserId = uint.Parse(rxm.Groups["userid"].Value); + + return; } } } + + throw new Exception("This is not an authentication server"); + } + + protected void AuthenticateUsingOldFormat(string username, string password) + { + var post = string.Format("{0}&&{1}", username, password); + + var uri = new UriBuilder + { + Scheme = "http", + Port = _port, + Host = _host, + Path = _path + }.Uri; + + var req = (HttpWebRequest)WebRequest.Create(uri); + req.Method = "POST"; + req.ContentType = "application/x-www-form-urlencoded"; + req.AllowAutoRedirect = true; + req.KeepAlive = false; + using (var reqStream = req.GetRequestStream()) + { + var buffer = Encoding.UTF8.GetBytes(post); + reqStream.Write(buffer, 0, post.Length); + reqStream.Flush(); + } + + // Response will be in this syntax: + // (ok|fail)#text#userid#username#email#sessiontoken + var rx = + new Regex( + "^(?ok|fail)#(?.+)#(?[0-9]+)#(?.+)#(?.+)#(?[^#]+)[#]*$"); + var resp = (HttpWebResponse)req.GetResponse(); + using (var respStream = resp.GetResponseStream()) + { + if (respStream == null) + throw new Exception(@"No answer from server"); + using (var respReader = new StreamReader(respStream)) + { + while (!respReader.EndOfStream) + { + var line = respReader.ReadLine(); + + // No answer? + if (string.IsNullOrEmpty(line)) + continue; + + // DW response line found? + if (!rx.IsMatch(line)) + continue; + + // This is a DW response line, analyze + var rxm = rx.Match(line); + + // Login succeeded? + if (rxm.Groups["status"].Value != "ok") + { + throw new InvalidCredentialException(rxm.Groups["text"].Value); + } + + // Store all data + Username = rxm.Groups["username"].Value; + UserEMail = rxm.Groups["usermail"].Value; + SessionToken = rxm.Groups["sessiontoken"].Value; + UserId = uint.Parse(rxm.Groups["userid"].Value); + + return; + } + } + } + + throw new Exception("This is not an authentication server"); } } } \ No newline at end of file diff --git a/src/libnpsharp/Authentication/SessionAuthenticationServer.cs b/src/libnpsharp/Authentication/SessionAuthenticationServer.cs index 8b08c5f..3783ceb 100644 --- a/src/libnpsharp/Authentication/SessionAuthenticationServer.cs +++ b/src/libnpsharp/Authentication/SessionAuthenticationServer.cs @@ -12,28 +12,45 @@ using uhttpsharp.RequestProviders; namespace NPSharp.Authentication { + /// + /// Represents a session authentication server which uses the HTTP protocol to send out session tokens to authenticating NP clients. + /// public class SessionAuthenticationServer { + private readonly ILog _log; private HttpServer _http; - private readonly ILog _log; - + /// + /// Constructs a new session authentication server. + /// public SessionAuthenticationServer() { SupportOldAuthentication = true; _log = LogManager.GetLogger("Auth"); } + /// + /// Support oldskool "user&&pass" authentication format. + /// + public bool SupportOldAuthentication { get; set; } + + /// + /// Will be triggered whenever a client tries to authenticate via this server. + /// public event Func Authenticating; protected virtual SessionAuthenticationResult OnAuthenticating(string username, string password) { var handler = Authenticating; - return handler != null ? handler(username, password) : new SessionAuthenticationResult { Reason = "Login currently disabled" }; + return handler != null + ? handler(username, password) + : new SessionAuthenticationResult {Reason = "Login currently disabled"}; } - public bool SupportOldAuthentication { get; set; } - + /// + /// Starts the authentication server. + /// + /// The port on which the authentication server should listen on. public void Start(ushort port = 12003) { if (_http != null) @@ -45,12 +62,21 @@ namespace NPSharp.Authentication _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()); + ctx.Response = HttpResponse.CreateWithMessage(HttpResponseCode.NotFound, "Not found", + ctx.Request.Headers.KeepAliveConnection()); return Task.Factory.GetCompleted(); })); _http.Start(); } + /// + /// Stops the authentication server. + /// + public void Stop() + { + _http.Dispose(); + } + protected class AuthenticateHandler : IHttpRequestHandler { private readonly SessionAuthenticationServer _authServer; @@ -64,44 +90,79 @@ namespace NPSharp.Authentication { SessionAuthenticationResult sar; - var login = Encoding.UTF8.GetString(context.Request.Post.Raw) - .Split(new[] {"&&"}, StringSplitOptions.None); - if (login.Length != 2) + string username; + string password; + + if (!context.Request.Post.Parsed.TryGetByName("user", out username) || + !context.Request.Post.Parsed.TryGetByName("pass", out password)) { - sar = new SessionAuthenticationResult{Reason = @"Invalid login data"}; - } - else - { - try + var login = Encoding.UTF8.GetString(context.Request.Post.Raw) + .Split(new[] {"&&"}, StringSplitOptions.None); + if (login.Length != 2) { - sar = _authServer.OnAuthenticating(login[0], login[1]); - } - catch (Exception error) - { - _authServer._log.Error(@"Authentication handler error", error); - sar = new SessionAuthenticationResult { Reason = @"Internal server error" }; + 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]; } - context.Response = new HttpResponse(HttpResponseCode.Ok, sar.ToString(), context.Request.Headers.KeepAliveConnection()); + try + { + sar = _authServer.OnAuthenticating(username, password); + } + catch (Exception error) + { + _authServer._log.Error(@"Authentication handler error", error); + sar = new SessionAuthenticationResult {Reason = @"Internal server error"}; + } + + context.Response = new HttpResponse(HttpResponseCode.Ok, sar.ToString(), + !sar.Success && context.Request.Headers.KeepAliveConnection()); return Task.Factory.GetCompleted(); } } - - public void Stop() - { - _http.Dispose(); - } } public class SessionAuthenticationResult { + /// + /// true if authentication was successful, otherwise false. + /// public bool Success { get; set; } + + /// + /// Reason for the given success state. Use this especially in authentication fail cases. + /// public string Reason { get; set; } + + /// + /// If authenticated set this to the user's unique ID. + /// public uint UserID { get; set; } + + /// + /// If authenticated set this to the user's session token. + /// public string SessionToken { get; set; } + + /// + /// If authenticated set this to the actual correctly spelled username. + /// public string UserName { get; set; } + + /// + /// If authenticated set this to the user's e-mail address. + /// public string UserMail { get; set; } + + /// + /// Returns the response line as it should be sent out to the client. + /// public override string ToString() { return String.Join("#", @@ -114,4 +175,4 @@ namespace NPSharp.Authentication String.Empty); } } -} +} \ No newline at end of file diff --git a/src/libnpsharp/NPServer.cs b/src/libnpsharp/NPServer.cs index 4571596..02e0e58 100644 --- a/src/libnpsharp/NPServer.cs +++ b/src/libnpsharp/NPServer.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using log4net; @@ -196,7 +195,7 @@ namespace NPSharp client.UserID = result.UserID; // Send "online" notification to all friends of this player - foreach (NPServerClient fconn in client.FriendConnections) + foreach (var fconn in client.FriendConnections) { fconn.RPC.Send(new FriendsPresenceMessage { @@ -207,6 +206,12 @@ namespace NPSharp }); } + // Send friends roster to player + client.RPC.Send(new FriendsRosterMessage + { + Friends = client.Friends.ToArray() + }); + OnClientAuthenticated(client); }); @@ -244,7 +249,7 @@ namespace NPSharp client.UserID = result.UserID; // Send "online" notification to all friends of this player - foreach (NPServerClient fconn in client.FriendConnections) + foreach (var fconn in client.FriendConnections) { fconn.RPC.Send(new FriendsPresenceMessage { @@ -255,6 +260,12 @@ namespace NPSharp }); } + // Send friends roster to player + client.RPC.Send(new FriendsRosterMessage + { + Friends = client.Friends.ToArray() + }); + OnClientAuthenticated(client); }); @@ -331,7 +342,7 @@ namespace NPSharp client.RPC.AttachHandlerForMessageType(msg => { - foreach (FriendsPresence pdata in msg.Presence) + foreach (var pdata in msg.Presence) { client.SetPresence(pdata.Key, pdata.Value); _log.DebugFormat("Client says presence \"{0}\" is \"{1}\"", pdata.Key, pdata.Value);