Java tutorial
/** * $RCSfile$ * $Revision: 1217 $ * $Date: 2005-04-11 18:11:06 -0300 (Mon, 11 Apr 2005) $ * * Copyright (C) 1999-2008 Jive Software. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.openfire.filetransfer.proxy; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.jivesoftware.openfire.IQHandlerInfo; import org.jivesoftware.openfire.PacketException; import org.jivesoftware.openfire.PacketRouter; import org.jivesoftware.openfire.RoutableChannelHandler; import org.jivesoftware.openfire.RoutingTable; import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.auth.UnauthorizedException; import org.jivesoftware.openfire.container.BasicModule; import org.jivesoftware.openfire.disco.DiscoInfoProvider; import org.jivesoftware.openfire.disco.DiscoItem; import org.jivesoftware.openfire.disco.DiscoItemsProvider; import org.jivesoftware.openfire.disco.DiscoServerItem; import org.jivesoftware.openfire.disco.ServerItemsProvider; import org.jivesoftware.openfire.filetransfer.FileTransferManager; import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.PropertyEventDispatcher; import org.jivesoftware.util.PropertyEventListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xmpp.forms.DataForm; import org.xmpp.packet.IQ; import org.xmpp.packet.JID; import org.xmpp.packet.Packet; import org.xmpp.packet.PacketError; /** * Manages the transfering of files between two remote entities on the jabber network. * This class acts independtly as a Jabber component from the rest of the server, according to * the Jabber <a href="http://www.jabber.org/jeps/jep-0065.html">SOCKS5 bytestreams protocol</a>. * * @author Alexander Wenckus */ public class FileTransferProxy extends BasicModule implements ServerItemsProvider, DiscoInfoProvider, DiscoItemsProvider, RoutableChannelHandler { private static final Logger Log = LoggerFactory.getLogger(FileTransferProxy.class); /** * The JiveProperty relating to whether or not the file treansfer proxy is enabled. */ public static final String JIVEPROPERTY_PROXY_ENABLED = "xmpp.proxy.enabled"; /** * The JiveProperty relating to the port the proxy is operating on. Changing this value requires a restart of the * proxy. */ public static final String JIVEPROPERTY_PORT = "xmpp.proxy.port"; /** * Whether or not the file transfer proxy is enabled by default. */ public static final boolean DEFAULT_IS_PROXY_ENABLED = true; /** * The default port of the file transfer proxy */ public static final int DEFAULT_PORT = 7777; private String proxyServiceName; private IQHandlerInfo info; private RoutingTable routingTable; private PacketRouter router; private String proxyIP; private ProxyConnectionManager connectionManager; private InetAddress bindInterface; public FileTransferProxy() { super("SOCKS5 file transfer proxy"); info = new IQHandlerInfo("query", FileTransferManager.NAMESPACE_BYTESTREAMS); PropertyEventDispatcher.addListener(new FileTransferPropertyListener()); } public boolean handleIQ(IQ packet) throws UnauthorizedException { Element childElement = packet.getChildElement(); String namespace = null; // ignore errors if (packet.getType() == IQ.Type.error) { return true; } if (childElement != null) { namespace = childElement.getNamespaceURI(); } if ("http://jabber.org/protocol/disco#info".equals(namespace)) { IQ reply = XMPPServer.getInstance().getIQDiscoInfoHandler().handleIQ(packet); router.route(reply); return true; } else if ("http://jabber.org/protocol/disco#items".equals(namespace)) { // a component IQ reply = XMPPServer.getInstance().getIQDiscoItemsHandler().handleIQ(packet); router.route(reply); return true; } else if (FileTransferManager.NAMESPACE_BYTESTREAMS.equals(namespace)) { if (packet.getType() == IQ.Type.get) { IQ reply = IQ.createResultIQ(packet); Element newChild = reply.setChildElement("query", FileTransferManager.NAMESPACE_BYTESTREAMS); Element response = newChild.addElement("streamhost"); response.addAttribute("jid", getServiceDomain()); response.addAttribute("host", proxyIP); response.addAttribute("port", String.valueOf(connectionManager.getProxyPort())); router.route(reply); return true; } else if (packet.getType() == IQ.Type.set && childElement != null) { String sid = childElement.attributeValue("sid"); JID from = packet.getFrom(); JID to = new JID(childElement.elementTextTrim("activate")); IQ reply = IQ.createResultIQ(packet); try { connectionManager.activate(from, to, sid); } catch (IllegalArgumentException ie) { Log.error("Error activating connection", ie); reply.setType(IQ.Type.error); reply.setError(new PacketError(PacketError.Condition.not_allowed)); } router.route(reply); return true; } } return false; } public IQHandlerInfo getInfo() { return info; } @Override public void initialize(XMPPServer server) { super.initialize(server); proxyServiceName = JiveGlobals.getProperty("xmpp.proxy.service", "proxy"); routingTable = server.getRoutingTable(); router = server.getPacketRouter(); // Load the external IP and port information String interfaceName = JiveGlobals.getXMLProperty("network.interface"); bindInterface = null; if (interfaceName != null) { if (interfaceName.trim().length() > 0) { try { bindInterface = InetAddress.getByName(interfaceName); } catch (UnknownHostException e) { Log.error("Error binding to network.interface", e); } } } try { proxyIP = JiveGlobals.getProperty("xmpp.proxy.externalip", (bindInterface != null ? bindInterface.getHostAddress() : InetAddress.getLocalHost().getHostAddress())); } catch (UnknownHostException e) { Log.error("Couldn't discover local host", e); } connectionManager = new ProxyConnectionManager(getFileTransferManager(server)); } private FileTransferManager getFileTransferManager(XMPPServer server) { return server.getFileTransferManager(); } @Override public void start() { super.start(); if (isEnabled()) { startProxy(); } else { XMPPServer.getInstance().getIQDiscoItemsHandler().removeServerItemsProvider(this); } } private void startProxy() { connectionManager.processConnections(bindInterface, getProxyPort()); routingTable.addComponentRoute(getAddress(), this); XMPPServer server = XMPPServer.getInstance(); server.getIQDiscoItemsHandler().addServerItemsProvider(this); } @Override public void stop() { super.stop(); XMPPServer.getInstance().getIQDiscoItemsHandler().removeComponentItem(getAddress().toString()); routingTable.removeComponentRoute(getAddress()); connectionManager.disable(); } @Override public void destroy() { super.destroy(); connectionManager.shutdown(); } public void enableFileTransferProxy(boolean isEnabled) { JiveGlobals.setProperty(FileTransferProxy.JIVEPROPERTY_PROXY_ENABLED, Boolean.toString(isEnabled)); } private void setEnabled(boolean isEnabled) { if (isEnabled) { startProxy(); } else { stop(); } } /** * Returns true if the file transfer proxy is currently enabled and false if it is not. * * @return Returns true if the file transfer proxy is currently enabled and false if it is not. */ public boolean isProxyEnabled() { return connectionManager.isRunning() && JiveGlobals.getBooleanProperty(JIVEPROPERTY_PROXY_ENABLED, DEFAULT_IS_PROXY_ENABLED); } private boolean isEnabled() { return JiveGlobals.getBooleanProperty(JIVEPROPERTY_PROXY_ENABLED, DEFAULT_IS_PROXY_ENABLED); } /** * Sets the port that the proxy operates on. This requires a restart of the file transfer proxy. * * @param port The port. */ public void setProxyPort(int port) { JiveGlobals.setProperty(JIVEPROPERTY_PORT, Integer.toString(port)); } /** * Returns the port that the file transfer proxy is opertating on. * * @return Returns the port that the file transfer proxy is opertating on. */ public int getProxyPort() { return JiveGlobals.getIntProperty(JIVEPROPERTY_PORT, DEFAULT_PORT); } /** * Returns the fully-qualifed domain name of this chat service. * The domain is composed by the service name and the * name of the XMPP server where the service is running. * * @return the file transfer server domain (service name + host name). */ public String getServiceDomain() { return proxyServiceName + "." + XMPPServer.getInstance().getServerInfo().getXMPPDomain(); } public JID getAddress() { return new JID(null, getServiceDomain(), null); } public Iterator<DiscoServerItem> getItems() { List<DiscoServerItem> items = new ArrayList<DiscoServerItem>(); if (!isEnabled()) { return items.iterator(); } final DiscoServerItem item = new DiscoServerItem(new JID(getServiceDomain()), "Socks 5 Bytestreams Proxy", null, null, this, this); items.add(item); return items.iterator(); } public Iterator<Element> getIdentities(String name, String node, JID senderJID) { List<Element> identities = new ArrayList<Element>(); // Answer the identity of the proxy Element identity = DocumentHelper.createElement("identity"); identity.addAttribute("category", "proxy"); identity.addAttribute("name", "SOCKS5 Bytestreams Service"); identity.addAttribute("type", "bytestreams"); identities.add(identity); return identities.iterator(); } public Iterator<String> getFeatures(String name, String node, JID senderJID) { return Arrays.asList(FileTransferManager.NAMESPACE_BYTESTREAMS, "http://jabber.org/protocol/disco#info") .iterator(); } public DataForm getExtendedInfo(String name, String node, JID senderJID) { return null; } public boolean hasInfo(String name, String node, JID senderJID) { return true; } public Iterator<DiscoItem> getItems(String name, String node, JID senderJID) { // A proxy server has no items return new ArrayList<DiscoItem>().iterator(); } public void process(Packet packet) throws UnauthorizedException, PacketException { // Check if the packet is a disco request or a packet with namespace iq:register if (packet instanceof IQ) { if (handleIQ((IQ) packet)) { // Do nothing } else { IQ reply = IQ.createResultIQ((IQ) packet); reply.setChildElement(((IQ) packet).getChildElement().createCopy()); reply.setError(PacketError.Condition.feature_not_implemented); router.route(reply); } } } private class FileTransferPropertyListener implements PropertyEventListener { public void propertySet(String property, Map params) { if (JIVEPROPERTY_PROXY_ENABLED.equalsIgnoreCase(property)) { Object value = params.get("value"); boolean isEnabled = (value != null ? Boolean.parseBoolean(value.toString()) : DEFAULT_IS_PROXY_ENABLED); setEnabled(isEnabled); } } public void propertyDeleted(String property, Map params) { if (JIVEPROPERTY_PROXY_ENABLED.equalsIgnoreCase(property)) { setEnabled(DEFAULT_IS_PROXY_ENABLED); } } public void xmlPropertySet(String property, Map params) { } public void xmlPropertyDeleted(String property, Map params) { } } }