diff --git a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java index 825cdbada..119da4e0f 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java @@ -338,6 +338,12 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { Thread thread = new Thread(runnable); thread.setName("Smack Cached Executor"); thread.setDaemon(true); + thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + LOGGER.log(Level.WARNING, t + " encountered uncaught exception", e); + } + }); return thread; } }); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java b/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java index b87f2ccd2..3a4b80089 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java @@ -25,6 +25,8 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.net.SocketFactory; import javax.net.ssl.HostnameVerifier; @@ -46,6 +48,7 @@ import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.parts.Resourcepart; import org.jxmpp.stringprep.XmppStringprepException; import org.minidns.dnsname.DnsName; +import org.minidns.dnsname.InvalidDnsNameException; import org.minidns.util.InetAddressUtil; /** @@ -81,6 +84,8 @@ public abstract class ConnectionConfiguration { SmackConfiguration.getVersion(); } + private static final Logger LOGGER = Logger.getLogger(ConnectionConfiguration.class.getName()); + /** * The XMPP domain of the XMPP Service. Usually servers use the same service name as the name * of the server. However, there are some servers like google where host would be @@ -88,6 +93,8 @@ public abstract class ConnectionConfiguration { */ protected final DomainBareJid xmppServiceDomain; + protected final DnsName xmppServiceDomainDnsName; + protected final InetAddress hostAddress; protected final DnsName host; protected final int port; @@ -162,6 +169,19 @@ public abstract class ConnectionConfiguration { if (xmppServiceDomain == null) { throw new IllegalArgumentException("Must define the XMPP domain"); } + + DnsName xmppServiceDomainDnsName; + try { + xmppServiceDomainDnsName = DnsName.from(xmppServiceDomain); + } catch (InvalidDnsNameException e) { + LOGGER.log(Level.INFO, + "Could not transform XMPP service domain '" + xmppServiceDomain + + "' to a DNS name. TLS X.509 certificate validiation may not be possible.", + e); + xmppServiceDomainDnsName = null; + } + this.xmppServiceDomainDnsName = xmppServiceDomainDnsName; + hostAddress = builder.hostAddress; host = builder.host; port = builder.port; @@ -225,6 +245,17 @@ public abstract class ConnectionConfiguration { return xmppServiceDomain; } + /** + * Returns the XMPP service domain as DNS name if possible. Note that since not every XMPP address domainpart is a + * valid DNS name, this method may return null. + * + * @return the XMPP service domain as DNS name or null. + * @since 4.3.4 + */ + public DnsName getXmppServiceDomainAsDnsNameIfPossible() { + return xmppServiceDomainDnsName; + } + /** * Returns the TLS security mode used when making the connection. By default, * the mode is {@link SecurityMode#ifpossible}. diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/XHTMLText.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/XHTMLText.java index b08507ad0..2ef66949f 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/XHTMLText.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/XHTMLText.java @@ -105,7 +105,7 @@ public class XHTMLText { private XHTMLText appendOpenBodyTag(String style, String lang) { text.halfOpenElement(Message.BODY); text.xmlnsAttribute(NAMESPACE); - text.optElement(STYLE, style); + text.optAttribute(STYLE, style); text.xmllangAttribute(lang); text.rightAngleBracket(); return this; diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java index 77499c68b..8729be063 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java @@ -124,6 +124,7 @@ import org.jivesoftware.smack.xml.XmlPullParserException; import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.parts.Resourcepart; import org.jxmpp.stringprep.XmppStringprepException; +import org.minidns.dnsname.DnsName; /** * Creates a socket connection to an XMPP server. This is the default connection @@ -710,8 +711,31 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { final HostnameVerifier verifier = getConfiguration().getHostnameVerifier(); if (verifier == null) { throw new IllegalStateException("No HostnameVerifier set. Use connectionConfiguration.setHostnameVerifier() to configure."); - } else if (!verifier.verify(getXMPPServiceDomain().toString(), sslSocket.getSession())) { - throw new CertificateException("Hostname verification of certificate failed. Certificate does not authenticate " + getXMPPServiceDomain()); + } + + final String verifierHostname; + { + DnsName xmppServiceDomainDnsName = getConfiguration().getXmppServiceDomainAsDnsNameIfPossible(); + // Try to convert the XMPP service domain, which potentially includes Unicode characters, into ASCII + // Compatible Encoding (ACE) to match RFC3280 dNSname IA5String constraint. + // See also: https://bugzilla.mozilla.org/show_bug.cgi?id=280839#c1 + if (xmppServiceDomainDnsName != null) { + verifierHostname = xmppServiceDomainDnsName.ace; + } + else { + LOGGER.log(Level.WARNING, "XMPP service domain name '" + getXMPPServiceDomain() + + "' can not be represented as DNS name. TLS X.509 certificate validiation may fail."); + verifierHostname = getXMPPServiceDomain().toString(); + } + } + + final boolean verificationSuccessful; + // Verify the TLS session. + verificationSuccessful = verifier.verify(verifierHostname, sslSocket.getSession()); + if (!verificationSuccessful) { + throw new CertificateException( + "Hostname verification of certificate failed. Certificate does not authenticate " + + getXMPPServiceDomain()); } // Set that TLS was successful