/* * Kontalk Java client * Copyright (C) 2016 Kontalk Devteam <devteam@kontalk.org> * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kontalk.client; import javax.net.ssl.SSLContext; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; import java.io.IOException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.logging.Level; import java.util.logging.Logger; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.ExceptionCallback; import org.jivesoftware.smack.SASLAuthentication; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.StanzaListener; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration.Builder; import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.parts.Resourcepart; import org.jxmpp.stringprep.XmppStringprepException; import org.kontalk.util.TrustUtils; /** * XMPP Connection to a Kontalk Server. * @author Alexander Bikadorov {@literal <bikaejkb@mail.tu-berlin.de>} */ final class KonConnection extends XMPPTCPConnection { private static final Logger LOGGER = Logger.getLogger(KonConnection.class.getName()); private static final String RESSOURCE = "Kontalk_Desktop"; public KonConnection(EndpointServer server, boolean validateCertificate) { this(server, null, null, validateCertificate); } public KonConnection(EndpointServer server, PrivateKey privateKey, X509Certificate bridgeCert, boolean validateCertificate) { super(buildConfiguration(RESSOURCE, server, privateKey, bridgeCert, validateCertificate) ); // blacklist PLAIN mechanism SASLAuthentication.blacklistSASLMechanism("PLAIN"); // enable SM without resumption (XEP-0198) this.setUseStreamManagement(true); this.setUseStreamManagementResumption(false); } private static XMPPTCPConnectionConfiguration buildConfiguration( String resource, EndpointServer server, PrivateKey privateKey, X509Certificate bridgeCert, boolean validateCertificate) { Builder builder = XMPPTCPConnectionConfiguration.builder(); DomainBareJid networkJid = null; Resourcepart resourcePart = null; try { networkJid = JidCreate.domainBareFrom(server.getNetwork()); resourcePart = Resourcepart.from(resource); } catch (XmppStringprepException ex) { LOGGER.log(Level.WARNING, "invalid settings", ex); } builder .setHost(server.getHost()) .setPort(server.getPort()) .setXmppDomain(networkJid) .setResource(resourcePart) .allowEmptyOrNullUsernames() .setCallbackHandler(new CallbackHandler() { @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (Callback cb : callbacks) LOGGER.info("got callback!?: " + cb); } }) // enable compression .setCompressionEnabled(true) // enable encryption .setSecurityMode(SecurityMode.required); // setup SSL SSLContext sslContext = null; if (!validateCertificate) { LOGGER.warning("disabling SSL certificate validation"); } try { sslContext = privateKey == null || bridgeCert == null ? TrustUtils.getCustomSSLContext(validateCertificate) : TrustUtils.getCustomSSLContext(privateKey, bridgeCert, validateCertificate); // Note: SASL EXTERNAL is already enabled in Smack } catch (NoSuchAlgorithmException | KeyStoreException | IOException | CertificateException | UnrecoverableKeyException | KeyManagementException ex) { LOGGER.log(Level.WARNING, "can't setup SSL connection", ex); } if (sslContext != null) builder.setCustomSSLContext(sslContext); return builder.build(); } @Override public void disconnect() { LOGGER.info("disconnecting"); super.disconnect(); } String getServer() { return this.getConfiguration().getXMPPServiceDomain().toString(); } boolean send(Stanza p) { try { super.sendStanza(p); } catch (SmackException.NotConnectedException | InterruptedException ex) { LOGGER.info("can't send packet, not connected."); return false; } LOGGER.config("packet: "+p); return true; } void sendWithCallback(IQ packet, StanzaListener callback) { LOGGER.config("packet: "+packet); try { super.sendIqWithResponseCallback(packet, callback, new ExceptionCallback() { @Override public void processException(Exception ex) { LOGGER.log(Level.WARNING, "exception response", ex); } }); } catch (SmackException.NotConnectedException | InterruptedException ex) { LOGGER.log(Level.WARNING, "not connected", ex); } } }