Translate

Monday, February 1, 2010

Connect to a UNC path with security credentials

I had an ASP.NET site where I wanted to access network resources, but did not have sufficient share permissions because the code ran under the ASP user. I also had a service that copied files every night between file shares in two different domains. I wanted a way to access remote resources without opening up security holes by changing permissions or running as a privileged user. Surprisingly, there is a lack of .Net managed code that will allow you to do this.

I have seen many coders who get around this problem via LogonUser() - programmatically log onto the box as a user who has the necessary privileges and perform the required task. This works fine if you grant the user local logon rights on the box. I'm not comfortable doing that on a public-facing box. I have also encountered code that shells out and performs a net use command. Tsk. So uncivilized. :)

The NetUseAdd() and NetUseDel() commands of NetApi32.dll provide a way to add access to a network resource with network credentials. This will allow you to run the process from whatever user you wish, but still access protected network resources.

The UNCAccessWithCredentials.cs class wraps these functions in an IDisposable class that allows you to encompass your code in a using block. When you encounter the end of the block, the class disconnects from the resource and the connection is closed. If you need a persistent connection, call the NetUseWithCredentials function, but remember to clean up your mess with the NetUseDelete function.

Example usage:

 
using (UNCAccessWithCredentials unc = new UNCAccessWithCredentials())
{
  if (unc.NetUseWithCredentials(UNCPath, UserName, Domain, Password))
  {
      //Code that accesses the remote resource
  }
  else
  {
      MessageBox.Show("Failed to connect to " + UNCPath + "\r\nLastError = " + unc.LastError.ToString(),
                      "Failed to connect",
                      MessageBoxButtons.OK,
                      MessageBoxIcon.Error);
  }

}
 
UNCAccessWithCredentials.cs:
 
using System;
using System.Runtime.InteropServices;
using BOOL = System.Boolean;
using DWORD = System.UInt32;
using LPWSTR = System.String;
using NET_API_STATUS = System.UInt32;

namespace ConnectUNCWithCredentials
{
 public class UNCAccessWithCredentials : IDisposable
 {
     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
     internal struct USE_INFO_2
     {
         internal LPWSTR ui2_local;
         internal LPWSTR ui2_remote;
         internal LPWSTR ui2_password;
         internal DWORD ui2_status;
         internal DWORD ui2_asg_type;
         internal DWORD ui2_refcount;
         internal DWORD ui2_usecount;
         internal LPWSTR ui2_username;
         internal LPWSTR ui2_domainname;
     }

     [DllImport("NetApi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
     internal static extern NET_API_STATUS NetUseAdd(
         LPWSTR UncServerName,
         DWORD Level,
         ref USE_INFO_2 Buf,
         out DWORD ParmError);

     [DllImport("NetApi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
     internal static extern NET_API_STATUS NetUseDel(
         LPWSTR UncServerName,
         LPWSTR UseName,
         DWORD ForceCond);

     private bool disposed = false;

     private string sUNCPath;
     private string sUser;
     private string sPassword;
     private string sDomain;
     private int iLastError;

     /// <summary>
     /// A disposeable class that allows access to a UNC resource with credentials.
     /// </summary>
     public UNCAccessWithCredentials()
     {
     }

     /// <summary>
     /// The last system error code returned from NetUseAdd or NetUseDel.  Success = 0
     /// </summary>
     public int LastError
     {
         get { return iLastError; }
     }

     public void Dispose()
     {
         if (!this.disposed)
         {
             NetUseDelete();
         }
         disposed = true;
         GC.SuppressFinalize(this);
     }

     /// <summary>
     /// Connects to a UNC path using the credentials supplied.
     /// </summary>
     /// <param name="UNCPath">Fully qualified domain name UNC path</param>
     /// <param name="User">A user with sufficient rights to access the path.</param>
     /// <param name="Domain">Domain of User.</param>
     /// <param name="Password">Password of User</param>
     /// <returns>True if mapping succeeds.  Use LastError to get the system error code.</returns>
     public bool NetUseWithCredentials(string UNCPath, string User, string Domain, string Password)
     {
         sUNCPath = UNCPath;
         sUser = User;
         sPassword = Password;
         sDomain = Domain;
         return NetUseWithCredentials();
     }

     private bool NetUseWithCredentials()
     {
         uint returncode;
         try
         {
             USE_INFO_2 useinfo = new USE_INFO_2();

             useinfo.ui2_remote = sUNCPath;
             useinfo.ui2_username = sUser;
             useinfo.ui2_domainname = sDomain;
             useinfo.ui2_password = sPassword;
             useinfo.ui2_asg_type = 0;
             useinfo.ui2_usecount = 1;
             uint paramErrorIndex;
             returncode = NetUseAdd(null, 2, ref useinfo, out paramErrorIndex);
             iLastError = (int)returncode;
             return returncode == 0;
         }
         catch
         {
             iLastError = Marshal.GetLastWin32Error();
             return false;
         }
     }

     /// <summary>
     /// Ends the connection to the remote resource 
     /// </summary>
     /// <returns>True if it succeeds.  Use LastError to get the system error code</returns>
     public bool NetUseDelete()
     {
         uint returncode;
         try
         {
             returncode = NetUseDel(null, sUNCPath, 2);
             iLastError = (int)returncode;
             return (returncode == 0);
         }
         catch
         {
             iLastError = Marshal.GetLastWin32Error();
             return false;
         }
     }

     ~UNCAccessWithCredentials()
     {
         Dispose();
     }

 }
}
 

6 comments:

Daniel Sirz said...

Thanks for this solution, i found it at codeproject, it solved my problem to 100%

Adrian Hayes said...

Good to hear. Thanks!

Britttd said...

Great post! I have this working. My only problem is that if there is a share that is currently accessed and I try to access again in my code, I get an error that it is already connected. Do you know of a way, in code, to see all current connections and force those close before attempting to connect?

Adrian Hayes said...

Thank you! I'm glad it helped.

You could probably use a NetUseGetInfo command (http://socuteurl.com/buddypuppydog) and pass it a Use_Info_2 structure (http://socuteurl.com/fishybunnytubby) with the connection specified. NetUseGetInfo should fill in the ui2_status with the connection status.

Let me know how it goes.

KH said...

Do you have an example of using a filestream within the "Insert your code" block?

I'm not quite sure how to use this correctly.

Thanks a lot!

Anonymous said...

You are my hero!