Blog Archives

Impersonation with Network Credentials in C# .Net

(Mostly) unmodified code, courtesy of Phil Harding (see references below for original post).

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Principal;

namespace Tools.Network
{
public enum LogonType
{
LOGON32_LOGON_INTERACTIVE = 2,
LOGON32_LOGON_NETWORK = 3,
LOGON32_LOGON_BATCH = 4,
LOGON32_LOGON_SERVICE = 5,
LOGON32_LOGON_UNLOCK = 7,
LOGON32_LOGON_NETWORK_CLEARTEXT = 8,// Win2K or higher
LOGON32_LOGON_NEW_CREDENTIALS = 9// Win2K or higher
};

public enum LogonProvider
{
LOGON32_PROVIDER_DEFAULT = 0,
LOGON32_PROVIDER_WINNT35 = 1,
LOGON32_PROVIDER_WINNT40 = 2,
LOGON32_PROVIDER_WINNT50 = 3
};

public enum ImpersonationLevel
{
SecurityAnonymous = 0,
SecurityIdentification = 1,
SecurityImpersonation = 2,
SecurityDelegation = 3
}

class Win32NativeMethods
{
[DllImport("advapi32.dll", SetLastError = true)]
public static extern int LogonUser(string lpszUserName,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int DuplicateToken(IntPtr hToken,
int impersonationLevel,
ref IntPtr hNewToken);

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool RevertToSelf();

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern bool CloseHandle(IntPtr handle);
}

/// <summary>
/// Allows code to be executed under the security context of a specified user account.
/// </summary>
/// <remarks>
///
/// Implements IDispose, so can be used via a using-directive or method calls;
///...
///
///var imp = new Impersonator( "myUsername", "myDomainname", "myPassword" );
///imp.UndoImpersonation();
///
///...
///
/// var imp = new Impersonator();
///imp.Impersonate("myUsername", "myDomainname", "myPassword");
///imp.UndoImpersonation();
///
///...
///
///using ( new Impersonator( "myUsername", "myDomainname", "myPassword" ) )
///{
///...
///
///...
///}
///
///...
/// </remarks>
public class Impersonator : IDisposable
{
private WindowsImpersonationContext _wic;

/// <summary>
/// Begins impersonation with the given credentials, Logon type and Logon provider.
/// </summary>
/// <param name="userName">Name of the user.</param>
/// <param name="domainName">Name of the domain.</param>
/// <param name="password">The password. <see cref="System.String"/></param>
/// <param name="logonType">Type of the logon.</param>
/// <param name="logonProvider">The logon provider. <see cref="Mit.Sharepoint.WebParts.EventLogQuery.Network.LogonProvider"/></param>
public Impersonator(string userName, string domainName, string password, LogonType logonType, LogonProvider logonProvider)
{
Impersonate(userName, domainName, password, logonType, logonProvider);
}

/// <summary>
/// Begins impersonation with the given credentials.
/// </summary>
/// <param name="userName">Name of the user.</param>
/// <param name="domainName">Name of the domain.</param>
/// <param name="password">The password. <see cref="System.String"/></param>
public Impersonator(string userName, string domainName, string password)
{
Impersonate(userName, domainName, password, LogonType.LOGON32_LOGON_INTERACTIVE, LogonProvider.LOGON32_PROVIDER_DEFAULT);
}

/// <summary>
/// Initializes a new instance of the <see cref="Impersonator"/> class.
/// </summary>
public Impersonator()
{}

/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
UndoImpersonation();
}

/// <summary>
/// Impersonates the specified user account.
/// </summary>
/// <param name="userName">Name of the user.</param>
/// <param name="domainName">Name of the domain.</param>
/// <param name="password">The password. <see cref="System.String"/></param>
public void Impersonate(string userName, string domainName, string password)
{
Impersonate(userName, domainName, password, LogonType.LOGON32_LOGON_INTERACTIVE, LogonProvider.LOGON32_PROVIDER_DEFAULT);
}

/// <summary>
/// Impersonates the specified user account.
/// </summary>
/// <param name="userName">Name of the user.</param>
/// <param name="domainName">Name of the domain.</param>
/// <param name="password">The password. <see cref="System.String"/></param>
/// <param name="logonType">Type of the logon.</param>
/// <param name="logonProvider">The logon provider. <see cref="Mit.Sharepoint.WebParts.EventLogQuery.Network.LogonProvider"/></param>
public void Impersonate(string userName, string domainName, string password, LogonType logonType, LogonProvider logonProvider)
{
UndoImpersonation();

IntPtr logonToken = IntPtr.Zero;
IntPtr logonTokenDuplicate = IntPtr.Zero;
try
{
// revert to the application pool identity, saving the identity of the current requestor
_wic = WindowsIdentity.Impersonate(IntPtr.Zero);

// do logon & impersonate
if (Win32NativeMethods.LogonUser(userName,
domainName,
password,
(int)logonType,
(int)logonProvider,
ref logonToken) != 0)
{
if (Win32NativeMethods.DuplicateToken(logonToken, (int)ImpersonationLevel.SecurityImpersonation, ref logonTokenDuplicate) != 0)
{
var wi = new WindowsIdentity(logonTokenDuplicate);
wi.Impersonate();// discard the returned identity context (which is the context of the application pool)
}
else
throw new Win32Exception(Marshal.GetLastWin32Error());
}
else
throw new Win32Exception(Marshal.GetLastWin32Error());
}
finally
{
if (logonToken != IntPtr.Zero)
Win32NativeMethods.CloseHandle(logonToken);

if (logonTokenDuplicate != IntPtr.Zero)
Win32NativeMethods.CloseHandle(logonTokenDuplicate);
}
}

/// <summary>
/// Stops impersonation.
/// </summary>
private void UndoImpersonation()
{
// restore saved requestor identity
if (_wic != null)
_wic.Undo();
_wic = null;
}
}
}

