FileTransferProxy.java 13.4 KB
Newer Older
1
/**
2 3 4
 * $RCSfile$
 * $Revision: 1217 $
 * $Date: 2005-04-11 18:11:06 -0300 (Mon, 11 Apr 2005) $
5
 *
6
 * Copyright (C) 1999-2008 Jive Software. All rights reserved.
7
 *
8 9 10 11 12 13 14 15 16 17 18
 * 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.
19 20
 */

21
package org.jivesoftware.openfire.filetransfer.proxy;
22

23 24 25 26
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
27
import java.util.Collections;
28 29 30
import java.util.Iterator;
import java.util.Map;

31 32
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
33 34 35 36 37 38
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;
39 40
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.container.BasicModule;
41 42 43 44 45
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;
46
import org.jivesoftware.openfire.filetransfer.FileTransferManager;
Gaston Dombiak's avatar
Gaston Dombiak committed
47 48 49
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener;
50 51
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
52
import org.xmpp.forms.DataForm;
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
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 {
68 69 70
	
	private static final Logger Log = LoggerFactory.getLogger(FileTransferProxy.class);

71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
    /**
     * 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;

92 93 94 95 96 97 98
    private String proxyServiceName;

    private IQHandlerInfo info;
    private RoutingTable routingTable;
    private PacketRouter router;
    private String proxyIP;
    private ProxyConnectionManager connectionManager;
99

100
    private InetAddress bindInterface;
101 102 103 104 105


    public FileTransferProxy() {
        super("SOCKS5 file transfer proxy");

106 107
        info = new IQHandlerInfo("query", FileTransferManager.NAMESPACE_BYTESTREAMS);

108
        PropertyEventDispatcher.addListener(new FileTransferPropertyListener());
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
    }

    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)) {
Gaston Dombiak's avatar
Gaston Dombiak committed
124 125 126
            IQ reply = XMPPServer.getInstance().getIQDiscoInfoHandler().handleIQ(packet);
            router.route(reply);
            return true;
127 128
        }
        else if ("http://jabber.org/protocol/disco#items".equals(namespace)) {
Gaston Dombiak's avatar
Gaston Dombiak committed
129 130 131 132
            // a component
            IQ reply = XMPPServer.getInstance().getIQDiscoItemsHandler().handleIQ(packet);
            router.route(reply);
            return true;
133
        }
134
        else if (FileTransferManager.NAMESPACE_BYTESTREAMS.equals(namespace)) {
135 136
            if (packet.getType() == IQ.Type.get) {
                IQ reply = IQ.createResultIQ(packet);
137 138
                Element newChild = reply.setChildElement("query",
                        FileTransferManager.NAMESPACE_BYTESTREAMS);
139
                Element response = newChild.addElement("streamhost");
140 141
                response.addAttribute("jid", getServiceDomain());
                response.addAttribute("host", proxyIP);
142
                response.addAttribute("port", String.valueOf(connectionManager.getProxyPort()));
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
                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;
    }

172 173
    @Override
	public void initialize(XMPPServer server) {
174 175 176 177 178 179 180
        super.initialize(server);

        proxyServiceName = JiveGlobals.getProperty("xmpp.proxy.service", "proxy");
        routingTable = server.getRoutingTable();
        router = server.getPacketRouter();

        // Load the external IP and port information
181 182 183 184 185 186 187 188 189 190 191 192 193
        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);
                }
            }
        }

194 195
        try {
            proxyIP = JiveGlobals.getProperty("xmpp.proxy.externalip",
196 197
                    (bindInterface != null ? bindInterface.getHostAddress()
                            : InetAddress.getLocalHost().getHostAddress()));
198 199 200 201
        }
        catch (UnknownHostException e) {
            Log.error("Couldn't discover local host", e);
        }
202 203
        
        connectionManager = new ProxyConnectionManager(getFileTransferManager(server));
204
    }
205

206 207
    private FileTransferManager getFileTransferManager(XMPPServer server) {
        return server.getFileTransferManager();
208 209
    }

210 211
    @Override
	public void start() {
212 213
        super.start();

214
        if (isEnabled()) {
215
            startProxy();
216 217
        }
        else {
218
            XMPPServer.getInstance().getIQDiscoItemsHandler().removeServerItemsProvider(this);
219
        }
220 221
    }

222
    private void startProxy() {
223
        connectionManager.processConnections(bindInterface, getProxyPort());
Gaston Dombiak's avatar
Gaston Dombiak committed
224
        routingTable.addComponentRoute(getAddress(), this);
225 226 227 228 229
        XMPPServer server = XMPPServer.getInstance();

        server.getIQDiscoItemsHandler().addServerItemsProvider(this);
    }

230 231
    @Override
	public void stop() {
232 233
        super.stop();

234 235
        XMPPServer.getInstance().getIQDiscoItemsHandler()
                .removeComponentItem(getAddress().toString());
Gaston Dombiak's avatar
Gaston Dombiak committed
236
        routingTable.removeComponentRoute(getAddress());
237 238 239
        connectionManager.disable();
    }

240 241
    @Override
	public void destroy() {
242 243 244 245 246
        super.destroy();

        connectionManager.shutdown();
    }

247
    public void enableFileTransferProxy(boolean isEnabled) {
248 249
        JiveGlobals.setProperty(FileTransferProxy.JIVEPROPERTY_PROXY_ENABLED,
                Boolean.toString(isEnabled));
250 251 252
    }

    private void setEnabled(boolean isEnabled) {
253
        if (isEnabled) {
254
            startProxy();
255 256 257 258 259 260
        }
        else {
            stop();
        }
    }

261 262 263 264 265 266 267 268 269 270 271
    /**
     * 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() {
272
        return JiveGlobals.getBooleanProperty(JIVEPROPERTY_PROXY_ENABLED, DEFAULT_IS_PROXY_ENABLED);
273 274
    }

275 276 277 278 279
    /**
     * Sets the port that the proxy operates on. This requires a restart of the file transfer proxy.
     *
     * @param port The port.
     */
280
    public void setProxyPort(int port) {
281
        JiveGlobals.setProperty(JIVEPROPERTY_PORT, Integer.toString(port));
282 283
    }

284 285 286 287 288
    /**
     * Returns the port that the file transfer proxy is opertating on.
     *
     * @return Returns the port that the file transfer proxy is opertating on.
     */
289
    public int getProxyPort() {
290
        return JiveGlobals.getIntProperty(JIVEPROPERTY_PORT, DEFAULT_PORT);
291 292 293 294 295 296 297 298 299 300
    }

    /**
     * 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() {
301
        return proxyServiceName + "." + XMPPServer.getInstance().getServerInfo().getXMPPDomain();
302 303
    }

304
    @Override
305 306 307 308
    public JID getAddress() {
        return new JID(null, getServiceDomain(), null);
    }

309
    @Override
Gaston Dombiak's avatar
Gaston Dombiak committed
310
    public Iterator<DiscoServerItem> getItems() {
311
        if(!isEnabled()) {
312
            return Collections.emptyIterator();
313
        }
314

315 316 317 318
        final DiscoServerItem item = new DiscoServerItem(new JID(
			getServiceDomain()), "Socks 5 Bytestreams Proxy", null, null, this,
			this);
        
319
        return Collections.singleton(item).iterator();
320 321
    }

322
    @Override
323 324 325 326 327 328 329
    public Iterator<Element> getIdentities(String name, String node, JID senderJID) {
        // 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");

330
        return Collections.singleton(identity).iterator();
331 332
    }

333
    @Override
334
    public Iterator<String> getFeatures(String name, String node, JID senderJID) {
335 336
        return Arrays.asList(FileTransferManager.NAMESPACE_BYTESTREAMS,
                "http://jabber.org/protocol/disco#info").iterator();
337 338
    }

339
    @Override
340
    public DataForm getExtendedInfo(String name, String node, JID senderJID) {
341 342 343
        return null;
    }

344
    @Override
345 346 347 348
    public boolean hasInfo(String name, String node, JID senderJID) {
        return true;
    }

349
    @Override
350
    public Iterator<DiscoItem> getItems(String name, String node, JID senderJID) {
351
        // A proxy server has no items
352
        return new ArrayList<DiscoItem>().iterator();
353 354
    }

355
    @Override
356 357 358 359
    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)) {
Gaston Dombiak's avatar
Gaston Dombiak committed
360
                // Do nothing
361 362 363 364 365 366 367 368 369
            }
            else {
                IQ reply = IQ.createResultIQ((IQ) packet);
                reply.setChildElement(((IQ) packet).getChildElement().createCopy());
                reply.setError(PacketError.Condition.feature_not_implemented);
                router.route(reply);
            }
        }
    }
370

371
    private class FileTransferPropertyListener implements PropertyEventListener {
372
        @Override
373 374 375
        public void propertySet(String property, Map params) {
            if(JIVEPROPERTY_PROXY_ENABLED.equalsIgnoreCase(property)) {
                Object value = params.get("value");
376 377
                boolean isEnabled = (value != null ? Boolean.parseBoolean(value.toString()) :
                        DEFAULT_IS_PROXY_ENABLED);
378
                setEnabled(isEnabled);
379 380 381
            }
        }

382
        @Override
383 384
        public void propertyDeleted(String property, Map params) {
            if(JIVEPROPERTY_PROXY_ENABLED.equalsIgnoreCase(property)) {
385
                setEnabled(DEFAULT_IS_PROXY_ENABLED);
386 387 388
            }
        }

389
        @Override
390 391 392
        public void xmlPropertySet(String property, Map params) {
        }

393
        @Override
394 395 396
        public void xmlPropertyDeleted(String property, Map params) {
        }
    }
397
}