Skip to content

Commit

Permalink
Issues, generalize
Browse files Browse the repository at this point in the history
  • Loading branch information
veniware committed Aug 15, 2024
1 parent d2b9e3b commit 4a719a8
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 64 deletions.
19 changes: 9 additions & 10 deletions Protest/Front/automation.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class Automation extends List {

this.UpdateAuthorization();

this.createButton.disabled = true;
this.deleteButton.disabled = true;
this.startButton.disabled = true;
this.pauseButton.disabled = true;
Expand Down Expand Up @@ -86,21 +87,19 @@ class Automation extends List {
okButton.value = entry ? "Save" : "Create";

okButton.onclick = async ()=> {

dialog.Close();
};
}

InflateElement(element, entry) { //overrides
let icon;
switch (entry.name.v.toLowerCase()) {
case "lifeline": icon = "mono/lifeline.svg"; break;
case "lastseen": icon = "mono/lastseen.svg"; break;
case "watchdog": icon = "mono/watchdog.svg"; break;
case "issues" : icon = "mono/issues.svg"; break;
case "fetch" : icon = "mono/fetch.svg"; break;
default : icon = "mono/task.svg"; break;
}
let icon = {
"lifeline": "mono/lifeline.svg",
"lastseen": "mono/lastseen.svg",
"watchdog": "mono/watchdog.svg",
"issues" : "mono/issues.svg",
"fetch" : "mono/fetch.svg"
}[entry.name.v.toLowerCase()] ?? "mono/task.svg";

const iconBox = document.createElement("div");
iconBox.className = "list-element-icon";
Expand Down
1 change: 1 addition & 0 deletions Protest/Http/Listener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ private static bool WebSocketHandler(HttpListenerContext ctx) {
case "/ws/dhcp": Protocols.Dhcp.WebSocketHandler(ctx); return true;
case "/ws/telnet": Protocols.Telnet.WebSocketHandler(ctx); return true;
case "/ws/ssh": Protocols.Ssh.WebSocketHandler(ctx); return true;
case "/ws/issues": Workers.Issues.WebSocketHandler(ctx); return true;
case "/ws/reverseproxy": Proxy.ReverseProxy.WebSocketHandler(ctx); return true;
case "/ws/portscan": Tools.PortScan.WebSocketHandler(ctx); return true;
case "/ws/traceroute": Tools.TraceRoute.WebSocketHandler(ctx); return true;
Expand Down
4 changes: 1 addition & 3 deletions Protest/Proxy/ReverseProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,7 @@ public static async void WebSocketHandler(HttpListenerContext ctx) {
}
}
}
catch {
}

catch {}

if (ws?.State == WebSocketState.Open) {
try {
Expand Down
60 changes: 10 additions & 50 deletions Protest/Tools/LiveStats.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,8 @@ public static async void UserStats(HttpListenerContext ctx) {
catch { }
}

if (entry.attributes.TryGetValue("password", out Database.Attribute password)) {
string value = password.value;
if (value.Length > 0 && PasswordStrength.Entropy(value) < 28) {
WsWriteText(ws, "{\"critical\":\"Weak password\",\"source\":\"Internal check\"}"u8.ToArray(), mutex);
}
if (Issues.CheckPasswordStrength(entry, out Issues.Issue? weakPsIssue)) {
WsWriteText(ws, weakPsIssue?.ToJsonBytes(), mutex);
}
}
catch (WebSocketException ex) when (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely) {
Expand Down Expand Up @@ -264,7 +261,6 @@ public static async void DeviceStats(HttpListenerContext ctx) {
}

if (!mismatch && wmiHostname is null && adHostname is null) {

netBios = await NetBios.GetBiosNameAsync(firstAlive, 500);
}

Expand Down Expand Up @@ -299,11 +295,8 @@ public static async void DeviceStats(HttpListenerContext ctx) {
}
}

if (entry.attributes.TryGetValue("password", out Database.Attribute password)) {
string value = password.value;
if (value.Length > 0 && PasswordStrength.Entropy(value) < 28) {
WsWriteText(ws, "{\"critical\":\"Weak password\",\"source\":\"Internal check\"}"u8.ToArray(), mutex);
}
if (Issues.CheckPasswordStrength(entry, out Issues.Issue? weakPsIssue)) {
WsWriteText(ws, weakPsIssue?.ToJsonBytes(), mutex);
}
}
catch (WebSocketException ex) when (ex.WebSocketErrorCode == WebSocketError.ConnectionClosedPrematurely) {
Expand Down Expand Up @@ -347,14 +340,8 @@ private static void WmiQuery(WebSocket ws, object mutex, string firstAlive, ref

WsWriteText(ws, $"{{\"drive\":\"{caption}\",\"total\":{nSize},\"used\":{nSize - nFree},\"path\":\"{Data.EscapeJsonText($"\\\\{firstAlive}\\{caption.Replace(":", String.Empty)}$")}\",\"source\":\"WMI\"}}", mutex);

if (percent <= 1) {
WsWriteText(ws, $"{{\"critical\":\"{percent}% free space on disk {Data.EscapeJsonText(caption)}\",\"source\":\"WMI\"}}", mutex);
}
else if (percent <= 5) {
WsWriteText(ws, $"{{\"error\":\"{percent}% free space on disk {Data.EscapeJsonText(caption)}\",\"source\":\"WMI\"}}", mutex);
}
else if (percent < 15) {
WsWriteText(ws, $"{{\"warning\":\"{percent}% free space on disk {Data.EscapeJsonText(caption)}\",\"source\":\"WMI\"}}", mutex);
if (Issues.CheckDiskCapacity(percent, caption, out Issues.Issue? diskIssue)) {
WsWriteText(ws, diskIssue?.ToJsonBytes(), mutex);
}
}

Expand Down Expand Up @@ -443,39 +430,12 @@ private static void SnmpQuery(WebSocket ws, object mutex, string firstAlive, str
WsWriteText(ws, $"{{\"info\":\"Total jobs: {Data.EscapeJsonText(snmpPrinterJobs)}\",\"source\":\"SNMP\"}}", mutex);
}

Dictionary<string, string> componentName = Protocols.Snmp.Polling.ParseResponse(Protocols.Snmp.Polling.SnmpQuery(ipAddress, profile, new string[] { Protocols.Snmp.Oid.PRINTER_TONERS }, Protocols.Snmp.Polling.SnmpOperation.Walk));
Dictionary<string, string> componentMax = Protocols.Snmp.Polling.ParseResponse(Protocols.Snmp.Polling.SnmpQuery(ipAddress, profile, new string[] { Protocols.Snmp.Oid.PRINTER_TONERS_MAX }, Protocols.Snmp.Polling.SnmpOperation.Walk));
Dictionary<string, string> componentCurrent = Protocols.Snmp.Polling.ParseResponse(Protocols.Snmp.Polling.SnmpQuery(ipAddress, profile, new string[] { Protocols.Snmp.Oid.PRINTER_TONER_CURRENT }, Protocols.Snmp.Polling.SnmpOperation.Walk));

if (componentName is not null && componentCurrent is not null && componentMax is not null &&
componentName.Count == componentCurrent.Count && componentCurrent.Count == componentMax.Count) {

string[][] componentNameArray = componentName.Select(pair=> new string[] { pair.Key, pair.Value }).ToArray();
string[][] componentMaxArray = componentMax.Select(pair=> new string[] { pair.Key, pair.Value }).ToArray();
string[][] componentCurrentArray = componentCurrent.Select(pair=> new string[] { pair.Key, pair.Value }).ToArray();

Array.Sort(componentNameArray, (x, y) => string.Compare(x[0], y[0]));
Array.Sort(componentMaxArray, (x, y) => string.Compare(x[0], y[0]));
Array.Sort(componentCurrentArray, (x, y) => string.Compare(x[0], y[0]));

for (int i = 0; i < componentNameArray.Length; i++) {
if (!int.TryParse(componentMaxArray[i][1], out int max)) { continue; }
if (!int.TryParse(componentCurrentArray[i][1], out int current)) { continue; }

if (current == -2 || max == -2) { continue; } //undefined
if (current == -3) { current = max; } //full

componentNameArray[i][1] = componentNameArray[i][1].TrimStart(' ', '!', '\"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '{', '|', '}', '~');

int used = 100 * current / max;
if (used < 5) {
WsWriteText(ws, $"{{\"error\":\"{used}% {componentNameArray[i][1]}\",\"source\":\"SNMP\"}}", mutex);
}
else if (used < 15) {
WsWriteText(ws, $"{{\"warning\":\"{used}% {componentNameArray[i][1]}\",\"source\":\"SNMP\"}}", mutex);
}
if (Issues.CheckPrinterComponent(ipAddress, profile, out Issues.Issue[] issues)) {
for (int i = 0; i < issues.Length; i++) {
WsWriteText(ws, issues[i].ToJsonBytes(), mutex);
}
}

}
else if (SWITCH_TYPES.Contains(type)) {
//TODO:
Expand Down
157 changes: 156 additions & 1 deletion Protest/Workers/Issues.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,92 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.Linq;
using System.Net;
using System.Net.WebSockets;
using System.Reflection.Emit;
using System.Runtime.Versioning;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Lextm.SharpSnmpLib;
using Lextm.SharpSnmpLib.Security;
using Protest.Http;
using Protest.Protocols;
using Protest.Tools;

namespace Protest.Workers;

internal static class Issues {
private const int WEAK_PASSWORD_ENTROPY_THRESHOLD = 28;

public enum IssueLevel { info, warning, error, critical }

public struct Issue {
public IssueLevel level;
public string message;
public string source;
}

public static byte[] ToJsonBytes(this Issue issue) => JsonSerializer.SerializeToUtf8Bytes(new Dictionary<string, string> {
{ issue.level.ToString(), issue.message },
{ "source", issue.source },
});

public static TaskWrapper task;

private static async Task WsWriteText(WebSocket ws, string data) {
if (ws.State == WebSocketState.Open) {
await ws.SendAsync(new ArraySegment<byte>(Encoding.ASCII.GetBytes(data), 0, data.Length), WebSocketMessageType.Text, true, CancellationToken.None);
}
}
private static async Task WsWriteText(WebSocket ws, byte[] data) {
if (ws.State == WebSocketState.Open) {
await ws.SendAsync(new ArraySegment<byte>(data, 0, data.Length), WebSocketMessageType.Text, true, CancellationToken.None);
}
}

public static async void WebSocketHandler(HttpListenerContext ctx) {
WebSocket ws;
try {
WebSocketContext wsc = await ctx.AcceptWebSocketAsync(null);
ws = wsc.WebSocket;
}
catch (WebSocketException ex) {
ctx.Response.Close();
Logger.Error(ex);
return;
}

if (!Auth.IsAuthenticatedAndAuthorized(ctx, ctx.Request.Url.AbsolutePath)) {
await ws.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
return;
}

try {
while (ws.State == WebSocketState.Open) {
if (!Auth.IsAuthenticatedAndAuthorized(ctx, "/ws/issues")) {
ctx.Response.Close();
return;
}

await WsWriteText(ws, "{\"test\":\"test\"}"u8.ToArray());
await Task.Delay(1000);
}
}
catch {
}

if (ws?.State == WebSocketState.Open) {
try {
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, String.Empty, CancellationToken.None);
}
catch { }
}
}

public static bool StartTask(string origin) {
if (task is not null) return false;

Expand All @@ -37,4 +113,83 @@ public static bool StopTask(string origin) {
private static void Scan() {

}
}

public static bool CheckPasswordStrength(Database.Entry entry, out Issue? issue) {
if (entry.attributes.TryGetValue("password", out Database.Attribute password)) {
string value = password.value;
if (value.Length > 0 && PasswordStrength.Entropy(value) < WEAK_PASSWORD_ENTROPY_THRESHOLD) {
issue = new Issues.Issue { level = Issues.IssueLevel.critical, message = "Weak password", source = "Internal check" };
return true;
}
}

issue = null;
return false;
}

public static bool CheckDiskCapacity(double percent, string diskCaption, out Issue? issue) {
if (percent <= 1) {
issue = new Issues.Issue { level = IssueLevel.critical, message = $"{percent}% free space on disk {Data.EscapeJsonText(diskCaption)}", source = "WMI" };
return true;
}

if (percent <= 5) {
issue = new Issues.Issue { level = IssueLevel.error, message = $"{percent}% free space on disk {Data.EscapeJsonText(diskCaption)}", source = "WMI" };
return true;
}

if (percent < 15) {
issue = new Issues.Issue { level = IssueLevel.warning, message = $"{percent}% free space on disk {Data.EscapeJsonText(diskCaption)}", source = "WMI" };
return true;
}

issue = null;
return false;
}

public static bool CheckPrinterComponent(IPAddress ipAddress, SnmpProfiles.Profile profile, out Issue[] issues) {
Dictionary<string, string> componentName = Protocols.Snmp.Polling.ParseResponse(Protocols.Snmp.Polling.SnmpQuery(ipAddress, profile, new string[] { Protocols.Snmp.Oid.PRINTER_TONERS }, Protocols.Snmp.Polling.SnmpOperation.Walk));
Dictionary<string, string> componentMax = Protocols.Snmp.Polling.ParseResponse(Protocols.Snmp.Polling.SnmpQuery(ipAddress, profile, new string[] { Protocols.Snmp.Oid.PRINTER_TONERS_MAX }, Protocols.Snmp.Polling.SnmpOperation.Walk));
Dictionary<string, string> componentCurrent = Protocols.Snmp.Polling.ParseResponse(Protocols.Snmp.Polling.SnmpQuery(ipAddress, profile, new string[] { Protocols.Snmp.Oid.PRINTER_TONER_CURRENT }, Protocols.Snmp.Polling.SnmpOperation.Walk));

if (componentName is not null && componentCurrent is not null && componentMax is not null &&
componentName.Count == componentCurrent.Count && componentCurrent.Count == componentMax.Count) {

string[][] componentNameArray = componentName.Select(pair=> new string[] { pair.Key, pair.Value }).ToArray();
string[][] componentMaxArray = componentMax.Select(pair=> new string[] { pair.Key, pair.Value }).ToArray();
string[][] componentCurrentArray = componentCurrent.Select(pair=> new string[] { pair.Key, pair.Value }).ToArray();

Array.Sort(componentNameArray, (x, y) => string.Compare(x[0], y[0]));
Array.Sort(componentMaxArray, (x, y) => string.Compare(x[0], y[0]));
Array.Sort(componentCurrentArray, (x, y) => string.Compare(x[0], y[0]));

List<Issue> arrays = new List<Issue>();

for (int i = 0; i < componentNameArray.Length; i++) {
if (!int.TryParse(componentMaxArray[i][1], out int max)) { continue; }
if (!int.TryParse(componentCurrentArray[i][1], out int current)) { continue; }

if (current == -2 || max == -2) { continue; } //undefined
if (current == -3) { current = max; } //full

componentNameArray[i][1] = componentNameArray[i][1].TrimStart(' ', '!', '\"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '{', '|', '}', '~');

int used = 100 * current / max;
if (used < 5) {
arrays.Add(new Issues.Issue { level = IssueLevel.error, message = $"{used}% {componentNameArray[i][1]}", source = "SNMP" });
}
else if (used < 15) {
arrays.Add(new Issues.Issue { level = IssueLevel.warning, message = $"{used}% {componentNameArray[i][1]}", source = "SNMP" });
}
}

if (arrays.Count > 0) {
issues = arrays.ToArray();
return true;
}
}

issues = null;
return false;
}
}

0 comments on commit 4a719a8

Please sign in to comment.