// 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.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.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.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.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(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(); 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(); 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); } } }