References
http://platinumdogs.wordpress.com/2008/10/30/net-c-impersonation-with-network-credentials/

Advertisement

aspnet_setreg in Server 2008

The aspnet_setreg utility is very useful for storing encrypted domain credentials, connection strings and other values referenced in a web.config which should not be visible in plaintext.

One such technique is when using the .Net “impersonation” mechanism. Typically, this would look something like:

<system.web>
<identity impersonate="true" userName="WindowsDomain\YourUserName" password="YourPassword" />
</system.web> 

However, if you download and unzip/install the aspnet_setreg utility, you can now use the following syntax to store these credentials in a binary encrypted field in the registry:

c:\Tools>aspnet_setreg.exe -k:SOFTWARE\MY_SECURE_APP\identity -u:"yourdomainname\username" -p:"password"

Your web.config should now be updated to reflect the new stored values. (Note: this is the exact syntax, don’t replace username and password with your own…):

<identity impersonate="true"
userName="registry:HKLM\SOFTWARE\MY_SECURE_APP\identity\ASPNET_SETREG,username"
password="registry:HKLM\SOFTWARE\MY_SECURE_APP\identity\ASPNET_SETREG,password" />

An important thing to note is in Server 2008 on a 64 bit machine after running this utility is a different location it is stored in the registry. To find it you must browse to:

[HKEY_CURRENT_USER\Software\Classes\VirtualStore\MACHINE\SOFTWARE\Wow6432Node]

You can then right click and export this key from here, then open the .reg file in notepad and change to the correct key and import. The end result should be a reg file to import that looks like this:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\Software\MY_SECURE_APP]

[HKEY_LOCAL_MACHINE\Software\MY_SECURE_APP\identity]

[HKEY_LOCAL_MACHINE\Software\MY_SECURE_APP\identity\ASPNET_SETREG]
"userName"=hex:01,00,00, etc
"password"=hex:01,00,00, etc

