mattjohnsonpint/SimpleImpersonation

Impersonation fails for printing on network printer.

Closed this issue · 1 comments

I'm trying to access the network printers by doing impersonation (using this code) to print a pdf document from IIS (wcf application). Since my wcf application run under 'LocalSystem' app pool identity, i need to do the impersonation. But Printing doesn't work with this impersonation, even I can't list out the network printers also.
If I change the logonType to 'LOGON32_LOGON_INTERACTIVE' and logonProvider as 'LOGON32_PROVIDER_DEFAULT' then I can list out the network printers but when I send the document to the printer it doesn't print. it shows with Size empty in the print queue. Any Idea please???

Impersonation Code:
` [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public class Impersonation : IDisposable
{
private readonly SafeTokenHandle _handle;
private readonly WindowsImpersonationContext _context;
bool disposed = false;

    // constants from winbase.h
    const int LOGON32_LOGON_INTERACTIVE = 2;
    const int LOGON32_LOGON_NETWORK = 3;
    const int LOGON32_LOGON_BATCH = 4;
    const int LOGON32_LOGON_SERVICE = 5;
    const int LOGON32_LOGON_UNLOCK = 7;
    const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8;
    const int LOGON32_LOGON_NEW_CREDENTIALS = 9;

    const int LOGON32_PROVIDER_DEFAULT = 0;
    const int LOGON32_PROVIDER_WINNT35 = 1;
    const int LOGON32_PROVIDER_WINNT40 = 2;
    const int LOGON32_PROVIDER_WINNT50 = 3;

    public Impersonation(ImpersonateUserDetails user) : this(user.Domain, user.UserName, user.Password)
    { }
    public Impersonation(string domain, string username, string password)
    {
        // if domain name was blank, assume local machine
        if (string.IsNullOrEmpty(domain))
            domain = System.Environment.MachineName;

        var ok = LogonUser(username, domain, password,
                       LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out this._handle);
        if (!ok)
        {
            var errorCode = Marshal.GetLastWin32Error();
            throw new ApplicationException(string.Format("Could not impersonate the elevated user.  LogonUser returned error code {0}.", errorCode));
        }

        this._context = WindowsIdentity.Impersonate(this._handle.DangerousGetHandle());
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposed)
            return;

        if (disposing)
        {
            this._context.Dispose();
            this._handle.Dispose();
        }           
        disposed = true;
    }

    ~Impersonation()
    {
        Dispose(false);
    }


    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken);

    sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        private SafeTokenHandle()
            : base(true) { }

        [DllImport("kernel32.dll")]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
        [SuppressUnmanagedCodeSecurity]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr handle);

        protected override bool ReleaseHandle()
        {
            return CloseHandle(handle);
        }
    }
}

public class ImpersonateUserDetails
{
    public string UserName { get; set; }

    public string Password { get; set; }

    public string Domain { get; set; }
}`

Printing code to Network Printer:
using (new Impersonation(domain, username, password)) { PrinterUtility pu = new PrinterUtility(); pu.Print(fileName, "TestFile.pdf", printerName); }
PrinterUtility Class:
` using System;
using System.Drawing.Printing;
using System.IO;
using System.Drawing;
using System.Text;

namespace NetworkPrintWCF
{
public class PrinterUtility
{
private Font printFont;
private StreamReader streamToPrint;

    private void pd_PrintPage(object sender, PrintPageEventArgs ev)
    {
        float linesPerPage = 0;
        float yPos = 0;
        int count = 0;
        float leftMargin = ev.MarginBounds.Left;
        float topMargin = ev.MarginBounds.Top;
        String line = null;

        // Calculate the number of lines per page.
        linesPerPage = ev.MarginBounds.Height /
           printFont.GetHeight(ev.Graphics);

        // Iterate over the file, printing each line.
        while (count < linesPerPage &&
           ((line = streamToPrint.ReadLine()) != null))
        {
            yPos = topMargin + (count * printFont.GetHeight(ev.Graphics));
            ev.Graphics.DrawString(line, printFont, Brushes.Black,
               leftMargin, yPos, new StringFormat());
            count++;
        }

        // If more lines exist, print another page.
        if (line != null)
            ev.HasMorePages = true;
        else
            ev.HasMorePages = false;
    }

    // Print the file.
    public void Print(string filePath, string fileName, string printerNetworkPath)
    {
        try
        {
            streamToPrint = new StreamReader(filePath);
            try
            {
                var printerSettings = new PrinterSettings
                {
                    PrinterName = printerNetworkPath,
                    PrintFileName = fileName,
                    PrintRange = PrintRange.AllPages,
                };
                printerSettings.DefaultPageSettings.Margins = new Margins(0, 0, 0, 0);

                printFont = new Font("Arial", 10);
                PrintDocument pd = new PrintDocument();
                pd.DocumentName = fileName;
                pd.PrinterSettings = printerSettings;
                //pd.PrintController = new StandardPrintController();
                pd.PrintPage += new PrintPageEventHandler(pd_PrintPage);
                // Print the document.
                pd.Print();
            }
            finally
            {
                streamToPrint.Close();
            }
        }
        catch
        {
            throw;
        }
    }
}

}`

I'm sure this is the problem with impersonation. because If I change my application's app pool identity to a domain user (same domain user credential I was trying to use to impersonate) then printing works fine.

Web Server Environment: Windows Server 2012 R2, IIS 8.5.

But with this impersonationated code, printing fails. Document sows up in print queue with NO size (kindly refer attached image)
printqueue

Sorry, I have no idea. It seems like you are using your own code, not mine. Do you get the same results if you use my library?

When I wrote this library (years ago), I used it for connecting to a SQL server under specific domain credentials. Others have used it for various other things. If it works with LogonUser in a C++ app, it should work with SimpleImpersonation in .NET. I just wrap the LogonUser function. How it works under the hood is beyond me.

Also, from a purely architectural point of view, I would not print directly from server-side asp.net code. Consider writing to a queue or database table, and having a separate background service read from there to handle printing. If that is out of the question, then consider using HostingEnvironment.QueueBackgroundWorkItem, introduced in .NET 4.5.2.