using System; using System.Collections.Generic; using System.Diagnostics; 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(); } public static bool VersionMismatch(out string errorMessage) { var splits = Server.GetComponent().Version().Split(' '); var theirs = new Version(splits[splits.Length - 1].Substring(1)); var ours = new Version(FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly().Location).FileVersion); if(theirs != ours) { errorMessage = String.Format("Version mismatch between interface version ({0}) and service version ({1}). Some functionality may crash this program.", ours, theirs); return true; } errorMessage = null; return false; } /// /// 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 { cf = ((ChannelFactory)ChannelFactoryCache[tot]); if (cf.State != CommunicationState.Opened) throw new Exception(); return cf.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; } } } }