Alternate Data Streams

NTFS has for a long time supported the concept of alternate data streams. The idea is  that you can store data under a file, not by inserting or appending into it but more like tagging it with data. Even if the feature is not widely know, it is used by Microsoft in many places e.g.  for storing the cached thumb nail images under each thumbs.db file and for marking downloaded files as blocked.

Not all shell commands support the feature, but this example should give you the idea:

echo "hello world" > test.txt
echo "hello you" > test.txt:hidden.txt
notepad test.txt:hidden.txt
dir *.txt

The first line just creates the parent for the alternate data stream created by the second command (<file or directory>:<stream name>). Note that the last dir command only shows the parent file.

Note that if you move a file,  the alternative data streams will only follow as long as the destination device also uses NTFS.

You can work with alternate data streams in C#, but only by using the Windows API as none of the standard .NET components support it directly. I made the following class to easily access the file handle (open/create) for creating and modifying an alternative data stream:

public static class AlternateDataStreams
{
    private const uint FILE_ATTRIBUTE_NORMAL = 0x80;
    private const uint GENERIC_ALL = 0x10000000;
    private const uint FILE_SHARE_READ = 0x00000001;
    private const uint OPEN_ALWAYS = 4;
    public static SafeFileHandle GetHandle(string path, string name)
    {
        if (string.IsNullOrEmpty(path))
            throw new ArgumentException("Invalid path", "path");
        if (string.IsNullOrEmpty(name))
            throw new ArgumentException("Invalid name", "name");
        string streamPath = path + ":" + name;
        SafeFileHandle handle = CreateFile(streamPath,
        GENERIC_ALL, FILE_SHARE_READ, IntPtr.Zero, OPEN_ALWAYS,
        FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);
        if (handle.IsInvalid)
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        return handle;
    }
    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess,
    uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition,
    uint dwFlagsAndAttributes, IntPtr hTemplateFile);
}

The handle can be passed to a FileStream for reading/writing.

I’m planning to use this for marking files in a way enabling my application to detect changes even if the file is left fully accessible to the users. I plan to achieve this by storing the file path+file hash data as a encrypted alternative data stream.

Advertisements

Quick Guide to Installing Windows XP Mode

Microsoft provides an virtual Windows XP environment for Windows 7 called Windows XP Mode to help with migrating old applications to work on the new operating system.

By completing these steps you’ll have Windows XP Mode up and running in a few seconds:

  1. First you need to make sure your CPU has the required support for virtualization. The easiest way to do this is by running a small application called SecurAble. You might need to enable the virtualization support from your BIOS.  After swtiching it on you might need to completely shut down your computer as a simple reboot might just not do it.
  2. Get the latest files for Windows XP Mode and Virtual PC from Microsoft and install them
  3. Select Windows XP Mode from you start menu under Windows Virtual PC and enter some logon credentials for the virtual environment

You’re done!

Note that while in Windows XP Mode you might run into the following error if you try to start some setup located on a external USB or network drive:

“The Windows Installer does not permit installation from a Remote Desktop Connection”

This is because Windows XP Mode sees you as a remote desktop user and for some security reason the Windows Installer can’t permit you to execute a “remote” setup. To solve this problem, first copy all the setup files into the virtual environment and then run the setup!

Sending Data to Océ Printer Using .NET

I was asked to turn a old Inventor printing macro into a .NET add-in. This was a fairly simple task and only translating the SendKeys and Shell commands to code required some thinking.

To print using a Océ 9600 large format printer the macro called the following shell command to send the custom settings along with the plot data (generated using Inventors PrintManager.PrintToFile function) to the printer:

"cmd /c copy settings.txt /a + drawing.plt /b "

This told me I needed to first read the custom settings from a file and turn it into ASCII, then combine that with the data in the binary plot file and finally send it to the printer.

I did not know how to send the data to the printer, so I just tried opening a file stream using the printer’s address (without expecting it to work). This gave me the following exceptionally informative error message:

"FileStream was asked to open a device that was not a file. For support for devices like 'com1:' or 'lpt1:', call CreateFile, then use the FileStream constructors that take an OS handle as an IntPtr."

The message told me exactly what I needed to do. I needed to use the CreateFile Windows API call to get a handle I could pass the the FileStream class. As this was the final piece of the puzzle I quite quickly produced the following code:

public void WriteToPrinter(string pltFile, string settings, string printer)
{
    IntPtr handle = IntPtr.Zero;
 
    try
    {
        handle = CreateFile(printer,
            (uint)EFileAccess.GenericWrite,
            (uint)(EFileShare.Read | EFileShare.Write),
            (uint)0,
            (uint)(ECreationDisposition.OpenExisting),
            (uint)(EFileAttributes.NoBuffering),
            (int)IntPtr.Zero);
 
        if (handle.ToInt32() == -1)
        {
            int hresult = Marshal.GetHRForLastWin32Error();
            Marshal.ThrowExceptionForHR(hresult);
        }
 
        using (FileStream stream = new FileStream(handle, FileAccess.Write))
        {
            using (BinaryWriter writer = new BinaryWriter(stream))
            {
                byte[] asciiArray = Encoding.Convert(Encoding.UTF8, 
                    Encoding.ASCII, File.ReadAllBytes(settings));
 
                writer.Write(asciiArray);
 
                byte[] binaryArray = File.ReadAllBytes(pltFile);
                writer.Write(binaryArray);
 
                writer.Close();
            }
        }
    }
    finally
    {
        if (handle != IntPtr.Zero && handle.ToInt32() != -1)
            CloseHandle(handle);
    }
}
 
