|
|
|
|
// using System.IO;
|
|
|
|
|
using System.Buffers.Text;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Specialized;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Net;
|
|
|
|
|
using System.Net.Security;
|
|
|
|
|
using System.Net.Sockets;
|
|
|
|
|
using System.Security.Authentication;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using UMC.SshNet;
|
|
|
|
|
using UMC.Net;
|
|
|
|
|
|
|
|
|
|
namespace UMC.ITME
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
public abstract class HttpMime : IDisposable
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
public abstract int Id { get; }
|
|
|
|
|
public int ActiveTime
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
get; set;
|
|
|
|
|
}
|
|
|
|
|
public int TimeOut { protected set; get; } = 20;
|
|
|
|
|
|
|
|
|
|
public virtual string Scheme => "http";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public void OutputFinish()
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
this.ActiveTime = UMC.Data.Utility.TimeSpan();
|
|
|
|
|
this.TimeOut = 20;
|
|
|
|
|
this.Request = null;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
public abstract void Write(byte[] buffer, int offset, int count);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public abstract void Dispose();
|
|
|
|
|
public abstract String Host { get; }
|
|
|
|
|
public abstract String RemoteIpAddress { get; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected MimeRequest Request;
|
|
|
|
|
|
|
|
|
|
protected void WebSocket(NetContext context)
|
|
|
|
|
{
|
|
|
|
|
if (context.Tag is NetHttpRequest)
|
|
|
|
|
{
|
|
|
|
|
var webr = context.Tag as NetHttpRequest;
|
|
|
|
|
this.WebSocket(webr);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
OutText(403, "WebSocket");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void WebSocket(NetHttpRequest webRequest)
|
|
|
|
|
{
|
|
|
|
|
var url = webRequest.Address;
|
|
|
|
|
webRequest.Headers["Connection"] = "Upgrade";
|
|
|
|
|
|
|
|
|
|
var eventArgs = new ConnectAsyncEventArgs
|
|
|
|
|
{
|
|
|
|
|
Connected = async (client) =>
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
byte[] _data = System.Buffers.ArrayPool<byte>.Shared.Rent(0x600);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (url.Scheme == "https")
|
|
|
|
|
{
|
|
|
|
|
SslStream ssl = new SslStream(new NetworkStream(client, true), false, (sender, certificate, chain, sslPolicyErrors) => true);
|
|
|
|
|
await ssl.AuthenticateAsClientAsync(url.Host, null, SslProtocols.None, false);
|
|
|
|
|
var textWriter = new TextWriter(ssl.Write, _data);
|
|
|
|
|
// textWriter
|
|
|
|
|
UMC.Net.NetHttpResponse.Header(webRequest, textWriter);
|
|
|
|
|
|
|
|
|
|
textWriter.Dispose();
|
|
|
|
|
|
|
|
|
|
int size = await ssl.ReadAsync(_data, 0, _data.Length);
|
|
|
|
|
|
|
|
|
|
if (NetBridge.ResponseHeader(_data, 0, size, new NameValueCollection(), out var statucode) && statucode == HttpStatusCode.SwitchingProtocols)
|
|
|
|
|
{
|
|
|
|
|
this.Request = new HttpsWebSocket(this, ssl);
|
|
|
|
|
|
|
|
|
|
this.Write(_data, 0, size);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
this.Write(_data, 0, size);
|
|
|
|
|
this.Dispose();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var textWriter = new TextWriter((b, c, s) =>
|
|
|
|
|
{
|
|
|
|
|
client.Send(b, c, s, SocketFlags.None);
|
|
|
|
|
}, _data);
|
|
|
|
|
UMC.Net.NetHttpResponse.Header(webRequest, textWriter);
|
|
|
|
|
textWriter.Dispose();
|
|
|
|
|
|
|
|
|
|
var size = client.Receive(_data, 0, _data.Length, SocketFlags.None);
|
|
|
|
|
if (NetBridge.ResponseHeader(_data, 0, size, new NameValueCollection(), out var statucode) && statucode == HttpStatusCode.SwitchingProtocols)
|
|
|
|
|
{
|
|
|
|
|
this.Request = new HttpWebSocket(this, client);
|
|
|
|
|
this.Write(_data, 0, size);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
this.Write(_data, 0, size);
|
|
|
|
|
this.Dispose();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
this.OutText(500, ex.ToString());
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
System.Buffers.ArrayPool<byte>.Shared.Return(_data);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Error = (ex) =>
|
|
|
|
|
{
|
|
|
|
|
this.OutText(500, ex.ToString());
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
eventArgs.Start(0, url.Host, url.Port);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HttpMimeServier.httpMimes.TryRemove(this.Id, out var _);
|
|
|
|
|
}
|
|
|
|
|
void SSH(SshClient sshClient, UMC.Host.HttpWebSocket webSocket, Entities.Device device, NameValueCollection query, String username)
|
|
|
|
|
{
|
|
|
|
|
sshClient.Connect();
|
|
|
|
|
const string terminalName = "vt100";
|
|
|
|
|
uint columns = Utility.Parse(query.Get("c"), 80u);
|
|
|
|
|
uint rows = Utility.Parse(query.Get("r"), 25u);
|
|
|
|
|
uint width = Utility.Parse(query.Get("w"), 640u);
|
|
|
|
|
uint height = Utility.Parse(query.Get("h"), 480u);
|
|
|
|
|
const int bufferSize = 100;
|
|
|
|
|
var inputIndex = 0;
|
|
|
|
|
var count = 0;
|
|
|
|
|
var input = new char[200];
|
|
|
|
|
var isPwd = false;
|
|
|
|
|
var isInput = false;
|
|
|
|
|
var strea = sshClient.CreateShellStream(terminalName, columns, rows, width, height, bufferSize);
|
|
|
|
|
webSocket.Close = () =>
|
|
|
|
|
{
|
|
|
|
|
sshClient.Disconnect();
|
|
|
|
|
strea.Dispose();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
webSocket.Send("{\"type\":\"info\",\"value\":{\"Title\":\"" + device.Caption + "\",\"Key\":\"" + device.Id + "\"}}");
|
|
|
|
|
strea.DataReceived += (b, e) =>
|
|
|
|
|
{
|
|
|
|
|
var lines = System.Text.Encoding.UTF8.GetString(e.Data).Split("\r\n", StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
|
if (lines.Length > 0)
|
|
|
|
|
{
|
|
|
|
|
var str = lines[lines.Length - 1];
|
|
|
|
|
|
|
|
|
|
if (isInput)
|
|
|
|
|
{
|
|
|
|
|
if (lines.Length == 1)
|
|
|
|
|
{
|
|
|
|
|
var IsTo = false;
|
|
|
|
|
var ToValue = -1;
|
|
|
|
|
for (var i = 0; i < str.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
var by = str[i];
|
|
|
|
|
switch (by)
|
|
|
|
|
{
|
|
|
|
|
case '\a':
|
|
|
|
|
break;
|
|
|
|
|
case '\b':
|
|
|
|
|
inputIndex--;
|
|
|
|
|
break;
|
|
|
|
|
case '\u001b':
|
|
|
|
|
if (i + 1 < str.Length)
|
|
|
|
|
{
|
|
|
|
|
if (str[i + 1] == '[')
|
|
|
|
|
{
|
|
|
|
|
ToValue = i + 2;
|
|
|
|
|
i++;
|
|
|
|
|
IsTo = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
if (IsTo)
|
|
|
|
|
{
|
|
|
|
|
if ((by >= 'a' && by <= 'z') || (by >= 'A' && by <= 'Z'))
|
|
|
|
|
{
|
|
|
|
|
var tv = ToValue == i ? "1" : str.Substring(ToValue, i - ToValue);
|
|
|
|
|
switch (by)
|
|
|
|
|
{
|
|
|
|
|
case 'K':
|
|
|
|
|
count = inputIndex;
|
|
|
|
|
break;
|
|
|
|
|
case 'C':
|
|
|
|
|
inputIndex += Utility.IntParse(tv, 1);
|
|
|
|
|
break;
|
|
|
|
|
case 'D':
|
|
|
|
|
inputIndex -= Utility.IntParse(tv, 1);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
IsTo = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (input.Length > inputIndex)
|
|
|
|
|
{
|
|
|
|
|
input[inputIndex] = by;
|
|
|
|
|
inputIndex++;
|
|
|
|
|
count = inputIndex;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (str[0] == '[')
|
|
|
|
|
{
|
|
|
|
|
isInput = device.Username.StartsWith(str, 1, device.Username.Length);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
isInput = str.StartsWith(device.Username);
|
|
|
|
|
}
|
|
|
|
|
if (isPwd && isInput)
|
|
|
|
|
{
|
|
|
|
|
if (lines.Length == 2)
|
|
|
|
|
{
|
|
|
|
|
if (lines[0].StartsWith("/"))
|
|
|
|
|
{
|
|
|
|
|
webSocket.Send("{\"type\":\"view\",\"value\":{\"Path\":\"" + lines[0] + "\",\"Key\":\"" + device.Id + "\"}}");
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
webSocket.Send(e.Data, 0, e.Data.Length);
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
webSocket.ReceiveData = (b, c, l) =>
|
|
|
|
|
{
|
|
|
|
|
if (b[c] == 13)
|
|
|
|
|
{
|
|
|
|
|
isInput = false;
|
|
|
|
|
if (count > 0)
|
|
|
|
|
{
|
|
|
|
|
var ls = new String(input.AsSpan(0, count));
|
|
|
|
|
|
|
|
|
|
isPwd = ls == "pwd";
|
|
|
|
|
inputIndex = 0;
|
|
|
|
|
count = 0;
|
|
|
|
|
Activities.SiteSSHActivities.SSHLog(device, username, ls);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
strea.Write(b, c, l);
|
|
|
|
|
strea.Flush();
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
webSocket.Disconnect();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
strea.ErrorOccurred += (e, b) =>
|
|
|
|
|
{
|
|
|
|
|
sshClient.Disconnect();
|
|
|
|
|
strea.Dispose();
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
public virtual void PrepareRespone(HttpMimeRequest request)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
this.TimeOut = 300;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
if (request.IsWebSocket)
|
|
|
|
|
{
|
|
|
|
|
if (request.RawUrl.StartsWith("/UMC.WS/"))
|
|
|
|
|
{
|
|
|
|
|
var Path = request.Url.AbsolutePath.Substring(8);
|
|
|
|
|
|
|
|
|
|
var ds = request.Cookies.GetValues(WebServlet.SessionCookieName) ?? new string[] { Path };
|
|
|
|
|
if (ds.Length > 0)
|
|
|
|
|
{
|
|
|
|
|
string secWebSocketKey = request.Headers["Sec-WebSocket-Key"];
|
|
|
|
|
if (String.IsNullOrEmpty(secWebSocketKey) == false)
|
|
|
|
|
{
|
|
|
|
|
var buffers = System.Buffers.ArrayPool<byte>.Shared.Rent(0x200);
|
|
|
|
|
Guid dID;
|
|
|
|
|
|
|
|
|
|
if (ds.Any(r => r == Path))
|
|
|
|
|
{
|
|
|
|
|
ds[0] = Path;
|
|
|
|
|
dID = UMC.Data.Utility.Guid(Path, true).Value;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
dID = SiteConfig.MD5Key(String.Join(',', ds), Path);
|
|
|
|
|
}
|
|
|
|
|
var webr = new UMC.Host.HttpWebSocket(this.Write, dID, this.Dispose);
|
|
|
|
|
this.Request = webr;
|
|
|
|
|
|
|
|
|
|
var size = secWebSocketKey.WriteBytes(buffers, 0);
|
|
|
|
|
size += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".WriteBytes(buffers, size);
|
|
|
|
|
int len = System.Security.Cryptography.SHA1.HashData(buffers.AsSpan(0, size), buffers.AsSpan(size, 24));
|
|
|
|
|
string secWebSocketAcceptString = Convert.ToBase64String(buffers.AsSpan(size, len));
|
|
|
|
|
var writer = new Net.TextWriter(request._context.Write, buffers);
|
|
|
|
|
writer.Write($"HTTP/1.1 101 {HttpStatusDescription.Get(101)}\r\n");
|
|
|
|
|
writer.Write("Connection: Upgrade\r\n");
|
|
|
|
|
writer.Write("Upgrade: websocket\r\n");
|
|
|
|
|
writer.Write($"Sec-WebSocket-Accept: {secWebSocketAcceptString}\r\n");
|
|
|
|
|
writer.Write("Server: ITME\r\n\r\n");
|
|
|
|
|
writer.Flush();
|
|
|
|
|
writer.Dispose();
|
|
|
|
|
System.Buffers.ArrayPool<byte>.Shared.Return(buffers);
|
|
|
|
|
HttpMimeServier.httpMimes.TryRemove(this.Id, out var _);
|
|
|
|
|
var devices = Path.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
|
if (devices.Length > 1)
|
|
|
|
|
{
|
|
|
|
|
var deviceId = Data.Utility.Guid(ds[0], true).Value;
|
|
|
|
|
var session = new Data.Session<Data.AccessToken>(deviceId.ToString());
|
|
|
|
|
if (session.Value != null && session.Value.Device == deviceId)
|
|
|
|
|
{
|
|
|
|
|
if (session.Value.IsInRole(UMC.Security.Membership.UserRole))
|
|
|
|
|
{
|
|
|
|
|
if (Security.AuthManager.Authorization(session.Value.Identity(), 0, $"WebSSH/{devices[1]}", out var isBiometric) > 0)
|
|
|
|
|
{
|
|
|
|
|
if (isBiometric && session.Value.BiometricTime == 0)
|
|
|
|
|
{
|
|
|
|
|
var seesionKey = UMC.Data.Utility.Guid(session.Value.Device.Value);
|
|
|
|
|
var url = $"/Biometric?oauth_callback={Uri.EscapeDataString(request.Url.AbsoluteUri)}&transfer={seesionKey}";
|
|
|
|
|
webr.Send("{\"type\":\"url\",\"value\":" + url + "\"}}");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var device = UMC.Data.HotCache.Get(new Entities.Device { Id = Utility.IntParse(devices[1], 0) });
|
|
|
|
|
if (device != null)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
var us = UMC.Data.License.GetLicense("WebSSH", 5);
|
|
|
|
|
Data.Caches.ICacheSet cacheSet2 = UMC.Data.HotCache.Cache<Entities.Device>();
|
|
|
|
|
if ((cacheSet2.Count > us.Quantity && us.Quantity > 0) || (us.ExpireTime > 0 && us.ExpireTime < Utility.TimeSpan()))
|
|
|
|
|
{
|
|
|
|
|
webr.Send("{\"type\":\"license\",\"msg\":\"设备数量超限,请保持合规\"}");
|
|
|
|
|
// return;
|
|
|
|
|
webr.Disconnect();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var password = UMC.Data.DataFactory.Instance().Password(SiteConfig.MD5Key(device.Ip, device.Username));
|
|
|
|
|
|
|
|
|
|
var ssh = new SshClient(device.Ip, device.Port ?? 22, device.Username, password);
|
|
|
|
|
|
|
|
|
|
var _QueryString = System.Web.HttpUtility.ParseQueryString(request.Url.Query);
|
|
|
|
|
webr.Send("{\"type\":\"device\",\"value\":\"" + Utility.Guid(dID) + "\"}");
|
|
|
|
|
|
|
|
|
|
this.SSH(ssh, webr, device, _QueryString, session.Value.Username);
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var lic = UMC.Data.License.GetLicense("UserSession", 1000);
|
|
|
|
|
|
|
|
|
|
Data.Caches.ICacheSet cacheSet = UMC.Data.HotCache.Cache<UMC.Data.Entities.Session>();
|
|
|
|
|
if ((cacheSet.Count > lic.Quantity && lic.Quantity > 0) || (lic.ExpireTime > 0 && lic.ExpireTime < Utility.TimeSpan()))
|
|
|
|
|
{
|
|
|
|
|
webr.Send("{\"msg\":\"会话规模超限,请保持合规\"}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
OutText(403, "not validate websocket headers");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
OutText(403, "Permission denied");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var context = new HttpMimeContext(request, new HttpMimeResponse(this, request));
|
|
|
|
|
context.ProcessRequest();
|
|
|
|
|
this.WebSocket(context);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var context = new HttpMimeContext(request, new HttpMimeResponse(this, request));
|
|
|
|
|
context.ProcessRequest();
|
|
|
|
|
context.ProcessAfter();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
OutText(500, "text/plain", ex.ToString());
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void OutText(int status, string contentType, String text)
|
|
|
|
|
{
|
|
|
|
|
var writer = new TextWriter(this.Write);
|
|
|
|
|
writer.Write($"HTTP/1.1 {status} {HttpStatusDescription.Get(status)}\r\n");
|
|
|
|
|
writer.Write($"Content-Type: {contentType}; charset=utf-8\r\n");
|
|
|
|
|
writer.Write($"Content-Length: {System.Text.Encoding.UTF8.GetByteCount(text)}\r\n");
|
|
|
|
|
writer.Write("Connection: close\r\n");
|
|
|
|
|
writer.Write("Server: ITME\r\n\r\n");
|
|
|
|
|
writer.Write(text);
|
|
|
|
|
writer.Flush();
|
|
|
|
|
writer.Close();
|
|
|
|
|
this.Dispose();
|
|
|
|
|
}
|
|
|
|
|
public void OutText(int status, String text)
|
|
|
|
|
{
|
|
|
|
|
this.OutText(status, "text/plain", text);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|