Add WIP fallback Mojang authentication support

This commit is contained in:
UnknownShadow200 2023-02-22 23:12:55 +11:00
parent af62cdf0c0
commit 17eaca3a80
4 changed files with 95 additions and 18 deletions

View File

@ -168,33 +168,33 @@ namespace MCGalaxy.SQL
#region Low level functions #region Low level functions
/// <summary> Executes an SQL command that does not return any results. </summary> /// <summary> Executes an SQL command that does not return any results. </summary>
public static void Execute(string sql, params object[] args) { public static int Execute(string sql, params object[] args) {
Do(sql, false, null, args); return Do(sql, false, null, args);
} }
/// <summary> Executes an SQL query, invoking callback function on each returned row. </summary> /// <summary> Executes an SQL query, invoking callback function on each returned row. </summary>
public static void Iterate(string sql, ReaderCallback callback, params object[] args) { public static int Iterate(string sql, ReaderCallback callback, params object[] args) {
Do(sql, false, callback, args); return Do(sql, false, callback, args);
} }
internal static void Do(string sql, bool createDB, ReaderCallback callback, object[] args) { internal static int Do(string sql, bool createDB, ReaderCallback callback, object[] args) {
IDatabaseBackend db = Backend; IDatabaseBackend db = Backend;
Exception e = null; Exception e = null;
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
try { try {
if (callback != null) { if (callback != null) {
db.Iterate(sql, args, callback); return db.Iterate(sql, args, callback);
} else { } else {
db.Execute(sql, args, createDB); return db.Execute(sql, args, createDB);
} }
return;
} catch (Exception ex) { } catch (Exception ex) {
e = ex; // try yet again e = ex; // try yet again
} }
} }
Logger.LogError("Error executing SQL statement: " + sql, e); Logger.LogError("Error executing SQL statement: " + sql, e);
return 0;
} }
#endregion #endregion

View File

@ -170,7 +170,9 @@ namespace MCGalaxy.SQL
} }
/// <summary> Excecutes an SQL query, invoking a callback on the returned rows one by one. </summary> /// <summary> Excecutes an SQL query, invoking a callback on the returned rows one by one. </summary>
public void Iterate(string sql, object[] parameters, ReaderCallback callback) { public int Iterate(string sql, object[] parameters, ReaderCallback callback) {
int rows = 0;
using (ISqlConnection conn = CreateConnection()) { using (ISqlConnection conn = CreateConnection()) {
conn.Open(); conn.Open();
if (MultipleSchema) if (MultipleSchema)
@ -179,11 +181,12 @@ namespace MCGalaxy.SQL
using (ISqlCommand cmd = conn.CreateCommand(sql)) { using (ISqlCommand cmd = conn.CreateCommand(sql)) {
FillParams(cmd, parameters); FillParams(cmd, parameters);
using (ISqlReader reader = cmd.ExecuteReader()) { using (ISqlReader reader = cmd.ExecuteReader()) {
while (reader.Read()) { callback(reader); } while (reader.Read()) { callback(reader); rows++; }
} }
} }
conn.Close(); conn.Close();
} }
return rows;
} }

View File

