613 lines
20 KiB
Plaintext
613 lines
20 KiB
Plaintext
// ----------------------------------------------------------------------------
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
//
|
|
//
|
|
//This program is a tool for managing instances of the SMB client service.
|
|
//The tool allows users to list running instances of the SMB client process,
|
|
//to display their configuration and status, and to create new SMB client
|
|
//processes.
|
|
//
|
|
//This program provides some of the functionality of the Windows "net" command,
|
|
//such as "net use" command, etc.
|
|
//
|
|
//
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Diagnostics;
|
|
using System.Net.IP;
|
|
using NetStack.Contracts;
|
|
using NetStack.Channels.Public;
|
|
using Microsoft.Contracts;
|
|
using Microsoft.Singularity;
|
|
using Microsoft.Singularity.Channels;
|
|
using Microsoft.Singularity.Directory;
|
|
using Microsoft.Singularity.Applications;
|
|
using Microsoft.Singularity.Io;
|
|
using Microsoft.Singularity.Configuration;
|
|
using Microsoft.SingSharp;
|
|
using Microsoft.SingSharp.Reflection;
|
|
using Smb.PublicChannels;
|
|
|
|
|
|
[assembly: Transform(typeof(ApplicationResourceTransform))]
|
|
|
|
namespace Microsoft.Singularity.Applications.Network
|
|
{
|
|
#if !false
|
|
[ConsoleCategory(HelpMessage="Show help for SMB control client", DefaultAction=true)]
|
|
internal class DefaultCommand {
|
|
[InputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdin;
|
|
|
|
[OutputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdout;
|
|
|
|
reflective internal DefaultCommand();
|
|
|
|
internal int AppMain()
|
|
{
|
|
Console.WriteLine("Specify a command to execute, or '-?' for a list of commands.");
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
[ConsoleCategory(Action="list", HelpMessage="List active SMB client processes" )]
|
|
internal class ListClientsParameters
|
|
{
|
|
[InputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdin;
|
|
|
|
[OutputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdout;
|
|
|
|
//[BoolParameter("v", Mandatory=false, HelpMessage="List details about each client")]
|
|
public bool Verbose = true;
|
|
|
|
reflective internal ListClientsParameters();
|
|
|
|
internal int AppMain()
|
|
{
|
|
try {
|
|
SmbClientManagerContract.Imp! manager = SmbControl.ConnectSmbClientManagerRequired();
|
|
try {
|
|
|
|
manager.SendEnumClients();
|
|
|
|
switch receive {
|
|
case manager.ClientList(char[][]! in ExHeap mountPaths):
|
|
|
|
for (int i = 0; i < mountPaths.Length; i++) {
|
|
expose (mountPaths[i]) {
|
|
char[] in ExHeap exmountPath = mountPaths[i];
|
|
if (exmountPath == null)
|
|
continue;
|
|
|
|
string! mountPath = Bitter.ToString2(exmountPath);
|
|
SmbControl.ShowClientInfo(manager, mountPath);
|
|
}
|
|
}
|
|
|
|
delete mountPaths;
|
|
return 0;
|
|
|
|
case manager.RequestFailed(errorCode, optionalErrorText):
|
|
SmbControl.ShowRequestFailed(errorCode, optionalErrorText);
|
|
delete optionalErrorText;
|
|
return -1;
|
|
}
|
|
}
|
|
finally {
|
|
delete manager;
|
|
}
|
|
|
|
}
|
|
catch (Exception ex) {
|
|
SmbControl.ShowException(ex);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
[ConsoleCategory(Action="info", HelpMessage="Shows the status of an SMB client, given its mount path.")]
|
|
internal class ShowInfoCommand
|
|
{
|
|
[InputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdin;
|
|
|
|
[OutputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdout;
|
|
|
|
reflective internal ShowInfoCommand();
|
|
|
|
[StringParameter("mountpath", Mandatory=true, Position=0, HelpMessage="The mount path of the SMB filesystem to query, e.g. /foo.")]
|
|
internal string MountPath;
|
|
|
|
internal int AppMain()
|
|
{
|
|
try {
|
|
SmbControl.ShowClientInfo((!)MountPath);
|
|
return 0;
|
|
}
|
|
catch (Exception ex) {
|
|
SmbControl.ShowException(ex);
|
|
return 1;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
[ConsoleCategory(Action="unmount", HelpMessage="Disconnect an SMB client process from the Singularity namespace.")]
|
|
internal class UnmountCommand
|
|
{
|
|
[InputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdin;
|
|
|
|
[OutputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdout;
|
|
|
|
[StringParameter("mountpath", Mandatory=true, Position=0, HelpMessage="The mount path of SMB client, e.g. /files")]
|
|
public string MountPath;
|
|
|
|
reflective internal UnmountCommand();
|
|
|
|
internal int AppMain()
|
|
{
|
|
try {
|
|
string! mountPath = (!)this.MountPath;
|
|
|
|
SmbClientManagerContract.Imp manager = SmbControl.ConnectSmbClientManager();
|
|
if (manager == null)
|
|
return -1;
|
|
|
|
try {
|
|
manager.SendStopClient(Bitter.FromString2(mountPath));
|
|
|
|
switch receive {
|
|
case manager.Ok():
|
|
break;
|
|
|
|
case manager.RequestFailed(SmbErrorCode error, char[] in ExHeap errortext):
|
|
delete errortext;
|
|
break;
|
|
}
|
|
|
|
}
|
|
finally {
|
|
delete manager;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
catch (Exception ex) {
|
|
SmbControl.ShowException(ex);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if false // disabled for now
|
|
[ConsoleCategory(Action="reset", HelpMessage="Resets an SMB client, causing it to disconnect and reconnect.")]
|
|
internal class ResetCommand
|
|
{
|
|
[InputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdin;
|
|
|
|
[OutputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdout;
|
|
|
|
[StringParameter("clientid", HelpMessage="The local mount path, or control endpoint, of the SMB filesystem to reset.", Mandatory=true, Position=0)]
|
|
public string ClientId;
|
|
|
|
reflective internal ResetCommand();
|
|
|
|
int AppMain()
|
|
{
|
|
try {
|
|
SmbClientControlContract.Imp! control = SmbControl.BindSmbControlClient(ClientId);
|
|
try {
|
|
control.SendReconnect();
|
|
switch receive {
|
|
case control.AckReconnect():
|
|
Console.WriteLine("Successfully sent reset/reconnect request to SMB client.");
|
|
return 0;
|
|
|
|
case control.ChannelClosed():
|
|
Console.WriteLine("SMB client reset channel before responding.");
|
|
return -1;
|
|
}
|
|
}
|
|
finally {
|
|
delete control;
|
|
}
|
|
}
|
|
catch (Exception ex) {
|
|
SmbControl.ShowException(ex);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
[ConsoleCategory(Action="mount", HelpMessage="Create a mapping to an SMB file share.")]
|
|
internal class MountCommand
|
|
{
|
|
[InputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdin;
|
|
|
|
[OutputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdout;
|
|
|
|
[StringParameter("mountpath", Mandatory=true, Position=0, HelpMessage="The local path at which to mount the remote namespace, e.g. /foo")]
|
|
public string MountPath;
|
|
|
|
[StringParameter("unc", Mandatory=true, Position=1, HelpMessage=@"The UNC path of the share, e.g. \\server\share")]
|
|
public string UncPath;
|
|
|
|
[StringParameter("user", Mandatory=false, HelpMessage="The [domain\\]username to use when authenticating.")]
|
|
public string CredentialsName;
|
|
|
|
[StringParameter("tag", Mandatory=false, HelpMessage="If 'user' is specified, this parameter allows you to pass a credentials tag.")]
|
|
public string Tag;
|
|
|
|
reflective internal MountCommand();
|
|
|
|
internal int AppMain()
|
|
{
|
|
try {
|
|
string! unc = (!)this.UncPath;
|
|
string! mountpath = (!)this.MountPath;
|
|
|
|
if (mountpath.Length == 0 || mountpath[0] != '/')
|
|
throw new Exception("The mount path is invalid. It must be an absolute path.");
|
|
|
|
SmbClientManagerContract.Imp manager = SmbControl.ConnectSmbClientManager();
|
|
if (manager == null)
|
|
return -1;
|
|
|
|
try {
|
|
SmbClientConfig config = new SmbClientConfig();
|
|
config.UncPath = Bitter.FromString2(unc);
|
|
config.MountPath = Bitter.FromString2(mountpath);
|
|
config.CredentialsName = Bitter.FromString2(this.CredentialsName != null ? this.CredentialsName : "");
|
|
config.Tag = Bitter.FromString2(this.Tag != null ? this.Tag : "");
|
|
|
|
manager.SendCreateClient(config);
|
|
|
|
switch receive {
|
|
case manager.Ok():
|
|
Console.WriteLine("Filesystem mapping has been created.");
|
|
Console.WriteLine("");
|
|
Console.WriteLine("Be advised! Creating the mapping (mount point) does NOT mean");
|
|
Console.WriteLine("that the local SMB redirector (client) has been able to connect");
|
|
Console.WriteLine("to the remote file server. The mapping represents the goal state;");
|
|
Console.WriteLine("you can see the current state by using the 'net @list' command.");
|
|
break;
|
|
|
|
case manager.RequestFailed(SmbErrorCode error, char[] in ExHeap errortext):
|
|
Console.WriteLine("The request failed.");
|
|
Console.WriteLine("Error: {0} {1}", error, Bitter.ToString(errortext));
|
|
delete errortext;
|
|
return -1;
|
|
|
|
case manager.ChannelClosed():
|
|
Console.WriteLine("SMB client closed channel before responding!");
|
|
return -1;
|
|
}
|
|
}
|
|
finally {
|
|
delete manager;
|
|
}
|
|
|
|
}
|
|
catch (Exception ex) {
|
|
SmbControl.ShowException(ex);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
class SmbControl
|
|
{
|
|
static void ParseUncPath(string! unc, out string! server, out string! resource)
|
|
{
|
|
if (!unc.StartsWith("\\\\"))
|
|
throw new Exception(String.Format("The UNC path '{0}' is invalid. All UNC paths must begin with '\\\\'.", unc));
|
|
|
|
int separator = unc.IndexOf('\\', 2);
|
|
assert (separator < 0) || (separator >= 2);
|
|
if (separator < 0 || separator == 2)
|
|
throw new Exception(String.Format("The UNC path '{0}' is invalid. The server name is missing.", unc));
|
|
|
|
if (separator + 1 == unc.Length)
|
|
throw new Exception(String.Format("The UNC path '{0}' is invalid. The resource name is missing.", unc));
|
|
|
|
server = unc.Substring(2, separator - 2);
|
|
resource = unc.Substring(separator + 1);
|
|
}
|
|
|
|
|
|
public static SmbClientManagerContract.Imp! ConnectSmbClientManagerRequired()
|
|
{
|
|
SmbClientManagerContract.Imp manager = ConnectSmbClientManager();
|
|
if (manager == null)
|
|
throw new Exception("Failed to connect to SMB client manager service.");
|
|
return manager;
|
|
}
|
|
|
|
// This method connects to the SMB Client Manager service.
|
|
// If it cannot connect to the service, it prints an error message on the text console
|
|
// and returns null.
|
|
public static SmbClientManagerContract.Imp ConnectSmbClientManager()
|
|
{
|
|
DirectoryServiceContract.Imp! rootds = DirectoryService.NewClientEndpoint();
|
|
|
|
try {
|
|
SmbClientManagerContract.Imp! manager;
|
|
SmbClientManagerContract.Exp! manager_exp;
|
|
SmbClientManagerContract.NewChannel (out manager, out manager_exp);
|
|
|
|
ErrorCode error;
|
|
if (!SdsUtils.Bind(SmbClientManagerContract.ManagerControlPath, rootds, manager_exp, out error)) {
|
|
Console.WriteLine("Failed to connect to SMB client manager service.");
|
|
Console.WriteLine("Error: " + SdsUtils.ErrorCodeToString(error));
|
|
delete manager;
|
|
return null;
|
|
}
|
|
|
|
manager.RecvSuccess();
|
|
return manager;
|
|
|
|
}
|
|
finally {
|
|
delete rootds;
|
|
}
|
|
}
|
|
|
|
|
|
static void DebugLine(string msg)
|
|
{
|
|
DebugStub.WriteLine("SMBCONTROL: " + msg);
|
|
}
|
|
|
|
static void DebugLine(string format, params object[] args)
|
|
{
|
|
DebugLine(String.Format(format, args));
|
|
}
|
|
|
|
static void Bind(string! path, [Claims]ServiceContract.Exp! exp)
|
|
{
|
|
DirectoryServiceContract.Imp! rootdir = DirectoryService.NewClientEndpoint();
|
|
try {
|
|
ErrorCode errorCode;
|
|
if (SdsUtils.Bind(path, rootdir, exp, out errorCode)) {
|
|
return;
|
|
}
|
|
else {
|
|
delete rootdir;
|
|
throw new Exception(String.Format("Failed to bind to path '{0}'. ErrorCode = {1}.", path, SdsUtils.ErrorCodeToString(errorCode)));
|
|
}
|
|
}
|
|
finally {
|
|
delete rootdir;
|
|
}
|
|
}
|
|
|
|
static DirectoryServiceContract.Imp! BindDirectory(string! path)
|
|
{
|
|
DirectoryServiceContract.Exp! dir_exp;
|
|
DirectoryServiceContract.Imp! dir_imp;
|
|
DirectoryServiceContract.NewChannel(out dir_imp, out dir_exp);
|
|
|
|
DirectoryServiceContract.Imp! rootdir = DirectoryService.NewClientEndpoint();
|
|
|
|
ErrorCode errorCode;
|
|
if (SdsUtils.Bind(path, rootdir, dir_exp, out errorCode)) {
|
|
delete rootdir;
|
|
dir_imp.RecvSuccess();
|
|
return dir_imp;
|
|
}
|
|
else {
|
|
delete rootdir;
|
|
delete dir_imp;
|
|
throw new Exception(String.Format("Failed to bind to directory '{0}'. ErrorCode = {1}.", path, SdsUtils.ErrorCodeToString(errorCode)));
|
|
}
|
|
}
|
|
|
|
static EnumerationRecords[]! in ExHeap EnumerateDirectory(string! path)
|
|
{
|
|
DirectoryServiceContract.Imp! dir = BindDirectory(path);
|
|
|
|
try {
|
|
ErrorCode error;
|
|
EnumerationRecords[] in ExHeap records = SdsUtils.EnumerateDirectory(dir, out error);
|
|
if (records != null)
|
|
return records;
|
|
throw new Exception(String.Format("Failed to enumerate contents of path '{0}': {1}", SdsUtils.ErrorCodeToString(error)));
|
|
}
|
|
finally {
|
|
delete dir;
|
|
}
|
|
}
|
|
|
|
static internal SmbClientControlContract.Imp! BindSmbControlClient(string! mountPath)
|
|
{
|
|
SmbClientManagerContract.Imp! manager = ConnectSmbClientManagerRequired();
|
|
try {
|
|
return BindSmbControlClient(manager, mountPath);
|
|
}
|
|
finally {
|
|
delete manager;
|
|
}
|
|
}
|
|
|
|
static internal SmbClientControlContract.Imp! BindSmbControlClient(SmbClientManagerContract.Imp! manager, string! mountPath)
|
|
{
|
|
SmbClientControlContract.Imp! control_imp;
|
|
SmbClientControlContract.Exp! control_exp;
|
|
SmbClientControlContract.NewChannel(out control_imp, out control_exp);
|
|
|
|
manager.SendBindClient(Bitter.FromString2(mountPath), control_exp);
|
|
|
|
switch receive {
|
|
case manager.Ok():
|
|
control_imp.RecvSuccess();
|
|
return control_imp;
|
|
|
|
case manager.RequestFailed(errorcode, optionalErrorText):
|
|
string local_errorText = Bitter.ToString(optionalErrorText);
|
|
delete optionalErrorText;
|
|
throw new Exception(String.Format("Failed to bind to SMB control endpoint for mount path '{0}': {1} {2}", mountPath, errorcode.ToString(), local_errorText));
|
|
}
|
|
}
|
|
|
|
|
|
internal static void ShowClientInfo(string! mountPath)
|
|
{
|
|
SmbClientManagerContract.Imp! manager = ConnectSmbClientManagerRequired();
|
|
try {
|
|
ShowClientInfo(manager, mountPath);
|
|
}
|
|
finally {
|
|
delete manager;
|
|
}
|
|
}
|
|
|
|
internal static void ShowClientInfo(SmbClientManagerContract.Imp! manager, string! clientId)
|
|
{
|
|
Console.WriteLine("SMB client:");
|
|
|
|
try {
|
|
try {
|
|
|
|
manager.SendQueryClientConfig(Bitter.FromString2(clientId));
|
|
|
|
const string format = " {0,-20} : {1}";
|
|
|
|
switch receive
|
|
{
|
|
case manager.ClientConfigReport(SmbClientConfig config):
|
|
Console.WriteLine(format, "UNC path", Bitter.ToString2(config.UncPath));
|
|
Console.WriteLine(format, "Mount path", Bitter.ToString2(config.MountPath));
|
|
Console.WriteLine(format, "User name", Bitter.ToString2(config.CredentialsName));
|
|
config.Dispose();
|
|
break;
|
|
|
|
case manager.RequestFailed(code, text):
|
|
Console.WriteLine("Failed to query client configuration.");
|
|
SmbControl.ShowRequestFailed(code, text);
|
|
delete text;
|
|
break;
|
|
|
|
case manager.ChannelClosed():
|
|
Console.WriteLine("SMB service closed channel without responding to control query.");
|
|
break;
|
|
}
|
|
|
|
manager.SendQueryClientStatus(Bitter.FromString2(clientId));
|
|
|
|
switch receive {
|
|
case manager.ClientStatusReport(SmbClientStatus status):
|
|
|
|
string! connectionStatusText = ToString(status.ConnectionStatus);
|
|
if (status.ConnectionStatus == SmbClientConnectionStatus.Disconnected) {
|
|
connectionStatusText = connectionStatusText + " (reason: " + ToString(status.ReasonDisconnected) + ")";
|
|
}
|
|
|
|
Console.WriteLine(format, "Connection status", connectionStatusText);
|
|
if (status.ServerMachineName != null) Console.WriteLine(format, "Server machine name", Bitter.ToString(status.ServerMachineName));
|
|
if (status.ServerDomainName != null) Console.WriteLine(format, "Server domain name", Bitter.ToString(status.ServerDomainName));
|
|
if (status.ServerOperatingSystem != null) Console.WriteLine(format, "Server OS", Bitter.ToString(status.ServerOperatingSystem));
|
|
if (status.ActiveCredentialsName != null) Console.WriteLine(format, "Active Credentials", Bitter.ToString2(status.ActiveCredentialsName));
|
|
|
|
status.Dispose();
|
|
break;
|
|
|
|
case manager.RequestFailed(code, text):
|
|
Console.WriteLine("Failed to query client status.");
|
|
SmbControl.ShowRequestFailed(code, text);
|
|
delete text;
|
|
break;
|
|
|
|
case manager.ChannelClosed():
|
|
Console.WriteLine("Error - Control channel was closed.");
|
|
break;
|
|
}
|
|
|
|
}
|
|
finally {
|
|
}
|
|
|
|
}
|
|
catch (Exception ex) {
|
|
// Console.WriteLine("Failed to bind to control endpoint '{0}'.", ex);
|
|
ShowException(ex);
|
|
}
|
|
|
|
Console.WriteLine("");
|
|
}
|
|
|
|
static string! ToStringOrNull(char[] in ExHeap str)
|
|
{
|
|
if (str != null)
|
|
return Bitter.ToString2(str);
|
|
else
|
|
return "(null)";
|
|
}
|
|
|
|
static string! ToString(SmbClientConnectionStatus status)
|
|
{
|
|
switch (status)
|
|
{
|
|
case SmbClientConnectionStatus.Disconnected: return "Disconnected";
|
|
case SmbClientConnectionStatus.Negotiating: return "Negotiating";
|
|
case SmbClientConnectionStatus.TransportConnecting: return "Connecting";
|
|
case SmbClientConnectionStatus.Authenticating: return "Authenticating";
|
|
case SmbClientConnectionStatus.Connected: return "Connected";
|
|
default: return "Unknown (" + ((int)status).ToString() + ")";
|
|
}
|
|
}
|
|
|
|
static string! ToString(SmbReasonDisconnected reason)
|
|
{
|
|
switch (reason)
|
|
{
|
|
case SmbReasonDisconnected.Idle: return "Idle";
|
|
case SmbReasonDisconnected.TransportConnectFailed: return "Failed to connect transport";
|
|
case SmbReasonDisconnected.NegotiationFailed: return "Negotiation failed";
|
|
case SmbReasonDisconnected.ResourceConnectFailed: return "Resource connect rejected";
|
|
case SmbReasonDisconnected.AuthenticationFailed: return "Authentication failed";
|
|
case SmbReasonDisconnected.AuthorizationFailed: return "Authorization failed";
|
|
case SmbReasonDisconnected.NoResponse: return "No response from server";
|
|
case SmbReasonDisconnected.ProtocolViolation: return "Protocol violation";
|
|
case SmbReasonDisconnected.TransportFailure: return "Transport failure";
|
|
default: return "Unknown (" + ((int)reason).ToString() + ")";
|
|
}
|
|
}
|
|
|
|
internal static void ShowException(Exception! chain)
|
|
{
|
|
Exception current = chain;
|
|
while (current != null)
|
|
{
|
|
Console.WriteLine("{0}: {1}", current.GetType().FullName, current.Message);
|
|
current = current.InnerException;
|
|
}
|
|
}
|
|
|
|
internal static void ShowRequestFailed(SmbErrorCode code, char[] in ExHeap optionalErrorText)
|
|
{
|
|
if (optionalErrorText != null)
|
|
Console.WriteLine("Error: SmbErrorCode.{0} : {1}", code, Bitter.ToString2(optionalErrorText));
|
|
else
|
|
Console.WriteLine("Error: SmbErrorCode.{0}", code);
|
|
}
|
|
}
|
|
}
|