#region Windows API calls
 
[DllImport("kernel32", SetLastError = true)]
static extern unsafe IntPtr CreateFile(
    string FileName, 
    uint DesiredAccess, 
    uint ShareMode, 
    uint SecurityAttributes, 
    uint CreationDisposition, 
    uint FlagsAndAttributes, 
    int hTemplateFile);
 
[DllImport("kernel32", SetLastError = true)]
static extern unsafe bool CloseHandle(
    IntPtr hObject);  
 
[Flags]
public enum EFileAccess : uint
{
    GenericWrite = 0x40000000
}
 
[Flags]
public enum EFileShare : uint
{
    Read = 0x00000001,
    Write = 0x00000002
}
 
public enum ECreationDisposition : uint
{
    OpenExisting = 3
}
 
[Flags]
public enum EFileAttributes : uint
{
    NoBuffering = 0x20000000
}
 
#endregion

WCF: Windows Authentication and External Users

Using Windows authentication with WCF enables developers to support centralized access management for enterprise services. As default the framework uses the computer login credentials of the Windows user accessing the service.

This default behavior works for all the cases where the computer belongs to the same domain as the server, e.g. when using a company computer. But a when external users needs to access a service that uses Windows authentication, the client needs to send credentials the server can identify. For this you need to define the credentials prior to creating the channel.

factory.Credentials.Windows.ClientCredential =
new System.Net.NetworkCredential(name, password, domain);

 
Make sure not to mix the factory.Credentials.Windows.ClientCredential and factory.ClientCredential properties.

There is also a good resource about debugging Windows authentication errors at MSDN.

WCF and Windows Security Revisited

Configuring WCF to require Windows authentication is a pretty trivial task. But this alone does not set any requirements for what the clients credentials need to be. If you want the clients to belong to a certain Active Directory group, you are required to do some coding to achive it.

One option is to define PrincipalPermission attributes in every class to define its security requirements. But this means hard coding the requirements. The only flexible solution I found was to write my own  ServiceAuthorizationManager and read the group name from the app.config.

public class CustomAuthorizationManager : ServiceAuthorizationManager
{
protected override bool CheckAccessCore(OperationContext operationContext)
{

    // For mex support
    if (operationContext.ServiceSecurityContext.IsAnonymous)
        return true;

    // When Windows authentication has been setup using an application setting
    if (Properties.Settings.Default["UserGroup"] != null)
    {

        WindowsIdentity identity = operationContext.ServiceSecurityContext.WindowsIdentity;

        if
(!identity.IsAuthenticated)
            throw new SecurityTokenValidationException("Windows authentication required");

        WindowsPrincipal principal = new WindowsPrincipal(identity);
        string group = Properties.Settings.Default["UserGroup"].ToString();

        return principal.IsInRole(group);

    } else {

        return base.CheckAccessCore(operationContext);}

    }

}

In the service app.config:

<serviceBehaviors><behavior name="Service1Behavior">
...
<serviceAuthorization serviceAuthorizationManagerType="CustomAuthorizationManager, MyAssembly"/>
</
behavior>
</
serviceBehaviors>

WCF and Windows Security

Working on my first real WCF service project I encountered problems with getting the client to connect to the host when running it on another computer and a different user. This was probably challenging only because of my inexperience with the technology and the overwhelming amount of documentation (but very little discussion) on the subject.

When you want to use Windows security with a netTcpBinding, you must first configure the client and the host to do so:

<security mode="Transport">
<
transport clientCredentialType="Windows" protectionLevel="EncryptAndSign" />
<message clientCredentialType="Windows" />
</security>

This was pretty trivial.

The magic lays behind the identity configuration of the client endpoint. I’m still not sure why this is required, but to get Windows authentication to work, you need to define a correct User Principal Name (UPN) or Service Princial Name (SPN). 

So, if the host is running with user credentials, you should use its UPN:

<identity>
<
userPrincipalName value="user@some.com" />
</identity>

And if the host is running as s service, define a SPN:

<identity>
<
servicePrincipalName value="Host/MYCOMPUTER" />
</identity>

Host/<server> is the default SPN, but a domain administartor can also create a service specific one.

What I noticed when trying different configurations, was that if I leave the UPN/SPN value blank (or entered any value), the client will for some reason connect. This has to do something with the fact that as default Kerberos is used for the authentication, but if that fails NTLM takes over. So to make sure your settings are correct, try the following:

<client>
<endpoint behaviorConfiguration = "clientEndpointCredential">
...
</
client>
<
behaviors>
<
endpointBehaviors>
<
behavior name="clientEndpointCredential">
<
clientCredentials>
<
windows allowNtlm="false" />
</
clientCredentials>
</
behavior>
</
endpointBehaviors>
</
behaviors>

Comprehensive guide to WCF security: http://www.codeplex.com/WCFSecurityGuide