/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.action.xmpp.internal; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.util.Dictionary; import javax.net.ssl.SSLContext; import org.apache.commons.lang.StringUtils; import org.jivesoftware.smack.AbstractConnectionListener; import org.jivesoftware.smack.ChatManager; import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.util.DNSUtil; import org.jivesoftware.smack.util.dns.javax.JavaxResolver; import org.jivesoftware.smackx.muc.MultiUserChat; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.geekplace.javapinning.JavaPinning; /** * This class provides XMPP access. An account can be configured, which is then * used for sending and receiving messages. * * @author Kai Kreuzer * @since 0.4.0 */ public class XMPPConnect implements ManagedService { static { // Workaround for SMACK-635. This can be removed once Smack 4.1 (or higher) is used // See https://igniterealtime.org/issues/browse/SMACK-635 DNSUtil.setDNSResolver(JavaxResolver.getInstance()); } static private final Logger logger = LoggerFactory.getLogger(XMPPConnect.class); private static String servername; private static String proxy; private static Integer port; private static String username; private static String password; private static String chatroom; private static String chatnickname; private static String chatpassword; private static String[] consoleUsers; private static SecurityMode securityMode = SecurityMode.disabled; private static String tlsPin; private static boolean initialized = false; private static XMPPConnection connection; private static MultiUserChat chat; @Override @SuppressWarnings("rawtypes") public void updated(Dictionary config) throws ConfigurationException { if (config == null) { return; } XMPPConnect.servername = (String) config.get("servername"); XMPPConnect.proxy = (String) config.get("proxy"); String portString = (String) config.get("port"); if (portString != null) { XMPPConnect.port = Integer.valueOf(portString); } XMPPConnect.username = (String) config.get("username"); XMPPConnect.password = (String) config.get("password"); XMPPConnect.chatroom = (String) config.get("chatroom"); XMPPConnect.chatnickname = (String) config.get("chatnickname"); XMPPConnect.chatpassword = (String) config.get("chatpassword"); String securityModeString = (String) config.get("securitymode"); if (securityModeString != null) { securityMode = SecurityMode.valueOf(securityModeString); } XMPPConnect.tlsPin = (String) config.get("tlspin"); String users = (String) config.get("consoleusers"); if (!StringUtils.isEmpty(users)) { XMPPConnect.consoleUsers = users.split(","); } else { XMPPConnect.consoleUsers = new String[0]; } // check mandatory settings if (StringUtils.isEmpty(servername)) { return; } if (StringUtils.isEmpty(username)) { return; } if (StringUtils.isEmpty(password)) { return; } // set defaults for optional settings if (port == null) { port = 5222; } if (StringUtils.isEmpty(chatnickname)) { chatnickname = "openhab-bot"; } establishConnection(); } private static void establishConnection() { if (servername == null) { return; } ConnectionConfiguration config; // Create a connection to the jabber server on the given port if (proxy != null) { config = new ConnectionConfiguration(servername, port, proxy); } else { config = new ConnectionConfiguration(servername, port); } config.setSecurityMode(securityMode); if (tlsPin != null) { try { SSLContext sc = JavaPinning.forPin(tlsPin); config.setCustomSSLContext(sc); } catch (KeyManagementException | NoSuchAlgorithmException e) { logger.error("Could not create TLS Pin for XMPP connection", e); } } if (connection != null && connection.isConnected()) { try { connection.disconnect(); } catch (NotConnectedException e) { logger.debug("Already disconnected", e); } } connection = new XMPPTCPConnection(config); try { connection.connect(); connection.login(username, password, null); if (consoleUsers.length > 0) { ChatManager.getInstanceFor(connection).addChatListener(new XMPPConsole(consoleUsers)); connection.addConnectionListener(new XMPPConnectionListener()); } logger.info("Connection to XMPP as '{}' has been established. Is secure/encrypted: {}", connection.getUser(), connection.isSecureConnection()); initialized = true; } catch (Exception e) { logger.error("Could not establish connection to XMPP server '" + servername + ":" + port + "': {}", e.getMessage()); } } private static void joinChat() throws NotInitializedException { if (chatroom == null) { return; } if (!initialized) { establishConnection(); if (!initialized) { throw new NotInitializedException(); } } chat = new MultiUserChat(connection, chatroom); try { if (chatpassword != null) { chat.join(chatnickname, chatpassword); } else { chat.join(chatnickname); } logger.info("Successfuly joined chat '{}' with nickname '{}'.", chatroom, chatnickname); } catch (XMPPException e) { logger.error("Could not join chat '{}' with nickname '{}': {}", chatroom, chatnickname, e.getMessage()); } catch (SmackException e) { logger.error("Could not join chat '{}' with nickname '{}': {}", chatroom, chatnickname, e.getMessage()); } } /** * returns the active connection which can be used to send messages * * @return the XMPP connection * @throws NotInitializedException * if the connection has not been established successfully */ public static XMPPConnection getConnection() throws NotInitializedException { if (!initialized) { establishConnection(); if (!initialized) { throw new NotInitializedException(); } } return connection; } /** * returns the active chat which can be used to send messages to a room * * @return the XMPP connection * @throws NotInitializedException * if the chat has not been successfully joined */ public static MultiUserChat getChat() throws NotInitializedException { if (chat == null) { joinChat(); } if (!chat.isJoined()) { joinChat(); if (!chat.isJoined()) { throw new NotInitializedException(); } } return chat; } private static class XMPPConnectionListener extends AbstractConnectionListener { @Override public void connectionClosed() { logger.debug("XMPP connection has been closed."); initialized = false; } @Override public void connectionClosedOnError(Exception e) { // Log a warning and the *full* exception as the stacktrace could be useful to diagnose // the issue for uncommon exceptions besides e.g. a broken pipe logger.warn("XMPP connection has been closed on error: {}", e); try { if (!connection.isConnected()) { initialized = false; getConnection(); } logger.info("XMPP re-connection succeeded."); } catch (NotInitializedException nie) { logger.error("XMPP re-connection failed, giving up: {}", nie.getMessage()); } } } }