├── src
├── Version
│ ├── .gitignore
│ └── GlobalAssemblyInfo.cs
├── AppSecInc.ProcessDomain.UnitTests
│ ├── OtherApp.config
│ ├── packages.config
│ ├── App.config
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── RemoteTestObject.cs
│ ├── AppSecInc.ProcessDomain.UnitTests.csproj
│ └── TestProcessDomain.cs
├── AppSecInc.ProcessDomain
│ ├── Properties
│ │ ├── AssemblyInfo.cs
│ │ ├── Resources.Designer.cs
│ │ └── Resources.resx
│ ├── Remoting
│ │ ├── ProcessStatus.cs
│ │ ├── Attributes
│ │ │ ├── PropertyNameAttribute.cs
│ │ │ └── DefaultValueAttribute.cs
│ │ ├── DeleteOnUnloadException.cs
│ │ ├── IActivation.cs
│ │ ├── AssemblyGeneratorCompilerException.cs
│ │ ├── Activator.cs
│ │ ├── ActivatorClient.cs
│ │ ├── ActivatorHostAssemblyGenerator.cs
│ │ ├── ActivatorHost.cs
│ │ ├── RemotingOptions.cs
│ │ └── ActivatorProcess.cs
│ ├── Utils
│ │ ├── AssemblyUtils.cs
│ │ └── LoggingConfigurator.cs
│ ├── WinApi.cs
│ ├── PlatformTarget.cs
│ ├── ProcessDomain.cs
│ ├── AppSecInc.ProcessDomain.csproj
│ ├── Resources
│ │ └── Program.cs
│ └── ProcessDomainSetup.cs
├── AppSecInc.ProcessDomain.Sample
│ ├── RemoteException.cs
│ ├── App.config
│ ├── RemoteObject.cs
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── AppSecInc.ProcessDomain.Sample.csproj
│ └── Program.cs
└── AppSecInc.ProcessDomain.Host
│ ├── Properties
│ └── AssemblyInfo.cs
│ └── AppSecInc.ProcessDomain.Host.csproj
├── .nuget
├── NuGet.exe
├── packages.config
├── NuGet.Config
└── NuGet.targets
├── Version.proj
├── .github
└── workflows
│ ├── pull-request.yml
│ └── release.yml
├── ProcessDomain.nuspec
├── README.md
├── ProcessDomain.proj
├── .gitignore
├── ProcessDomain.sln
└── LICENSE
/src/Version/.gitignore:
--------------------------------------------------------------------------------
1 | !.gitignore
2 |
--------------------------------------------------------------------------------
/.nuget/NuGet.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/legendary-code/process-domain/HEAD/.nuget/NuGet.exe
--------------------------------------------------------------------------------
/.nuget/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.nuget/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain.UnitTests/OtherApp.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | [assembly: AssemblyTitle("ProcessDomain")]
5 | [assembly: Guid("ED103427-51E1-428c-AA90-6F89BB81940D")]
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/Remoting/ProcessStatus.cs:
--------------------------------------------------------------------------------
1 | namespace AppSecInc.ProcessDomain.Remoting
2 | {
3 | internal enum ProcessStatus
4 | {
5 | Active,
6 | Killed,
7 | Terminated
8 | }
9 | }
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain.UnitTests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/Utils/AssemblyUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace AppSecInc.ProcessDomain.Utils
4 | {
5 | public static class AssemblyUtils
6 | {
7 | public static string GetFilePathFromFileUri(string uri)
8 | {
9 | var fileUri = new Uri(uri);
10 | return Uri.UnescapeDataString(fileUri.AbsolutePath).Replace('/', '\\');
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain.UnitTests/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain.Sample/RemoteException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.Serialization;
3 |
4 | namespace AppSecInc.ProcessDomain.Sample
5 | {
6 | [Serializable]
7 | public class RemoteException : Exception
8 | {
9 | public RemoteException(SerializationInfo info, StreamingContext context)
10 | : base(info, context)
11 | {
12 | }
13 |
14 | public RemoteException(string message)
15 | : base(message)
16 | {
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/Remoting/Attributes/PropertyNameAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace AppSecInc.ProcessDomain.Remoting.Attributes
6 | {
7 | [AttributeUsage(AttributeTargets.Property)]
8 | public sealed class PropertyNameAttribute : Attribute
9 | {
10 | public PropertyNameAttribute(string name)
11 | {
12 | Name = name;
13 | }
14 |
15 | public PropertyNameAttribute()
16 | {
17 | }
18 |
19 | public string Name { get; set; }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/Remoting/Attributes/DefaultValueAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace AppSecInc.ProcessDomain.Remoting.Attributes
6 | {
7 | [AttributeUsage(AttributeTargets.Property)]
8 | public sealed class DefaultValueAttribute : Attribute
9 | {
10 | public DefaultValueAttribute(object value)
11 | {
12 | Value = value;
13 | }
14 |
15 | public DefaultValueAttribute()
16 | {
17 | }
18 |
19 | public object Value { get; set; }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/Remoting/DeleteOnUnloadException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.Serialization;
3 |
4 | namespace AppSecInc.ProcessDomain.Remoting
5 | {
6 | [Serializable]
7 | public class DeleteOnUnloadException : Exception
8 | {
9 | public DeleteOnUnloadException(SerializationInfo info, StreamingContext context)
10 | : base(info, context)
11 | {
12 | }
13 |
14 | public DeleteOnUnloadException(string message, Exception innerException)
15 | : base(message, innerException)
16 | {
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Version.proj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Process Domain
4 |
5 |
6 |
7 |
8 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/Version/GlobalAssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | [assembly: System.Runtime.InteropServices.ComVisible(false)]
12 | [assembly: System.Reflection.AssemblyProduct("Process Domain")]
13 | [assembly: System.Reflection.AssemblyFileVersion("1.10.0.0")]
14 | [assembly: System.Reflection.AssemblyVersion("1.10.0.0")]
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/WinApi.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 | using System.Text;
3 |
4 | namespace AppSecInc.ProcessDomain
5 | {
6 | internal static class WinApi
7 | {
8 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
9 | [return: MarshalAs(UnmanagedType.Bool)]
10 | public static extern bool SetDllDirectory(string lpPathName);
11 |
12 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
13 | public static extern int GetDllDirectory(int bufferLength, StringBuilder directory);
14 |
15 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
16 | public static extern void SetLastError(uint dwErrCode);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.github/workflows/pull-request.yml:
--------------------------------------------------------------------------------
1 | name: Pull Request
2 |
3 | on: pull_request
4 |
5 | jobs:
6 | build:
7 | runs-on: windows-latest
8 | steps:
9 | - name: Checkout
10 | uses: actions/checkout@v1
11 |
12 | - name: Setup MSBuild
13 | uses: microsoft/setup-msbuild@v1.0.2
14 |
15 | - name: Setup NuGet
16 | uses: nuget/setup-nuget@v1
17 |
18 | - name: Setup VSTest
19 | uses: darenm/Setup-VSTest@v1
20 |
21 | - name: Install NuGet Packages
22 | run: nuget restore ProcessDomain.sln
23 |
24 | - name: Build
25 | run: msbuild ProcessDomain.sln
26 |
27 | - name: Run Tests
28 | working-directory: src\AppSecInc.ProcessDomain.UnitTests\bin\Debug
29 | run: vstest.console.exe AppSecInc.ProcessDomain.UnitTests.dll
30 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/Remoting/IActivation.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using System.Reflection;
3 | using System.Security.Policy;
4 |
5 | namespace AppSecInc.ProcessDomain.Remoting
6 | {
7 | ///
8 | /// Interface that the activator and process domain must implement to expose methods for creating object instances in a remote process
9 | ///
10 | public interface IActivation
11 | {
12 | object CreateInstanceAndUnwrap(string assemblyName, string typeName);
13 | object CreateInstanceAndUnwrap(string assemblyName, string typeName, object[] activationAttributes);
14 | object CreateInstanceAndUnwrap(string assemblyName, string typeName, bool ignoreCase, BindingFlags bindingAttr, Binder binder, object[] args, CultureInfo culture, object[] activationAttributes, Evidence securityAttributes);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/PlatformTarget.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace AppSecInc.ProcessDomain
4 | {
5 | public enum PlatformTarget
6 | {
7 | AnyCPU,
8 | x86,
9 | Itanium,
10 | x64
11 | }
12 |
13 | public static class PlatformTargetUtil
14 | {
15 | public static string GetCompilerArgument(PlatformTarget target)
16 | {
17 | switch (target)
18 | {
19 | case PlatformTarget.AnyCPU:
20 | return "/platform:anycpu";
21 | case PlatformTarget.Itanium:
22 | return "/platform:Itanium";
23 | case PlatformTarget.x64:
24 | return "/platform:x64";
25 | case PlatformTarget.x86:
26 | return "/platform:x86";
27 | }
28 |
29 | throw new NotSupportedException("Unknown platform target specified");
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain.Sample/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/Utils/LoggingConfigurator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using log4net.Config;
4 |
5 | namespace AppSecInc.ProcessDomain.Utils
6 | {
7 | ///
8 | /// Utility class for configuring log4net remotely in another process domain
9 | ///
10 | [Serializable]
11 | public class LoggingConfigurator : MarshalByRefObject
12 | {
13 | public static LoggingConfigurator CreateConfigurator(ProcessDomain domain)
14 | {
15 | return (LoggingConfigurator)domain.CreateInstanceAndUnwrap(typeof(LoggingConfigurator).Assembly.FullName, typeof(LoggingConfigurator).FullName);
16 | }
17 |
18 | public void ConfigureBasic()
19 | {
20 | BasicConfigurator.Configure();
21 | }
22 |
23 | public void ConfigureAndWatchAppConfig()
24 | {
25 | XmlConfigurator.ConfigureAndWatch(new FileInfo(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile));
26 | }
27 |
28 | public void ConfigureAppConfig()
29 | {
30 | XmlConfigurator.Configure(new FileInfo(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile));
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/ProcessDomain.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ProcessDomain
5 | $version$
6 | eternal0,vivekrathod
7 | eternal0,vivekrathod
8 | false
9 | MIT
10 | https://github.com/legendary-code/process-domain
11 | ProcessDomain implements a remoting solution for creating out-of-process AppDomains. It's written in C# and the assemblies will work with .NET Framework 2.0.
12 | Moving to GitHub actions for pull request checks and release process. Removing copyrights as the company no longer exists.
13 | AppDomain ProcessDomain Sandbox
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/Remoting/AssemblyGeneratorCompilerException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.CodeDom.Compiler;
3 | using System.Runtime.Serialization;
4 | using System.Text;
5 |
6 | namespace AppSecInc.ProcessDomain.Remoting
7 | {
8 | [Serializable]
9 | public class AssemblyGeneratorCompilerException : Exception
10 | {
11 | public AssemblyGeneratorCompilerException(SerializationInfo info, StreamingContext context)
12 | : base(info, context)
13 | {
14 | }
15 |
16 | public AssemblyGeneratorCompilerException(string message, CompilerErrorCollection errors)
17 | : base(message)
18 | {
19 | Errors = errors;
20 | }
21 |
22 | public CompilerErrorCollection Errors
23 | {
24 | get;
25 | private set;
26 | }
27 |
28 | public override string ToString()
29 | {
30 | var sb = new StringBuilder();
31 | sb.AppendLine(Message);
32 |
33 | foreach (CompilerError error in Errors)
34 | {
35 | if (error.IsWarning)
36 | continue;
37 | sb.AppendFormat("{0}\r\n", error);
38 | }
39 |
40 | return sb.ToString();
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | release:
5 | types:
6 | - published
7 |
8 | jobs:
9 | release:
10 | runs-on: windows-latest
11 | steps:
12 | - name: Get Release Version
13 | id: get_version
14 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
15 | shell: bash
16 |
17 | - uses: actions/checkout@v1
18 | name: Checkout
19 |
20 | - name: Setup MSBuild
21 | uses: microsoft/setup-msbuild@v1.0.2
22 |
23 | - name: Setup NuGet
24 | uses: nuget/setup-nuget@v1
25 | with:
26 | nuget-api-key: ${{ secrets.NUGET_API_KEY }}
27 |
28 | - name: Install NuGet Packages
29 | run: nuget restore ProcessDomain.sln
30 |
31 | - name: Install MSBuildTasks Package
32 | run: nuget install MSBuildTasks -Version 1.5.0.235
33 |
34 | - name: Generate GlobalAssemblyInfo.cs
35 | run: msbuild .\Version.proj /p:Version=${{ steps.get_version.outputs.VERSION }}
36 |
37 | - name: Build Release
38 | run: msbuild ProcessDomain.sln /property:Configuration=Release
39 |
40 | - name: Create NuGet Package
41 | run: nuget pack .\ProcessDomain.nuspec -p version=${{ steps.get_version.outputs.VERSION }}
42 |
43 | - name: Publish NuGet Package
44 | run: nuget push ProcessDomain.${{ steps.get_version.outputs.VERSION }}.nupkg -Source https://api.nuget.org/v3/index.json
45 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain.Sample/RemoteObject.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Configuration;
3 | using System.Diagnostics;
4 | using System.IO;
5 | using log4net;
6 | using log4net.Config;
7 |
8 | namespace AppSecInc.ProcessDomain.Sample
9 | {
10 | [Serializable]
11 | public class RemoteObject : MarshalByRefObject
12 | {
13 | static readonly ILog Logger = LogManager.GetLogger(typeof(RemoteObject));
14 |
15 | public void ConfigLogging()
16 | {
17 | XmlConfigurator.ConfigureAndWatch(new FileInfo(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile));
18 | }
19 |
20 | public string GetProcessMainModuleFileName()
21 | {
22 | Logger.Info("Called GetProcessMainModuleFileName()");
23 | return Process.GetCurrentProcess().MainModule.FileName;
24 | }
25 |
26 | public string GetAppConfigLocation()
27 | {
28 | Logger.Info("Called GetAppConfigLocation()");
29 | return AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
30 | }
31 |
32 | public string GetAppConfigValue(string key)
33 | {
34 | Logger.Info("Called GetAppConfigValue()");
35 | return ConfigurationManager.AppSettings[key];
36 | }
37 |
38 | public void ThrowException()
39 | {
40 | throw new RemoteException("This is an exception");
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain.Host/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // General Information about an assembly is controlled through the following
5 | // set of attributes. Change these attribute values to modify the information
6 | // associated with an assembly.
7 | [assembly: AssemblyConfiguration("")]
8 | [assembly: AssemblyCompany("Microsoft")]
9 | [assembly: AssemblyProduct("AppSecInc.ProcessDomain.Host")]
10 | [assembly: AssemblyCopyright("Copyright © Microsoft 2010")]
11 | [assembly: AssemblyTrademark("")]
12 | [assembly: AssemblyCulture("")]
13 |
14 | // Setting ComVisible to false makes the types in this assembly not visible
15 | // to COM components. If you need to access a type in this assembly from
16 | // COM, set the ComVisible attribute to true on that type.
17 | [assembly: ComVisible(false)]
18 |
19 | // The following GUID is for the ID of the typelib if this project is exposed to COM
20 | [assembly: Guid("76a40f2f-56dd-4171-8a69-4ca649b59799")]
21 |
22 | // Version information for an assembly consists of the following four values:
23 | //
24 | // Major Version
25 | // Minor Version
26 | // Build Number
27 | // Revision
28 | //
29 | // You can specify all the values or you can default the Build and Revision Numbers
30 | // by using the '*' as shown below:
31 | // [assembly: AssemblyVersion("1.0.*")]
32 | [assembly: AssemblyVersion("1.0.0.0")]
33 | [assembly: AssemblyFileVersion("1.0.0.0")]
34 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain.UnitTests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("TestProcessDomain")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("Microsoft")]
12 | [assembly: AssemblyProduct("TestProcessDomain")]
13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2010")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("0031e17e-2cfc-4b5f-9f46-ddd220cfd9aa")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain.Sample/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("AppSecInc.ProcessDomain.Sample")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("Microsoft")]
12 | [assembly: AssemblyProduct("AppSecInc.ProcessDomain.Sample")]
13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2010")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("4213161e-80b0-4913-a9e3-850298170f18")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ProcessDomain
2 |
3 | ProcessDomain implements a remoting solution for creating out-of-process AppDomains. It's written in C# and the assemblies will work with .NET Framework 2.0. Some possible usages include:
4 | * Further code isolation
5 | * Allow for native code to run with different environment variables
6 | * Allow for multiple versions of a native library to be loaded without writing separate applications. E.g. A managed application that uses native database drivers, but needs to support loading multiple versions so that connectivity can occur for multiple versions of the database.
7 |
8 | See the example project for some usages
9 |
10 | # Features
11 |
12 | * AppDomain-like semantics, so its usage is familiar and easy
13 | * Implements the IDisposable pattern
14 | * Event handlers for when the remote process exits or is restarted
15 | * Automatic restart of remote process supported
16 | * Remote process assembly is generated on-the-fly at runtime
17 | * Remote process' AppDomain is fully configurable and by default takes on the settings of the AppDomain creating the ProcessDomain
18 |
19 | # Installation
20 |
21 | As of version 1.8, ProcessDomain will be available via NuGet as well: https://nuget.org/packages/ProcessDomain/1.8
22 |
23 | # Requirements
24 |
25 | ### To Build Source
26 |
27 | Visual Studio 2013
28 | [MSBuild Community Tasks 1.3](http://msbuildtasks.tigris.org/MSBuild.Community.Tasks.Nightly.msi)
29 |
30 | ### To Use Assemblies
31 | .NET Framework 2.0
32 |
33 | ### T Publish a Release to NuGet
34 |
35 | Simply publish a GitHub Release with the next correct semantic version, and the GitHub Actions workflow will take of building the package and publishing it to NuGet.
36 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/Remoting/Activator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Reflection;
4 | using System.Security.Policy;
5 |
6 | namespace AppSecInc.ProcessDomain.Remoting
7 | {
8 | ///
9 | /// This is a remotable object responsible for creating instances of objects remotely in another process
10 | ///
11 | [Serializable]
12 | internal class Activator : MarshalByRefObject, IActivation
13 | {
14 | // Make sure that the object instance is never garbage collected because
15 | // it's not gc-rooted
16 | public override object InitializeLifetimeService()
17 | {
18 | return null;
19 | }
20 |
21 | public object CreateInstanceAndUnwrap(string assemblyName, string typeName)
22 | {
23 | return AppDomain.CurrentDomain.CreateInstanceAndUnwrap(assemblyName, typeName);
24 | }
25 |
26 | public object CreateInstanceAndUnwrap(string assemblyName, string typeName, object[] activationAttributes)
27 | {
28 | return AppDomain.CurrentDomain.CreateInstanceAndUnwrap(assemblyName, typeName, activationAttributes);
29 | }
30 |
31 | public object CreateInstanceAndUnwrap(string assemblyName, string typeName, bool ignoreCase, BindingFlags bindingAttr, Binder binder, object[] args, CultureInfo culture, object[] activationAttributes, Evidence securityAttributes)
32 | {
33 | return AppDomain.CurrentDomain.CreateInstanceAndUnwrap(assemblyName, typeName, ignoreCase, bindingAttr, binder, args, culture, activationAttributes, securityAttributes);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/Remoting/ActivatorClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Runtime.Remoting.Channels;
4 | using System.Runtime.Remoting.Channels.Ipc;
5 |
6 | namespace AppSecInc.ProcessDomain.Remoting
7 | {
8 | ///
9 | /// This class provides access to an Activator in a remote process
10 | ///
11 | internal class ActivatorClient : IDisposable
12 | {
13 | readonly Activator _activator;
14 | readonly IChannel _channel;
15 |
16 | public ActivatorClient(string guid, ProcessDomainSetup setup)
17 | {
18 | var serverProvider = new BinaryServerFormatterSinkProvider { TypeFilterLevel = setup.TypeFilterLevel };
19 | var clientProvider = new BinaryClientFormatterSinkProvider();
20 |
21 | var properties = new Hashtable();
22 | properties["portName"] = string.Format(ActivatorHost.ClientChannelName, guid);
23 | properties["name"] = string.Format(ActivatorHost.ClientChannelName, guid);
24 | setup.Remoting.ApplyClientProperties(properties);
25 |
26 | _channel = new IpcChannel(properties, clientProvider, serverProvider);
27 | ChannelServices.RegisterChannel(_channel, false);
28 |
29 | _activator = (Activator)System.Activator.GetObject(typeof(Activator), string.Format("ipc://{0}/{1}", string.Format(ActivatorHost.ServerChannelName, guid), ActivatorHost.ActivatorName));
30 | }
31 |
32 | public Activator Activator
33 | {
34 | get { return _activator; }
35 | }
36 |
37 | public void Dispose()
38 | {
39 | ChannelServices.UnregisterChannel(_channel);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain.UnitTests/RemoteTestObject.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Configuration;
3 | using System.Diagnostics;
4 | using System.Security.Principal;
5 | using System.Threading;
6 |
7 | namespace AppSecInc.ProcessDomain.UnitTests
8 | {
9 | [Serializable]
10 | public class RemoteTestObject : MarshalByRefObject
11 | {
12 | public delegate void Callback();
13 | public event Callback CallbackEvent;
14 |
15 | public override object InitializeLifetimeService()
16 | {
17 | return null;
18 | }
19 |
20 | public string GetProcessFileName()
21 | {
22 | return Process.GetCurrentProcess().MainModule.FileName;
23 | }
24 |
25 | public int GetProcessId()
26 | {
27 | return Process.GetCurrentProcess().Id;
28 | }
29 |
30 | public string CurrentDirectory
31 | {
32 | get { return Environment.CurrentDirectory; }
33 | }
34 |
35 | public string GetAppConfigValue(string key)
36 | {
37 | return ConfigurationManager.AppSettings[key];
38 | }
39 |
40 | public ProcessPriorityClass GetPriority()
41 | {
42 | return Process.GetCurrentProcess().PriorityClass;
43 | }
44 |
45 | public bool RunningAsAdministrator()
46 | {
47 | var identity = WindowsIdentity.GetCurrent();
48 | var principal = new WindowsPrincipal(identity);
49 | return principal.IsInRole(WindowsBuiltInRole.Administrator);
50 | }
51 |
52 | public void OnCallback()
53 | {
54 | if (CallbackEvent != null)
55 | {
56 | CallbackEvent();
57 | }
58 | }
59 |
60 | public bool CalledBack { get; set; }
61 |
62 | ///
63 | /// This method is used for CallbackEvent because the target method needs to be in a class
64 | /// that is also serializable for subscribing to events to work
65 | ///
66 | public void SetCalledBack()
67 | {
68 | CalledBack = true;
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain.Host/AppSecInc.ProcessDomain.Host.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | 9.0.21022
7 | 2.0
8 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}
9 | WinExe
10 | Properties
11 | AppSecInc.ProcessDomain.Host
12 | AppSecInc.ProcessDomain.Host
13 | v2.0
14 | 512
15 |
16 |
17 |
18 |
19 |
20 |
21 | 3.5
22 |
23 |
24 | true
25 | full
26 | false
27 | bin\Debug\
28 | DEBUG;TRACE
29 | prompt
30 | 4
31 |
32 |
33 | pdbonly
34 | true
35 | bin\Release\
36 | TRACE
37 | prompt
38 | 4
39 |
40 |
41 |
42 |
43 |
44 |
45 | Program.cs
46 |
47 |
48 |
49 |
50 |
57 |
--------------------------------------------------------------------------------
/ProcessDomain.proj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug;Release
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.34209
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace AppSecInc.ProcessDomain.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("AppSecInc.ProcessDomain.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized string
65 | ///
66 | internal static string Program {
67 | get {
68 | return ResourceManager.GetString("Program", resourceCulture);
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/Remoting/ActivatorHostAssemblyGenerator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.CodeDom.Compiler;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Reflection;
6 |
7 | using log4net;
8 | using Microsoft.CSharp;
9 |
10 | namespace AppSecInc.ProcessDomain.Remoting
11 | {
12 | ///
13 | /// Generates an assembly to run in a separate process in order to host an Activator
14 | ///
15 | internal static class ActivatorHostAssemblyGenerator
16 | {
17 | static readonly ILog Logger = LogManager.GetLogger(typeof(ActivatorHostAssemblyGenerator));
18 | const string AssemblyName = @"{0}.exe";
19 | private static readonly String[] ReferencedAssemblies = new[] { "System.dll" };
20 |
21 | public static string CreateRemoteHostAssembly(string friendlyName, ProcessDomainSetup setupInfo)
22 | {
23 | if (!Directory.Exists(setupInfo.ExecutableDirectory))
24 | {
25 | Directory.CreateDirectory(setupInfo.ExecutableDirectory);
26 | }
27 |
28 | var providerOptions = new Dictionary
29 | {
30 | { "CompilerVersion", setupInfo.CompilerVersion ?? ProcessDomainSetup.DefaultCompilerVersion }
31 | };
32 |
33 | var provider = new CSharpCodeProvider(providerOptions);
34 |
35 | var compilerArgs = new List {PlatformTargetUtil.GetCompilerArgument(setupInfo.Platform)};
36 |
37 | var compilerParameters = new CompilerParameters
38 | {
39 | GenerateExecutable = true,
40 | GenerateInMemory = false,
41 | CompilerOptions = string.Join(" ", compilerArgs.ToArray()),
42 | OutputAssembly = Path.Combine(setupInfo.ExecutableDirectory,
43 | string.Format(AssemblyName, friendlyName))
44 | };
45 |
46 | compilerParameters.ReferencedAssemblies.AddRange(ReferencedAssemblies);
47 |
48 | string assemblySource = Properties.Resources.Program
49 | .Replace("${ActivatorHostTypeName}", typeof (ActivatorHost).AssemblyQualifiedName)
50 | .Replace("${ProcessDomainAssemblyName}", typeof(ActivatorHost).Assembly.FullName);
51 |
52 | var results = provider.CompileAssemblyFromSource(compilerParameters, assemblySource);
53 |
54 | if (results.Errors.HasErrors)
55 | {
56 | throw new AssemblyGeneratorCompilerException("Failed to compile assembly for process domain due to compiler errors", results.Errors);
57 | }
58 |
59 | if (results.Errors.HasWarnings)
60 | {
61 | Logger.Warn("Process domain assembly compilation returned with warnings:");
62 | foreach (var error in results.Errors)
63 | {
64 | Logger.WarnFormat("Compiler Warning: {0}", error);
65 | }
66 | }
67 | if (setupInfo.CopyConfigurationFile)
68 | {
69 | File.Copy(setupInfo.AppDomainSetupInformation.ConfigurationFile,
70 | results.PathToAssembly + ".config", true);
71 | }
72 | return results.PathToAssembly;
73 | }
74 | }
75 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | build/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studo 2015 cache/options directory
26 | .vs/
27 |
28 | # MSTest test Results
29 | [Tt]est[Rr]esult*/
30 | [Bb]uild[Ll]og.*
31 |
32 | # NUNIT
33 | *.VisualState.xml
34 | TestResult.xml
35 |
36 | # Build Results of an ATL Project
37 | [Dd]ebugPS/
38 | [Rr]eleasePS/
39 | dlldata.c
40 |
41 | *_i.c
42 | *_p.c
43 | *_i.h
44 | *.ilk
45 | *.meta
46 | *.obj
47 | *.pch
48 | *.pdb
49 | *.pgc
50 | *.pgd
51 | *.rsp
52 | *.sbr
53 | *.tlb
54 | *.tli
55 | *.tlh
56 | *.tmp
57 | *.tmp_proj
58 | *.log
59 | *.vspscc
60 | *.vssscc
61 | .builds
62 | *.pidb
63 | *.svclog
64 | *.scc
65 |
66 | # Chutzpah Test files
67 | _Chutzpah*
68 |
69 | # Visual C++ cache files
70 | ipch/
71 | *.aps
72 | *.ncb
73 | *.opensdf
74 | *.sdf
75 | *.cachefile
76 |
77 | # Visual Studio profiler
78 | *.psess
79 | *.vsp
80 | *.vspx
81 |
82 | # TFS 2012 Local Workspace
83 | $tf/
84 |
85 | # Guidance Automation Toolkit
86 | *.gpState
87 |
88 | # ReSharper is a .NET coding add-in
89 | _ReSharper*/
90 | *.[Rr]e[Ss]harper
91 | *.DotSettings.user
92 |
93 | # JustCode is a .NET coding addin-in
94 | .JustCode
95 |
96 | # TeamCity is a build add-in
97 | _TeamCity*
98 |
99 | # DotCover is a Code Coverage Tool
100 | *.dotCover
101 |
102 | # NCrunch
103 | _NCrunch_*
104 | .*crunch*.local.xml
105 |
106 | # MightyMoose
107 | *.mm.*
108 | AutoTest.Net/
109 |
110 | # Web workbench (sass)
111 | .sass-cache/
112 |
113 | # Installshield output folder
114 | [Ee]xpress/
115 |
116 | # DocProject is a documentation generator add-in
117 | DocProject/buildhelp/
118 | DocProject/Help/*.HxT
119 | DocProject/Help/*.HxC
120 | DocProject/Help/*.hhc
121 | DocProject/Help/*.hhk
122 | DocProject/Help/*.hhp
123 | DocProject/Help/Html2
124 | DocProject/Help/html
125 |
126 | # Click-Once directory
127 | publish/
128 |
129 | # Publish Web Output
130 | *.[Pp]ublish.xml
131 | *.azurePubxml
132 | # TODO: Comment the next line if you want to checkin your web deploy settings
133 | # but database connection strings (with potential passwords) will be unencrypted
134 | *.pubxml
135 | *.publishproj
136 |
137 | # NuGet Packages
138 | *.nupkg
139 | # The packages folder can be ignored because of Package Restore
140 | **/packages/*
141 | # except build/, which is used as an MSBuild target.
142 | !**/packages/build/
143 | # Uncomment if necessary however generally it will be regenerated when needed
144 | #!**/packages/repositories.config
145 |
146 | # Windows Azure Build Output
147 | csx/
148 | *.build.csdef
149 |
150 | # Windows Store app package directory
151 | AppPackages/
152 |
153 | # Others
154 | *.[Cc]ache
155 | ClientBin/
156 | [Ss]tyle[Cc]op.*
157 | ~$*
158 | *~
159 | *.dbmdl
160 | *.dbproj.schemaview
161 | *.pfx
162 | *.publishsettings
163 | node_modules/
164 | bower_components/
165 |
166 | # RIA/Silverlight projects
167 | Generated_Code/
168 |
169 | # Backup & report files from converting an old project file
170 | # to a newer Visual Studio version. Backup files are not needed,
171 | # because we have git ;-)
172 | _UpgradeReport_Files/
173 | Backup*/
174 | UpgradeLog*.XML
175 | UpgradeLog*.htm
176 |
177 | # SQL Server files
178 | *.mdf
179 | *.ldf
180 |
181 | # Business Intelligence projects
182 | *.rdl.data
183 | *.bim.layout
184 | *.bim_*.settings
185 |
186 | # Microsoft Fakes
187 | FakesAssemblies/
188 |
189 | # Node.js Tools for Visual Studio
190 | .ntvs_analysis.dat
191 |
192 | # Visual Studio 6 build log
193 | *.plg
194 |
195 | # Visual Studio 6 workspace options file
196 | *.opt
197 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain.Sample/AppSecInc.ProcessDomain.Sample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | 9.0.21022
7 | 2.0
8 | {AA4C3A45-921B-422F-ABFF-B1848686596D}
9 | Exe
10 | Properties
11 | AppSecInc.ProcessDomain.Sample
12 | AppSecInc.ProcessDomain.Sample
13 | v3.5
14 | 512
15 |
16 |
17 |
18 |
19 | 3.5
20 | publish\
21 | true
22 | Disk
23 | false
24 | Foreground
25 | 7
26 | Days
27 | false
28 | false
29 | true
30 | 0
31 | 1.0.0.%2a
32 | false
33 | false
34 | true
35 | ..\..\
36 | true
37 |
38 |
39 | true
40 | full
41 | false
42 | bin\Debug\
43 | DEBUG;TRACE
44 | prompt
45 | 4
46 |
47 |
48 | pdbonly
49 | true
50 | bin\Release\
51 | TRACE
52 | prompt
53 | 4
54 |
55 |
56 |
57 |
58 |
59 | 3.5
60 |
61 |
62 | 3.5
63 |
64 |
65 | 3.5
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | {D69AD209-D78A-4091-9F5F-4D7562D66223}
82 | AppSecInc.ProcessDomain
83 |
84 |
85 |
86 |
87 | False
88 | .NET Framework 3.5 SP1 Client Profile
89 | false
90 |
91 |
92 | False
93 | .NET Framework 3.5 SP1
94 | true
95 |
96 |
97 |
98 |
99 | 2.0.12
100 |
101 |
102 |
103 |
104 |
111 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain.UnitTests/AppSecInc.ProcessDomain.UnitTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | 9.0.21022
8 | 2.0
9 | {6B78A648-1147-4D39-B125-7F07D09B649C}
10 | Library
11 | Properties
12 | AppSecInc.ProcessDomain.UnitTests
13 | AppSecInc.ProcessDomain.UnitTests
14 | v3.5
15 | 512
16 |
17 |
18 |
19 |
20 |
21 |
22 | 3.5
23 | ..\..\
24 | true
25 |
26 |
27 |
28 |
29 |
30 | true
31 | full
32 | false
33 | bin\Debug\
34 | DEBUG;TRACE
35 | prompt
36 | 4
37 |
38 |
39 | pdbonly
40 | true
41 | bin\Release\
42 | TRACE
43 | prompt
44 | 4
45 |
46 |
47 |
48 | False
49 | ..\..\packages\NUnit.2.6.1\lib\nunit.framework.dll
50 |
51 |
52 |
53 |
54 | 3.5
55 |
56 |
57 | 3.5
58 |
59 |
60 | 3.5
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}
73 | AppSecInc.ProcessDomain.Host
74 |
75 |
76 | {D69AD209-D78A-4091-9F5F-4D7562D66223}
77 | AppSecInc.ProcessDomain
78 |
79 |
80 |
81 |
82 |
83 |
84 | PreserveNewest
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
93 |
94 |
95 |
96 |
103 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/ProcessDomain.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Reflection;
4 | using System.Security.Policy;
5 | using System.Threading;
6 |
7 | using log4net;
8 |
9 | using AppSecInc.ProcessDomain.Remoting;
10 |
11 | namespace AppSecInc.ProcessDomain
12 | {
13 | ///
14 | /// Represents an isolated environment in a separate process in which objects can be created and invoked
15 | ///
16 | public sealed class ProcessDomain : IActivation, IDisposable
17 | {
18 | static readonly ILog Logger = LogManager.GetLogger(typeof(ProcessDomain));
19 |
20 | public event AttachedDelegate Attached;
21 | public event DetachedDelegate Detached;
22 |
23 | readonly AutoResetEvent _attachedEvent = new AutoResetEvent(false);
24 | readonly AutoResetEvent _detachedEvent = new AutoResetEvent(false);
25 | readonly string _friendlyName;
26 | readonly ActivatorProcess _process;
27 |
28 | int _unloaded;
29 |
30 | private ProcessDomain(string friendlyName, ProcessDomainSetup setupInfo)
31 | {
32 | _friendlyName = friendlyName;
33 | _process = new ActivatorProcess(friendlyName, setupInfo);
34 | _process.Attached += Process_Attached;
35 | _process.Detached += Process_Detached;
36 | _process.Start();
37 | }
38 |
39 | private void Process_Attached()
40 | {
41 | var tmp = Attached;
42 | if (tmp != null)
43 | {
44 | Attached();
45 | }
46 | _attachedEvent.Set();
47 | }
48 |
49 | private void Process_Detached()
50 | {
51 | var tmp = Detached;
52 | if (tmp != null)
53 | {
54 | Detached();
55 | }
56 | _detachedEvent.Set();
57 | }
58 |
59 | ///
60 | /// Creates a ProcessDomain which allows hosting objects and code out-of-process
61 | ///
62 | /// The friendly name of the process domain which directly will also be the file name of the remote process
63 | /// Additional settings for creating the process domain
64 | public static ProcessDomain CreateDomain(string friendlyName, ProcessDomainSetup setupInfo)
65 | {
66 | Logger.InfoFormat("Creating process domain '{0}'", friendlyName);
67 | return new ProcessDomain(friendlyName, setupInfo);
68 | }
69 |
70 | ///
71 | /// Creates a ProcessDomain which allows hosting objects and code out-of-process
72 | ///
73 | /// The friendly name of the process domain which directly will also be the file name of the remote process
74 | public static ProcessDomain CreateDomain(string friendlyName)
75 | {
76 | return CreateDomain(friendlyName, new ProcessDomainSetup());
77 | }
78 |
79 | ///
80 | /// Unloads a given process domain by terminating the process
81 | ///
82 | /// The process domain to unload
83 | public static void Unload(ProcessDomain domain)
84 | {
85 | Logger.InfoFormat("Unloading process domain '{0}'", domain._friendlyName);
86 | domain.Unload();
87 | }
88 |
89 | ///
90 | /// Creates an object of the specified type
91 | ///
92 | public object CreateInstanceAndUnwrap(string assemblyName, string typeName)
93 | {
94 | return _process.Activator.CreateInstanceAndUnwrap(assemblyName, typeName);
95 | }
96 |
97 | ///
98 | /// Creates an object of the specified type
99 | ///
100 | public object CreateInstanceAndUnwrap(string assemblyName, string typeName, object[] activationAttributes)
101 | {
102 | return _process.Activator.CreateInstanceAndUnwrap(assemblyName, typeName, activationAttributes);
103 | }
104 |
105 | ///
106 | /// Creates an object of the specified type
107 | ///
108 | public object CreateInstanceAndUnwrap(string assemblyName, string typeName, bool ignoreCase, BindingFlags bindingAttr, Binder binder, object[] args, CultureInfo culture, object[] activationAttributes, Evidence securityAttributes)
109 | {
110 | return _process.Activator.CreateInstanceAndUnwrap(assemblyName, typeName, ignoreCase, bindingAttr, binder, args, culture, activationAttributes, securityAttributes);
111 | }
112 |
113 | ///
114 | /// Terminates the process, and then right after that it restarts again.
115 | ///
116 | public void Terminate()
117 | {
118 | if (_process != null)
119 | {
120 | _process.Terminate();
121 | }
122 | }
123 |
124 | private void Unload()
125 | {
126 | if (Interlocked.CompareExchange(ref _unloaded, 1, 0) == 1)
127 | return;
128 |
129 | if (_process != null)
130 | {
131 | _process.Kill();
132 | _process.Dispose();
133 | }
134 | }
135 |
136 | ~ProcessDomain()
137 | {
138 | Unload();
139 | }
140 |
141 | void IDisposable.Dispose()
142 | {
143 | Unload();
144 | }
145 | }
146 | }
--------------------------------------------------------------------------------
/ProcessDomain.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2013
4 | VisualStudioVersion = 12.0.21005.1
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppSecInc.ProcessDomain", "src\AppSecInc.ProcessDomain\AppSecInc.ProcessDomain.csproj", "{D69AD209-D78A-4091-9F5F-4D7562D66223}"
7 | ProjectSection(ProjectDependencies) = postProject
8 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840} = {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}
9 | EndProjectSection
10 | EndProject
11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppSecInc.ProcessDomain.UnitTests", "src\AppSecInc.ProcessDomain.UnitTests\AppSecInc.ProcessDomain.UnitTests.csproj", "{6B78A648-1147-4D39-B125-7F07D09B649C}"
12 | EndProject
13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppSecInc.ProcessDomain.Host", "src\AppSecInc.ProcessDomain.Host\AppSecInc.ProcessDomain.Host.csproj", "{81E2FA83-26A1-4BE3-8F38-2C7327EC7840}"
14 | EndProject
15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppSecInc.ProcessDomain.Sample", "src\AppSecInc.ProcessDomain.Sample\AppSecInc.ProcessDomain.Sample.csproj", "{AA4C3A45-921B-422F-ABFF-B1848686596D}"
16 | EndProject
17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{1FA63DD9-AED9-4C59-9037-CBCBCA352871}"
18 | ProjectSection(SolutionItems) = preProject
19 | .gitignore = .gitignore
20 | build.cmd = build.cmd
21 | LICENSE = LICENSE
22 | ProcessDomain.proj = ProcessDomain.proj
23 | README.md = README.md
24 | Version.proj = Version.proj
25 | EndProjectSection
26 | EndProject
27 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{92399DD0-ABB6-4510-96E0-8AD536B0AA88}"
28 | ProjectSection(SolutionItems) = preProject
29 | .nuget\NuGet.Config = .nuget\NuGet.Config
30 | .nuget\NuGet.exe = .nuget\NuGet.exe
31 | .nuget\NuGet.targets = .nuget\NuGet.targets
32 | .nuget\packages.config = .nuget\packages.config
33 | EndProjectSection
34 | EndProject
35 | Global
36 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
37 | Debug|Any CPU = Debug|Any CPU
38 | Debug|Mixed Platforms = Debug|Mixed Platforms
39 | Debug|Win32 = Debug|Win32
40 | Release|Any CPU = Release|Any CPU
41 | Release|Mixed Platforms = Release|Mixed Platforms
42 | Release|Win32 = Release|Win32
43 | EndGlobalSection
44 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
45 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
46 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Debug|Any CPU.Build.0 = Debug|Any CPU
47 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
48 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
49 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Debug|Win32.ActiveCfg = Debug|Any CPU
50 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Release|Any CPU.ActiveCfg = Release|Any CPU
51 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Release|Any CPU.Build.0 = Release|Any CPU
52 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
53 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Release|Mixed Platforms.Build.0 = Release|Any CPU
54 | {D69AD209-D78A-4091-9F5F-4D7562D66223}.Release|Win32.ActiveCfg = Release|Any CPU
55 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
56 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Debug|Any CPU.Build.0 = Debug|Any CPU
57 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
58 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
59 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Debug|Win32.ActiveCfg = Debug|Any CPU
60 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Release|Any CPU.ActiveCfg = Release|Any CPU
61 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Release|Any CPU.Build.0 = Release|Any CPU
62 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
63 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Release|Mixed Platforms.Build.0 = Release|Any CPU
64 | {6B78A648-1147-4D39-B125-7F07D09B649C}.Release|Win32.ActiveCfg = Release|Any CPU
65 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
66 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Debug|Any CPU.Build.0 = Debug|Any CPU
67 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
68 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
69 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Debug|Win32.ActiveCfg = Debug|Any CPU
70 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Release|Any CPU.ActiveCfg = Release|Any CPU
71 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Release|Any CPU.Build.0 = Release|Any CPU
72 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
73 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Release|Mixed Platforms.Build.0 = Release|Any CPU
74 | {81E2FA83-26A1-4BE3-8F38-2C7327EC7840}.Release|Win32.ActiveCfg = Release|Any CPU
75 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
76 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Debug|Any CPU.Build.0 = Debug|Any CPU
77 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
78 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
79 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Debug|Win32.ActiveCfg = Debug|Any CPU
80 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Release|Any CPU.ActiveCfg = Release|Any CPU
81 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Release|Any CPU.Build.0 = Release|Any CPU
82 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
83 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
84 | {AA4C3A45-921B-422F-ABFF-B1848686596D}.Release|Win32.ActiveCfg = Release|Any CPU
85 | EndGlobalSection
86 | GlobalSection(SolutionProperties) = preSolution
87 | HideSolutionNode = FALSE
88 | EndGlobalSection
89 | EndGlobal
90 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain.Sample/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Linq;
4 | using System.Threading;
5 | using AppSecInc.ProcessDomain.Utils;
6 |
7 | namespace AppSecInc.ProcessDomain.Sample
8 | {
9 | public class Program
10 | {
11 | static void Main(string[] args)
12 | {
13 | try
14 | {
15 | var localObject = new RemoteObject();
16 | Console.WriteLine("Local object - Main Module Location: {0}", localObject.GetProcessMainModuleFileName());
17 | Console.WriteLine("Local object - App.config location: {0}", localObject.GetAppConfigLocation());
18 | Console.WriteLine("Local object - App.config value: {0}", localObject.GetAppConfigValue("MyString"));
19 |
20 | var setup = new ProcessDomainSetup
21 | {
22 | ProcessStartTimeout = new TimeSpan(0, 0, 5),
23 | };
24 |
25 | using (ProcessDomain processDomain = ProcessDomain.CreateDomain("RemoteProcess", setup))
26 | {
27 | LoggingConfigurator.CreateConfigurator(processDomain).ConfigureAppConfig();
28 |
29 | var remoteObject = (RemoteObject)processDomain.CreateInstanceAndUnwrap(typeof(RemoteObject).Assembly.FullName, typeof(RemoteObject).FullName);
30 | Console.WriteLine("Remote object - Main Module Location: {0}", remoteObject.GetProcessMainModuleFileName());
31 | Console.WriteLine("Remote object - App.config location: {0}", remoteObject.GetAppConfigLocation());
32 | Console.WriteLine("Remote object - App.config value: {0}", remoteObject.GetAppConfigValue("MyString"));
33 |
34 | var detachedEvent = new ManualResetEvent(false);
35 | var attachedEvent = new ManualResetEvent(false);
36 | processDomain.Detached += () => detachedEvent.Set();
37 | processDomain.Attached += () =>
38 | {
39 | LoggingConfigurator.CreateConfigurator(processDomain).ConfigureAppConfig();
40 | attachedEvent.Set();
41 | };
42 |
43 | Console.WriteLine("Finding RemoteProcess and killing it...");
44 | Process.GetProcessesByName("RemoteProcess").FirstOrDefault().Kill();
45 |
46 | if (!detachedEvent.WaitOne(10000))
47 | {
48 | throw new Exception("Timed-out while waiting for process to die");
49 | }
50 |
51 | Console.WriteLine("Waiting for new process to spawn");
52 | if (!attachedEvent.WaitOne(10000))
53 | {
54 | throw new Exception("Timed-out while waiting for process to restart");
55 | }
56 |
57 | Console.WriteLine("Re-creating remote object in newly spawned process");
58 | remoteObject = (RemoteObject)processDomain.CreateInstanceAndUnwrap(typeof(RemoteObject).Assembly.FullName, typeof(RemoteObject).FullName);
59 | Console.WriteLine("Remote object - Main Module Location: {0}", remoteObject.GetProcessMainModuleFileName());
60 | Console.WriteLine("Remote object - App.config location: {0}", remoteObject.GetAppConfigLocation());
61 | Console.WriteLine("Remote object - App.config value: {0}", remoteObject.GetAppConfigValue("MyString"));
62 |
63 | Console.WriteLine("Throwing an exception...");
64 | try
65 | {
66 | remoteObject.ThrowException();
67 | Console.WriteLine("Did not catch an exception...");
68 | }
69 | catch (RemoteException rex)
70 | {
71 | Console.WriteLine("Caught exception: {0}", rex.Message);
72 | }
73 | }
74 |
75 | Console.WriteLine("Two process domains at the same time");
76 |
77 | using (var processDomain1 = ProcessDomain.CreateDomain("RemoteProcess1", setup))
78 | using (var processDomain2 = ProcessDomain.CreateDomain("RemoteProcess2", setup))
79 | {
80 | var remoteObject1 = (RemoteObject)processDomain1.CreateInstanceAndUnwrap(typeof(RemoteObject).Assembly.FullName, typeof(RemoteObject).FullName);
81 | var remoteObject2 = (RemoteObject)processDomain2.CreateInstanceAndUnwrap(typeof(RemoteObject).Assembly.FullName, typeof(RemoteObject).FullName);
82 |
83 | Console.WriteLine("Remote object #1 - App.config value: {0}", remoteObject1.GetAppConfigValue("MyString"));
84 | Console.WriteLine("Remote object #2 - App.config value: {0}", remoteObject2.GetAppConfigValue("MyString"));
85 | }
86 |
87 | Console.WriteLine("Process domain in alternate location");
88 | setup.AppDomainSetupInformation.ApplicationBase = @"c:\";
89 | setup.ExternalAssemblies[typeof(Program).Assembly.GetName()] = typeof(Program).Assembly.Location;
90 | using (var processDomain = ProcessDomain.CreateDomain("RemoteProcess", setup))
91 | {
92 | var remoteObject = (RemoteObject)processDomain.CreateInstanceAndUnwrap(typeof(RemoteObject).Assembly.FullName, typeof(RemoteObject).FullName);
93 |
94 | Console.WriteLine("Remote object - App.config value: {0}", remoteObject.GetAppConfigValue("MyString"));
95 | }
96 | }
97 | catch (Exception ex)
98 | {
99 | Console.WriteLine("Exception: {0}", ex);
100 | }
101 |
102 | Console.WriteLine("Press any key to exit");
103 | Console.ReadKey();
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/AppSecInc.ProcessDomain.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | 9.0.30729
7 | 2.0
8 | {D69AD209-D78A-4091-9F5F-4D7562D66223}
9 | Library
10 | Properties
11 | AppSecInc.ProcessDomain
12 | AppSecInc.ProcessDomain
13 | v2.0
14 | 512
15 | false
16 |
17 |
18 |
19 |
20 |
21 |
22 | 3.5
23 | publish\
24 | true
25 | Disk
26 | false
27 | Foreground
28 | 7
29 | Days
30 | false
31 | false
32 | true
33 | 0
34 | 1.0.0.%2a
35 | false
36 | false
37 | true
38 | ..\..\
39 | true
40 |
41 |
42 | true
43 | full
44 | false
45 | ..\..\Build\Debug\
46 | DEBUG;TRACE
47 | prompt
48 | 4
49 |
50 |
51 | pdbonly
52 | true
53 | ..\..\Build\Release\
54 | TRACE
55 | prompt
56 | 4
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | Properties\GlobalAssemblyInfo.cs
67 |
68 |
69 |
70 |
71 |
72 | True
73 | True
74 | Resources.resx
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | ResXFileCodeGenerator
95 | Resources.Designer.cs
96 | Designer
97 |
98 |
99 |
100 |
101 | False
102 | .NET Framework 3.5 SP1 Client Profile
103 | false
104 |
105 |
106 | False
107 | .NET Framework 3.5 SP1
108 | true
109 |
110 |
111 |
112 |
113 | 2.0.12
114 |
115 |
116 |
117 |
124 |
125 |
126 |
127 |
128 |
129 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | ..\Resources\Program.cs;System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8
123 |
124 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/Remoting/ActivatorHost.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.ComponentModel;
4 | using System.Diagnostics;
5 | using System.IO;
6 | using System.Reflection;
7 | using System.Runtime.Remoting;
8 | using System.Runtime.Remoting.Channels;
9 | using System.Runtime.Remoting.Channels.Ipc;
10 | using System.Threading;
11 |
12 | namespace AppSecInc.ProcessDomain.Remoting
13 | {
14 | ///
15 | /// This class hosts an Activator in a new process on an IPC channel
16 | ///
17 | [Serializable]
18 | internal class ActivatorHost : MarshalByRefObject
19 | {
20 | public const string ServerChannelName = "ProcessDomainServer_{0}";
21 | public const string ClientChannelName = "ProcessDomainClient_{0}";
22 | public const string EventName = "ProcessDomainEvent_{0}";
23 | public const string ActivatorName = "Activator";
24 |
25 | readonly Process _process;
26 | readonly IpcChannel _channel;
27 |
28 | public ActivatorHost(string guid, int processId, ProcessDomainSetup setup)
29 | {
30 | SetupDllDirectories(setup);
31 | var serverProvider = new BinaryServerFormatterSinkProvider { TypeFilterLevel = setup.TypeFilterLevel };
32 | var clientProvider = new BinaryClientFormatterSinkProvider();
33 | _process = Process.GetProcessById(processId);
34 |
35 | var properties = new Hashtable();
36 | properties["portName"] = string.Format(ServerChannelName, guid);
37 | properties["name"] = string.Format(ServerChannelName, guid);
38 | properties["rejectRemoteRequests"] = true;
39 | setup.Remoting.ApplyServerProperties(properties);
40 |
41 | _channel = new IpcChannel(properties, clientProvider, serverProvider);
42 | ChannelServices.RegisterChannel(_channel, false);
43 | RemotingConfiguration.RegisterWellKnownServiceType(typeof(Activator), ActivatorName, WellKnownObjectMode.Singleton);
44 |
45 | EventWaitHandle serverStartedHandle = null;
46 | try
47 | {
48 | bool created;
49 | serverStartedHandle = new EventWaitHandle(false, EventResetMode.ManualReset, string.Format(EventName, guid), out created);
50 |
51 | if (created)
52 | {
53 | throw new Exception("Event handle did not exist for remote process");
54 | }
55 |
56 | serverStartedHandle.Set();
57 | }
58 | finally
59 | {
60 | if (serverStartedHandle != null)
61 | {
62 | serverStartedHandle.Close();
63 | }
64 | }
65 | }
66 |
67 | ///
68 | /// Waits for the parent process to exit
69 | ///
70 | public void WaitForExit()
71 | {
72 | _process.WaitForExit();
73 | }
74 |
75 | ///
76 | /// Runs the Activator Host and blocks until the parent process exits
77 | ///
78 | public static void Run(string[] args)
79 | {
80 | // args[0] = process domain assembly path
81 | // args[1] = guid
82 | // args[2] = parent process id
83 | // args[3] = ProcessDomainSetup file
84 |
85 | if (args.Length != 4)
86 | {
87 | return;
88 | }
89 |
90 | string friendlyName = Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location);
91 | string guid = args[1];
92 | int processId = int.Parse(args[2]);
93 |
94 | var domainSetup = ProcessDomainSetup.Deserialize(args[3]);
95 |
96 | var domain = AppDomain.CreateDomain(friendlyName, domainSetup.Evidence, domainSetup.AppDomainSetupInformation);
97 |
98 | var type = Assembly.GetEntryAssembly().GetType("AppSecInc.ProcessDomain.AssemblyResolver");
99 |
100 | if (type == null)
101 | {
102 | throw new TypeLoadException("Could not load type for assembly resolver");
103 | }
104 |
105 | // add ProcessDomain assembly to resolver
106 | if (domainSetup.ExternalAssemblies == null)
107 | {
108 | domainSetup.ExternalAssemblies = new System.Collections.Generic.Dictionary();
109 | }
110 | domainSetup.ExternalAssemblies[typeof(ActivatorHost).Assembly.GetName()] = typeof(ActivatorHost).Assembly.Location;
111 |
112 | var resolver = domain.CreateInstanceFromAndUnwrap(type.Assembly.Location,
113 | type.FullName,
114 | false,
115 | BindingFlags.CreateInstance | BindingFlags.Public | BindingFlags.Instance,
116 | null,
117 | new[] { domainSetup.ExternalAssemblies },
118 | null, null, null);
119 |
120 | type.InvokeMember("Setup", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, resolver, null);
121 |
122 | var host = (ActivatorHost) domain.CreateInstanceFromAndUnwrap(typeof (ActivatorHost).Assembly.Location,
123 | typeof (ActivatorHost).FullName,
124 | false,
125 | BindingFlags.CreateInstance | BindingFlags.Public | BindingFlags.Instance,
126 | null,
127 | new object[] {guid, processId, domainSetup},
128 | null, null, null);
129 |
130 | host.WaitForExit();
131 |
132 | type.InvokeMember("TearDown", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, resolver, null);
133 |
134 | // If parent process (host) finishes, the current process must end.
135 | Environment.Exit(0);
136 | }
137 |
138 | private static void SetupDllDirectories(ProcessDomainSetup setup)
139 | {
140 | if (!string.IsNullOrEmpty(setup.DllDirectory))
141 | {
142 | if (!WinApi.SetDllDirectory(setup.DllDirectory))
143 | {
144 | throw new Win32Exception();
145 | }
146 | }
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/Resources/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.IO;
5 | using System.Reflection;
6 |
7 | /*
8 | * This file contains source code that will get compiled at run-time in order to create a
9 | * hosting process. It should remain as light-weight as possible, while the bulk of the
10 | * work should be done in the created type 'ActivatorHost'. It should also not have any
11 | * references to assemblies not in the GAC, as this program will fail to load because the
12 | * assembly resolver won't have a chance to get configured to find assemblies in a different
13 | * location than the application domain's Base Directory.
14 | */
15 |
16 | [assembly: AssemblyTitle("ProcessDomain Host")]
17 | [assembly: AssemblyDescription("Provides an isolated environment for creating objects and executing code in another process")]
18 |
19 | namespace AppSecInc.ProcessDomain
20 | {
21 | public class Program
22 | {
23 | static void Main(string[] args)
24 | {
25 | if (args.Length < 1)
26 | {
27 | Log("Invalid arguments");
28 | return;
29 | }
30 |
31 | try
32 | {
33 | var resolveMap = new Dictionary
34 | {
35 | { new AssemblyName("${ProcessDomainAssemblyName}"), args[0] }
36 | };
37 | var resolver = new AssemblyResolver(resolveMap);
38 | resolver.Setup();
39 |
40 | Type hostType = Type.GetType("${ActivatorHostTypeName}");
41 |
42 | if (hostType == null)
43 | {
44 | throw new TypeLoadException(string.Format("Could not load ActivatorHost type '${ActivatorHostTypeName}' using resolver with '${ProcessDomainAssemblyName}' mapped to '{0}'", args[0]));
45 | }
46 |
47 | var methodInfo = hostType.GetMethod("Run", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string[]) }, null);
48 |
49 | if(methodInfo == null)
50 | {
51 | throw new Exception("'Run' method on ActivatorHost not found.");
52 | }
53 |
54 | methodInfo.Invoke(null, new[] { args });
55 | }
56 | catch (Exception ex)
57 | {
58 | Log("Failed to launch Activator Host: {0}", ex);
59 | }
60 | }
61 |
62 | // Just very basic logging. Any extra logging configuration should be done by creating
63 | // the logging configurator object in the new process domain
64 | static StreamWriter _logFile;
65 | static void OpenLogFile()
66 | {
67 | string fileName = string.Format("{0}-{1}.log", Assembly.GetEntryAssembly().Location, Process.GetCurrentProcess().Id);
68 | try
69 | {
70 | _logFile = new StreamWriter(fileName, false);
71 | }
72 | catch(Exception ex)
73 | {
74 | Console.WriteLine("Failed to open process domain bootstrap log: {0}", ex);
75 | }
76 | }
77 |
78 | static void Log(string message, params object[] args)
79 | {
80 | if (_logFile == null)
81 | {
82 | OpenLogFile();
83 | }
84 |
85 | if (_logFile != null)
86 | {
87 | _logFile.WriteLine(string.Format("[{0}] {1}", DateTime.Now, message), args);
88 | _logFile.Flush();
89 | }
90 | }
91 | }
92 |
93 | public class AssemblyResolver : MarshalByRefObject
94 | {
95 | // Prevent garbage collection
96 | public override object InitializeLifetimeService()
97 | {
98 | return null;
99 | }
100 |
101 | private readonly Dictionary> _mapByName;
102 |
103 | public AssemblyResolver(Dictionary map)
104 | {
105 | _mapByName = new Dictionary>();
106 |
107 | if (map != null)
108 | {
109 | foreach (var entry in map)
110 | {
111 | Dictionary subMap;
112 | if (!_mapByName.TryGetValue(entry.Key.Name, out subMap))
113 | {
114 | _mapByName[entry.Key.Name] = subMap = new Dictionary();
115 | }
116 |
117 | subMap[entry.Key] = entry.Value;
118 | }
119 | }
120 | }
121 |
122 | public void Setup()
123 | {
124 | AppDomain.CurrentDomain.AssemblyResolve += Resolve;
125 | AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += ReflectionOnlyResolve;
126 | }
127 |
128 | public void TearDown()
129 | {
130 | AppDomain.CurrentDomain.AssemblyResolve -= Resolve;
131 | AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= ReflectionOnlyResolve;
132 | }
133 |
134 | private Assembly Resolve(object sender, ResolveEventArgs args)
135 | {
136 | var assemblyFile = FindAssemblyName(args.Name);
137 | if (assemblyFile != null)
138 | {
139 | return Assembly.LoadFrom(assemblyFile);
140 | }
141 |
142 | return null;
143 | }
144 |
145 | private Assembly ReflectionOnlyResolve(object sender, ResolveEventArgs args)
146 | {
147 | var assemblyFile = FindAssemblyName(args.Name);
148 | if (assemblyFile != null)
149 | {
150 | return Assembly.ReflectionOnlyLoadFrom(assemblyFile);
151 | }
152 |
153 | return null;
154 | }
155 |
156 | private static bool PublicKeysTokenEqual(byte[] lhs, byte[] rhs)
157 | {
158 | if (lhs == null || rhs == null)
159 | {
160 | return lhs == rhs;
161 | }
162 |
163 | if (lhs.Length != rhs.Length)
164 | return false;
165 |
166 | for (int i = 0; i < lhs.Length; i++)
167 | {
168 | if (lhs[i] != rhs[i])
169 | return false;
170 | }
171 |
172 | return true;
173 | }
174 |
175 | private string FindAssemblyName(string name)
176 | {
177 | var assemblyName = new AssemblyName(name);
178 | Dictionary subMap;
179 |
180 | if (!_mapByName.TryGetValue(assemblyName.Name, out subMap))
181 | {
182 | return null;
183 | }
184 |
185 | foreach (var entry in subMap)
186 | {
187 | // do weak assembly name matching, matching only values specified in assembly name
188 | if (assemblyName.Version != null && assemblyName.Version != entry.Key.Version)
189 | continue;
190 |
191 | if (assemblyName.CultureInfo != null && !assemblyName.CultureInfo.Equals(entry.Key.CultureInfo))
192 | continue;
193 |
194 | if (!PublicKeysTokenEqual(assemblyName.GetPublicKeyToken(), entry.Key.GetPublicKeyToken()))
195 | continue;
196 |
197 | return entry.Value;
198 | }
199 |
200 | return null;
201 | }
202 | }
203 | }
--------------------------------------------------------------------------------
/.nuget/NuGet.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildProjectDirectory)\..\
5 |
6 |
7 | false
8 |
9 |
10 | false
11 |
12 |
13 | true
14 |
15 |
16 | false
17 |
18 |
19 |
20 |
21 |
22 |
26 |
27 |
28 |
29 |
30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget"))
31 |
32 |
33 |
34 |
35 | $(SolutionDir).nuget
36 |
37 |
38 |
39 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config
40 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config
41 |
42 |
43 |
44 | $(MSBuildProjectDirectory)\packages.config
45 | $(PackagesProjectConfig)
46 |
47 |
48 |
49 |
50 | $(NuGetToolsPath)\NuGet.exe
51 | @(PackageSource)
52 |
53 | "$(NuGetExePath)"
54 | mono --runtime=v4.0.30319 "$(NuGetExePath)"
55 |
56 | $(TargetDir.Trim('\\'))
57 |
58 | -RequireConsent
59 | -NonInteractive
60 |
61 | "$(SolutionDir) "
62 | "$(SolutionDir)"
63 |
64 |
65 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)
66 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols
67 |
68 |
69 |
70 | RestorePackages;
71 | $(BuildDependsOn);
72 |
73 |
74 |
75 |
76 | $(BuildDependsOn);
77 | BuildPackage;
78 |
79 |
80 |
81 |
82 |
83 |
84 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
99 |
100 |
103 |
104 |
105 |
106 |
108 |
109 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
141 |
142 |
143 |
144 |
145 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/ProcessDomainSetup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Diagnostics;
5 | using System.IO;
6 | using System.Reflection;
7 | using System.Runtime.InteropServices;
8 | using System.Runtime.Serialization.Formatters;
9 | using System.Runtime.Serialization.Formatters.Binary;
10 | using System.Security.Policy;
11 | using System.Text;
12 | using AppSecInc.ProcessDomain.Remoting;
13 |
14 | namespace AppSecInc.ProcessDomain
15 | {
16 | ///
17 | /// Parameters for a process domain
18 | ///
19 | [Serializable]
20 | public class ProcessDomainSetup
21 | {
22 | public const string DefaultCompilerVersion = "v3.5";
23 |
24 | public ProcessDomainSetup()
25 | {
26 | ExecutableDirectory = Path.GetTempPath();
27 | ProcessStartTimeout = new TimeSpan(0, 0, 60);
28 | FileDeletionTimeout = new TimeSpan(0, 0, 10);
29 | DeleteOnUnload = true;
30 | RestartOnProcessExit = true;
31 | AppDomainSetupInformation = AppDomain.CurrentDomain.SetupInformation;
32 | WorkingDirectory = Environment.CurrentDirectory;
33 | Evidence = AppDomain.CurrentDomain.Evidence;
34 | TypeFilterLevel = TypeFilterLevel.Low;
35 | DllDirectory = CurrentDllDirectory;
36 | EnvironmentVariables = new Dictionary();
37 | ExternalAssemblies = new Dictionary();
38 | PriorityClass = ProcessPriorityClass.Normal;
39 | CompilerVersion = DefaultCompilerVersion;
40 | CopyConfigurationFile = false;
41 | Remoting = new RemotingOptions();
42 | }
43 |
44 | ///
45 | /// Determines whether the AppDomainSetup configuration file has to be copied into assembly name dot config file.
46 | ///
47 | public bool CopyConfigurationFile { get; set; }
48 |
49 | ///
50 | /// Specifies the c# compiler version for the remote process
51 | ///
52 | public string CompilerVersion { get; set; }
53 |
54 | ///
55 | /// Specifies maximum time spent trying to delete a assembly from the disk.
56 | ///
57 | public TimeSpan FileDeletionTimeout
58 | {
59 | get;
60 | set;
61 | }
62 |
63 | ///
64 | /// Specifies where the temporary remote process executable file will be created
65 | ///
66 | public string ExecutableDirectory
67 | {
68 | get;
69 | set;
70 | }
71 |
72 | ///
73 | /// Specifies the working directory for the remote process
74 | ///
75 | public string WorkingDirectory
76 | {
77 | get;
78 | set;
79 | }
80 |
81 | ///
82 | /// Specifies a directory to invoke SetDllDirectory with to redirect DLL probing to the working directory
83 | ///
84 | public string DllDirectory
85 | {
86 | get;
87 | set;
88 | }
89 |
90 | ///
91 | /// Gets the currently configured DLL search path as set by SetDllDirectory
92 | ///
93 |
94 | public static string CurrentDllDirectory
95 | {
96 | get
97 | {
98 | int bytesNeeded = WinApi.GetDllDirectory(0, null);
99 | if (bytesNeeded == 0)
100 | {
101 | throw new Win32Exception();
102 | }
103 | var sb = new StringBuilder(bytesNeeded);
104 | // reset the last error on this thread
105 | WinApi.SetLastError(0);
106 | bytesNeeded = WinApi.GetDllDirectory(bytesNeeded, sb);
107 |
108 | // does 0 mean failure or empty string?
109 | if (bytesNeeded == 0)
110 | {
111 | int errorCode = Marshal.GetLastWin32Error();
112 | if (errorCode != 0)
113 | {
114 | throw new Win32Exception(errorCode);
115 | }
116 | }
117 | return sb.ToString();
118 | }
119 | }
120 |
121 | ///
122 | /// Specifies how long to wait for the remote process to start
123 | ///
124 | public TimeSpan ProcessStartTimeout
125 | {
126 | get;
127 | set;
128 | }
129 |
130 | ///
131 | /// Specifies whether or not to delete the generated executable after the process domain has unloaded
132 | ///
133 | public bool DeleteOnUnload
134 | {
135 | get;
136 | set;
137 | }
138 |
139 | ///
140 | /// Specifices whether the process domain process should be relaunched should the process exit prematurely
141 | ///
142 | public bool RestartOnProcessExit
143 | {
144 | get;
145 | set;
146 | }
147 |
148 | ///
149 | /// Setup information for the AppDomain that the object will be created in, in the remote process. By default, this will be the
150 | /// current domain's setup information from which the proxy is being created
151 | ///
152 | public AppDomainSetup AppDomainSetupInformation
153 | {
154 | get;
155 | set;
156 | }
157 |
158 | ///
159 | /// Allows specifying which platform to compile the target remote process assembly for
160 | ///
161 | public PlatformTarget Platform
162 | {
163 | get;
164 | set;
165 | }
166 |
167 | ///
168 | /// Remote security policy
169 | ///
170 | public Evidence Evidence
171 | {
172 | get;
173 | set;
174 | }
175 |
176 |
177 | ///
178 | /// Remoting type filter level - controls how much functionality is exposed via remoting the process domain remotely
179 | ///
180 | public TypeFilterLevel TypeFilterLevel
181 | {
182 | get;
183 | set;
184 | }
185 |
186 | ///
187 | /// Environment variables of the remote process
188 | ///
189 | public Dictionary EnvironmentVariables
190 | {
191 | get;
192 | set;
193 | }
194 |
195 | ///
196 | /// A map of assembly names to assembly file locations that will need to be resolved inside the Process Domain
197 | ///
198 | public Dictionary ExternalAssemblies
199 | {
200 | get;
201 | set;
202 | }
203 |
204 | ///
205 | /// The priority to run the remote process at
206 | ///
207 | public ProcessPriorityClass PriorityClass
208 | {
209 | get;
210 | set;
211 | }
212 |
213 | ///
214 | /// Advanced remoting-related options
215 | ///
216 | public RemotingOptions Remoting { get; private set; }
217 |
218 | internal static void Serialize(ProcessDomainSetup setup, string filename)
219 | {
220 | var formatter = new BinaryFormatter();
221 |
222 | using (var fs = new FileStream(filename, FileMode.Create, FileAccess.Write))
223 | {
224 | formatter.Serialize(fs, setup);
225 | }
226 | }
227 |
228 | internal static ProcessDomainSetup Deserialize(string filename)
229 | {
230 | var formatter = new BinaryFormatter();
231 |
232 | using (var fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
233 | {
234 | return (ProcessDomainSetup)formatter.Deserialize(fs);
235 | }
236 | }
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Eclipse Public License - v 1.0
2 |
3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
6 |
7 | 1. DEFINITIONS
8 |
9 | "Contribution" means:
10 |
11 | a) in the case of the initial Contributor, the initial code and documentation
12 | distributed under this Agreement, and
13 | b) in the case of each subsequent Contributor:
14 | i) changes to the Program, and
15 | ii) additions to the Program;
16 |
17 | where such changes and/or additions to the Program originate from and are
18 | distributed by that particular Contributor. A Contribution 'originates'
19 | from a Contributor if it was added to the Program by such Contributor
20 | itself or anyone acting on such Contributor's behalf. Contributions do not
21 | include additions to the Program which: (i) are separate modules of
22 | software distributed in conjunction with the Program under their own
23 | license agreement, and (ii) are not derivative works of the Program.
24 |
25 | "Contributor" means any person or entity that distributes the Program.
26 |
27 | "Licensed Patents" mean patent claims licensable by a Contributor which are
28 | necessarily infringed by the use or sale of its Contribution alone or when
29 | combined with the Program.
30 |
31 | "Program" means the Contributions distributed in accordance with this
32 | Agreement.
33 |
34 | "Recipient" means anyone who receives the Program under this Agreement,
35 | including all Contributors.
36 |
37 | 2. GRANT OF RIGHTS
38 | a) Subject to the terms of this Agreement, each Contributor hereby grants
39 | Recipient a non-exclusive, worldwide, royalty-free copyright license to
40 | reproduce, prepare derivative works of, publicly display, publicly
41 | perform, distribute and sublicense the Contribution of such Contributor,
42 | if any, and such derivative works, in source code and object code form.
43 | b) Subject to the terms of this Agreement, each Contributor hereby grants
44 | Recipient a non-exclusive, worldwide, royalty-free patent license under
45 | Licensed Patents to make, use, sell, offer to sell, import and otherwise
46 | transfer the Contribution of such Contributor, if any, in source code and
47 | object code form. This patent license shall apply to the combination of
48 | the Contribution and the Program if, at the time the Contribution is
49 | added by the Contributor, such addition of the Contribution causes such
50 | combination to be covered by the Licensed Patents. The patent license
51 | shall not apply to any other combinations which include the Contribution.
52 | No hardware per se is licensed hereunder.
53 | c) Recipient understands that although each Contributor grants the licenses
54 | to its Contributions set forth herein, no assurances are provided by any
55 | Contributor that the Program does not infringe the patent or other
56 | intellectual property rights of any other entity. Each Contributor
57 | disclaims any liability to Recipient for claims brought by any other
58 | entity based on infringement of intellectual property rights or
59 | otherwise. As a condition to exercising the rights and licenses granted
60 | hereunder, each Recipient hereby assumes sole responsibility to secure
61 | any other intellectual property rights needed, if any. For example, if a
62 | third party patent license is required to allow Recipient to distribute
63 | the Program, it is Recipient's responsibility to acquire that license
64 | before distributing the Program.
65 | d) Each Contributor represents that to its knowledge it has sufficient
66 | copyright rights in its Contribution, if any, to grant the copyright
67 | license set forth in this Agreement.
68 |
69 | 3. REQUIREMENTS
70 |
71 | A Contributor may choose to distribute the Program in object code form under
72 | its own license agreement, provided that:
73 |
74 | a) it complies with the terms and conditions of this Agreement; and
75 | b) its license agreement:
76 | i) effectively disclaims on behalf of all Contributors all warranties
77 | and conditions, express and implied, including warranties or
78 | conditions of title and non-infringement, and implied warranties or
79 | conditions of merchantability and fitness for a particular purpose;
80 | ii) effectively excludes on behalf of all Contributors all liability for
81 | damages, including direct, indirect, special, incidental and
82 | consequential damages, such as lost profits;
83 | iii) states that any provisions which differ from this Agreement are
84 | offered by that Contributor alone and not by any other party; and
85 | iv) states that source code for the Program is available from such
86 | Contributor, and informs licensees how to obtain it in a reasonable
87 | manner on or through a medium customarily used for software exchange.
88 |
89 | When the Program is made available in source code form:
90 |
91 | a) it must be made available under this Agreement; and
92 | b) a copy of this Agreement must be included with each copy of the Program.
93 | Contributors may not remove or alter any copyright notices contained
94 | within the Program.
95 |
96 | Each Contributor must identify itself as the originator of its Contribution,
97 | if
98 | any, in a manner that reasonably allows subsequent Recipients to identify the
99 | originator of the Contribution.
100 |
101 | 4. COMMERCIAL DISTRIBUTION
102 |
103 | Commercial distributors of software may accept certain responsibilities with
104 | respect to end users, business partners and the like. While this license is
105 | intended to facilitate the commercial use of the Program, the Contributor who
106 | includes the Program in a commercial product offering should do so in a manner
107 | which does not create potential liability for other Contributors. Therefore,
108 | if a Contributor includes the Program in a commercial product offering, such
109 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
110 | every other Contributor ("Indemnified Contributor") against any losses,
111 | damages and costs (collectively "Losses") arising from claims, lawsuits and
112 | other legal actions brought by a third party against the Indemnified
113 | Contributor to the extent caused by the acts or omissions of such Commercial
114 | Contributor in connection with its distribution of the Program in a commercial
115 | product offering. The obligations in this section do not apply to any claims
116 | or Losses relating to any actual or alleged intellectual property
117 | infringement. In order to qualify, an Indemnified Contributor must:
118 | a) promptly notify the Commercial Contributor in writing of such claim, and
119 | b) allow the Commercial Contributor to control, and cooperate with the
120 | Commercial Contributor in, the defense and any related settlement
121 | negotiations. The Indemnified Contributor may participate in any such claim at
122 | its own expense.
123 |
124 | For example, a Contributor might include the Program in a commercial product
125 | offering, Product X. That Contributor is then a Commercial Contributor. If
126 | that Commercial Contributor then makes performance claims, or offers
127 | warranties related to Product X, those performance claims and warranties are
128 | such Commercial Contributor's responsibility alone. Under this section, the
129 | Commercial Contributor would have to defend claims against the other
130 | Contributors related to those performance claims and warranties, and if a
131 | court requires any other Contributor to pay any damages as a result, the
132 | Commercial Contributor must pay those damages.
133 |
134 | 5. NO WARRANTY
135 |
136 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
137 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
138 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
139 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
140 | Recipient is solely responsible for determining the appropriateness of using
141 | and distributing the Program and assumes all risks associated with its
142 | exercise of rights under this Agreement , including but not limited to the
143 | risks and costs of program errors, compliance with applicable laws, damage to
144 | or loss of data, programs or equipment, and unavailability or interruption of
145 | operations.
146 |
147 | 6. DISCLAIMER OF LIABILITY
148 |
149 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
150 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
151 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
152 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
153 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
154 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
155 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
156 | OF SUCH DAMAGES.
157 |
158 | 7. GENERAL
159 |
160 | If any provision of this Agreement is invalid or unenforceable under
161 | applicable law, it shall not affect the validity or enforceability of the
162 | remainder of the terms of this Agreement, and without further action by the
163 | parties hereto, such provision shall be reformed to the minimum extent
164 | necessary to make such provision valid and enforceable.
165 |
166 | If Recipient institutes patent litigation against any entity (including a
167 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself
168 | (excluding combinations of the Program with other software or hardware)
169 | infringes such Recipient's patent(s), then such Recipient's rights granted
170 | under Section 2(b) shall terminate as of the date such litigation is filed.
171 |
172 | All Recipient's rights under this Agreement shall terminate if it fails to
173 | comply with any of the material terms or conditions of this Agreement and does
174 | not cure such failure in a reasonable period of time after becoming aware of
175 | such noncompliance. If all Recipient's rights under this Agreement terminate,
176 | Recipient agrees to cease use and distribution of the Program as soon as
177 | reasonably practicable. However, Recipient's obligations under this Agreement
178 | and any licenses granted by Recipient relating to the Program shall continue
179 | and survive.
180 |
181 | Everyone is permitted to copy and distribute copies of this Agreement, but in
182 | order to avoid inconsistency the Agreement is copyrighted and may only be
183 | modified in the following manner. The Agreement Steward reserves the right to
184 | publish new versions (including revisions) of this Agreement from time to
185 | time. No one other than the Agreement Steward has the right to modify this
186 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The
187 | Eclipse Foundation may assign the responsibility to serve as the Agreement
188 | Steward to a suitable separate entity. Each new version of the Agreement will
189 | be given a distinguishing version number. The Program (including
190 | Contributions) may always be distributed subject to the version of the
191 | Agreement under which it was received. In addition, after a new version of the
192 | Agreement is published, Contributor may elect to distribute the Program
193 | (including its Contributions) under the new version. Except as expressly
194 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
195 | licenses to the intellectual property of any Contributor under this Agreement,
196 | whether expressly, by implication, estoppel or otherwise. All rights in the
197 | Program not expressly granted under this Agreement are reserved.
198 |
199 | This Agreement is governed by the laws of the State of New York and the
200 | intellectual property laws of the United States of America. No party to this
201 | Agreement will bring a legal action under this Agreement more than one year
202 | after the cause of action arose. Each party waives its rights to a jury trial in
203 | any resulting litigation.
204 |
205 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/Remoting/RemotingOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Net;
5 | using System.Net.Security;
6 | using System.Reflection;
7 | using System.Security.Principal;
8 | using System.Text;
9 | using AppSecInc.ProcessDomain.Remoting.Attributes;
10 |
11 | namespace AppSecInc.ProcessDomain.Remoting
12 | {
13 | // Source: http://msdn.microsoft.com/en-us/library/bb187421(v=vs.80).aspx
14 | ///
15 | /// Various options for configuring remoting between parent and child processes. The client settings will apply to the parent process creating the process domain and the server settings will apply
16 | /// to the process domain process itself.
17 | ///
18 | [Serializable]
19 | public sealed class RemotingOptions
20 | {
21 | public RemotingOptions()
22 | {
23 | Server = new ServerOptions();
24 | Client = new ClientOptions();
25 | SetPropertiesToDefaultValues(Server);
26 | SetPropertiesToDefaultValues(Client);
27 | }
28 |
29 | [Serializable]
30 | public class GeneralOptions
31 | {
32 | internal GeneralOptions()
33 | {
34 | ExtraProperties = new Hashtable();
35 | }
36 |
37 | ///
38 | /// A Boolean value (true or false) that specifies whether communications on the channel are secure. The default is false. When it is set to true, the TokenImpersonationLevel property is set to Identification
39 | ///
40 | [PropertyName("secure")]
41 | [DefaultValue(false)]
42 | public bool Secure { get; set; }
43 |
44 | ///
45 | /// A value of type ProtectionLevel. The default is None, unless the secure property is set to true, in which case the default is EncryptAndSign. You must set the secure property to true to set the ProtectionLevel property to any value other than None. Note that None is the only setting that is compatible with Windows 95, Windows 98, or Windows Me.
46 | ///
47 | [PropertyName("protectionLevel")]
48 | [DefaultValue(ProtectionLevel.None)]
49 | public ProtectionLevel ProtectionLevel { get; set; }
50 |
51 | ///
52 | /// Any extra unimplemented properties to pass to IpcChannel
53 | ///
54 | public IDictionary ExtraProperties { get; private set; }
55 | }
56 |
57 | [Serializable]
58 | public sealed class ServerOptions : GeneralOptions
59 | {
60 | internal ServerOptions()
61 | {
62 | }
63 |
64 | ///
65 | /// A string that specifies the assembly, namespace, and class name of a class that implements the IAuthorizeRemotingConnection interface. The format of the string must be AuthorizationModuleNameSpace.AuthorizationModuleClass,AuthorizationModuleAssembly.
66 | ///
67 | [PropertyName("authorizationModule")]
68 | [DefaultValue(null)]
69 | public string AuthorizationModule { get; set; }
70 |
71 | ///
72 | /// A string that specifies the group or user that has permission to connect to this channel. The default is to allow access to all authorized users.
73 | ///
74 | [PropertyName("authorizedGroup")]
75 | [DefaultValue(null)]
76 | public string AuthorizedGroup { get; set; }
77 |
78 | ///
79 | /// A Boolean value (true or false) that specifies whether the server should impersonate the client. The default is false.
80 | ///
81 | [PropertyName("impersonate")]
82 | [DefaultValue(false)]
83 | public bool Impersonate { get; set; }
84 | }
85 |
86 | ///
87 | /// Remoting server options for the remote process domain process
88 | ///
89 | public ServerOptions Server { get; private set; }
90 |
91 | [Serializable]
92 | public sealed class ClientOptions : GeneralOptions
93 | {
94 | internal ClientOptions()
95 | {
96 | }
97 |
98 | ///
99 | /// A string that specifies the name that to be used as the connection group name on the server if the unsafeAuthenticatedConnectionSharing value is set to true. This property is ignored if unsafeAuthenticatedConnectionSharing is not set to true. If specified, make sure that this name maps to only one authenticated user. This property is supported only by version 1.1 or greater of the .NET Framework on the following platforms: Windows 98, Windows NT 4.0, Windows Me, Windows 2000, Windows XP Home Edition, Windows XP Professional, and the Windows Server 2003 family.
100 | ///
101 | [PropertyName("connectionGroupName")]
102 | [DefaultValue(null)]
103 | public string ConnectionGroupName { get; set; }
104 |
105 | ///
106 | /// An object that implements the ICredentials interface that represents the identity of the client.
107 | ///
108 | [PropertyName("credentials")]
109 | [DefaultValue(null)]
110 | public ICredentials Credentials { get; set; }
111 |
112 | ///
113 | /// A string that specifies a domain name to be used, in conjunction with the user name specified by username and the password specified by password, when authenticating to a server channel.
114 | ///
115 | [PropertyName("domain")]
116 | [DefaultValue(null)]
117 | public string Domain { get; set; }
118 |
119 | ///
120 | /// A string that specifies a password to be used, in conjunction with the user name specified by username and the domain specified by domain, when authenticating to a server channel.
121 | ///
122 | [PropertyName("password")]
123 | [DefaultValue(null)]
124 | public string Password { get; set; }
125 |
126 |
127 | ///
128 | /// A string that specifies the servicePrincipalName for Kerberos authentication. The default value is null.
129 | ///
130 | [PropertyName("serverPrincipalName")]
131 | [DefaultValue(null)]
132 | public string ServerPrincipalName { get; set; }
133 |
134 | ///
135 | /// A value of type TokenImpersonationLevel. This property specifies how the client is authenticated with the server. The default is None, unless the secure property is set to true, in which case the default is Identification.
136 | ///
137 | [PropertyName("tokenImpersonationLevel")]
138 | [DefaultValue(TokenImpersonationLevel.None)]
139 | public TokenImpersonationLevel TokenImpersonationLevel { get; set; }
140 |
141 | ///
142 | /// A Boolean value that indicates whether to allow high-speed NTLM-authenticated connection sharing. If this value is set to true, the connectionGroupName value must map to only one authenticated user. This property is ignored if the useAuthenticatedConnectionSharing value is set to true. This property is supported only by version 1.1 or greater of the .NET Framework on the following platforms: Windows 98, Windows NT 4.0, Windows Me, Windows 2000, Windows XP Home Edition, Windows XP Professional, and Windows Server 2003.
143 | ///
144 | [PropertyName("unsafeAuthenticatedConnectionSharing")]
145 | [DefaultValue(false)]
146 | public bool UnsafeAuthenticatedConnectionSharing { get; set; }
147 |
148 | ///
149 | /// A Boolean value that indicates whether the server channel reuses authenticated connections rather than authenticate each incoming call. By default, this value is set to true if the useDefaultCredentials value is also set to true; otherwise, the value is set to false, which means that each call is authenticated if the server requires authentication. This also applies to the programmatic equivalent, which is achieved either by creating an object that implements IDictionary, setting the credentials property to CredentialCache.DefaultCredentials, and passing that value to the channel sink, or by using the IDictionary returned from the ChannelServices.GetChannelSinkProperties method. This name/value pair is supported only by version 1.1 or greater of the .NET Framework on the following platforms: Microsoft Windows 98, Windows NT 4.0, Windows Millennium Edition (Windows Me), Windows 2000, Windows XP Home Edition, Windows XP Professional, and Windows Server 2003.
150 | ///
151 | [PropertyName("useAuthenticatedConnectionSharing")]
152 | [DefaultValue(true)]
153 | public bool UseAuthenticatedConnectionSharing { get; set; }
154 |
155 | ///
156 | /// A Boolean value that specifies whether to present credentials for the identity associated with the current thread when authenticating to a server channel.
157 | ///
158 | [PropertyName("useDefaultCredentials")]
159 | [DefaultValue(false)]
160 | public bool UseDefaultCredentials { get; set; }
161 |
162 | [PropertyName("username")]
163 | [DefaultValue(null)]
164 | public string Username { get; set; }
165 | }
166 |
167 | ///
168 | /// Remoting client options for the process creating and interacting with a process domain process
169 | ///
170 | public ClientOptions Client { get; private set; }
171 |
172 | internal void ApplyClientProperties(IDictionary properties)
173 | {
174 | Apply(Client, properties);
175 | }
176 |
177 | internal void ApplyServerProperties(IDictionary properties)
178 | {
179 | Apply(Server, properties);
180 | }
181 |
182 | private T GetAttribute(PropertyInfo propInfo)
183 | {
184 | var attribs = propInfo.GetCustomAttributes(typeof(T), false);
185 | if (attribs.Length == 0)
186 | return default(T);
187 | return (T)attribs[0];
188 | }
189 |
190 | private void SetPropertiesToDefaultValues(GeneralOptions options)
191 | {
192 | if (options == null)
193 | throw new ArgumentNullException("options");
194 |
195 | foreach (var propInfo in options.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
196 | {
197 | var propNameAttrib = GetAttribute(propInfo);
198 | var defaultValueAttrib = GetAttribute(propInfo);
199 | if (propNameAttrib == null || defaultValueAttrib == null)
200 | continue;
201 | propInfo.SetValue(options, defaultValueAttrib.Value, null);
202 | }
203 | }
204 |
205 | private void Apply(GeneralOptions options, IDictionary properties)
206 | {
207 | if (options == null)
208 | throw new ArgumentNullException("options");
209 | if (properties == null)
210 | throw new ArgumentNullException("properties");
211 |
212 | foreach (var propInfo in options.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
213 | {
214 | var propNameAttrib = GetAttribute(propInfo);
215 | var defaultValueAttrib = GetAttribute(propInfo);
216 | if (propNameAttrib == null || defaultValueAttrib == null)
217 | continue;
218 | var value = propInfo.GetValue(options, null);
219 | if ((value == null && defaultValueAttrib.Value == null) || (value != null && value.Equals(defaultValueAttrib.Value)))
220 | continue;
221 | properties[propNameAttrib.Name] = value;
222 | }
223 |
224 | if (options.ExtraProperties != null)
225 | {
226 | foreach (var key in options.ExtraProperties.Keys)
227 | {
228 | properties[key] = options.ExtraProperties[key];
229 | }
230 | }
231 | }
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain/Remoting/ActivatorProcess.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Threading;
5 | using log4net;
6 | using AppSecInc.ProcessDomain.Utils;
7 |
8 | namespace AppSecInc.ProcessDomain.Remoting
9 | {
10 | public delegate void AttachedDelegate();
11 | public delegate void DetachedDelegate();
12 |
13 | ///
14 | /// Represents a process for a Process Domain and handles things such as attach/detach events and restarting the process
15 | ///
16 | internal class ActivatorProcess : IDisposable
17 | {
18 | static readonly ILog Logger = LogManager.GetLogger(typeof(ActivatorProcess));
19 | public const string AssemblyName = "{0}.exe";
20 | public const string ConfigName = "{0}.ctl";
21 |
22 | public event AttachedDelegate Attached;
23 | public event DetachedDelegate Detached;
24 |
25 | readonly ProcessDomainSetup _setupInfo;
26 | readonly string _assemblyFile;
27 | readonly Process _process;
28 | readonly string _friendlyName;
29 | readonly string _setupInfoFile;
30 | ProcessStatus _processStatus;
31 | ActivatorClient _client;
32 | bool _disposed;
33 |
34 | private delegate void DeleteAssemblyFileDelegate(ManualResetEvent cancelEvent);
35 |
36 | public ActivatorProcess(string friendlyName, ProcessDomainSetup setupInfo)
37 | {
38 | Logger.InfoFormat("Creating ActivatorProcess for Process Domain '{0}' with the following configuration:", friendlyName);
39 | LogProcessDomainSetup(setupInfo);
40 |
41 | _friendlyName = friendlyName;
42 | _setupInfo = setupInfo;
43 | _assemblyFile = ActivatorHostAssemblyGenerator.CreateRemoteHostAssembly(friendlyName, setupInfo);
44 | Logger.InfoFormat("Generated Assembly: {0}", _assemblyFile);
45 |
46 | var startInfo = new ProcessStartInfo
47 | {
48 | FileName = _assemblyFile,
49 | CreateNoWindow = true,
50 | UseShellExecute = false,
51 | ErrorDialog = false,
52 | WorkingDirectory = _setupInfo.WorkingDirectory,
53 | };
54 |
55 | if (_setupInfo.EnvironmentVariables != null)
56 | {
57 | foreach (var kv in _setupInfo.EnvironmentVariables)
58 | {
59 | startInfo.EnvironmentVariables[kv.Key] = kv.Value;
60 | }
61 | }
62 |
63 | _process = new Process
64 | {
65 | StartInfo = startInfo
66 | };
67 |
68 | _setupInfoFile = Path.Combine(setupInfo.ExecutableDirectory, string.Format(ConfigName, friendlyName));
69 |
70 | _process.Exited += Process_Exited;
71 | _process.EnableRaisingEvents = true;
72 | }
73 |
74 | private static void LogProcessDomainSetup(ProcessDomainSetup setupInfo)
75 | {
76 | Logger.InfoFormat("ExecutableDirectory = {0}", setupInfo.ExecutableDirectory);
77 | Logger.InfoFormat("ProcessStartTimeout = {0}", setupInfo.ProcessStartTimeout);
78 | Logger.InfoFormat("FileDeletionTimeout = {0}", setupInfo.FileDeletionTimeout);
79 | Logger.InfoFormat("DeleteOnUnload = {0}", setupInfo.DeleteOnUnload);
80 | Logger.InfoFormat("RestartOnProcessExit = {0}", setupInfo.RestartOnProcessExit);
81 | Logger.InfoFormat("WorkingDirectory = {0}", setupInfo.WorkingDirectory);
82 | }
83 |
84 | private void Process_Exited(object sender, EventArgs e)
85 | {
86 | LogExitReason();
87 |
88 | var tmp = Detached;
89 |
90 | if (tmp != null)
91 | {
92 | tmp();
93 | }
94 |
95 | // If Process Status is Active is because it was running and ended unexpectedly,
96 | // If status is Terminated then it was due to Terminate method invoked.
97 | if (_setupInfo.RestartOnProcessExit
98 | && _processStatus != ProcessStatus.Killed
99 | && _client != null)
100 | {
101 | Logger.InfoFormat("Restarting process for Process Domain '{0}'", _friendlyName);
102 | try
103 | {
104 | Start();
105 | }
106 | catch (Exception ex)
107 | {
108 | Logger.Error(string.Format("Error restarting process for Process Domain '{0}'", _friendlyName), ex);
109 | }
110 | }
111 | }
112 |
113 | ///
114 | /// Logs the current action taken.
115 | ///
116 | private void LogExitReason()
117 | {
118 | switch (_processStatus)
119 | {
120 | case ProcessStatus.Active:
121 | Logger.WarnFormat("Process Domain '{0}' process exited unexpectedly", _friendlyName);
122 | break;
123 | case ProcessStatus.Killed:
124 | Logger.InfoFormat("Process Domain '{0}' process exited", _friendlyName);
125 | break;
126 | case ProcessStatus.Terminated:
127 | Logger.InfoFormat("Process Domain '{0}' process restarted", _friendlyName);
128 | break;
129 | }
130 | }
131 |
132 | ///
133 | /// Starts the remote process which will host an Activator
134 | ///
135 | public void Start()
136 | {
137 | CheckDisposed();
138 | DisposeClient();
139 |
140 | Logger.InfoFormat("Starting process for Process Domain '{0}'", _friendlyName);
141 |
142 | String processGuid = Guid.NewGuid().ToString();
143 |
144 | bool created;
145 | var serverStartedHandle = new EventWaitHandle(false, EventResetMode.ManualReset, string.Format(ActivatorHost.EventName, processGuid), out created);
146 |
147 | // We set guid to a new value every time therefore this "should" never happen.
148 | if (!created)
149 | {
150 | throw new Exception("Event handle already existed for remote process");
151 | }
152 |
153 | string processDomainAssemblyPath = AssemblyUtils.GetFilePathFromFileUri(typeof(ActivatorProcess).Assembly.CodeBase);
154 | ProcessDomainSetup.Serialize(_setupInfo, _setupInfoFile);
155 |
156 | // args[0] = process domain assembly path
157 | // args[1] = guid
158 | // args[2] = process id
159 | // args[3] = ProcessDomainSetup file
160 | _process.StartInfo.Arguments = string.Format("\"{0}\" {1} {2} \"{3}\"", processDomainAssemblyPath, processGuid, Process.GetCurrentProcess().Id, _setupInfoFile);
161 |
162 | if (!_process.Start())
163 | {
164 | throw new Exception(string.Format("Failed to start process from: {0}", _process.StartInfo.FileName));
165 | }
166 |
167 | Logger.InfoFormat("Process successfully started with process id {0}", _process.Id);
168 |
169 | if (!serverStartedHandle.WaitOne(_setupInfo.ProcessStartTimeout))
170 | {
171 | throw new Exception("Timed-out waiting for remote process to start");
172 | }
173 |
174 | serverStartedHandle.Close();
175 |
176 | _processStatus = ProcessStatus.Active;
177 | _process.PriorityClass = _setupInfo.PriorityClass;
178 | _client = new ActivatorClient(processGuid, _setupInfo);
179 |
180 | var tmp = Attached;
181 | if (tmp != null)
182 | {
183 | tmp();
184 | }
185 | }
186 |
187 | ///
188 | /// A proxy to the remote activator to use to create remote object instances
189 | ///
190 | public Activator Activator
191 | {
192 | get { return _client != null ? _client.Activator : null; }
193 | }
194 |
195 | ///
196 | /// Terminates this process, then it starts again.
197 | ///
198 | public void Terminate()
199 | {
200 | Logger.InfoFormat("Ternating Process Domain '{0}' process", _friendlyName);
201 | _processStatus = ProcessStatus.Terminated;
202 |
203 | try
204 | {
205 | if (!_process.HasExited)
206 | {
207 | _process.Kill();
208 | }
209 | }
210 | catch (Exception ex)
211 | {
212 | Logger.Error("Failed to terminate Process Domain '{0}' process due to an exception", ex);
213 | throw;
214 | }
215 | }
216 |
217 | ///
218 | /// Kills the remote process.
219 | ///
220 | public void Kill()
221 | {
222 | Logger.InfoFormat("Killing Process Domain '{0}' process", _friendlyName);
223 | _processStatus = ProcessStatus.Killed;
224 |
225 | try
226 | {
227 | if (!_process.HasExited)
228 | {
229 | _process.Kill();
230 | _process.WaitForExit();
231 | }
232 | }
233 | catch (Exception ex)
234 | {
235 | Logger.Error("Failed to kill Process Domain '{0}' process due to an exception", ex);
236 | throw;
237 | }
238 |
239 | FreeResources();
240 | }
241 |
242 | ///
243 | /// It deletes the assembly and the setup info file from the system.
244 | ///
245 | private void FreeResources()
246 | {
247 | if (_setupInfo.DeleteOnUnload)
248 | {
249 | DeleteAssemblyFileDelegate deleteAssemblyFileDelegate = DeleteAssemblyFile;
250 |
251 | var cancelEvent = new ManualResetEvent(false);
252 | var result = deleteAssemblyFileDelegate.BeginInvoke(cancelEvent, null, null);
253 |
254 | Logger.InfoFormat("Deleting file with timeout: {0}", _setupInfo.FileDeletionTimeout);
255 | if (!result.AsyncWaitHandle.WaitOne(_setupInfo.FileDeletionTimeout))
256 | {
257 | //this will send a signal to cancel delete operation.
258 | cancelEvent.Set();
259 | }
260 |
261 | // Free resourses and get the last exception thrown by delete file logic.
262 | // we need a loop here because operating system might not necessarily release file handle immediately
263 | // after stopping the process.
264 | try
265 | {
266 | deleteAssemblyFileDelegate.EndInvoke(result);
267 | }
268 | catch (Exception lastException)
269 | {
270 | Logger.Error(string.Format("Failed to delete Process Domain '{0}' assembly due to an exception", _friendlyName), lastException);
271 | throw new DeleteOnUnloadException(string.Format("Failed to delete Process Domain '{0}' assembly", _friendlyName), lastException);
272 | }
273 |
274 | try
275 | {
276 | File.Delete(_setupInfoFile);
277 | }
278 | catch (Exception lastException)
279 | {
280 | Logger.Error(string.Format("Failed to delete Process Domain '{0}' configuration file due to an exception", _friendlyName), lastException);
281 | throw new DeleteOnUnloadException(string.Format("Failed to delete Process Domain '{0}' configuration file", _friendlyName), lastException);
282 | }
283 | }
284 | }
285 |
286 | ///
287 | /// Deletes assembly file from the disk.
288 | /// This method will try until it succeeds or until stopped by an even.
289 | ///
290 | private void DeleteAssemblyFile(ManualResetEvent cancelEvent)
291 | {
292 | bool deleted = false;
293 | bool canceled;
294 |
295 | Exception lastException = null;
296 |
297 | do
298 | {
299 | try
300 | {
301 | File.Delete(_assemblyFile);
302 | if (_setupInfo.CopyConfigurationFile)
303 | {
304 | var configFile = _assemblyFile + ".config";
305 | if (File.Exists(configFile))
306 | File.Delete(configFile);
307 | }
308 | deleted = true;
309 | }
310 | catch (Exception ex)
311 | {
312 | //save last exception.
313 | lastException = ex;
314 | Thread.Sleep(100);
315 | }
316 |
317 | canceled = cancelEvent.WaitOne(0);
318 |
319 | } while (!deleted && !canceled);
320 |
321 | if (!deleted && lastException != null)
322 | {
323 | throw lastException;
324 | }
325 | }
326 |
327 | public void Dispose()
328 | {
329 | if (_disposed)
330 | return;
331 | _disposed = true;
332 | DisposeClient();
333 | }
334 |
335 | private void CheckDisposed()
336 | {
337 | if (_disposed)
338 | {
339 | throw new ObjectDisposedException("ActivatorProcess");
340 | }
341 | }
342 |
343 | private void DisposeClient()
344 | {
345 | if (_client != null)
346 | {
347 | _client.Dispose();
348 | _client = null;
349 | }
350 | }
351 | }
352 | }
--------------------------------------------------------------------------------
/src/AppSecInc.ProcessDomain.UnitTests/TestProcessDomain.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Runtime.Remoting;
6 | using System.Runtime.Serialization;
7 | using System.Runtime.Serialization.Formatters;
8 | using System.Security;
9 | using System.Security.Permissions;
10 | using System.Threading;
11 | using AppSecInc.ProcessDomain.Remoting;
12 | using AppSecInc.ProcessDomain.Utils;
13 | using NUnit.Framework;
14 |
15 | namespace AppSecInc.ProcessDomain.UnitTests
16 | {
17 | [TestFixture]
18 | public class TestProcessDomain
19 | {
20 | static readonly string TestObjectAssemblyName = typeof(RemoteTestObject).Assembly.FullName;
21 | static readonly string TestObjectTypeName = typeof(RemoteTestObject).FullName;
22 |
23 | static readonly ProcessDomainSetup DefaultSetupInfo = new ProcessDomainSetup
24 | {
25 | TypeFilterLevel = TypeFilterLevel.Full
26 | };
27 |
28 | [Test]
29 | public void TestFriendlyName()
30 | {
31 | using (var domain = ProcessDomain.CreateDomain("ProcessDomain", DefaultSetupInfo))
32 | {
33 | var obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName);
34 | Assert.That(obj.GetProcessId(), Is.Not.EqualTo(Process.GetCurrentProcess().Id));
35 | Assert.That(obj.GetProcessFileName(), Is.Not.EqualTo(Process.GetCurrentProcess().MainModule.FileName));
36 | Assert.That(obj.GetProcessFileName().EndsWith("ProcessDomain.exe"));
37 | }
38 | }
39 |
40 | [Test]
41 | public void TestDomainAttachDetach()
42 | {
43 | var attachedEvent = new ManualResetEvent(false);
44 | var detachedEvent = new ManualResetEvent(false);
45 |
46 | using (var domain = ProcessDomain.CreateDomain("ProcessDomain", DefaultSetupInfo))
47 | {
48 | domain.Attached += () => attachedEvent.Set();
49 | domain.Detached += () => detachedEvent.Set();
50 |
51 | var obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName);
52 | Assert.That(!attachedEvent.WaitOne(0));
53 | Assert.That(!detachedEvent.WaitOne(0));
54 |
55 | // restart should occur, but our current object will be invalid
56 | Process.GetProcessById(obj.GetProcessId()).Kill();
57 | Assert.That(detachedEvent.WaitOne(10000), "Timed-out waiting for process to die");
58 | Assert.That(attachedEvent.WaitOne(10000), "Timed-out waiting for process to respawn");
59 | Assert.Throws(() => obj.GetProcessId());
60 |
61 | // create object in restarted domain
62 | obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName);
63 | Assert.That(obj.GetProcessId(), Is.Not.EqualTo(Process.GetCurrentProcess().Id));
64 |
65 | }
66 |
67 | var setupInfo = new ProcessDomainSetup
68 | {
69 | RestartOnProcessExit = false,
70 | TypeFilterLevel = TypeFilterLevel.Full
71 | };
72 |
73 | // now restart should not occur
74 | using (var domain = ProcessDomain.CreateDomain("RemoteProcess2", setupInfo))
75 | {
76 | domain.Attached += () => attachedEvent.Set();
77 | domain.Detached += () => detachedEvent.Set();
78 |
79 | var obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName);
80 | Assert.That(obj.GetProcessId(), Is.Not.EqualTo(Process.GetCurrentProcess().Id));
81 |
82 | attachedEvent.Reset();
83 | detachedEvent.Reset();
84 |
85 | Process.GetProcessById(obj.GetProcessId()).Kill();
86 |
87 | Assert.That(detachedEvent.WaitOne(10000), "Timed-out waiting for process to die");
88 | Assert.That(!attachedEvent.WaitOne(5000), "Unexpected re-attach");
89 | Assert.Throws(() => obj.GetProcessId());
90 | Assert.Throws(() => obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName));
91 | }
92 | }
93 |
94 | [Test]
95 | public void TestDefaultWorkingDirectory()
96 | {
97 | using (var domain = ProcessDomain.CreateDomain("ProcessDomain", DefaultSetupInfo))
98 | {
99 | var obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName);
100 | Assert.That(obj.CurrentDirectory, Is.EqualTo(Environment.CurrentDirectory));
101 | }
102 | }
103 |
104 | [Test]
105 | public void TestExecutableLocation()
106 | {
107 | string desiredExecutableFileName = Path.Combine(Environment.CurrentDirectory, "MyDomain.exe");
108 |
109 | var setupInfo = new ProcessDomainSetup
110 | {
111 | ExecutableDirectory = Environment.CurrentDirectory,
112 | TypeFilterLevel = TypeFilterLevel.Full
113 | };
114 |
115 | // default uses temp directory
116 | using (var domain1 = ProcessDomain.CreateDomain("MyDomain", DefaultSetupInfo))
117 | {
118 | var obj = (RemoteTestObject)domain1.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName);
119 | Assert.That(obj.GetProcessFileName(), Is.Not.EqualTo(desiredExecutableFileName));
120 | }
121 |
122 | // now using our specified location
123 | using (var domain2 = ProcessDomain.CreateDomain("MyDomain", setupInfo))
124 | {
125 | var obj = (RemoteTestObject)domain2.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName);
126 | Assert.That(obj.GetProcessFileName(), Is.EqualTo(desiredExecutableFileName));
127 | }
128 |
129 | // test if file exists, it will be overwritten
130 | using (var writer = new StreamWriter(desiredExecutableFileName, false))
131 | {
132 | writer.WriteLine("Garbage");
133 |
134 | // will fail to compile because file is open
135 | Assert.Throws(() => ProcessDomain.CreateDomain("MyDomain", setupInfo));
136 |
137 | writer.Flush();
138 | }
139 |
140 | // file is now closed, but contains garbage that can't execute,
141 | // but the file will get overwritten
142 | using (var domain3 = ProcessDomain.CreateDomain("MyDomain", setupInfo))
143 | {
144 | var obj = (RemoteTestObject)domain3.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName);
145 | Assert.That(obj.GetProcessFileName(), Is.EqualTo(desiredExecutableFileName));
146 | }
147 |
148 | // and once we're done, file is gone
149 | Assert.That(!File.Exists(desiredExecutableFileName));
150 | }
151 |
152 | [Test]
153 | public void TestDeleteFileOnExit()
154 | {
155 | string desiredExecutableFileName = Path.Combine(Environment.CurrentDirectory, "ProcessDomain.exe");
156 |
157 | var setupInfo = new ProcessDomainSetup
158 | {
159 | ExecutableDirectory = Environment.CurrentDirectory,
160 | DeleteOnUnload = false
161 | };
162 |
163 | ProcessDomain.Unload(ProcessDomain.CreateDomain("ProcessDomain", setupInfo));
164 | Assert.That(File.Exists(desiredExecutableFileName));
165 |
166 | setupInfo.DeleteOnUnload = true;
167 |
168 | ProcessDomain.Unload(ProcessDomain.CreateDomain("ProcessDomain", setupInfo));
169 | Assert.That(!File.Exists(desiredExecutableFileName));
170 | }
171 |
172 | [Test]
173 | public void TestConfigurationLocation()
174 | {
175 | // by default uses our app config
176 | using (var domain1 = ProcessDomain.CreateDomain("Domain1", DefaultSetupInfo))
177 | {
178 | var obj = (RemoteTestObject)domain1.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName);
179 | Assert.That(obj.GetAppConfigValue("MyValue"), Is.EqualTo("MyValue"));
180 | }
181 |
182 | // now point it at a different app config
183 | var setupInfo = new ProcessDomainSetup
184 | {
185 | AppDomainSetupInformation =
186 | {
187 | ConfigurationFile = Path.Combine(Path.GetDirectoryName(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile), "OtherApp.config"),
188 | },
189 | TypeFilterLevel = TypeFilterLevel.Full
190 | };
191 |
192 | using (var domain2 = ProcessDomain.CreateDomain("Domain2", setupInfo))
193 | {
194 | var obj = (RemoteTestObject)domain2.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName);
195 | Assert.That(obj.GetAppConfigValue("MyValue"), Is.EqualTo("OtherValue"));
196 | }
197 | }
198 |
199 | [Test]
200 | public void TestProcessDomainAssemblyResolver()
201 | {
202 | string prevDirectory = Environment.CurrentDirectory;
203 |
204 | try
205 | {
206 | Directory.SetCurrentDirectory(Path.Combine(prevDirectory, ".."));
207 |
208 | using (var domain = ProcessDomain.CreateDomain("ProcessDomain", DefaultSetupInfo))
209 | {
210 | // this used to fail because it would try to load the ProcessDomain assembly from 'current directory'
211 | }
212 | }
213 | finally
214 | {
215 | Directory.SetCurrentDirectory(prevDirectory);
216 | }
217 | }
218 |
219 | [Test]
220 | public void TestAssemblyUtils()
221 | {
222 | string uriPathUnescaped = "file:///c:/somepath/i have spaces";
223 | string uriPathEscaped = "file:///c:/somepath/i%20have%20spaces";
224 | string forwardSlashPath = "c:/somepath/i have spaces";
225 | string path = @"c:\somepath\i have spaces";
226 |
227 | Assert.That(AssemblyUtils.GetFilePathFromFileUri(path), Is.EqualTo(path));
228 | Assert.That(AssemblyUtils.GetFilePathFromFileUri(forwardSlashPath), Is.EqualTo(path));
229 | Assert.That(AssemblyUtils.GetFilePathFromFileUri(uriPathUnescaped), Is.EqualTo(path));
230 | Assert.That(AssemblyUtils.GetFilePathFromFileUri(uriPathEscaped), Is.EqualTo(path));
231 | }
232 | [Test]
233 | public void TestTypeFilterLevel()
234 | {
235 | // using an event handler requires type filter level Full for a remoting channel
236 |
237 | // by default, it's low, which will fail
238 | using (var domain = ProcessDomain.CreateDomain("Domain"))
239 | {
240 | Assert.Throws(
241 | () => { var obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName); }
242 | );
243 | }
244 |
245 | // now enable the remoting channel with type filter level Full
246 | var setup = new ProcessDomainSetup { TypeFilterLevel = TypeFilterLevel.Full };
247 | using (var domain = ProcessDomain.CreateDomain("Domain", setup))
248 | {
249 | var obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName);
250 |
251 | obj.CalledBack = false;
252 | Assert.That(!obj.CalledBack);
253 | Assert.DoesNotThrow(() => obj.CallbackEvent += obj.SetCalledBack);
254 | obj.OnCallback();
255 | Assert.That(obj.CalledBack);
256 | }
257 | }
258 |
259 | [Test]
260 | public void TestMultipleProcessDomains()
261 | {
262 | // Along with enabling support for TypeFilterLevel = Full, we also have to allow multiple
263 | // channels to be created. This will simply do just that and ensure there's no
264 | // duplicate channel registration exceptions
265 | using (var domain1 = ProcessDomain.CreateDomain("Domain1", DefaultSetupInfo))
266 | using (var domain2 = ProcessDomain.CreateDomain("Domain2", DefaultSetupInfo))
267 | {
268 | var obj1 = (RemoteTestObject)domain1.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName);
269 | var obj2 = (RemoteTestObject)domain2.CreateInstanceAndUnwrap(TestObjectAssemblyName, TestObjectTypeName);
270 |
271 | Assert.That(!obj1.CalledBack);
272 | Assert.That(!obj2.CalledBack);
273 | obj1.SetCalledBack();
274 | obj2.SetCalledBack();
275 | Assert.That(obj1.CalledBack);
276 | Assert.That(obj2.CalledBack);
277 | }
278 | }
279 |
280 | [Test]
281 | public void TestProcessPriority()
282 | {
283 | // Default case
284 | using (var domain = ProcessDomain.CreateDomain("TestPriorityDomain", DefaultSetupInfo))
285 | {
286 | var obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(typeof(RemoteTestObject).Assembly.FullName, typeof(RemoteTestObject).FullName);
287 | Assert.That(obj.GetPriority(), Is.EqualTo(ProcessPriorityClass.Normal));
288 | }
289 |
290 | // Try each priority
291 | foreach (ProcessPriorityClass priority in Enum.GetValues(typeof(ProcessPriorityClass)))
292 | {
293 | var setup = new ProcessDomainSetup { PriorityClass = priority, TypeFilterLevel = TypeFilterLevel.Full };
294 | using (var domain = ProcessDomain.CreateDomain("TestPriorityDomain", setup))
295 | {
296 | var obj = (RemoteTestObject)domain.CreateInstanceAndUnwrap(typeof(RemoteTestObject).Assembly.FullName, typeof(RemoteTestObject).FullName);
297 |
298 | // If not running as administrator, we can't run as RealTime, it will become High
299 | var expectedPriority = priority == ProcessPriorityClass.RealTime && !obj.RunningAsAdministrator() ? ProcessPriorityClass.High : priority;
300 |
301 | Assert.That(obj.GetPriority(), Is.EqualTo(expectedPriority));
302 | }
303 | }
304 | }
305 | }
306 | }
307 |
--------------------------------------------------------------------------------