Fix ip address parser, domain name check and reconnect (#333)

* bug fix in cert parser: return normalized ipaddress string instead of hex byte array string
* connect with domain cert check,
* fix ip address compares,
* add dialog to connect anyway if cert domain check fails
* fix reconnect case when cert domain check fails
This commit is contained in:
Martin Regen
2018-01-25 08:55:26 -07:00
committed by GitHub
parent 7bf9c79ba6
commit dbef06aa82
8 changed files with 292 additions and 174 deletions
+120 -90
View File
@@ -122,6 +122,7 @@ namespace Opc.Ua.Client
m_handle = template.m_handle;
m_identity = template.m_identity;
m_keepAliveInterval = template.m_keepAliveInterval;
m_checkDomain = template.m_checkDomain;
if (copyEventHandlers)
{
@@ -137,7 +138,9 @@ namespace Opc.Ua.Client
this.AddSubscription(new Subscription(subscription, copyEventHandlers));
}
}
#endregion
#region Private Methods
/// <summary>
/// Initializes the channel.
/// </summary>
@@ -270,6 +273,67 @@ namespace Opc.Ua.Client
m_defaultSubscription.Priority = 255;
m_defaultSubscription.PublishingEnabled = true;
}
private static void CheckCertificateDomain(ConfiguredEndpoint endpoint)
{
bool domainFound = false;
X509Certificate2 serverCertificate = new X509Certificate2(endpoint.Description.ServerCertificate);
// check the certificate domains.
IList<string> domains = Utils.GetDomainsFromCertficate(serverCertificate);
if (domains != null)
{
string hostname;
string dnsHostName = hostname = endpoint.EndpointUrl.DnsSafeHost;
bool isLocalHost = false;
if (endpoint.EndpointUrl.HostNameType == UriHostNameType.Dns)
{
if (dnsHostName.ToLowerInvariant() == "localhost")
{
isLocalHost = true;
}
else
{ // strip domain names from hostname
hostname = dnsHostName.Split('.')[0];
}
}
else
{ // dnsHostname is a IPv4 or IPv6 address
// normalize ip addresses, cert parser returns normalized addresses
hostname = Utils.NormalizedIPAddress(dnsHostName);
if (hostname == "127.0.0.1" || hostname == "::1")
{
isLocalHost = true;
}
}
if (isLocalHost)
{
dnsHostName = Utils.GetFullQualifiedDomainName();
hostname = Utils.GetHostName();
}
for (int ii = 0; ii < domains.Count; ii++)
{
if (String.Compare(hostname, domains[ii], StringComparison.OrdinalIgnoreCase) == 0 ||
String.Compare(dnsHostName, domains[ii], StringComparison.OrdinalIgnoreCase) == 0)
{
domainFound = true;
break;
}
}
}
if (!domainFound)
{
string message = Utils.Format(
"The domain '{0}' is not listed in the server certificate.",
endpoint.EndpointUrl.DnsSafeHost);
throw new ServiceResultException(StatusCodes.BadCertificateHostNameInvalid, message);
}
}
#endregion
#region IDisposable Members
@@ -694,8 +758,7 @@ namespace Opc.Ua.Client
}
#endregion
#region Public Methods
#region Public Static Methods
/// <summary>
/// Creates a new communication session with a server by invoking the CreateSession service
/// </summary>
@@ -813,49 +876,6 @@ namespace Opc.Ua.Client
return session;
}
private static void CheckCertificateDomain(ConfiguredEndpoint endpoint)
{
bool domainFound = false;
if (endpoint.EndpointUrl.HostNameType != UriHostNameType.Dns)
{
// ignore endpoints configured with IPv4 / IPv6 addresses
return;
}
X509Certificate2 serverCertificate = new X509Certificate2(endpoint.Description.ServerCertificate);
// check the certificate domains.
IList<string> domains = Utils.GetDomainsFromCertficate(serverCertificate);
if (domains != null)
{
string [] hostnames = endpoint.EndpointUrl.DnsSafeHost.Split('.');
if (String.Compare(hostnames[0], "localhost", StringComparison.CurrentCultureIgnoreCase)==0)
{
hostnames[0] = Utils.GetHostName();
}
for (int ii = 0; ii < domains.Count; ii++)
{
foreach (string hostname in hostnames)
{
if (String.Compare(hostname, domains[ii], StringComparison.CurrentCultureIgnoreCase) == 0)
{
domainFound = true;
break;
}
}
}
if (!domainFound)
{
throw new ServiceResultException(StatusCodes.BadCertificateHostNameInvalid);
}
}
}
/// <summary>
/// Recreates a session based on a specified template.
@@ -882,7 +902,8 @@ namespace Opc.Ua.Client
template.m_sessionName,
(uint)template.m_sessionTimeout,
template.m_identity,
template.m_preferredLocales);
template.m_preferredLocales,
template.m_checkDomain);
// create the subscriptions.
foreach (Subscription subscription in session.Subscriptions)
@@ -898,7 +919,8 @@ namespace Opc.Ua.Client
return session;
}
#endregion
#region Delegates and Events
/// <summary>
/// Used to handle renews of user identity tokens before reconnect.
/// </summary>
@@ -914,7 +936,8 @@ namespace Opc.Ua.Client
}
private event RenewUserIdentityEventHandler m_RenewUserIdentity;
#endregion
#region Public Methods
/// <summary>
/// Reconnects to the server after a network failure.
/// </summary>
@@ -1938,11 +1961,11 @@ namespace Opc.Ua.Client
/// <param name="checkDomain">If set to <c>true</c> then the domain in the certificate must match the endpoint used.</param>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
public void Open(
string sessionName,
uint sessionTimeout,
string sessionName,
uint sessionTimeout,
IUserIdentity identity,
IList<string> preferredLocales,
bool checkDomain)
bool checkDomain)
{
// check connection state.
lock (SyncRoot)
@@ -1991,7 +2014,6 @@ namespace Opc.Ua.Client
// validate the server certificate.
X509Certificate2 serverCertificate = null;
byte[] certificateData = m_endpoint.Description.ServerCertificate;
if (certificateData != null && certificateData.Length > 0 && requireEncryption)
{
serverCertificate = Utils.ParseCertificateBlob(certificateData);
@@ -2001,6 +2023,8 @@ namespace Opc.Ua.Client
{
CheckCertificateDomain(m_endpoint);
}
// save for reconnect
m_checkDomain = checkDomain;
//X509Certificate2Collection certificateChain = Utils.ParseCertificateChainBlob(certificateData);
//if (certificateChain.Count > 0)
@@ -2291,7 +2315,7 @@ namespace Opc.Ua.Client
{
for (int i = 0; i < certificateResults.Count; i++)
{
Utils.Trace("ActivateSession result[{0}] = {1}", i, certificateResults[i]);
Utils.Trace("ActivateSession result[{0}] = {1}", i, certificateResults[i]);
}
}
@@ -2719,7 +2743,8 @@ namespace Opc.Ua.Client
}
}
}
#endregion
#region Close Methods
/// <summary>
/// Disconnects from the server and frees any network resources.
/// </summary>
@@ -2805,7 +2830,9 @@ namespace Opc.Ua.Client
Dispose();
return result;
}
#endregion
#region Subscription Methods
/// <summary>
/// Adds a subscription to the session.
/// </summary>
@@ -2909,8 +2936,9 @@ namespace Opc.Ua.Client
return true;
}
#endregion
#region Browse Methods
#region Browse Methods
/// <summary>
/// Invokes the Browse service.
/// </summary>
@@ -3056,9 +3084,9 @@ namespace Opc.Ua.Client
return responseHeader;
}
#endregion
#endregion
#region BrowseNext Methods
#region BrowseNext Methods
/// <summary>
/// Invokes the BrowseNext service.
/// </summary>
@@ -3148,8 +3176,9 @@ namespace Opc.Ua.Client
return responseHeader;
}
#endregion
#endregion
#region Call Methods
/// <summary>
/// Calls the specified method and returns the output arguments.
/// </summary>
@@ -3204,9 +3233,9 @@ namespace Opc.Ua.Client
return outputArguments;
}
#endregion
#endregion
#region Protected Methods
#region Protected Methods
/// <summary>
/// Returns the software certificates assigned to the application.
/// </summary>
@@ -3564,9 +3593,9 @@ namespace Opc.Ua.Client
return true;
}
#endregion
#endregion
#region Publish Methods
#region Publish Methods
/// <summary>
/// Sends an additional publish request.
/// </summary>
@@ -3675,7 +3704,7 @@ namespace Opc.Ua.Client
Utils.Trace("Error - Publish call finished. ResultCode={0}; SubscriptionId={1};", code.ToString(), subscriptionId);
}
}
// nothing more to do if session changed.
if (sessionId != SessionId)
{
@@ -4041,9 +4070,9 @@ namespace Opc.Ua.Client
Utils.Trace(e, "Session: Unexpected rrror while raising Notification event.");
}
}
#endregion
#endregion
#region Private Fields
#region Private Fields
private SubscriptionAcknowledgementCollection m_acknowledgementsToSend;
private Dictionary<uint, uint> m_latestAcknowledgementsSent;
private List<Subscription> m_subscriptions;
@@ -4061,6 +4090,7 @@ namespace Opc.Ua.Client
private ConfiguredEndpoint m_endpoint;
private X509Certificate2 m_instanceCertificate;
private X509Certificate2Collection m_instanceCertificateChain;
private bool m_checkDomain;
private List<IUserIdentity> m_identityHistory;
private string m_sessionName;
@@ -4094,16 +4124,16 @@ namespace Opc.Ua.Client
private event PublishErrorEventHandler m_PublishError;
private event EventHandler m_SubscriptionsChanged;
private event EventHandler m_SessionClosing;
#endregion
#endregion
}
#region KeepAliveEventArgs Class
#region KeepAliveEventArgs Class
/// <summary>
/// The event arguments provided when a keep alive response arrives.
/// </summary>
public class KeepAliveEventArgs : EventArgs
{
#region Constructors
#region Constructors
/// <summary>
/// Creates a new instance.
/// </summary>
@@ -4116,9 +4146,9 @@ namespace Opc.Ua.Client
m_currentState = currentState;
m_currentTime = currentTime;
}
#endregion
#endregion
#region Public Properties
#region Public Properties
/// <summary>
/// Gets the status associated with the keep alive operation.
/// </summary>
@@ -4151,29 +4181,29 @@ namespace Opc.Ua.Client
get { return m_cancelKeepAlive; }
set { m_cancelKeepAlive = value; }
}
#endregion
#endregion
#region Private Fields
#region Private Fields
private ServiceResult m_status;
private ServerState m_currentState;
private DateTime m_currentTime;
private bool m_cancelKeepAlive;
#endregion
#endregion
}
/// <summary>
/// The delegate used to receive keep alive notifications.
/// </summary>
public delegate void KeepAliveEventHandler(Session session, KeepAliveEventArgs e);
#endregion
#endregion
#region NotificationEventArgs Class
#region NotificationEventArgs Class
/// <summary>
/// Represents the event arguments provided when a new notification message arrives.
/// </summary>
public class NotificationEventArgs : EventArgs
{
#region Constructors
#region Constructors
/// <summary>
/// Creates a new instance.
/// </summary>
@@ -4186,9 +4216,9 @@ namespace Opc.Ua.Client
m_notificationMessage = notificationMessage;
m_stringTable = stringTable;
}
#endregion
#endregion
#region Public Properties
#region Public Properties
/// <summary>
/// Gets the subscription that the notification applies to.
/// </summary>
@@ -4212,28 +4242,28 @@ namespace Opc.Ua.Client
{
get { return m_stringTable; }
}
#endregion
#endregion
#region Private Fields
#region Private Fields
private Subscription m_subscription;
private NotificationMessage m_notificationMessage;
private IList<string> m_stringTable;
#endregion
#endregion
}
/// <summary>
/// The delegate used to receive publish notifications.
/// </summary>
public delegate void NotificationEventHandler(Session session, NotificationEventArgs e);
#endregion
#endregion
#region PublishErrorEventArgs Class
#region PublishErrorEventArgs Class
/// <summary>
/// Represents the event arguments provided when a publish error occurs.
/// </summary>
public class PublishErrorEventArgs : EventArgs
{
#region Constructors
#region Constructors
/// <summary>
/// Creates a new instance.
/// </summary>
@@ -4251,9 +4281,9 @@ namespace Opc.Ua.Client
m_subscriptionId = subscriptionId;
m_sequenceNumber = sequenceNumber;
}
#endregion
#endregion
#region Public Properties
#region Public Properties
/// <summary>
/// Gets the status associated with the keep alive operation.
/// </summary>
@@ -4277,18 +4307,18 @@ namespace Opc.Ua.Client
{
get { return m_sequenceNumber; }
}
#endregion
#endregion
#region Private Fields
#region Private Fields
private uint m_subscriptionId;
private uint m_sequenceNumber;
private ServiceResult m_status;
#endregion
#endregion
}
/// <summary>
/// The delegate used to receive pubish error notifications.
/// </summary>
public delegate void PublishErrorEventHandler(Session session, PublishErrorEventArgs e);
#endregion
#endregion
}
@@ -28,10 +28,7 @@
* ======================================================================*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Opc.Ua;
namespace Opc.Ua.Client
{
@@ -39,7 +36,7 @@ namespace Opc.Ua.Client
/// Attempts to reconnect to the server.
/// </summary>
public class SessionReconnectHandler : IDisposable
{
{
#region IDisposable Members
/// <summary>
/// Frees any unmanaged resources.
@@ -66,8 +63,8 @@ namespace Opc.Ua.Client
}
}
}
#endregion
#endregion
#region Public Methods
/// <summary>
/// Gets the session managed by the handler.
@@ -164,14 +161,16 @@ namespace Opc.Ua.Client
ServiceResultException sre = exception as ServiceResultException;
// check if the server endpoint could not be reached.
if ((sre != null && (sre.StatusCode == StatusCodes.BadTcpInternalError || sre.StatusCode == StatusCodes.BadCommunicationError)) ||
if ((sre != null &&
(sre.StatusCode == StatusCodes.BadTcpInternalError ||
sre.StatusCode == StatusCodes.BadCommunicationError ||
sre.StatusCode == StatusCodes.BadNotConnected)) ||
exception is System.ServiceModel.EndpointNotFoundException)
{
// check if reconnecting is still an option.
if (m_session.LastKeepAliveTime.AddMilliseconds(m_session.SessionTimeout) > DateTime.UtcNow)
{
Utils.Trace("Calling OnReconnectSession in {0} ms.", m_reconnectPeriod);
return false;
}
}
@@ -193,7 +193,7 @@ namespace Opc.Ua.Sample.Controls
}
catch (Exception exception)
{
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
e.UpdateControl = false;
}
}
@@ -206,7 +206,7 @@ namespace Opc.Ua.Sample.Controls
}
catch (Exception)
{
// GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
// GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
}
}
@@ -349,7 +349,7 @@ namespace Opc.Ua.Sample.Controls
}
catch (Exception exception)
{
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
}
}
@@ -387,7 +387,7 @@ namespace Opc.Ua.Sample.Controls
}
catch (Exception exception)
{
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
}
}
@@ -425,7 +425,7 @@ namespace Opc.Ua.Sample.Controls
}
catch (Exception exception)
{
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
}
}
@@ -440,7 +440,7 @@ namespace Opc.Ua.Sample.Controls
}
catch (Exception exception)
{
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception);
}
}
@@ -457,7 +457,7 @@ namespace Opc.Ua.Sample.Controls
}
catch (Exception exception)
{
Utils.Trace(exception, "Could not register with the LDS");
Utils.Trace(exception, "Could not register with the LDS");
}
}
@@ -27,19 +27,14 @@
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.Threading;
using System.Security.Cryptography.X509Certificates;
using Opc.Ua.Client;
using Opc.Ua.Client.Controls;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows.Forms;
namespace Opc.Ua.Sample.Controls
{
@@ -58,6 +53,7 @@ namespace Opc.Ua.Sample.Controls
private const string m_BrowseCertificates = "<Browse...>";
private static long m_Counter = 0;
private IList<string> m_preferredLocales;
private bool m_checkDomain = true;
#endregion
#region Public Interface
@@ -163,7 +159,7 @@ namespace Opc.Ua.Sample.Controls
Cursor = Cursors.WaitCursor;
ThreadPool.QueueUserWorkItem(Open, new object[] { m_session, SessionNameTB.Text, identity, m_preferredLocales });
ThreadPool.QueueUserWorkItem(Open, new object[] { m_session, SessionNameTB.Text, identity, m_preferredLocales, m_checkDomain });
CancelBTN.Enabled = false;
OkBTN.Enabled = false;
@@ -194,6 +190,35 @@ namespace Opc.Ua.Sample.Controls
{
Cursor = Cursors.Default;
ServiceResultException sre = e as ServiceResultException;
if (sre != null)
{
if (m_checkDomain && sre.StatusCode == StatusCodes.BadCertificateHostNameInvalid)
{
StringBuilder buffer = new StringBuilder();
buffer.AppendFormat(sre.Message);
buffer.AppendFormat("\r\n\r\nRetry without certificate hostname validation?");
DialogResult result = MessageBox.Show(
buffer.ToString(),
"Exception: BadCertificateHostNameInvalid",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning);
if (result == DialogResult.Yes)
{
m_checkDomain = false;
OkBTN.Enabled = true;
OkBTN.PerformClick();
return;
}
DialogResult = DialogResult.OK;
return;
}
}
if (e != null)
{
GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), (Exception)e);
@@ -228,9 +253,10 @@ namespace Opc.Ua.Sample.Controls
string sessionName = ((object[])state)[1] as string;
IUserIdentity identity = ((object[])state)[2] as IUserIdentity;
IList<string> preferredLocales = ((object[])state)[3] as IList<string>;
bool? checkDomain = ((object[])state)[4] as bool?;
// open the session.
session.Open(sessionName, (uint)session.SessionTimeout, identity, preferredLocales);
session.Open(sessionName, (uint)session.SessionTimeout, identity, preferredLocales, checkDomain ?? true);
OpenComplete(null);
}
@@ -54,6 +54,7 @@ namespace Opc.Ua.Sample.Controls
private const string m_BrowseCertificates = "<Browse...>";
private static long m_Counter = 0;
private IList<string> m_preferredLocales;
private bool m_checkDomain = false;
#endregion
#region Public Interface
@@ -171,7 +172,7 @@ namespace Opc.Ua.Sample.Controls
CancelBTN.IsEnabled = false;
OkBTN.IsEnabled = false;
object state = new object[] { m_session, SessionNameTB.Text, identity, m_preferredLocales };
object state = new object[] { m_session, SessionNameTB.Text, identity, m_preferredLocales, m_checkDomain };
await Task.Run(() => m_session = Open(state));
dialogPopup.IsOpen = false;
}
@@ -204,9 +205,10 @@ namespace Opc.Ua.Sample.Controls
string sessionName = ((object[])state)[1] as string;
IUserIdentity identity = ((object[])state)[2] as IUserIdentity;
IList<string> preferredLocales = ((object[])state)[3] as IList<string>;
bool? checkDomain = ((object[])state)[4] as bool?;
// open the session.
session.Open(sessionName, (uint)session.SessionTimeout, identity, preferredLocales);
session.Open(sessionName, (uint)session.SessionTimeout, identity, preferredLocales, checkDomain ?? true);
return session;
}
@@ -16,6 +16,7 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
@@ -91,7 +92,7 @@ namespace Opc.Ua
buffer.Append(m_uris[ii]);
}
for (int ii = 0; ii < m_domainNames.Count; ii++)
for (int ii = 0; ii < m_domainNames.Count; ii++)
{
if (buffer.Length > 0)
{
@@ -208,7 +209,7 @@ namespace Opc.Ua
domainNames.Add(generalName.Name.ToString());
break;
case Org.BouncyCastle.Asn1.X509.GeneralName.IPAddress:
ipAddresses.Add(generalName.Name.ToString());
ipAddresses.Add(IPAddressToString(Org.BouncyCastle.Asn1.DerOctetString.GetInstance(generalName.Name).GetOctets()));
break;
default:
break;
@@ -219,6 +220,27 @@ namespace Opc.Ua
m_ipAddresses = new ReadOnlyList<string>(ipAddresses);
}
/// <summary>
/// Create a normalized IPv4 or IPv6 address from a 4 byte or 16 byte array.
/// </summary>
private string IPAddressToString(byte[] encodedIPAddress)
{
try
{
IPAddress address = new IPAddress(encodedIPAddress);
return address.ToString();
}
catch
{
throw new ServiceResultException(
StatusCodes.BadCertificateInvalid,
"Certificate contains invalid IP address.");
}
}
/// <summary>
/// Parse certificate for alternate name extension.
/// </summary>
private void Parse(byte[] data)
{
if (base.Oid.Value == SubjectAltNameOid ||
@@ -252,6 +274,6 @@ namespace Opc.Ua
private ReadOnlyList<string> m_uris;
private ReadOnlyList<string> m_domainNames;
private ReadOnlyList<string> m_ipAddresses;
#endregion
#endregion
}
}
@@ -23,9 +23,9 @@ namespace Opc.Ua
/// <summary>
/// Loads the configuration section for an application.
/// </summary>
public class ApplicationConfigurationSection
{
#region IConfigurationSectionHandler Members
public class ApplicationConfigurationSection
{
#region IConfigurationSectionHandler Members
/// <summary>
/// Creates the configuration object from the configuration section.
/// </summary>
@@ -33,21 +33,21 @@ namespace Opc.Ua
/// <param name="configContext">The configuration context object.</param>
/// <param name="section">The section as XML node.</param>
/// <returns>The created section handler object.</returns>
public object Create(object parent, object configContext, System.Xml.XmlNode section)
{
public object Create(object parent, object configContext, System.Xml.XmlNode section)
{
if (section == null)
{
throw new ArgumentNullException("section");
}
XmlNode element = section.FirstChild;
XmlNode element = section.FirstChild;
while (element != null && typeof(XmlElement) != element.GetType())
{
element = element.NextSibling;
}
XmlReader reader = XmlReader.Create(new StringReader(element.OuterXml));
while (element != null && typeof(XmlElement) != element.GetType())
{
element = element.NextSibling;
}
XmlReader reader = XmlReader.Create(new StringReader(element.OuterXml));
try
{
@@ -59,54 +59,54 @@ namespace Opc.Ua
{
reader.Dispose();
}
}
#endregion
}
}
#endregion
}
/// <summary>
/// Represents the location of a configuration file.
/// </summary>
[DataContract(Namespace = Namespaces.OpcUaConfig)]
public class ConfigurationLocation
{
#region Persistent Properties
#region Persistent Properties
/// <summary>
/// Gets or sets the relative or absolute path to the configuration file.
/// </summary>
/// <value>The file path.</value>
[DataMember(IsRequired=true, Order=0)]
[DataMember(IsRequired = true, Order = 0)]
public string FilePath
{
get { return m_filePath; }
set { m_filePath = value; }
}
#endregion
#endregion
#region Private Fields
#region Private Fields
private string m_filePath;
#endregion
#endregion
}
/// <summary>
/// Stores the configurable configuration information for a UA application.
/// </summary>
public partial class ApplicationConfiguration
{
#region Public Methods
{
#region Public Methods
/// <summary>
/// Gets the file that was used to load the configuration.
/// </summary>
/// <value>The source file path.</value>
public string SourceFilePath
{
get { return m_sourceFilePath; }
get { return m_sourceFilePath; }
}
public CertificateValidator CertificateValidator
{
get { return m_certificateValidator; }
get { return m_certificateValidator; }
set { m_certificateValidator = value; }
}
/// <summary>
/// Returns the domain names which the server is configured to use.
/// </summary>
@@ -154,9 +154,13 @@ namespace Opc.Ua
string domainName = url.DnsSafeHost;
if (String.Compare(domainName, "localhost", StringComparison.OrdinalIgnoreCase) == 0)
if (url.HostNameType == UriHostNameType.Dns)
{
domainName = Utils.GetHostName();
domainName = Utils.ReplaceLocalhost(domainName);
}
else // IPv4/IPv6 address
{
domainName = Utils.NormalizedIPAddress(domainName);
}
if (!Utils.FindStringIgnoreCase(domainNames, domainName))
@@ -167,7 +171,7 @@ namespace Opc.Ua
return domainNames;
}
/// <summary>
/// Creates the message context from the configuration.
/// </summary>
@@ -198,7 +202,7 @@ namespace Opc.Ua
[Obsolete("Warning: Behavoir changed return a copy instead of a reference. Should call CreateMessageContext() instead.")]
public ServiceMessageContext MessageContext
{
get
get
{
if (m_messageContext == null)
{
@@ -230,7 +234,7 @@ namespace Opc.Ua
public static async Task<ApplicationConfiguration> Load(string sectionName, ApplicationType applicationType, Type systemType)
{
string filePath = GetFilePathFromAppConfig(sectionName);
FileInfo file = new FileInfo(filePath);
if (!file.Exists)
@@ -244,7 +248,7 @@ namespace Opc.Ua
return await Load(file, applicationType, systemType);
}
/// <summary>
/// Loads but does not validate the application configuration from a configuration section.
/// </summary>
@@ -376,7 +380,7 @@ namespace Opc.Ua
/// <param name="filePath">The file path.</param>
/// <remarks>Calls GetType() on the current instance and passes that to the DataContractSerializer.</remarks>
public void SaveToFile(string filePath)
{
{
Stream ostrm = File.Open(filePath, FileMode.Create, FileAccess.ReadWrite);
XmlWriterSettings settings = new XmlWriterSettings();
@@ -504,7 +508,7 @@ namespace Opc.Ua
if (filePath == null)
{
filePath = m_clientConfiguration.EndpointCacheFilePath;
if (!Utils.IsPathRooted(filePath))
{
FileInfo sourceFile = new FileInfo(this.SourceFilePath);
@@ -584,16 +588,16 @@ namespace Opc.Ua
public void UpdateExtension(Type type, object value)
{
}
#endregion
#endregion
}
#region TraceConfiguration Class
#region TraceConfiguration Class
/// <summary>
/// Specifies parameters used for tracing.
/// </summary>
public partial class TraceConfiguration
{
#region Public Methods
#region Public Methods
/// <summary>
/// Applies the trace settings to the current process.
/// </summary>
@@ -611,17 +615,17 @@ namespace Opc.Ua
Utils.SetTraceOutput(Utils.TraceOutput.DebugAndFile);
}
}
#endregion
#endregion
}
#endregion
#endregion
#region ServerBaseConfiguration Class
#region ServerBaseConfiguration Class
/// <summary>
/// Specifies the configuration for a server application.
/// </summary>
public partial class ServerBaseConfiguration
{
#region Public Methods
#region Public Methods
/// <summary>
/// Validates the configuration.
/// </summary>
@@ -632,17 +636,17 @@ namespace Opc.Ua
m_securityPolicies.Add(new ServerSecurityPolicy());
}
}
#endregion
#endregion
}
#endregion
#endregion
#region ServerConfiguration Class
#region ServerConfiguration Class
/// <summary>
/// Specifies the configuration for a server application.
/// </summary>
public partial class ServerConfiguration : ServerBaseConfiguration
{
#region Public Methods
#region Public Methods
/// <summary>
/// Validates the configuration.
/// </summary>
@@ -655,17 +659,17 @@ namespace Opc.Ua
m_userTokenPolicies.Add(new UserTokenPolicy());
}
}
#endregion
#endregion
}
#endregion
#endregion
#region ClientConfiguration Class
#region ClientConfiguration Class
/// <summary>
/// The configuration for a client application.
/// </summary>
public partial class ClientConfiguration
{
#region Public Methods
#region Public Methods
/// <summary>
/// Validates the configuration.
/// </summary>
@@ -676,7 +680,7 @@ namespace Opc.Ua
WellKnownDiscoveryUrls.AddRange(Utils.DiscoveryUrls);
}
}
#endregion
#endregion
}
#endregion
#endregion
}
+35
View File
@@ -920,6 +920,41 @@ namespace Opc.Ua
return Dns.GetHostName().Split('.')[0].ToLowerInvariant();
}
public static string GetFullQualifiedDomainName()
{
string domainName = null;
try
{
#if !NETSTANDARD1_4 && !NETSTANDARD1_3
domainName = Dns.GetHostEntry("localhost").HostName;
#endif
}
catch
{
}
if (String.IsNullOrEmpty(domainName))
{
return Dns.GetHostName();
}
return domainName;
}
/// <summary>
/// Normalize ipv4/ipv6 address for comparisons.
/// </summary>
public static string NormalizedIPAddress(string ipAddress)
{
try
{
IPAddress normalizedAddress = IPAddress.Parse(ipAddress);
return normalizedAddress.ToString();
}
catch
{
return ipAddress;
}
}
/// <summary>
/// Replaces the localhost domain with the current host name.
/// </summary>