References
MSDN, http://support.microsoft.com/kb/329290
ASPDEV, http://www.aspdev.org/articles/web.config/
ASPNET FORUMS, http://forums.asp.net/t/1650965.aspx/1?aspnet_setreg+under+Win+2008

C# and VB Equivalents

VB:

'shorthand object constructors; assigned initial properties
dim p as New Person() With {.FirstName = "john", .LastName="smith"}

'add handler for events
AddHandler context.BeginRequest, AddressOf Applicaton_BeginRequest

C#:

//shorthand object constructors; assigned initial properties
Person p = new Person() with {FirstName = "john", LastName="smith"}

//add handler for events
context.BeginRequest += Application_BeginRequest;

Quick Silverlight References

Most of the links below are relatively introductory, but they do serve as a quick refresher if it has been awhile since you have worked with Silverlight.

Though not all of the links are specific to SL4, I would recommend a path of RIA services in conjunction with ADO .Net Entity framework for your business apps as these new process flows simplify the project structure and are improvements upon their predecessors.

For quick “agile” development, Linq to SQL is still the way to go IMO, but ADO .Net is also great nonetheless.

Walkthrough creating a silverlight business application (4.0 or later) and retrieve data using WCF service.
http://www.silverlight.net/learn/tutorials/silverlight-4/aspnet-and-silverlight/

Using ADO .Net Entity Model / Framework with Silverlight (4.0 or later)
http://msdn.microsoft.com/en-us/library/ee796239%28v=vs.91%29.aspx

Silverlight custom data forms (3.0 or later)
http://www.silverlightshow.net/items/Creating-Rich-Data-Forms-in-Silverlight-3-Customization.aspx

Basic Animation in Silverlight
http://www.silverlight.net/learn/videos/silverlight-videos/basic-animation-silverlight-3/

General Reference (all versions)
http://www.silverlight.net/learn/

Run Silverlight on Desktop (Out of Browser Application)
http://www.silverlightshow.net/items/Silverlight-3-as-a-Desktop-Application-Out-of-Browser-Applications.aspx

Quick .Net Email Reference

I’ve done a bit of encapsulation here to abstract the concept into clearer functionality, IMO.

Make sure to add “using” reference directives for System.Threading and System.Net.Mail accordingly, when necessary.

Note usage of basic boolean “sync lock” style logic in place of static members for thread safety.

(This code is purely illustration of concept and functionality is not assured. Correctness of code is coincidental. 🙂 )

C#:

private Automailer mailer;

private void StartEmailAutomailer() {
      int interval = 100000;
                    System.Net.Mail.SmtpClient client = new System.Net.Mail.SmtpClient();
                    //set host, port, etc from your data layer
                    List<System.Net.Mail.MailAddress> recipients = DataAccess.MailInfo.recipients;

                    if (mailer == null)
                    {    
                        mailer = new Automailer(interval,client,recipients);
                    }
                    else
                    {
                        mailer.AccessMailInterval = interval;
                        mailer.AccessMailClient = client;
                        mailer.AccessMailRecipients = recipients;
                    }
                    mailer.Start();
}

