using System; using System.DirectoryServices.AccountManagement; using System.IO; using System.Security.Principal; using System.ServiceModel; using System.Threading; using TGServiceInterface; using TGServiceInterface.Components; namespace TGServerService { //note this only works with MACHINE LOCAL groups and admins for now //if someone wants AD shit, code it yourself sealed partial class ServerInstance : ServiceAuthorizationManager, ITGAdministration { /// /// The of the Windows group authorized to access the /// SecurityIdentifier TheDroidsWereLookingFor; /// /// Used for multithreading safety /// object authLock = new object(); /// /// The of the last to attempt to access the /// string LastSeenUser; /// /// The of the account the is running as /// readonly SecurityIdentifier ServiceSID = WindowsIdentity.GetCurrent().User; /// public string GetCurrentAuthorizedGroup() { try { if (TheDroidsWereLookingFor == null) return "ADMIN"; var pc = new PrincipalContext(ContextType.Machine); return GroupPrincipal.FindByIdentity(pc, IdentityType.Sid, TheDroidsWereLookingFor.Value).Name; } catch { return null; } } /// public string SetAuthorizedGroup(string groupName) { if (groupName == null) { TheDroidsWereLookingFor = null; var config = Properties.Settings.Default; config.AuthorizedGroupSID = null; config.Save(); return "ADMIN"; } return FindTheDroidsWereLookingFor(groupName); } /// /// Set based off either an ed name or a string from the config /// /// The name of the group to search for /// The name of the group allowed to access the if it could be found, otherwise string FindTheDroidsWereLookingFor(string search = null) { //find the group that is authorized to use the tools var pc = new PrincipalContext(ContextType.Machine); var config = Properties.Settings.Default; var groupName = search ?? config.AuthorizedGroupSID; if (String.IsNullOrWhiteSpace(groupName)) return null; var gp = GroupPrincipal.FindByIdentity(pc, search != null ? IdentityType.Name : IdentityType.Sid, groupName); if (gp == null) { if (search != null) //try again with all types gp = GroupPrincipal.FindByIdentity(pc, search); if (gp == null) return null; } TheDroidsWereLookingFor = gp.Sid; if (search != null) { config.AuthorizedGroupSID = TheDroidsWereLookingFor.Value; config.Save(); } return gp.Name; } /// /// Called by WCF whenever a component call is made. Checks to see that the supplied user account has access to the requested component /// /// Various parameters about the operation supplied by WCF /// if the supplied user account may use the requested component, otherwise protected override bool CheckAccessCore(OperationContext operationContext) { var contract = operationContext.EndpointDispatcher.ContractName; if (contract == typeof(ITGConnectivity).Name) //always allow connectivity checks return true; var windowsIdent = operationContext.ServiceSecurityContext.WindowsIdentity; if (contract == typeof(ITGInterop).Name) { //only allow the same user the service is running as to use interop, because that's what DD is running as, and don't spam the logs with it unless it fails var result = windowsIdent.User == ServiceSID; if(!result) Service.WriteAccess(windowsIdent.Name, false); return result; } var wp = new WindowsPrincipal(windowsIdent); //first allow admins var authSuccess = wp.IsInRole(WindowsBuiltInRole.Administrator); //if we're not an admin, check that we aren't trying to access the admin interface if (!authSuccess && operationContext.EndpointDispatcher.ContractName != typeof(ITGAdministration).Name && TheDroidsWereLookingFor != null) authSuccess = wp.IsInRole(new SecurityIdentifier(Properties.Settings.Default.AuthorizedGroupSID)); lock (authLock) { var user = windowsIdent.Name; if (LastSeenUser != user) { LastSeenUser = user; Service.WriteAccess(user, authSuccess); } } return authSuccess; } /// public ushort RemoteAccessPort() { return Properties.Settings.Default.RemoteAccessPort; } /// public string SetRemoteAccessPort(ushort port) { if (port == 0) return "Cannot bind to port 0"; var Config = Properties.Settings.Default; Config.RemoteAccessPort = port; Config.Save(); return null; } /// public string MoveServer(string new_location) { var Config = Properties.Settings.Default; try { var di1 = new DirectoryInfo(Config.ServerDirectory); var di2 = new DirectoryInfo(new_location); var copy = di1.Root.FullName != di2.Root.FullName; if (copy && File.Exists(PrivateKeyPath)) return String.Format("Unable to perform a cross drive server move with the {0}. Copy aborted!", PrivateKeyPath); new_location = di2.FullName; while (di2.Parent != null) if (di2.Parent.FullName == di1.FullName) return "Cannot move to child of current directory!"; else di2 = di2.Parent; if (!Monitor.TryEnter(RepoLock)) return "Repo locked!"; try { if (RepoBusy) return "Repo busy!"; DisposeRepo(); if (!Monitor.TryEnter(ByondLock)) return "BYOND locked"; try { if (updateStat != ByondStatus.Idle) return "BYOND busy!"; if (!Monitor.TryEnter(CompilerLock)) return "Compiler locked!"; try { if (compilerCurrentStatus != CompilerStatus.Uninitialized && compilerCurrentStatus != CompilerStatus.Initialized) return "Compiler busy!"; if (!Monitor.TryEnter(watchdogLock)) return "Watchdog locked!"; try { if (currentStatus != DreamDaemonStatus.Offline) return "Watchdog running!"; lock (configLock) { CleanGameFolder(); Program.DeleteDirectory(GameDir); string error = null; if (copy) { Program.CopyDirectory(Config.ServerDirectory, new_location); Directory.CreateDirectory(new_location); Environment.CurrentDirectory = new_location; try { Program.DeleteDirectory(Config.ServerDirectory); } catch (Exception e) { error = "The move was successful, but the path " + Config.ServerDirectory + " was unable to be deleted fully!"; Service.WriteWarning(String.Format("Server move from {0} to {1} partial success: {2}", Config.ServerDirectory, new_location, e.ToString()), EventID.ServerMovePartial); } } else { try { Environment.CurrentDirectory = di2.Root.FullName; Directory.Move(Config.ServerDirectory, new_location); Environment.CurrentDirectory = new_location; } catch { Environment.CurrentDirectory = Config.ServerDirectory; throw; } } Service.WriteInfo(String.Format("Server moved from {0} to {1}", Config.ServerDirectory, new_location), EventID.ServerMoveComplete); Config.ServerDirectory = new_location; return null; } } finally { Monitor.Exit(watchdogLock); } } finally { Monitor.Exit(CompilerLock); } } finally { Monitor.Exit(ByondLock); } } finally { Monitor.Exit(RepoLock); } } catch (Exception e) { Service.WriteError(String.Format("Server move from {0} to {1} failed: {2}", Config.ServerDirectory, new_location, e.ToString()), EventID.ServerMoveFailed); return e.ToString(); } } /// public string RecreateStaticFolder() { if (!Monitor.TryEnter(RepoLock)) return "Repo locked!"; try { if (!Monitor.TryEnter(watchdogLock)) return "Watchdog locked!"; try { if (!Monitor.TryEnter(configLock)) return "Static dir locked!"; try { if (currentStatus != DreamDaemonStatus.Offline) return "Watchdog running!"; BackupAndDeleteStaticDirectory(); InitialConfigureRepository(); } finally { Monitor.Exit(configLock); } } finally { Monitor.Exit(watchdogLock); } } catch(Exception e) { return e.ToString(); } finally { Monitor.Exit(RepoLock); } return null; } } }