@ -18,6 +18,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using MCGalaxy.Network; using MCGalaxy.Network;
namespace MCGalaxy.Authentication namespace MCGalaxy.Authentication
@ -27,6 +30,7 @@ namespace MCGalaxy.Authentication
public string URL; public string URL;
public string NameSuffix = ""; public string NameSuffix = "";
public string SkinPrefix = ""; public string SkinPrefix = "";
public bool MojangAuth;
} }
public class AuthService public class AuthService
@ -37,8 +41,24 @@ namespace MCGalaxy.Authentication
public Heartbeat Beat; public Heartbeat Beat;
public AuthServiceConfig Config; public AuthServiceConfig Config;
/// <summary> Attempts to authenticate the given player with the given mppass </summary>
public virtual bool Authenticate(Player p, string mppass) { public virtual bool Authenticate(Player p, string mppass) {
if (!VerifyLogin(p, mppass)) return false; string calculated = Server.CalcMppass(p.truename, Beat.Salt);
if (!mppass.CaselessEq(calculated)) return false;
AcceptPlayer(p);
return true;
}
public virtual bool FallbackAuthenticate(Player p) {
if (!Config.MojangAuth) return false;
if (!MojangAuth.HasJoined(p.truename)) return false;
AcceptPlayer(p);
return true;
}
protected virtual void AcceptPlayer(Player p) {
AuthServiceConfig cfg = Config; AuthServiceConfig cfg = Config;
p.verifiedName = true; p.verifiedName = true;
@ -47,13 +67,6 @@ namespace MCGalaxy.Authentication
p.name += cfg.NameSuffix; p.name += cfg.NameSuffix;
p.truename += cfg.NameSuffix; p.truename += cfg.NameSuffix;
p.DisplayName += cfg.NameSuffix; p.DisplayName += cfg.NameSuffix;
return true;
}
/// <summary> Whether the given player is allowed to login with the given mppass </summary>
protected virtual bool VerifyLogin(Player p, string mppass) {
string calculated = Server.CalcMppass(p.truename, Beat.Salt);
return mppass.CaselessEq(calculated);
} }
@ -132,6 +145,9 @@ namespace MCGalaxy.Authentication
} else if (key.CaselessEq("skin-prefix")) { } else if (key.CaselessEq("skin-prefix")) {
if (cur == null) return; if (cur == null) return;
cur.SkinPrefix = value; cur.SkinPrefix = value;
} else if (key.CaselessEq("mojang-auth")) {
if (cur == null) return;
bool.TryParse(value, out cur.MojangAuth);
} }
} }
@ -151,6 +167,9 @@ namespace MCGalaxy.Authentication
w.WriteLine("#skin-prefix = string"); w.WriteLine("#skin-prefix = string");
w.WriteLine("# Characters that are prefixed to skin name of players that login through the authentication service"); w.WriteLine("# Characters that are prefixed to skin name of players that login through the authentication service");
w.WriteLine("# (used to ensure players from other authentication services see the correct skin)"); w.WriteLine("# (used to ensure players from other authentication services see the correct skin)");
w.WriteLine("#mojang-auth = boolean");
w.WriteLine("# Whether to try verifying users using Mojang's authentication servers if mppass verification fails");
w.WriteLine("# NOTE: This should only be used for the Betacraft.uk authentication service");
w.WriteLine(); w.WriteLine();
foreach (AuthServiceConfig c in configs) foreach (AuthServiceConfig c in configs)
@ -158,9 +177,59 @@ namespace MCGalaxy.Authentication
w.WriteLine("URL = " + c.URL); w.WriteLine("URL = " + c.URL);
w.WriteLine("name-suffix = " + c.NameSuffix); w.WriteLine("name-suffix = " + c.NameSuffix);
w.WriteLine("skin-prefix = " + c.SkinPrefix); w.WriteLine("skin-prefix = " + c.SkinPrefix);
w.WriteLine("mojang-auth = " + c.MojangAuth);
w.WriteLine(); w.WriteLine();
} }
} }
} }
} }
public static class MojangAuth
{
const string HAS_JOINED_URL = "https://sessionserver.mojang.com/session/minecraft/hasJoined?username={0}&serverId={1}";
public static bool HasJoined(string username) {
string url = string.Format(HAS_JOINED_URL, username, GetServerID());
try
{
HttpWebRequest req = HttpUtil.CreateRequest(url);
req.Timeout = 5 * 1000;
req.ReadWriteTimeout = 5 * 1000;
using (HttpWebResponse response = (HttpWebResponse)req.GetResponse())
{
return response.StatusCode == HttpStatusCode.OK;
}
} catch (Exception ex) {
HttpUtil.DisposeErrorResponse(ex);
Logger.LogError("Verifying Mojang session for " + username, ex);
}
return false;
}
static string GetServerID() {
UpdateExternalIP();
byte[] data = Encoding.UTF8.GetBytes(externalIP + ":" + Server.Config.Port);
byte[] hash = new SHA1Managed().ComputeHash(data);
// TODO this is bad, redo it
return hash.Join(b => b.ToString("x2"), "");
}
static string externalIP;
static void UpdateExternalIP() {
if (externalIP != null) return;
try {
HttpWebRequest req = HttpUtil.CreateRequest("http://classicube.net/api/myip/");
using (WebResponse response = req.GetResponse())
{
externalIP = HttpUtil.GetResponseText(response);
}
} catch (Exception ex) {
Logger.LogError("Retrieving external IP", ex);
}
}
}
} }

View File

@ -38,6 +38,11 @@ namespace MCGalaxy.Authentication
if (auth.Authenticate(p, mppass)) return true; if (auth.Authenticate(p, mppass)) return true;
} }
foreach (AuthService auth in AuthService.Services)
{
if (auth.FallbackAuthenticate(p)) return true;
}
return !Server.Config.VerifyNames || IPUtil.IsPrivate(p.IP); return !Server.Config.VerifyNames || IPUtil.IsPrivate(p.IP);
} }