//contain in job/agent type definitions
public class Automailer
    {
        private System.Timers.Timer mailerthread;

        public bool IsRunning { get; private set; }
        private bool sending { get; set; }

        public double AccessMailInterval
        {
            get
            {
                return mailerthread.Interval;
            }
            set
            {
                if (sending == false)
                {
                    mailerthread.Interval = AccessMailInterval;
                }
                else
                {
                    utils.cout("Cannot modify interval while mail cycle is processing.");
                }
            }
        }

        private SmtpClient MailClient;
        public SmtpClient AccessMailClient { 
            get {
                    return MailClient;
        }
            set
            {
                if (sending == false)
                {
                    MailClient = AccessMailClient;
                }
                else
                {
                    utils.cout("Cannot modify mail client while mail cycle is processing.");
                }
            }
        }

        private List<MailAddress> MailRecipients;
        public List<MailAddress> AccessMailRecipients { 
            get {
                    return MailRecipients;
        } 
            set {
                if (sending == false)
                {
                    MailRecipients = AccessMailRecipients;
                }
                else
                {
                    utils.cout("Cannot modify mail recipients while mail cycle is processing.");
                }

            } 
        }

        /// Must be manually started / stopped.
        public Automailer(double interval,SmtpClient client,List<MailAddress> recipients)
        {
            try
            {
                MailClient = client;
                MailRecipients = recipients;

                sending = false;
                mailerthread = new System.Timers.Timer(interval);
                mailerthread.Elapsed += new System.Timers.ElapsedEventHandler(SendMail);
            }
            catch (Exception ex)
            {
                utils.cout("Error sending email. Details:" + "\r\n" + ex.ToString());
            }
        }

        public void Start() {
            if (IsRunning)
            {
                utils.cout("Automailer agent already running.");
            }
            else
            {
                utils.cout("Starting automailer agent...");
                mailerthread.Start();
                IsRunning = true;
                utils.cout("Initialized.");
            }
        }
        
        public void Stop() {
            if (IsRunning)
            {
                utils.cout("Sending halt and exit command to automailer agent...");
                mailerthread.Stop();
                utils.cout("Stopped.");

                if (sending)
                {
                    utils.cout("Warning: automailer stopped during mail cycle.");
                }
            }
            else
            {
                utils.cout("Automailer agent is not running.");
            }
            
        }

        /// Fires on timer or can be called manually.
        public void SendMail(object source, System.Timers.ElapsedEventArgs e)
        {
            if (sending == false)
            {
                sending = true;
                int successcount = 0;
                try
                {
                    utils.cout("Starting mail cycle...");
                    int rollingcount = 0;
                    foreach (MailAddress address in MailRecipients)
                    {
                        rollingcount += 1;
                        utils.cout(String.Format("Sending email {0} of {1}.", rollingcount, MailRecipients.Count));

                        MailMessage message = new MailMessage();

                        try
                        {
                            message.Body = DataAccess.MailInfo.body;
                            message.IsBodyHtml = DataAccess.MailInfo.ishtml;
                        }
                        catch (Exception ex4)
                        {
                            utils.cout("Could not populate body from template. Message will be sent using defaults. Details:" + "\r\n" + ex4.ToString());
                            message.IsBodyHtml = true;
                            message.Body = String.Format("Thank-you! <br /><br /> \r\n \r\n - {0}",DataAccess.LicenseInfo.company);
                        }

                        try
                        {
                            message.To.Add(address);

                            try
                            {
                                MailClient.Send(message);
                                utils.cout(String.Format("Email sent to {0}.",address.Address));
                                successcount += 1;
                                //async can be used alternatively
                                //MailClient.SendAsync(message,)
                                //MailClient.SendCompleted += new SendCompletedEventHandler(SendMailComplete);
                            }
                            catch (Exception ex2)
                            {
                                utils.cout(String.Format("Could not send to {0}. Details:", address.Address) + "\r\n" + ex2.ToString());
                            }
                        }
                        catch (Exception ex3)
                        {
                            utils.cout(String.Format("Unable to add address {0} to the list. Details:", address.Address) + "\r\n" + ex3.ToString());
                        }

                        message.Dispose();
                    }
                   
                }
                catch (Exception ex)
                {
                    utils.cout("Error sending email. Details:" + "\r\n" + ex.ToString());
                }

                utils.cout(String.Format("Mail cycle complete. {0} sent successful, {1} failed to send.", successcount.ToString(), (MailRecipients.Count - successcount).ToString()));

                sending = false;
            }
        }

        //not used, can be used to engage on asyc calls
        private void SendMailComplete(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
        {
            
        }
    }

//data layer
public static class DataAccess {
public static class Mailinfo {
//contains static methods and properties for retrieving data from database or file, etc
}
}

//in a generic lib
public static class utils {
public static void cout(string outputtext) {
//output text to console, window, form, etc
}
}