Add BizTalk endpoint reachability monitoring
This commit is contained in:
@@ -0,0 +1,310 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace BizTalkCheckmkPulse
|
||||
{
|
||||
/// <summary>
|
||||
/// Reduziert adapter-spezifische BizTalk-Adressen auf einen geheimnisfreien Host/Port-Test.
|
||||
/// </summary>
|
||||
internal static class EndpointAddressParser
|
||||
{
|
||||
private static readonly Regex HostPortPattern = new Regex(
|
||||
@"^(?<host>\[[0-9a-fA-F:]+\]|[a-zA-Z0-9._-]+):(?<port>\d{1,5})(?:[/\\].*)?$",
|
||||
RegexOptions.CultureInvariant);
|
||||
|
||||
/// <summary>
|
||||
/// Erstellt aus einer aktiven BizTalk-Transportadresse einen TCP-/UDP-Katalogeintrag.
|
||||
/// </summary>
|
||||
public static bool TryCreate(
|
||||
EndpointCandidate candidate,
|
||||
out EndpointCatalogEntry entry,
|
||||
out string reason)
|
||||
{
|
||||
entry = null;
|
||||
reason = string.Empty;
|
||||
if (candidate == null)
|
||||
{
|
||||
reason = "Endpoint-Kandidat fehlt.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (candidate.Dynamic)
|
||||
{
|
||||
reason = "Dynamischer Send Port besitzt kein statisch pruefbares Ziel.";
|
||||
return false;
|
||||
}
|
||||
|
||||
var address = (candidate.Address ?? string.Empty).Trim();
|
||||
if (address.Length == 0)
|
||||
{
|
||||
reason = "Transportadresse ist leer.";
|
||||
return false;
|
||||
}
|
||||
|
||||
string protocol;
|
||||
string host;
|
||||
int port;
|
||||
if (!TryResolve(address, candidate.AdapterName, out protocol, out host, out port, out reason))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
entry = new EndpointCatalogEntry
|
||||
{
|
||||
Key = candidate.Key,
|
||||
ArtifactType = candidate.ArtifactType,
|
||||
ApplicationName = candidate.ApplicationName,
|
||||
ArtifactName = candidate.ArtifactName,
|
||||
TransportRole = candidate.TransportRole,
|
||||
AdapterName = candidate.AdapterName,
|
||||
Protocol = protocol,
|
||||
Host = host,
|
||||
Port = port,
|
||||
Enabled = true,
|
||||
AutoDiscovered = true
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unterscheidet externe, aber nicht automatisch aufloesbare Ziele von bewusst lokalen/variablen Adressen.
|
||||
/// </summary>
|
||||
public static bool IsPotentialExternalEndpoint(EndpointCandidate candidate)
|
||||
{
|
||||
if (candidate == null || candidate.Dynamic)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var address = (candidate.Address ?? string.Empty).Trim();
|
||||
if (address.Length == 0
|
||||
|| LooksLikeEmailRecipients(address)
|
||||
|| Regex.IsMatch(address, @"^[a-zA-Z]:[\\/]", RegexOptions.CultureInvariant)
|
||||
|| address.StartsWith("/", StringComparison.Ordinal)
|
||||
|| address.StartsWith("net.pipe:", StringComparison.OrdinalIgnoreCase)
|
||||
|| address.StartsWith("npipe:", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Contains(candidate.AdapterName, "SMTP")
|
||||
|| Contains(candidate.AdapterName, "FILE") && !address.StartsWith("\\\\", StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (address.StartsWith("\\\\", StringComparison.Ordinal)
|
||||
|| HostPortPattern.IsMatch(address)
|
||||
|| Regex.IsMatch(address, @"(?:FORMATNAME:)?DIRECT=(?:OS|TCP):", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Uri uri;
|
||||
if (Uri.TryCreate(address, UriKind.Absolute, out uri))
|
||||
{
|
||||
return !string.Equals(uri.Scheme, "file", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(uri.Scheme, "mailto", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return Contains(candidate.AdapterName, "SFTP")
|
||||
|| Contains(candidate.AdapterName, "FTP")
|
||||
|| Contains(candidate.AdapterName, "HTTP")
|
||||
|| Contains(candidate.AdapterName, "SOAP")
|
||||
|| Contains(candidate.AdapterName, "WCF");
|
||||
}
|
||||
|
||||
private static bool TryResolve(
|
||||
string address,
|
||||
string adapterName,
|
||||
out string protocol,
|
||||
out string host,
|
||||
out int port,
|
||||
out string reason)
|
||||
{
|
||||
protocol = "TCP";
|
||||
host = string.Empty;
|
||||
port = 0;
|
||||
reason = string.Empty;
|
||||
|
||||
if (LooksLikeEmailRecipients(address))
|
||||
{
|
||||
reason = "SMTP-Empfaengerliste ist kein pruefbarer Netzwerk-Endpunkt.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (address.StartsWith("\\\\", StringComparison.Ordinal))
|
||||
{
|
||||
var end = address.IndexOf('\\', 2);
|
||||
host = end < 0 ? address.Substring(2) : address.Substring(2, end - 2);
|
||||
port = 445;
|
||||
return Validate(host, port, out reason);
|
||||
}
|
||||
|
||||
if (Regex.IsMatch(address, @"^[a-zA-Z]:[\\/]", RegexOptions.CultureInvariant)
|
||||
|| Path.IsPathRooted(address) && !address.StartsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
reason = "Lokaler Dateipfad benoetigt keine externe Netzwerkprobe.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryResolveMsmq(address, out host))
|
||||
{
|
||||
port = 1801;
|
||||
return Validate(host, port, out reason);
|
||||
}
|
||||
|
||||
Uri uri;
|
||||
if (Uri.TryCreate(address, UriKind.Absolute, out uri)
|
||||
&& !string.IsNullOrWhiteSpace(uri.Scheme))
|
||||
{
|
||||
var scheme = uri.Scheme.ToLowerInvariant();
|
||||
if (scheme == "file")
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(uri.Host))
|
||||
{
|
||||
reason = "Lokaler file-Endpunkt benoetigt keine externe Netzwerkprobe.";
|
||||
return false;
|
||||
}
|
||||
|
||||
host = uri.Host;
|
||||
port = 445;
|
||||
return Validate(host, port, out reason);
|
||||
}
|
||||
|
||||
if (scheme == "net.pipe" || scheme == "npipe")
|
||||
{
|
||||
reason = "Named Pipes sind keine TCP-/UDP-Endpunkte.";
|
||||
return false;
|
||||
}
|
||||
|
||||
host = uri.Host;
|
||||
protocol = scheme == "udp" ? "UDP" : "TCP";
|
||||
port = ResolvePort(uri, scheme);
|
||||
if (port <= 0 && HasExplicitPort(address))
|
||||
{
|
||||
port = uri.Port;
|
||||
}
|
||||
|
||||
if (port <= 0)
|
||||
{
|
||||
reason = "Fuer das Schema '" + scheme + "' konnte kein TCP-/UDP-Port bestimmt werden.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return Validate(host, port, out reason);
|
||||
}
|
||||
|
||||
var match = HostPortPattern.Match(address);
|
||||
if (match.Success)
|
||||
{
|
||||
host = match.Groups["host"].Value.Trim('[', ']');
|
||||
if (!int.TryParse(match.Groups["port"].Value, out port))
|
||||
{
|
||||
port = 0;
|
||||
}
|
||||
|
||||
return Validate(host, port, out reason);
|
||||
}
|
||||
|
||||
if (Contains(adapterName, "SFTP"))
|
||||
{
|
||||
host = address.Trim('/');
|
||||
port = 22;
|
||||
return Validate(host, port, out reason);
|
||||
}
|
||||
|
||||
if (Contains(adapterName, "FTP"))
|
||||
{
|
||||
host = address.Trim('/');
|
||||
port = 21;
|
||||
return Validate(host, port, out reason);
|
||||
}
|
||||
|
||||
reason = "Transportadresse enthaelt keinen sicher bestimmbaren Host und Port.";
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int ResolvePort(Uri uri, string scheme)
|
||||
{
|
||||
if (uri.Port > 0 && !uri.IsDefaultPort)
|
||||
{
|
||||
return uri.Port;
|
||||
}
|
||||
|
||||
switch (scheme)
|
||||
{
|
||||
case "http": return 80;
|
||||
case "https": return 443;
|
||||
case "ftp": return 21;
|
||||
case "sftp": return 22;
|
||||
case "smb": return 445;
|
||||
case "smtp": return 25;
|
||||
case "ldap": return 389;
|
||||
case "ldaps": return 636;
|
||||
case "pop3": return 110;
|
||||
case "pop3s": return 995;
|
||||
case "imap": return 143;
|
||||
case "imaps": return 993;
|
||||
case "msmq": return 1801;
|
||||
default: return uri.Port > 0 ? uri.Port : 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasExplicitPort(string address)
|
||||
{
|
||||
var authorityEnd = address.IndexOfAny(new[] { '/', '?' }, address.IndexOf("://", StringComparison.Ordinal) + 3);
|
||||
var authority = authorityEnd < 0 ? address : address.Substring(0, authorityEnd);
|
||||
return Regex.IsMatch(authority, @":\d{1,5}$", RegexOptions.CultureInvariant);
|
||||
}
|
||||
|
||||
private static bool TryResolveMsmq(string address, out string host)
|
||||
{
|
||||
host = string.Empty;
|
||||
var match = Regex.Match(
|
||||
address,
|
||||
@"(?:FORMATNAME:)?DIRECT=(?:OS|TCP):(?<host>[^\\/;]+)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
if (!match.Success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
host = match.Groups["host"].Value.Trim();
|
||||
return host.Length > 0;
|
||||
}
|
||||
|
||||
private static bool LooksLikeEmailRecipients(string value)
|
||||
{
|
||||
return value.IndexOf('@') > 0
|
||||
&& value.IndexOf("://", StringComparison.Ordinal) < 0
|
||||
&& !value.StartsWith("\\\\", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static bool Contains(string value, string fragment)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(value)
|
||||
&& value.IndexOf(fragment, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
private static bool Validate(string host, int port, out string reason)
|
||||
{
|
||||
host = (host ?? string.Empty).Trim();
|
||||
if (host.Length == 0 || host.IndexOfAny(new[] { ' ', '\t', '\r', '\n', '*', '%' }) >= 0)
|
||||
{
|
||||
reason = "Host ist leer oder enthaelt nicht aufgeloeste Platzhalter.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (port < 1 || port > 65535)
|
||||
{
|
||||
reason = "Port liegt ausserhalb des gueltigen Bereichs.";
|
||||
return false;
|
||||
}
|
||||
|
||||
reason = string.Empty;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user