diff --git a/TGServiceInterface/Interop.cs b/TGServiceInterface/Interop.cs index fa5581f70b..25e2dc8ed5 100644 --- a/TGServiceInterface/Interop.cs +++ b/TGServiceInterface/Interop.cs @@ -36,17 +36,10 @@ namespace TGServiceInterface { try { - ChannelFactory channel = null; - try - { - Server.GetComponentAndChannel(out channel).InteropMessage(String.Join(" ", args)); - channel.Close(); - } - catch - { - if(channel != null) - channel.Abort(); - } + var channel = Server.CreateChannel(); + channel.CreateChannel().InteropMessage(String.Join(" ", args)); + Server.CloseChannel(channel); + } catch { } return 0; diff --git a/TGServiceInterface/Server.cs b/TGServiceInterface/Server.cs index 9e1d0f7f4a..60a67df326 100644 --- a/TGServiceInterface/Server.cs +++ b/TGServiceInterface/Server.cs @@ -1,201 +1,240 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Security; -using System.Security.Principal; -using System.ServiceModel; -namespace TGServiceInterface -{ - public class Server - { - /// - /// List of types that can be used with GetComponen - /// - public static readonly IList ValidInterfaces = new List { typeof(ITGByond), typeof(ITGChat), typeof(ITGCompiler), typeof(ITGConfig), typeof(ITGDreamDaemon), typeof(ITGRepository), typeof(ITGSService), typeof(ITGConnectivity), typeof(ITGAdministration), typeof(ITGInterop) }; - - /// - /// The maximum message size to and from a local server - /// - public static readonly long TransferLimitLocal = Int32.MaxValue; //2GB can't go higher - - /// - /// The maximum message size to and from a remote server - /// - public static readonly long TransferLimitRemote = 10485760; //10 MB - - /// - /// Base name of the communication pipe - /// they are formatted as MasterPipeName/ComponentName - /// - public static string MasterInterfaceName = "TGStationServerService"; - - /// - /// If this is set, we will try and connect to an HTTPS server running at this address - /// - static string HTTPSURL; - - /// - /// The port used by the service - /// - static ushort HTTPSPort = 38607; - - /// - /// Username for remote operations - /// - static string HTTPSUsername; - - /// - /// Password for remote operations - /// - static string HTTPSPassword; - - - public static void SetBadCertificateHandler(Func handler) - { - ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, error) => - { - string ErrorMessage; - switch (error) - { - case SslPolicyErrors.None: - return true; - case SslPolicyErrors.RemoteCertificateChainErrors: - ErrorMessage = "There are certificate chain errors."; - break; - case SslPolicyErrors.RemoteCertificateNameMismatch: - ErrorMessage = "The certificate name does not match."; - break; - case SslPolicyErrors.RemoteCertificateNotAvailable: - ErrorMessage = "The certificate doesn't exist in the trust store."; - break; - default: - ErrorMessage = "An unknown error occurred."; - break; - } - ErrorMessage = String.Format("The certificate failed to verify for {0}:{1}. {2} {3}", HTTPSURL, HTTPSPort, ErrorMessage, cert.ToString()); - return handler(ErrorMessage); - }; - } - - /// - /// Set the interface to look for services on the current computer - /// - public static void MakeLocalConnection() - { - HTTPSURL = null; - HTTPSPassword = null; - } - - /// - /// Set the interface to look for services on a remote computer - /// - /// - /// - public static void SetRemoteLoginInformation(string address, ushort port, string username, string password) - { - HTTPSURL = address; - HTTPSPort = port; - HTTPSUsername = username; - HTTPSPassword = password; - } - - /// - /// Returns the requested server component interface. This does not guarantee a successful connection - /// - /// The type of the component to retrieve - /// The correct component - public static T GetComponent() - { - return GetComponentAndChannel(out ChannelFactory ignored); - } - - public static T GetComponentAndChannel(out ChannelFactory outChannel) - { - var ToT = typeof(T); - if (!ValidInterfaces.Contains(ToT)) - throw new Exception("Invalid type!"); - var InterfaceName = typeof(T).Name; - if (HTTPSURL == null) - { - outChannel = new ChannelFactory( - new NetNamedPipeBinding { SendTimeout = new TimeSpan(0, 0, 30), MaxReceivedMessageSize = TransferLimitLocal }, new EndpointAddress(String.Format("net.pipe://localhost/{0}/{1}", MasterInterfaceName, InterfaceName))); //10 megs - outChannel.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation; - return outChannel.CreateChannel(); - } - - //okay we're going over - var binding = new WSHttpBinding() - { - SendTimeout = new TimeSpan(0, 0, 40), - MaxReceivedMessageSize = TransferLimitRemote - }; - var requireAuth = InterfaceName != typeof(ITGConnectivity).Name; - binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None; - binding.Security.Mode = requireAuth ? SecurityMode.TransportWithMessageCredential : SecurityMode.Transport; //do not require auth for a connectivity check - binding.Security.Message.ClientCredentialType = requireAuth ? MessageCredentialType.UserName : MessageCredentialType.None; - var address = new EndpointAddress(String.Format("https://{0}:{1}/{2}/{3}", HTTPSURL, HTTPSPort, MasterInterfaceName, InterfaceName)); - outChannel = new ChannelFactory(binding, address); - if (requireAuth) - { - outChannel.Credentials.UserName.UserName = HTTPSUsername; - outChannel.Credentials.UserName.Password = HTTPSPassword; - } - return outChannel.CreateChannel(); - } - - /// - /// Used to test if the service is avaiable on the machine - /// Note that state can technically change at any time - /// and any call to the service may throw an exception because it failed - /// - /// null on successful connection, error message on failure - public static string VerifyConnection() - { - try - { - GetComponent().VerifyConnection(); - return null; - } - catch (Exception e) - { - return e.ToString(); - } - } - - /// - /// As opposed to VerifyConnection(), this check user credentials - /// Requires a prior call to - /// - /// true if credentials are valid, false otherwise - public static bool Authenticate() - { - try - { - GetComponent().Version(); - return true; - } - catch - { - return false; - } - } - - /// - /// As opposed to Authentication() this returns true if the current login can use the interface. - /// Requires a prior call to - /// - /// true if the connection may use the interface, false otherwise - public static bool AuthenticateAdmin() - { - try - { - GetComponent().GetCurrentAuthorizedGroup(); - return true; - } - catch - { - return false; - } - } - } -} +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Security; +using System.Security.Principal; +using System.ServiceModel; +namespace TGServiceInterface +{ + public class Server + { + /// + /// List of types that can be used with GetComponen + /// + public static readonly IList ValidInterfaces = new List { typeof(ITGByond), typeof(ITGChat), typeof(ITGCompiler), typeof(ITGConfig), typeof(ITGDreamDaemon), typeof(ITGRepository), typeof(ITGSService), typeof(ITGConnectivity), typeof(ITGAdministration), typeof(ITGInterop) }; + + /// + /// The maximum message size to and from a local server + /// + public static readonly long TransferLimitLocal = Int32.MaxValue; //2GB can't go higher + + /// + /// The maximum message size to and from a remote server + /// + public static readonly long TransferLimitRemote = 10485760; //10 MB + + /// + /// Base name of the communication pipe + /// they are formatted as MasterPipeName/ComponentName + /// + public static string MasterInterfaceName = "TGStationServerService"; + + /// + /// If this is set, we will try and connect to an HTTPS server running at this address + /// + static string HTTPSURL; + + /// + /// The port used by the service + /// + static ushort HTTPSPort = 38607; + + /// + /// Username for remote operations + /// + static string HTTPSUsername; + + /// + /// Password for remote operations + /// + static string HTTPSPassword; + + static Dictionary ChannelFactoryCache = new Dictionary(); + public static void SetBadCertificateHandler(Func handler) + { + ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, error) => + { + string ErrorMessage; + switch (error) + { + case SslPolicyErrors.None: + return true; + case SslPolicyErrors.RemoteCertificateChainErrors: + ErrorMessage = "There are certificate chain errors."; + break; + case SslPolicyErrors.RemoteCertificateNameMismatch: + ErrorMessage = "The certificate name does not match."; + break; + case SslPolicyErrors.RemoteCertificateNotAvailable: + ErrorMessage = "The certificate doesn't exist in the trust store."; + break; + default: + ErrorMessage = "An unknown error occurred."; + break; + } + ErrorMessage = String.Format("The certificate failed to verify for {0}:{1}. {2} {3}", HTTPSURL, HTTPSPort, ErrorMessage, cert.ToString()); + return handler(ErrorMessage); + }; + } + + /// + /// Set the interface to look for services on the current computer + /// + public static void MakeLocalConnection() + { + HTTPSURL = null; + HTTPSPassword = null; + ClearCachedChannels(); + } + + static void ClearCachedChannels() + { + foreach (var I in ChannelFactoryCache) + CloseChannel(I.Value); + ChannelFactoryCache.Clear(); + } + + /// + /// Set the interface to look for services on a remote computer + /// + /// + /// + public static void SetRemoteLoginInformation(string address, ushort port, string username, string password) + { + HTTPSURL = address; + HTTPSPort = port; + HTTPSUsername = username; + HTTPSPassword = password; + ClearCachedChannels(); + } + + public static void CloseChannel(ChannelFactory cf) + { + try + { + cf.Close(); + } + catch + { + cf.Abort(); + } + } + + /// + /// Returns the requested server component interface. This does not guarantee a successful connection + /// + /// The type of the component to retrieve + /// The correct component + public static T GetComponent() + { + var tot = typeof(T); + ChannelFactory cf; + + lock (ChannelFactoryCache) + { + if (ChannelFactoryCache.ContainsKey(tot)) + try + { + return ((ChannelFactory)ChannelFactoryCache[tot]).CreateChannel(); + } + catch + { + ChannelFactoryCache[tot].Abort(); + ChannelFactoryCache.Remove(tot); + } + cf = CreateChannel(); + ChannelFactoryCache[tot] = cf; + } + return cf.CreateChannel(); + } + + public static ChannelFactory CreateChannel() + { + var ToT = typeof(T); + if (!ValidInterfaces.Contains(ToT)) + throw new Exception("Invalid type!"); + var InterfaceName = typeof(T).Name; + if (HTTPSURL == null) + { + var res2 = new ChannelFactory( + new NetNamedPipeBinding { SendTimeout = new TimeSpan(0, 0, 30), MaxReceivedMessageSize = TransferLimitLocal }, new EndpointAddress(String.Format("net.pipe://localhost/{0}/{1}", MasterInterfaceName, InterfaceName))); //10 megs + res2.Credentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation; + return res2; + } + + //okay we're going over + var binding = new WSHttpBinding() + { + SendTimeout = new TimeSpan(0, 0, 40), + MaxReceivedMessageSize = TransferLimitRemote + }; + var requireAuth = InterfaceName != typeof(ITGConnectivity).Name; + binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None; + binding.Security.Mode = requireAuth ? SecurityMode.TransportWithMessageCredential : SecurityMode.Transport; //do not require auth for a connectivity check + binding.Security.Message.ClientCredentialType = requireAuth ? MessageCredentialType.UserName : MessageCredentialType.None; + var address = new EndpointAddress(String.Format("https://{0}:{1}/{2}/{3}", HTTPSURL, HTTPSPort, MasterInterfaceName, InterfaceName)); + var res = new ChannelFactory(binding, address); + if (requireAuth) + { + res.Credentials.UserName.UserName = HTTPSUsername; + res.Credentials.UserName.Password = HTTPSPassword; + } + return res; + } + + /// + /// Used to test if the service is avaiable on the machine + /// Note that state can technically change at any time + /// and any call to the service may throw an exception because it failed + /// + /// null on successful connection, error message on failure + public static string VerifyConnection() + { + try + { + GetComponent().VerifyConnection(); + return null; + } + catch (Exception e) + { + return e.ToString(); + } + } + + /// + /// As opposed to VerifyConnection(), this check user credentials + /// Requires a prior call to + /// + /// true if credentials are valid, false otherwise + public static bool Authenticate() + { + try + { + GetComponent().Version(); + return true; + } + catch + { + return false; + } + } + + /// + /// As opposed to Authentication() this returns true if the current login can use the interface. + /// Requires a prior call to + /// + /// true if the connection may use the interface, false otherwise + public static bool AuthenticateAdmin() + { + try + { + GetComponent().GetCurrentAuthorizedGroup(); + return true; + } + catch + { + return false; + } + } + } +}