mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-11-20 04:18:51 -07:00
598 lines
24 KiB
C#
598 lines
24 KiB
C#
// This code is derived from jcifs smb client library <jcifs at samba dot org>
|
|
// Ported by J. Arturo <webmaster at komodosoft dot net>
|
|
//
|
|
// This library is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU Lesser General Public
|
|
// License as published by the Free Software Foundation; either
|
|
// version 2.1 of the License, or (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Net;
|
|
using SharpCifs.Netbios;
|
|
using SharpCifs.Util.Sharpen;
|
|
|
|
namespace SharpCifs.Smb
|
|
{
|
|
public sealed class SmbSession
|
|
{
|
|
private static readonly string LogonShare
|
|
= Config.GetProperty("jcifs.smb.client.logonShare", null);
|
|
|
|
private static readonly int LookupRespLimit
|
|
= Config.GetInt("jcifs.netbios.lookupRespLimit", 3);
|
|
|
|
private static readonly string Domain
|
|
= Config.GetProperty("jcifs.smb.client.domain", null);
|
|
|
|
private static readonly string Username
|
|
= Config.GetProperty("jcifs.smb.client.username", null);
|
|
|
|
private static readonly int CachePolicy
|
|
= Config.GetInt("jcifs.netbios.cachePolicy", 60 * 10) * 60;
|
|
|
|
internal static NbtAddress[] DcList;
|
|
|
|
internal static long DcListExpiration;
|
|
|
|
internal static int DcListCounter;
|
|
|
|
/// <exception cref="SharpCifs.Smb.SmbException"></exception>
|
|
private static NtlmChallenge Interrogate(NbtAddress addr)
|
|
{
|
|
UniAddress dc = new UniAddress(addr);
|
|
SmbTransport trans = SmbTransport.GetSmbTransport(dc, 0);
|
|
if (Username == null)
|
|
{
|
|
trans.Connect();
|
|
if (SmbTransport.LogStatic.Level >= 3)
|
|
{
|
|
SmbTransport.LogStatic.WriteLine(
|
|
"Default credentials (jcifs.smb.client.username/password)"
|
|
+ " not specified. SMB signing may not work propertly."
|
|
+ " Skipping DC interrogation.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SmbSession ssn = trans.GetSmbSession(NtlmPasswordAuthentication.Default);
|
|
ssn.GetSmbTree(LogonShare, null).TreeConnect(null, null);
|
|
}
|
|
return new NtlmChallenge(trans.Server.EncryptionKey, dc);
|
|
}
|
|
|
|
/// <exception cref="SharpCifs.Smb.SmbException"></exception>
|
|
/// <exception cref="UnknownHostException"></exception>
|
|
public static NtlmChallenge GetChallengeForDomain()
|
|
{
|
|
if (Domain == null)
|
|
{
|
|
throw new SmbException("A domain was not specified");
|
|
}
|
|
lock (Domain)
|
|
{
|
|
long now = Runtime.CurrentTimeMillis();
|
|
int retry = 1;
|
|
do
|
|
{
|
|
if (DcListExpiration < now)
|
|
{
|
|
NbtAddress[] list = NbtAddress.GetAllByName(Domain, 0x1C, null, null);
|
|
DcListExpiration = now + CachePolicy * 1000L;
|
|
if (list != null && list.Length > 0)
|
|
{
|
|
DcList = list;
|
|
}
|
|
else
|
|
{
|
|
DcListExpiration = now + 1000 * 60 * 15;
|
|
if (SmbTransport.LogStatic.Level >= 2)
|
|
{
|
|
SmbTransport.LogStatic.WriteLine("Failed to retrieve DC list from WINS");
|
|
}
|
|
}
|
|
}
|
|
int max = Math.Min(DcList.Length, LookupRespLimit);
|
|
for (int j = 0; j < max; j++)
|
|
{
|
|
int i = DcListCounter++ % max;
|
|
if (DcList[i] != null)
|
|
{
|
|
try
|
|
{
|
|
return Interrogate(DcList[i]);
|
|
}
|
|
catch (SmbException se)
|
|
{
|
|
if (SmbTransport.LogStatic.Level >= 2)
|
|
{
|
|
SmbTransport.LogStatic.WriteLine("Failed validate DC: " + DcList[i]);
|
|
if (SmbTransport.LogStatic.Level > 2)
|
|
{
|
|
Runtime.PrintStackTrace(se, SmbTransport.LogStatic);
|
|
}
|
|
}
|
|
}
|
|
DcList[i] = null;
|
|
}
|
|
}
|
|
DcListExpiration = 0;
|
|
}
|
|
while (retry-- > 0);
|
|
DcListExpiration = now + 1000 * 60 * 15;
|
|
}
|
|
throw new UnknownHostException(
|
|
"Failed to negotiate with a suitable domain controller for " + Domain);
|
|
}
|
|
|
|
/// <exception cref="SharpCifs.Smb.SmbException"></exception>
|
|
/// <exception cref="UnknownHostException"></exception>
|
|
public static byte[] GetChallenge(UniAddress dc)
|
|
{
|
|
return GetChallenge(dc, 0);
|
|
}
|
|
|
|
/// <exception cref="SharpCifs.Smb.SmbException"></exception>
|
|
/// <exception cref="UnknownHostException"></exception>
|
|
public static byte[] GetChallenge(UniAddress dc, int port)
|
|
{
|
|
SmbTransport trans = SmbTransport.GetSmbTransport(dc, port);
|
|
trans.Connect();
|
|
return trans.Server.EncryptionKey;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Authenticate arbitrary credentials represented by the
|
|
/// <tt>NtlmPasswordAuthentication</tt> object against the domain controller
|
|
/// specified by the <tt>UniAddress</tt> parameter.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Authenticate arbitrary credentials represented by the
|
|
/// <tt>NtlmPasswordAuthentication</tt> object against the domain controller
|
|
/// specified by the <tt>UniAddress</tt> parameter. If the credentials are
|
|
/// not accepted, an <tt>SmbAuthException</tt> will be thrown. If an error
|
|
/// occurs an <tt>SmbException</tt> will be thrown. If the credentials are
|
|
/// valid, the method will return without throwing an exception. See the
|
|
/// last <a href="../../../faq.html">FAQ</a> question.
|
|
/// <p>
|
|
/// See also the <tt>jcifs.smb.client.logonShare</tt> property.
|
|
/// </remarks>
|
|
/// <exception cref="SmbException"></exception>
|
|
public static void Logon(UniAddress dc, NtlmPasswordAuthentication auth)
|
|
{
|
|
Logon(dc, -1, auth);
|
|
}
|
|
|
|
/// <exception cref="SharpCifs.Smb.SmbException"></exception>
|
|
public static void Logon(UniAddress dc, int port, NtlmPasswordAuthentication auth)
|
|
{
|
|
SmbTree tree = SmbTransport.GetSmbTransport(dc, port)
|
|
.GetSmbSession(auth)
|
|
.GetSmbTree(LogonShare, null);
|
|
if (LogonShare == null)
|
|
{
|
|
tree.TreeConnect(null, null);
|
|
}
|
|
else
|
|
{
|
|
Trans2FindFirst2 req = new Trans2FindFirst2("\\", "*", SmbFile.AttrDirectory);
|
|
Trans2FindFirst2Response resp = new Trans2FindFirst2Response();
|
|
tree.Send(req, resp);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Clear All Cached Transport-Connections
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Alias of SmbTransport.ClearCachedConnections
|
|
/// </remarks>
|
|
public static void ClearCachedConnections()
|
|
{
|
|
SmbTransport.ClearCachedConnections();
|
|
}
|
|
|
|
|
|
internal int ConnectionState;
|
|
|
|
internal int Uid;
|
|
|
|
internal List<object> Trees;
|
|
|
|
private UniAddress _address;
|
|
|
|
private int _port;
|
|
|
|
private int _localPort;
|
|
|
|
private IPAddress _localAddr;
|
|
|
|
internal SmbTransport transport;
|
|
|
|
internal NtlmPasswordAuthentication Auth;
|
|
|
|
internal long Expiration;
|
|
|
|
internal string NetbiosName;
|
|
|
|
internal SmbSession(UniAddress address,
|
|
int port,
|
|
IPAddress localAddr,
|
|
int localPort,
|
|
NtlmPasswordAuthentication auth)
|
|
{
|
|
// Transport parameters allows trans to be removed from CONNECTIONS
|
|
this._address = address;
|
|
this._port = port;
|
|
this._localAddr = localAddr;
|
|
this._localPort = localPort;
|
|
this.Auth = auth;
|
|
Trees = new List<object>();
|
|
ConnectionState = 0;
|
|
}
|
|
|
|
internal SmbTree GetSmbTree(string share, string service)
|
|
{
|
|
lock (this)
|
|
{
|
|
SmbTree t;
|
|
if (share == null)
|
|
{
|
|
share = "IPC$";
|
|
}
|
|
/*
|
|
for (IEnumeration e = trees.GetEnumerator(); e.MoveNext(); )
|
|
{
|
|
t = (SmbTree)e.Current;
|
|
if (t.Matches(share, service))
|
|
{
|
|
return t;
|
|
}
|
|
}
|
|
*/
|
|
foreach (var e in Trees)
|
|
{
|
|
t = (SmbTree)e;
|
|
if (t.Matches(share, service))
|
|
{
|
|
return t;
|
|
}
|
|
}
|
|
|
|
t = new SmbTree(this, share, service);
|
|
Trees.Add(t);
|
|
return t;
|
|
}
|
|
}
|
|
|
|
internal bool Matches(NtlmPasswordAuthentication auth)
|
|
{
|
|
return this.Auth == auth || this.Auth.Equals(auth);
|
|
}
|
|
|
|
internal SmbTransport Transport()
|
|
{
|
|
lock (this)
|
|
{
|
|
if (transport == null)
|
|
{
|
|
transport = SmbTransport.GetSmbTransport(_address,
|
|
_port,
|
|
_localAddr,
|
|
_localPort,
|
|
null);
|
|
}
|
|
return transport;
|
|
}
|
|
}
|
|
|
|
/// <exception cref="SharpCifs.Smb.SmbException"></exception>
|
|
internal void Send(ServerMessageBlock request, ServerMessageBlock response)
|
|
{
|
|
lock (Transport())
|
|
{
|
|
if (response != null)
|
|
{
|
|
response.Received = false;
|
|
}
|
|
Expiration = Runtime.CurrentTimeMillis() + SmbConstants.SoTimeout;
|
|
SessionSetup(request, response);
|
|
if (response != null && response.Received)
|
|
{
|
|
return;
|
|
}
|
|
if (request is SmbComTreeConnectAndX)
|
|
{
|
|
SmbComTreeConnectAndX tcax = (SmbComTreeConnectAndX)request;
|
|
if (NetbiosName != null && tcax.path.EndsWith("\\IPC$"))
|
|
{
|
|
tcax.path = "\\\\" + NetbiosName + "\\IPC$";
|
|
}
|
|
}
|
|
request.Uid = Uid;
|
|
request.Auth = Auth;
|
|
try
|
|
{
|
|
transport.Send(request, response);
|
|
}
|
|
catch (SmbException se)
|
|
{
|
|
if (request is SmbComTreeConnectAndX)
|
|
{
|
|
Logoff(true);
|
|
}
|
|
request.Digest = null;
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <exception cref="SharpCifs.Smb.SmbException"></exception>
|
|
internal void SessionSetup(ServerMessageBlock andx, ServerMessageBlock andxResponse)
|
|
{
|
|
lock (Transport())
|
|
{
|
|
NtlmContext nctx = null;
|
|
SmbException ex = null;
|
|
SmbComSessionSetupAndX request;
|
|
SmbComSessionSetupAndXResponse response;
|
|
byte[] token = new byte[0];
|
|
int state = 10;
|
|
while (ConnectionState != 0)
|
|
{
|
|
if (ConnectionState == 2 || ConnectionState == 3)
|
|
{
|
|
// connected or disconnecting
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
Runtime.Wait(transport);
|
|
}
|
|
catch (Exception ie)
|
|
{
|
|
throw new SmbException(ie.Message, ie);
|
|
}
|
|
}
|
|
ConnectionState = 1;
|
|
// trying ...
|
|
try
|
|
{
|
|
transport.Connect();
|
|
if (transport.Log.Level >= 4)
|
|
{
|
|
transport.Log.WriteLine("sessionSetup: accountName=" + Auth.Username
|
|
+ ",primaryDomain=" + Auth.Domain);
|
|
}
|
|
Uid = 0;
|
|
do
|
|
{
|
|
switch (state)
|
|
{
|
|
case 10:
|
|
{
|
|
if (Auth != NtlmPasswordAuthentication.Anonymous
|
|
&& transport.HasCapability(SmbConstants.CapExtendedSecurity))
|
|
{
|
|
state = 20;
|
|
break;
|
|
}
|
|
request = new SmbComSessionSetupAndX(this, andx, Auth);
|
|
response = new SmbComSessionSetupAndXResponse(andxResponse);
|
|
if (transport.IsSignatureSetupRequired(Auth))
|
|
{
|
|
if (Auth.HashesExternal
|
|
&& NtlmPasswordAuthentication.DefaultPassword
|
|
!= NtlmPasswordAuthentication.Blank)
|
|
{
|
|
transport.GetSmbSession(NtlmPasswordAuthentication.Default)
|
|
.GetSmbTree(LogonShare, null)
|
|
.TreeConnect(null, null);
|
|
}
|
|
else
|
|
{
|
|
byte[] signingKey
|
|
= Auth.GetSigningKey(transport.Server.EncryptionKey);
|
|
request.Digest = new SigningDigest(signingKey, false);
|
|
}
|
|
}
|
|
request.Auth = Auth;
|
|
try
|
|
{
|
|
transport.Send(request, response);
|
|
}
|
|
catch (SmbAuthException sae)
|
|
{
|
|
throw;
|
|
}
|
|
catch (SmbException se)
|
|
{
|
|
ex = se;
|
|
}
|
|
if (response.IsLoggedInAsGuest
|
|
&& Runtime.EqualsIgnoreCase("GUEST", Auth.Username) == false
|
|
&& transport.Server.Security != SmbConstants.SecurityShare
|
|
&& Auth != NtlmPasswordAuthentication.Anonymous)
|
|
{
|
|
throw new SmbAuthException(NtStatus.NtStatusLogonFailure);
|
|
}
|
|
if (ex != null)
|
|
{
|
|
throw ex;
|
|
}
|
|
Uid = response.Uid;
|
|
if (request.Digest != null)
|
|
{
|
|
transport.Digest = request.Digest;
|
|
}
|
|
ConnectionState = 2;
|
|
state = 0;
|
|
break;
|
|
}
|
|
|
|
case 20:
|
|
{
|
|
if (nctx == null)
|
|
{
|
|
bool doSigning
|
|
= (transport.Flags2
|
|
& SmbConstants.Flags2SecuritySignatures) != 0;
|
|
nctx = new NtlmContext(Auth, doSigning);
|
|
}
|
|
if (SmbTransport.LogStatic.Level >= 4)
|
|
{
|
|
SmbTransport.LogStatic.WriteLine(nctx);
|
|
}
|
|
if (nctx.IsEstablished())
|
|
{
|
|
NetbiosName = nctx.GetNetbiosName();
|
|
ConnectionState = 2;
|
|
state = 0;
|
|
break;
|
|
}
|
|
try
|
|
{
|
|
token = nctx.InitSecContext(token, 0, token.Length);
|
|
}
|
|
catch (SmbException se)
|
|
{
|
|
try
|
|
{
|
|
transport.Disconnect(true);
|
|
}
|
|
catch (IOException)
|
|
{
|
|
}
|
|
Uid = 0;
|
|
throw;
|
|
}
|
|
if (token != null)
|
|
{
|
|
request = new SmbComSessionSetupAndX(this, null, token);
|
|
response = new SmbComSessionSetupAndXResponse(null);
|
|
if (transport.IsSignatureSetupRequired(Auth))
|
|
{
|
|
byte[] signingKey = nctx.GetSigningKey();
|
|
if (signingKey != null)
|
|
{
|
|
request.Digest = new SigningDigest(signingKey, true);
|
|
}
|
|
}
|
|
request.Uid = Uid;
|
|
Uid = 0;
|
|
try
|
|
{
|
|
transport.Send(request, response);
|
|
}
|
|
catch (SmbAuthException sae)
|
|
{
|
|
throw;
|
|
}
|
|
catch (SmbException se)
|
|
{
|
|
ex = se;
|
|
try
|
|
{
|
|
transport.Disconnect(true);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
}
|
|
if (response.IsLoggedInAsGuest
|
|
&& Runtime.EqualsIgnoreCase("GUEST", Auth.Username)
|
|
== false)
|
|
{
|
|
throw new SmbAuthException(NtStatus.NtStatusLogonFailure);
|
|
}
|
|
if (ex != null)
|
|
{
|
|
throw ex;
|
|
}
|
|
Uid = response.Uid;
|
|
if (request.Digest != null)
|
|
{
|
|
transport.Digest = request.Digest;
|
|
}
|
|
token = response.Blob;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
throw new SmbException("Unexpected session setup state: " + state);
|
|
}
|
|
}
|
|
}
|
|
while (state != 0);
|
|
}
|
|
catch (SmbException se)
|
|
{
|
|
Logoff(true);
|
|
ConnectionState = 0;
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
Runtime.NotifyAll(transport);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void Logoff(bool inError)
|
|
{
|
|
lock (Transport())
|
|
{
|
|
if (ConnectionState != 2)
|
|
{
|
|
// not-connected
|
|
return;
|
|
}
|
|
ConnectionState = 3;
|
|
// disconnecting
|
|
NetbiosName = null;
|
|
|
|
foreach (SmbTree t in Trees)
|
|
{
|
|
t.TreeDisconnect(inError);
|
|
}
|
|
|
|
if (!inError && transport.Server.Security != SmbConstants.SecurityShare)
|
|
{
|
|
SmbComLogoffAndX request = new SmbComLogoffAndX(null);
|
|
request.Uid = Uid;
|
|
try
|
|
{
|
|
transport.Send(request, null);
|
|
}
|
|
catch (SmbException)
|
|
{
|
|
}
|
|
Uid = 0;
|
|
}
|
|
ConnectionState = 0;
|
|
Runtime.NotifyAll(transport);
|
|
}
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "SmbSession[accountName=" + Auth.Username
|
|
+ ",primaryDomain=" + Auth.Domain
|
|
+ ",uid=" + Uid
|
|
+ ",connectionState=" + ConnectionState + "]";
|
|
}
|
|
}
|
|
}
|