Skip to main content
Titanis provides a full SMB 2.x/3.x client in Titanis.Smb2. This page walks through setting up a project and connecting to a remote share to read a file.

Prerequisites

  • .NET 8 SDK installed
  • Access to a remote Windows host with SMB enabled

Getting started

1

Create a new console project

dotnet new console -n MySmb2App
cd MySmb2App
2

Add package references

The SMB2 client depends on networking, I/O, and security packages. Add all of the following to your .csproj:
MySmb2App.csproj
<ItemGroup>
  <PackageReference Include="Titanis.Core" Version="0.9.0" />
  <PackageReference Include="Titanis.IO" Version="0.9.0" />
  <PackageReference Include="Titanis.Net" Version="0.9.0" />
  <PackageReference Include="Titanis.Smb2" Version="0.9.0" />
  <PackageReference Include="Titanis.Security" Version="0.9.0" />
  <PackageReference Include="Titanis.Security.Spnego" Version="0.9.0" />
  <PackageReference Include="Titanis.Security.Ntlm" Version="0.9.0" />
  <PackageReference Include="Titanis.Security.Kerberos" Version="0.9.0" />
</ItemGroup>
Titanis.Security.Kerberos is listed here because SPNEGO can negotiate Kerberos in addition to NTLM. If you are certain your target environment only uses NTLM, you may omit it, but including it keeps your tool flexible.
3

Configure credentials

Instantiate a ClientCredentialDictionary and supply a factory that builds the authentication context for each connection. The factory receives the service principal name (spn) and the capabilities the server advertised, and returns a completed SpnegoClientContext:
Program.cs
using Titanis.Net;
using Titanis.Security.Ntlm;
using Titanis.Security.Spnego;

string userName = "milchick";
string domain   = "LUMON";
string password  = "Br3@kr00m!";

ClientCredentialDictionary credService = new ClientCredentialDictionary();
credService.DefaultCredentialFactory = (spn, caps) =>
{
    NtlmPasswordCredential cred = new NtlmPasswordCredential(userName, domain, password);
    NtlmClientContext ntlmContext = new NtlmClientContext(cred, true);
    ntlmContext.RequiredCapabilities |= caps;

    SpnegoClientContext negoContext = new SpnegoClientContext();
    negoContext.Contexts.Add(ntlmContext);

    return negoContext;
};
4

Configure name resolution and sockets

Smb2Client relies on service objects for name resolution and socket creation. For a single known target you can use DictionaryNameResolver to avoid a live DNS lookup:
Program.cs
using System.Net;
using Titanis.Net;

string serverName  = "LUMON-FS1";
IPAddress hostAddress = IPAddress.Parse("10.66.0.13");

var resolver = new DictionaryNameResolver();
resolver.SetAddress(serverName, new IPAddress[] { hostAddress });

var socketService = new PlatformSocketService(resolver, log);
Replace log with a logger instance from your application or null if you do not need connection-level logging.
5

Create the client and open a file

Construct Smb2Client with the credential and socket services, then open a remote file by its UNC path:
Program.cs
using Titanis.Smb2;

Smb2Client client = new Smb2Client(
    credService,
    socketService: socketService
);

string shareName         = "ADMIN$";
string shareRelativePath = "explorer.exe";

using var file   = await client.OpenFileReadAsync(
    $@"\\{serverName}\{shareName}\{shareRelativePath}",
    cancellationToken);
using var stream = file.GetStream(false);
6

Read or copy the file

Once you have the stream you can use any standard .NET stream API. To copy the file to disk:
Program.cs
string outputPath = "fetchedFile";

using var outStream = File.Create(outputPath);
await stream.CopyToAsync(outStream, cancellationToken);

Complete example

The following is the complete Smb2GetSample from the Titanis samples directory. It retrieves a file from a remote share using NTLM authentication over SPNEGO and writes it locally.
Program.cs
using System.ComponentModel;
using System.Net;
using Titanis;
using Titanis.Cli;
using Titanis.Net;
using Titanis.Security;
using Titanis.Security.Ntlm;
using Titanis.Security.Spnego;
using Titanis.Smb2;

namespace Smb2GetSample
{
    [Command]
    [Description("Gets a file from a remote computer via SMB")]
    internal class Program : Command
    {
        static async Task Main(string[] args)
            => RunProgramAsync<Program>(args);

        protected sealed override async Task<int> RunAsync(CancellationToken cancellationToken)
        {
            // Parameters
            string serverName = "LUMON-FS1";
            IPAddress hostAddress = IPAddress.Parse($"10.66.0.13");
            string shareName = "ADMIN$";
            string shareRelativePath = $"explorer.exe";
            string userName = "milchick";
            string domain = "LUMON";
            string password = "Br3@kr00m!";

            string outputPath = @"fetchedFile";

            ClientCredentialDictionary credService = new ClientCredentialDictionary();
            credService.DefaultCredentialFactory = (spn, caps) =>
            {
                // Configure the credentials
                NtlmPasswordCredential cred = new NtlmPasswordCredential(userName, domain, password);
                NtlmClientContext ntlmContext = new NtlmClientContext(cred, true);
                ntlmContext.RequiredCapabilities |= caps;

                SpnegoClientContext negoContext = new SpnegoClientContext();
                negoContext.Contexts.Add(ntlmContext);

                return negoContext;
            };

            // Configure resolver and socket service
            var resolver = new DictionaryNameResolver();
            resolver.SetAddress(serverName, new IPAddress[] { hostAddress });
            var socketService = new PlatformSocketService(resolver, this.Log);

            // Configure the client
            Smb2Client client = new Smb2Client(
                credService,
                socketService: socketService
                );

            // Open the file
            using var file = await client.OpenFileReadAsync(
                $@"\\{serverName}\{shareName}\{shareRelativePath}",
                cancellationToken);
            using var stream = file.GetStream(false);

            // Open the target file
            using var outStream = File.Create(outputPath);

            // Copy the file
            await stream.CopyToAsync(outStream, cancellationToken);

            return 0;
        }
    }
}

Authentication options

The credential factory returns a SpnegoClientContext, which can wrap multiple authentication mechanisms. NTLM (shown above) is the simplest option. To add Kerberos as a preferred mechanism, add a Kerberos context before the NTLM context in negoContext.Contexts:
SpnegoClientContext negoContext = new SpnegoClientContext();
negoContext.Contexts.Add(kerberosContext); // negotiated first
negoContext.Contexts.Add(ntlmContext);     // fallback
SPNEGO selects the first mechanism that both sides support.
See Using services for details on DictionaryNameResolver, PlatformSocketService, and IClientCredentialService — the service interfaces that control how the client resolves names, creates sockets, and obtains credentials.

Build docs developers (and LLMs) love