//******************************************************************************************************
// ServiceHelper.cs - Gbtc
//
// Copyright © 2015, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 08/29/2006 - Pinal C. Patel
// Original version of source code generated.
// 11/30/2007 - Pinal C. Patel
// Modified the "design time" check in EndInit() method to use LicenseManager. UsageMode property
// instead of DesignMode property as the former is more accurate than the latter.
// 12/14/2007 - Pinal C. Patel
// Made monitoring of service health optional via the MonitorServiceHealth property.
// 09/30/2008 - J. Ritchie Carroll
// Converted to C#.
// 03/06/2009 - Pinal C. Patel
// Edited code comments.
// 06/19/2009 - Pinal C. Patel
// Modified Initialize() method to just load settings from the config file.
// 06/30/2009 - Pinal C. Patel
// Changed ServiceComponents to be a collection of object instead of ISupportLifecycle so it can
// host a wide range of object implementing various interfaces used by the ServiceHelper commands.
// 07/15/2009 - Pinal C. Patel
// Added AllowedRemoteUsers and ImpersonateRemoteUser properties as part of security.
// 07/16/2009 - Pinal C. Patel
// Added SupportTelnetSessions property so that telnet support can be optionally turned-on.
// 07/17/2009 - Pinal C. Patel
// Modified MonitorServiceHealth to default to false as it is not always needed.
// 07/30/2009 - Pinal C. Patel
// Modified to set the principal of the thread where client request are handled to that of the the
// process or remote client (if transmitted by the remote client upon connection).
// 08/07/2009 - Pinal C. Patel
// Subscribed to ErrorLogger.LoggingException event.
// 08/10/2009 - Josh L. Patterson
// Edited Comments
// 09/08/2009 - Pinal C. Patel
// Modified GetProcess(), GetConnectedClient() and GetClientRequestHandler() to use lambda
// functions instead of manual iteration of collection items.
// Renamed GetProcess(), GetConnectedClient() and GetClientRequestHandler() to FindProcess(),
// FindConnectedClient() and FindClientRequestHandler() respectively.
// 09/14/2009 - Stephen C. Wills
// Added new header and license agreement.
// 10/23/2009 - Pinal C. Patel
// Modified UpdateStatus() method to allow the type of update to be specified.
// 12/18/2009 - Pinal C. Patel
// Added message flooding control to UpdateStatus().
// 06/16/2010 - Pinal C. Patel
// Made changes necessary to implement role-based security.
// Added thread synchronization to list-based member variable.
// 10/14/2010 - Pinal C. Patel
// Updated security implementation to include the entire client request text including command
// and arguments instead of just the command.
// 12/16/2010 - Pinal C. Patel
// Added SupportSystemCommands property that can be used to enable or disable system-level access
// via the build-in commands.
// 01/04/2011 - J. Ritchie Carroll
// Changed default performance sampling interval on services to every 5 seconds instead of every
// second to reduce overhead and increase performance. Added a "-lifetime" option to the Health
// command to show lifetime performance statistics. Made sampling interval a user config option.
// 01/17/2011 - J. Ritchie Carroll
// Added default "version" and "time" service commands.
// 02/11/2011 - Pinal C. Patel
// Updated VerifySecurity() to work correctly with TCP integrated windows authentication.
// 03/09/2011 - Pinal C. Patel
// Moved UpdateType enumeration to GSF namespace in GSF.Core.dll for broader usage.
// 04/14/2011 - Pinal C. Patel
// Updated to use new serialization methods in GSF.Serialization class.
// 09/22/2011 - J. Ritchie Carroll
// Added Mono implementation exception regions.
// 02/17/2012 - Stephen C. Wills
// Added ReloadCryptoCache service command.
// 04/12/2012 - Pinal C. Patel
// Added new UnscheduleProcess() and RemoveScheduledProcess methods.
// Changed AddProcess(), AddScheduledProcess() and ScheduleProcess() method to return a boolean.
// 12/20/2012 - Starlynn Danyelle Gilliam
// Modified Header.
// 02/09/2016 - Pinal C. Patel
// Added new Files and Transfer commands.
//
//******************************************************************************************************
using GSF.Annotations;
using GSF.Collections;
using GSF.Communication;
using GSF.Configuration;
using GSF.Console;
using GSF.Diagnostics;
using GSF.ErrorManagement;
using GSF.IO;
using GSF.Reflection;
using GSF.Scheduling;
using GSF.Security;
using GSF.Security.Cryptography;
using GSF.Threading;
using GSF.Units;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security;
using System.Security.Principal;
using System.ServiceProcess;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web;
// ReSharper disable InconsistentlySynchronizedField
namespace GSF.ServiceProcess
{
#region [ Enumerations ]
///
/// Indicates the state of a Windows Service.
///
public enum ServiceState
{
///
/// Service has started.
///
Started,
///
/// Service has stopped.
///
Stopped,
///
/// Service has paused.
///
Paused,
///
/// Service has resumed.
///
Resumed,
///
/// Service has shutdown.
///
Shutdown
}
#endregion
///
/// Component that provides added functionality to a Windows Service.
///
///
///
///
///
///
[ToolboxBitmap(typeof(ServiceHelper))]
public class ServiceHelper : Component, ISupportLifecycle, ISupportInitialize, IProvideStatus, IPersistSettings
{
#region [ Members ]
// Nested Types
private class StatusUpdate
{
public StatusUpdate(Guid client, UpdateType type, string message)
{
Client = client;
Type = type;
Message = message;
}
public readonly Guid Client;
public readonly UpdateType Type;
public readonly string Message;
}
private class ClientFilter
{
public ClientFilter()
{
TypeInclusionFilters = new List();
TypeExclusionFilters = new List();
PatternInclusionFilters = new List();
PatternExclusionFilters = new List();
}
public bool PassesFilter(StatusUpdate update)
{
UpdateType updateType = update.Type;
string message = update.Message;
return TypeInclusionFilters.DefaultIfEmpty(update.Type).Any(type => type == updateType) &&
TypeExclusionFilters.All(type => type != updateType) &&
PatternInclusionFilters.DefaultIfEmpty("").Any(pattern => Regex.IsMatch(message, pattern, RegexOptions.IgnoreCase)) &&
PatternExclusionFilters.All(pattern => !Regex.IsMatch(message, pattern, RegexOptions.IgnoreCase));
}
public readonly List TypeInclusionFilters;
public readonly List TypeExclusionFilters;
public readonly List PatternInclusionFilters;
public readonly List PatternExclusionFilters;
}
private class ClientStatusUpdateConfiguration
{
#region [ Members ]
// Constants
private const int HighPriority = 2;
private const int LowPriority = 1;
// Fields
private readonly Guid m_clientID;
private readonly ClientFilter m_filter;
private readonly LogicalThread m_thread;
private readonly List> m_messageCounts;
private readonly ServiceHelper m_serviceHelper;
#endregion
#region [ Constructors ]
public ClientStatusUpdateConfiguration(Guid clientID, ServiceHelper serviceHelper)
{
m_clientID = clientID;
m_filter = new ClientFilter();
m_thread = serviceHelper.m_threadScheduler.CreateThread(2);
m_messageCounts = new List>();
m_serviceHelper = serviceHelper;
}
#endregion
#region [ Methods ]
public void PrioritizeUpdate(StatusUpdate update)
{
m_thread.Push(HighPriority, () => m_serviceHelper.SendUpdateClientStatusResponse(update.Client, update.Type, update.Message));
}
public void SendBroadcastUpdates(List updates)
{
m_thread.Push(LowPriority, () =>
{
UpdateType type = UpdateType.Information;
StringBuilder messageBuilder = new StringBuilder();
List messages = new List();
int messageCount = GetRecentMessageCount();
int suppressedMessageCount = 0;
foreach (StatusUpdate update in updates.Where(m_filter.PassesFilter))
{
// If the message count exceeds the maximum status update frequency, suppress the message
if (messageCount + messages.Count >= m_serviceHelper.m_maxStatusUpdatesFrequency)
{
suppressedMessageCount++;
continue;
}
// If the string builder is empty,
// set the initial state of type
if (messageBuilder.Length == 0)
type = update.Type;
// If the current update's type is not the same as the
// current message, start a new message with the new type
if (update.Type != type)
{
// Add the current message to the list of messages
messages.Add(new StatusUpdate(m_clientID, type, messageBuilder.ToString()));
// If the message count exceeds the maximum status update frequency, begin message suppression
if (messageCount + messages.Count >= m_serviceHelper.m_maxStatusUpdatesFrequency)
{
suppressedMessageCount++;
continue;
}
// Update the message type
// and reset the string builder
type = update.Type;
messageBuilder.Clear();
}
// Update the message and increment the counter variable
messageBuilder.Append(update.Message);
}
// If the maximum frequency has not been reached, add the final message to the list of messages;
// otherwise, add a message warning the user that messages were suppressed due to high frequency
if (messageCount + messages.Count < m_serviceHelper.m_maxStatusUpdatesFrequency)
messages.Add(new StatusUpdate(m_clientID, type, messageBuilder.ToString()));
else
messages.Add(new StatusUpdate(m_clientID, UpdateType.Warning, $"Suppressed {suppressedMessageCount:N0} status updates due to high frequency to avoid flooding.\r\n\r\n"));
// Send messages to the client
foreach (StatusUpdate message in messages)
m_serviceHelper.SendUpdateClientStatusResponse(message.Client, message.Type, message.Message);
// Add the number of messages sent to the list of messages for the current time
m_messageCounts.Add(Tuple.Create(DateTime.UtcNow, messages.Count));
});
}
public void UpdateFilters(ClientFilter mergeFilter, List removalIDs)
{
m_thread.Push(HighPriority, () =>
{
foreach (int filterID in removalIDs.OrderByDescending(id => id))
RemoveFilter(filterID);
m_filter.TypeInclusionFilters.AddRange(mergeFilter.TypeInclusionFilters);
m_filter.TypeExclusionFilters.AddRange(mergeFilter.TypeExclusionFilters);
m_filter.PatternInclusionFilters.AddRange(mergeFilter.PatternInclusionFilters);
m_filter.PatternExclusionFilters.AddRange(mergeFilter.PatternExclusionFilters);
});
}
public void ListFilters(ClientRequestInfo requestInfo)
{
m_thread.Push(HighPriority, () =>
{
StringBuilder messageBuilder = new StringBuilder();
StringBuilder commandBuilder = new StringBuilder();
int i = 0;
commandBuilder.Append("Filter");
// List type inclusion filters
if (m_filter.TypeInclusionFilters.Any())
{
messageBuilder.AppendLine("Type Inclusion Filters:");
foreach (UpdateType filter in m_filter.TypeInclusionFilters)
{
messageBuilder.AppendLine($"[{i++}] {filter}");
commandBuilder.Append($" -Include Type {Arguments.Escape(filter.ToString())}");
}
}
// List type exclusion filters
if (m_filter.TypeExclusionFilters.Any())
{
if (messageBuilder.Length > 0)
messageBuilder.AppendLine();
messageBuilder.AppendLine("Type Exclusion Filters:");
foreach (UpdateType filter in m_filter.TypeExclusionFilters)
{
messageBuilder.AppendLine($"[{i++}] {filter}");
commandBuilder.Append($" -Exclude Type {Arguments.Escape(filter.ToString())}");
}
}
// List pattern exclusion filters
if (m_filter.PatternInclusionFilters.Any())
{
if (messageBuilder.Length > 0)
messageBuilder.AppendLine();
messageBuilder.AppendLine("Pattern Inclusion Filters:");
foreach (string filter in m_filter.PatternInclusionFilters)
{
messageBuilder.AppendLine($"[{i++}] {filter}");
commandBuilder.Append($" -Include Regex {Arguments.Escape(filter)}");
}
}
// List pattern exclusion filters
if (m_filter.PatternExclusionFilters.Any())
{
if (messageBuilder.Length > 0)
messageBuilder.AppendLine();
messageBuilder.AppendLine("Pattern Exclusion Filters:");
foreach (string filter in m_filter.PatternExclusionFilters)
{
messageBuilder.AppendLine($"[{i++}] {filter}");
commandBuilder.Append($" -Exclude Regex {Arguments.Escape(filter)}");
}
}
// Display a message if no filters are defined
if (messageBuilder.Length == 0)
{
messageBuilder.AppendLine("No filters defined.");
}
else
{
string command = commandBuilder.ToString();
string argument = Arguments.Escape(command);
string url = HttpUtility.UrlEncode(command);
messageBuilder.AppendLine();
messageBuilder.AppendLine("Command:");
messageBuilder.AppendLine(command);
messageBuilder.AppendLine();
messageBuilder.AppendLine("Argument:");
messageBuilder.AppendLine(argument);
messageBuilder.AppendLine();
messageBuilder.AppendLine("URL:");
messageBuilder.AppendLine(url);
}
// Send the message to the client
m_serviceHelper.SendActionableResponse(requestInfo, true, null, messageBuilder.ToString().TrimEnd());
});
}
private int GetRecentMessageCount()
{
// Get the total number of messages sent to the client in the last second
DateTime lastSecond = DateTime.UtcNow.AddSeconds(-1.0D);
int oldestMessageIndex = m_messageCounts.TakeWhile(tuple => tuple.Item1 < lastSecond).Count();
m_messageCounts.RemoveRange(0, oldestMessageIndex);
return m_messageCounts.Sum(tuple => tuple.Item2);
}
private void RemoveFilter(int filterID)
{
int index = filterID;
// Attempt to remove a filter from the list of type inclusion filters
if (index < m_filter.TypeInclusionFilters.Count)
{
m_filter.TypeInclusionFilters.RemoveAt(index);
return;
}
index -= m_filter.TypeInclusionFilters.Count;
// Attempt to remove a filter from the list of type exclusion filters
if (index < m_filter.TypeExclusionFilters.Count)
{
m_filter.TypeExclusionFilters.RemoveAt(index);
return;
}
index -= m_filter.TypeExclusionFilters.Count;
// Attempt to remove a filter from the list of pattern inclusion filters
if (index < m_filter.PatternInclusionFilters.Count)
{
m_filter.PatternInclusionFilters.RemoveAt(index);
return;
}
index -= m_filter.PatternInclusionFilters.Count;
// Attempt to remove a filter from the list of pattern exclusion filters
if (index < m_filter.PatternExclusionFilters.Count)
m_filter.PatternExclusionFilters.RemoveAt(index);
}
#endregion
}
// Delegates
private delegate bool TryGetClientPrincipalFunctionSignature(Guid clientID, out WindowsPrincipal clientPrincipal);
// Constants
///
/// Specifies the default value for the property.
///
public const bool DefaultLogStatusUpdates = true;
///
/// Specifies the default value for the property.
///
public const int DefaultMaxStatusUpdatesLength = 8192;
///
/// Specifies the default value for the property.
///
public const int DefaultMaxStatusUpdatesFrequency = 30;
///
/// Specifies the default value for the property.
///
public const bool DefaultMonitorServiceHealth = false;
///
/// Specifies the default value for the property.
///
public const double DefaultHealthMonitorInterval = 5.0D;
///
/// Specifies the default value for the property.
///
public const int DefaultRequestHistoryLimit = 50;
///
/// Specifies the default value for the property.
///
public const bool DefaultSupportFileManagementCommands = false;
///
/// Specifies the default value for the property.
///
public const bool DefaultSupportTelnetSessions = false;
///
/// Specifies the default value for the property.
///
public const bool DefaultSupportSystemCommands = false;
///
/// Specifies the default value for the property.
///
public const bool DefaultSecureRemoteInteractions = false;
///
/// Specifies the default value for the property.
///
public const SerializationFormat DefaultSerializationFormat = SerializationFormat.Binary;
///
/// Specifies the default value for the property.
///
public const bool DefaultPersistSettings = false;
///
/// Specifies the default value for the property.
///
public const string DefaultSettingsCategory = "ServiceHelper";
// Events
///
/// Occurs when the is starting.
///
[Category("Service"),
Description("Occurs when the ParentService is starting.")]
public event EventHandler> ServiceStarting;
///
/// Occurs when the has started.
///
[Category("Service"),
Description("Occurs when the ParentService has started.")]
public event EventHandler ServiceStarted;
///
/// Occurs when the is stopping.
///
[Category("Service"),
Description("Occurs when the ParentService is stopping.")]
public event EventHandler ServiceStopping;
///
/// Occurs when the has stopped.
///
[Category("Service"),
Description("Occurs when the ParentService has stopped.")]
public event EventHandler ServiceStopped;
///
/// Occurs when the is pausing.
///
[Category("Service"),
Description("Occurs when the ParentService is pausing.")]
public event EventHandler ServicePausing;
///
/// Occurs when the has paused.
///
[Category("Service"),
Description("Occurs when the ParentService has paused.")]
public event EventHandler ServicePaused;
///
/// Occurs when the is resuming.
///
[Category("Service"),
Description("Occurs when the ParentService is resuming.")]
public event EventHandler ServiceResuming;
///
/// Occurs when the has resumed.
///
[Category("Service"),
Description("Occurs when the ParentService has resumed.")]
public event EventHandler ServiceResumed;
///
/// Occurs when the system is being shutdown.
///
[Category("System"),
Description("Occurs when the system is being shutdown.")]
public event EventHandler SystemShutdown;
///
/// Occurs when a request is received from one of the .
///
///
/// is the ID of the remote client that sent the request.
/// is the sent by the remote client.
///
[Category("Client"),
Description("Occurs when a request is received from one of the RemoteClients.")]
public event EventHandler> ReceivedClientRequest;
///
/// Occurs when the state of a defined changes.
///
///
/// is the .
/// is the
///
[Category("Process"),
Description("Occurs when the state of a defined ServiceProcess changes.")]
public event EventHandler> ProcessStateChanged;
///
/// Provides notification of when status messages were sent to consumer(s).
///
///
/// is the ID of the consumer(s).
/// is the status message sent to consumer(s).
/// is the message .
///
[Category("Notification"),
Description("Occurs when there are status messages sent to consumer(s).")]
public event EventHandler> UpdatedStatus;
///
/// Provides notification of when there is an exception logged.
///
///
/// is the exception that was logged.
///
[Category("Notification"),
Description("Occurs when there is an exception logged.")]
public event EventHandler> LoggedException;
// Fields
private bool m_logStatusUpdates;
private int m_maxStatusUpdatesLength;
private int m_maxStatusUpdatesFrequency;
private bool m_monitorServiceHealth;
private double m_healthMonitorInterval;
private int m_requestHistoryLimit;
private bool m_supportFileManagementCommands;
private bool m_supportTelnetSessions;
private bool m_supportSystemCommands;
private bool m_secureRemoteInteractions;
private SerializationFormat m_serializationFormat;
private bool m_persistSettings;
private string m_settingsCategory;
private string m_telnetSessionPassword;
private ServiceBase m_parentService;
private ServerBase m_remotingServer;
private readonly LogFile m_statusLog;
private readonly ScheduleManager m_processScheduler;
private readonly ErrorLogger m_errorLogger;
private PerformanceMonitor m_performanceMonitor;
private readonly List m_processes;
private readonly List