2013-06-24 13:41:48 +02:00
/ * Copyright ( c ) Citrix Systems Inc .
* All rights reserved .
*
* Redistribution and use in source and binary forms ,
* with or without modification , are permitted provided
* that the following conditions are met :
*
* * Redistributions of source code must retain the above
* copyright notice , this list of conditions and the
* following disclaimer .
* * Redistributions in binary form must reproduce the above
* copyright notice , this list of conditions and the
* following disclaimer in the documentation and / or other
* materials provided with the distribution .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES ,
* INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT , INDIRECT , INCIDENTAL ,
* SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING ,
* BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS
* INTERRUPTION ) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY ,
* WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT ( INCLUDING
* NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE .
* /
using System ;
using System.ComponentModel ;
using System.Diagnostics ;
using System.Windows.Forms ;
using XenAdmin.Network ;
using System.Threading ;
using System.Collections.Generic ;
using System.IO ;
using XenAdmin.Core ;
using XenAPI ;
using XenAdmin.Plugins ;
using System.Xml ;
using System.Collections.ObjectModel ;
using System.Text ;
using XenAdmin.Dialogs ;
namespace XenAdmin.Actions
{
internal class ExternalPluginAction : AsyncAction
{
private static readonly log4net . ILog log = log4net . LogManager . GetLogger ( System . Reflection . MethodBase . GetCurrentMethod ( ) . DeclaringType ) ;
private const string EmptyParameter = "null" ;
private const string BlankParamter = "blank" ;
private static readonly string SnapInTrustedCertXml =
2016-01-15 02:57:36 +01:00
Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ) ,
2016-01-19 02:05:08 +01:00
Branding . COMPANY_NAME_SHORT , "\\XenServerPSSnapIn\\XenServer_Known_Certificates.xml" ) ;
2013-06-24 13:41:48 +02:00
private static readonly string SnapInTrustedCertXmlDir =
2016-01-15 02:57:36 +01:00
Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . ApplicationData ) ,
2016-01-19 02:05:08 +01:00
Branding . COMPANY_NAME_SHORT , "\\XenServerPSSnapIn" ) ;
2013-06-24 13:41:48 +02:00
private readonly ReadOnlyCollection < IXenObject > _targets = new ReadOnlyCollection < IXenObject > ( new List < IXenObject > ( ) ) ;
private readonly bool XenCenterNodeTarget = false ;
private readonly MenuItemFeature _menuItemFeature ;
private Process _extAppProcess ;
public ExternalPluginAction ( MenuItemFeature menuItemFeature , IEnumerable < IXenObject > targets , bool XenCenterNodeSelected )
: base ( null ,
string . Format ( Messages . EXTERNAL_PLUGIN_TITLE , menuItemFeature . ToString ( ) , menuItemFeature . PluginName ) ,
string . Format ( Messages . EXTERNAL_PLUGIN_RUNNING , menuItemFeature . ToString ( ) ) , false )
{
if ( targets ! = null )
{
_targets = new ReadOnlyCollection < IXenObject > ( new List < IXenObject > ( targets ) ) ;
if ( _targets . Count = = 1 )
{
Connection = this . _targets [ 0 ] . Connection ;
SetAppliesTo ( this . _targets [ 0 ] ) ;
}
}
XenCenterNodeTarget = XenCenterNodeSelected ;
ShowProgress = false ;
_menuItemFeature = menuItemFeature ;
}
protected override void Run ( )
{
try
{
// build up a list of params to pass to the process, checking each connection for permissions as we go
Dictionary < IXenConnection , bool > connectionsChecked = new Dictionary < IXenConnection , bool > ( ) ;
List < string > procParams = new List < string > ( ) ;
if ( XenCenterNodeTarget )
{
foreach ( IXenConnection c in ConnectionsManager . XenConnectionsCopy )
{
if ( c . IsConnected )
{
if ( ! connectionsChecked . ContainsKey ( c ) )
{
CheckPermission ( c ) ;
connectionsChecked . Add ( c , true ) ;
}
procParams . AddRange ( RetrieveParams ( c ) ) ;
}
}
}
if ( _targets ! = null )
{
foreach ( IXenObject o in _targets )
{
if ( ! connectionsChecked . ContainsKey ( o . Connection ) )
{
CheckPermission ( o . Connection ) ;
connectionsChecked . Add ( o . Connection , true ) ;
}
procParams . AddRange ( RetrieveParams ( o ) ) ;
}
}
_extAppProcess = _menuItemFeature . ShellCmd . CreateProcess ( procParams , _targets ) ;
_extAppProcess . OutputDataReceived + = new DataReceivedEventHandler ( _extAppProcess_OutputDataReceived ) ;
log . InfoFormat ( "Plugin process for {0} running with parameters {1}" , _extAppProcess . StartInfo . FileName , _extAppProcess . StartInfo . Arguments ) ;
_extAppProcess . Start ( ) ;
if ( _extAppProcess . StartInfo . RedirectStandardError )
_extAppProcess . BeginOutputReadLine ( ) ;
RecomputeCanCancel ( ) ;
try
{
while ( ! _extAppProcess . HasExited )
{
if ( Cancelling | | Cancelled )
throw new CancelledException ( ) ;
Thread . Sleep ( 500 ) ;
}
RecomputeCanCancel ( ) ;
if ( _extAppProcess . HasExited & & _extAppProcess . ExitCode ! = 0 )
{
log . ErrorFormat ( "Plugin process for {0} running with parameters {1} exited with Exit Code: {2}" , _extAppProcess . StartInfo . FileName , _extAppProcess . StartInfo . Arguments , _extAppProcess . ExitCode ) ;
throw new Exception ( String . Format ( Messages . EXTERNAL_PLUGIN_BAD_EXIT , _extAppProcess . ExitCode ) ) ;
}
}
catch ( InvalidOperationException )
{
if ( Cancelling | | Cancelled )
throw new CancelledException ( ) ;
}
Description = Messages . EXTERNAL_PLUGIN_FINISHED ;
}
catch ( Win32Exception ex )
{
log . Error ( "Error running plugin executable" , ex ) ;
throw new Win32Exception ( Messages . EXTERNAL_PLUGIN_WIN32 , ex ) ;
}
catch ( CancelledException )
{
Description = Messages . CANCELING ;
log . Error ( "User pressed cancel, sending close request and waiting" ) ;
if ( _extAppProcess ! = null & & ! _extAppProcess . HasExited )
{
_extAppProcess . CloseMainWindow ( ) ;
WatchForClose ( _extAppProcess ) ;
}
throw ;
}
finally
{
if ( _extAppProcess ! = null )
{
_extAppProcess . Close ( ) ;
_extAppProcess . Dispose ( ) ;
_extAppProcess = null ;
}
}
}
private void CheckPermission ( IXenConnection xenConnection )
{
ShellCmd cmd = _menuItemFeature . ShellCmd ;
RbacMethodList methodsToCheck = cmd . RequiredMethods . Count = = 0 ? _menuItemFeature . GetMethodList ( cmd . RequiredMethodList ) : cmd . RequiredMethods ;
2015-10-26 17:01:55 +01:00
if ( methodsToCheck = = null | | xenConnection . Session = = null | | xenConnection . Session . IsLocalSuperuser )
2013-06-24 13:41:48 +02:00
{
return ;
}
log . DebugFormat ( "Checking Plugin can run against connection {0}" , xenConnection . Name ) ;
List < Role > rolesAbleToCompleteAction ;
bool ableToCompleteAction = Role . CanPerform ( methodsToCheck , xenConnection , out rolesAbleToCompleteAction ) ;
log . DebugFormat ( "Roles able to complete action: {0}" , Role . FriendlyCSVRoleList ( rolesAbleToCompleteAction ) ) ;
log . DebugFormat ( "Subject {0} has roles: {1}" , xenConnection . Session . UserLogName , Role . FriendlyCSVRoleList ( xenConnection . Session . Roles ) ) ;
if ( ableToCompleteAction )
{
log . Debug ( "Subject authorized to complete action" ) ;
return ;
}
// Can't run on this connection, bail out
string desc = string . Format ( FriendlyErrorNames . RBAC_PERMISSION_DENIED_FRIENDLY_CONNECTION ,
xenConnection . Session . FriendlyRoleDescription ,
Role . FriendlyCSVRoleList ( rolesAbleToCompleteAction ) ,
xenConnection . Name ) ;
throw new Exception ( desc ) ;
}
private void WatchForClose ( Process _extAppProcess )
{
TimeSpan gracePeriod = new TimeSpan ( 0 , 0 , ( int ) _menuItemFeature . ShellCmd . DisposeTime ) ;
DateTime start = DateTime . Now ;
while ( DateTime . Now - start < gracePeriod )
{
if ( _extAppProcess . HasExited )
return ;
Thread . Sleep ( 1000 ) ;
}
if ( ! _extAppProcess . HasExited )
{
Program . Invoke ( Program . MainWindow , delegate
{
ThreeButtonDialog d = new ThreeButtonDialog (
new ThreeButtonDialog . Details ( System . Drawing . SystemIcons . Warning , string . Format ( Messages . FORCE_CLOSE_PLUGIN_PROMPT , _menuItemFeature . ToString ( ) ) ) ,
"ProcessForceClosePrompt" ,
new ThreeButtonDialog . TBDButton ( Messages . FORCE_CLOSE , DialogResult . Yes ) ,
new ThreeButtonDialog . TBDButton ( Messages . ALLOW_TO_CONTINUE , DialogResult . No ) ) ;
if ( d . ShowDialog ( Program . MainWindow ) = = DialogResult . Yes & & ! _extAppProcess . HasExited )
_extAppProcess . Kill ( ) ;
} ) ;
}
}
void _extAppProcess_OutputDataReceived ( object sender , DataReceivedEventArgs e )
{
log . Debug ( e . Data ) ;
}
// Returns a set of params which relate to the object you have selected in the treeview
private List < string > RetrieveParams ( IXenObject obj )
{
IXenConnection connection = obj . Connection ;
Host master = connection ! = null ? Helpers . GetMaster ( connection ) : null ; // get master asserts connection is not null
string masterAddress = EmptyParameter ;
if ( master ! = null )
{
masterAddress = Helpers . GetUrl ( master . Connection ) ;
WriteTrustedCertificates ( master . Connection ) ;
}
string sessionRef = connection . Session ! = null ? connection . Session . uuid : EmptyParameter ;
string objCls = obj ! = null ? obj . GetType ( ) . Name : EmptyParameter ;
string objUuid = obj ! = null & & connection . Session ! = null ? Helpers . GetUuid ( obj ) : EmptyParameter ;
return new List < string > ( new string [ ] { masterAddress , sessionRef , objCls , objUuid } ) ;
}
// Returns a set of params which relate to the connection in general, with no obj information
private List < string > RetrieveParams ( IXenConnection connection )
{
Host master = connection ! = null ? Helpers . GetMaster ( connection ) : null ; // get master asserts connection is not null
string masterAddress = EmptyParameter ;
if ( master ! = null )
{
masterAddress = Helpers . GetUrl ( master . Connection ) ;
WriteTrustedCertificates ( master . Connection ) ;
}
string sessionRef = connection . Session ! = null ? connection . Session . uuid : EmptyParameter ;
string objCls = BlankParamter ;
string objUuid = BlankParamter ;
return new List < string > ( new string [ ] { masterAddress , sessionRef , objCls , objUuid } ) ;
}
private void WriteTrustedCertificates ( IXenConnection connection )
{
if ( ! Directory . Exists ( SnapInTrustedCertXmlDir ) )
Directory . CreateDirectory ( SnapInTrustedCertXmlDir ) ; // CA-42052
Dictionary < string , string > trusted = Settings . KnownServers ;
if ( ! trusted . ContainsKey ( connection . Hostname ) )
return ;
XmlDocument doc = new XmlDocument ( ) ;
XmlNode cert = doc . CreateElement ( "certificate" ) ;
XmlAttribute hostname = doc . CreateAttribute ( "hostname" ) ;
XmlAttribute fingerprint = doc . CreateAttribute ( "fingerprint" ) ;
hostname . Value = connection . Hostname ;
fingerprint . Value = Helpers . PrettyFingerprint ( trusted [ connection . Hostname ] ) ;
cert . Attributes . Append ( hostname ) ;
cert . Attributes . Append ( fingerprint ) ;
XmlNode certs ;
if ( File . Exists ( SnapInTrustedCertXml ) )
{
doc . Load ( SnapInTrustedCertXml ) ;
certs = doc . SelectSingleNode ( "certificates" ) ;
foreach ( XmlNode node in certs . ChildNodes )
{
if ( node . Name = = "certificate" & & node . Attributes [ "hostname" ] ! = null & & node . Attributes [ "hostname" ] . Value = = hostname . Value )
certs . RemoveChild ( node ) ;
}
}
else
{
XmlNode decl = doc . CreateNode ( XmlNodeType . XmlDeclaration , "" , "" ) ;
certs = doc . CreateElement ( "certificates" ) ;
doc . AppendChild ( decl ) ;
doc . AppendChild ( certs ) ;
}
certs . AppendChild ( cert ) ;
doc . Save ( SnapInTrustedCertXml ) ;
}
public override void RecomputeCanCancel ( )
{
try
{
CanCancel = _extAppProcess ! = null & & ! _extAppProcess . HasExited ;
}
catch ( InvalidOperationException )
{
CanCancel = false ;
}
}
protected override string AuditDescription ( )
{
return string . Format ( "{0}: {1} {2} {3}" , this . GetType ( ) . Name , DescribeTargets ( ) , _menuItemFeature . ShellCmd . Filename , Description ) ;
}
protected string DescribeTargets ( )
{
StringBuilder sb = new StringBuilder ( ) ;
foreach ( IXenObject o in _targets )
{
if ( o is VM )
{
sb . Append ( DescribeVM ( o as VM ) ) ;
}
else if ( o is Pool )
{
sb . Append ( DescribePool ( o as Pool ) ) ;
}
else if ( o is Host )
{
sb . Append ( DescribeHost ( o as Host ) ) ;
}
else
{
sb . Append ( o . GetType ( ) . Name ) ;
sb . Append ( " " ) ;
sb . Append ( o . opaque_ref ) ;
sb . Append ( " : " ) ;
}
}
return sb . ToString ( ) ;
}
}
}