com.zextras.modules.chat.server.xmpp.netty.StanzaProcessor.java Source code

Java tutorial

Introduction

Here is the source code for com.zextras.modules.chat.server.xmpp.netty.StanzaProcessor.java

Source

/*
 * ZAL - The abstraction layer for Zimbra.
 * Copyright (C) 2017 ZeXtras S.r.l.
 *
 * This file is part of ZAL.
 *
 * 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, version 2 of
 * the License.
 *
 * 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 ZAL. If not, see <http://www.gnu.org/licenses/>.
 */

package com.zextras.modules.chat.server.xmpp.netty;

import com.zextras.lib.log.ChatLog;
import com.zextras.modules.chat.properties.ChatProperties;
import com.zextras.modules.chat.server.xmpp.XmppEventFilter;
import com.zextras.modules.chat.server.xmpp.XmppFilterOut;
import com.zextras.modules.core.services.NettyService;
import com.zextras.modules.chat.server.events.EventManager;
import com.zextras.modules.chat.server.events.EventQueue;
import com.zextras.modules.chat.server.operations.ProxyAuthentication;
import com.zextras.modules.chat.server.operations.UnregisterSession;
import com.zextras.modules.chat.server.session.SessionManager;
import com.zextras.modules.chat.server.session.SessionUUID;
import com.zextras.modules.chat.server.operations.ChatOperation;
import com.zextras.modules.chat.server.xmpp.AnonymousXmppSession;
import com.zextras.modules.chat.server.xmpp.XmppHandlerFactory;
import com.zextras.modules.chat.server.xmpp.StanzaHandler;
import com.zextras.modules.chat.server.xmpp.XmppSession;
import com.zextras.modules.chat.server.xmpp.encoders.ProxyAuthRequestEncoder;
import com.zextras.modules.chat.server.xmpp.xml.SchemaProvider;
import org.jetbrains.annotations.Nullable;
import org.openzal.zal.Account;
import org.openzal.zal.Utils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.DefaultChannelPromise;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.concurrent.GenericFutureListener;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.List;

public class StanzaProcessor extends ChannelInboundHandlerAdapter {
    private final SchemaProvider mSchemaProvider;
    private final SSLContext mSslContext;
    private final boolean mSsl;
    private final ChatProperties mChatProperties;

    public static class XmppConnectionHandler {
        private final NettyService mNettyService;
        private Channel mSocketChannel;
        private final EventManager mEventManager;
        private final SSLContext mSSLContext;
        private final ChatProperties mChatProperties;
        private final ProxyAuthRequestEncoder mProxyAuthRequestEncoder;
        private final XmppEventFilter mXmppEventFilter;
        private final XmppFilterOut mXmppFilterOut;
        private boolean mNew = true;
        private XmppSession mSession;

        public XmppConnectionHandler(NettyService nettyService, Channel socketChannel, EventManager eventManager,
                SSLContext sslContext, boolean ssl, ChatProperties chatProperties,
                ProxyAuthRequestEncoder proxyAuthRequestEncoder, XmppEventFilter xmppEventFilter,
                XmppFilterOut xmppFilterOut) {
            mNettyService = nettyService;
            mSocketChannel = socketChannel;
            mEventManager = eventManager;
            mSSLContext = sslContext;
            mChatProperties = chatProperties;
            mProxyAuthRequestEncoder = proxyAuthRequestEncoder;
            mXmppEventFilter = xmppEventFilter;
            mXmppFilterOut = xmppFilterOut;
            mSession = new AnonymousXmppSession(SessionUUID.randomUUID(), new EventQueue(), chatProperties,
                    mXmppEventFilter, mXmppFilterOut);
            mSession.setUsingSSL(ssl);
        }

        public XmppSession getSession() {
            return mSession;
        }

        public void setSession(final XmppSession session, final SessionManager sessionManager) {
            mSession = session;
            mSocketChannel.closeFuture().addListener(new GenericFutureListener<ChannelFuture>() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    ChatLog.log.info("Closed XMPP connection of user " + mSession.getExposedAddress().toString());
                    mEventManager.execOperations(Collections.<ChatOperation>singletonList(
                            new UnregisterSession(mSession.getExposedAddress(), mSession.getId())));
                }
            });
        }

        public void close() {
            mSocketChannel.close();
        }

        public ChannelFuture write(ByteBuf stanza) {
            if (mSocketChannel.isOpen()) {
                return mSocketChannel.writeAndFlush(stanza);
            } else {
                ChatLog.log.info("Lost XMPP connection: " + mSocketChannel.toString());
                mSocketChannel.close();
                return new DefaultChannelPromise(mSocketChannel);
            }
        }

        public void startTLS(ByteBuf stanza) {
            mSession.setUsingSSL(true);

            SSLEngine engine = mSSLContext.createSSLEngine();
            engine.setUseClientMode(false);

            mSocketChannel.pipeline().addFirst("ssl", new SslHandler(engine, true));

            write(stanza);
        }

        public void transparentProxySASLAuthForAccount(Account account) {
            transparentProxyForAccount(account, ProxyAuthentication.AuthType.SASL, mSession.isUsingSSL(), null,
                    null);
        }

        public void transparentProxyIQAuthForAccount(Account account, String eventId, String resource) {
            transparentProxyForAccount(account, ProxyAuthentication.AuthType.IQ, mSession.isUsingSSL(), eventId,
                    resource);
        }

        private void transparentProxyForAccount(Account account, ProxyAuthentication.AuthType type,
                boolean isUsingSSL, @Nullable String eventId, @Nullable String resource) {
            mSession.setIsProxy(true);
            mSocketChannel.pipeline().remove("XmlTagTokenizer");
            mSocketChannel.pipeline().remove("SubTagTokenizer");
            mSocketChannel.pipeline().remove("StanzaProcessor");
            new TransparentProxy(mChatProperties, mNettyService, account, mSocketChannel,
                    mProxyAuthRequestEncoder.buildInitialProxyPayload(account, isUsingSSL, type, eventId, resource))
                            .connect();
        }

        public void noLongerNew() {
            mNew = false;
        }

        public boolean isNew() {
            return mNew;
        }
    }

    private final NettyService mNettyService;
    private final ProxyAuthRequestEncoder mProxyAuthRequestEncoder;
    private final XmppEventFilter mXmppEventFilter;
    private final XmppFilterOut mXmppFilterOut;
    private final XmppHandlerFactory mXmppHandlerFactory;
    private final EventManager mEventManager;
    private XmppConnectionHandler mXmppConnectionHandler;

    public StanzaProcessor(XmppHandlerFactory xmppHandlerFactory, EventManager eventManager, Channel socketChannel,
            SchemaProvider schemaProvider, SSLContext sslContext, boolean ssl, ChatProperties chatProperties,
            XmppConnectionHandler xmppConnectionHandler, NettyService nettyService,
            ProxyAuthRequestEncoder proxyAuthRequestEncoder, XmppEventFilter xmppEventFilter,
            XmppFilterOut xmppFilterOut) {
        mXmppHandlerFactory = xmppHandlerFactory;
        mEventManager = eventManager;
        mSchemaProvider = schemaProvider;
        mSslContext = sslContext;
        mSsl = ssl;
        mChatProperties = chatProperties;
        mXmppConnectionHandler = xmppConnectionHandler;
        mNettyService = nettyService;
        mProxyAuthRequestEncoder = proxyAuthRequestEncoder;
        mXmppEventFilter = xmppEventFilter;
        mXmppFilterOut = xmppFilterOut;
    }

    public StanzaProcessor(XmppHandlerFactory xmppHandlerFactory, EventManager eventManager, Channel socketChannel,
            SchemaProvider schemaProvider, SSLContext sslContext, boolean ssl, ChatProperties chatProperties,
            NettyService nettyService, ProxyAuthRequestEncoder proxyAuthRequestEncoder,
            XmppEventFilter xmppEventFilter, XmppFilterOut xmppFilterOut) {
        this(xmppHandlerFactory, eventManager, socketChannel, schemaProvider, sslContext, ssl, chatProperties,
                new XmppConnectionHandler(nettyService, socketChannel, eventManager, sslContext, ssl,
                        chatProperties, proxyAuthRequestEncoder, xmppEventFilter, xmppFilterOut),
                nettyService, proxyAuthRequestEncoder, xmppEventFilter, xmppFilterOut);
    }

    public XmppConnectionHandler getConnectionHandler() {
        return mXmppConnectionHandler;
    }

    public void setChannel(Channel channel) {
        mXmppConnectionHandler = new XmppConnectionHandler(mNettyService, channel, mEventManager, mSslContext, mSsl,
                mChatProperties, mProxyAuthRequestEncoder, mXmppEventFilter, mXmppFilterOut);
    }

    public void resetSession(SessionManager sessionManager) {
        sessionManager.terminateSessionById(mXmppConnectionHandler.getSession().getId());
        mXmppConnectionHandler.setSession(new AnonymousXmppSession(SessionUUID.randomUUID(), new EventQueue(),
                mChatProperties, mXmppEventFilter, mXmppFilterOut), sessionManager);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        String stanza = (String) msg;
        ChatLog.log.debug("StanzaReader: " + stanza.trim().replaceAll("\n", "\\n"));

        ByteArrayInputStream xmlInputStream;
        try {
            xmlInputStream = new ByteArrayInputStream(stanza.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("unsupported utf-8?");
        }

        try {
            StanzaHandler handler = mXmppHandlerFactory.createHandler(mXmppConnectionHandler, stanza);
            handler.parse(xmlInputStream, mSchemaProvider);

            List<ChatOperation> operations = handler.handle();
            //ChatLog.log.debug("operation list: " + ZEUtils.objectToString(operations));
            mEventManager.execOperations(operations, mXmppConnectionHandler.getSession().getOutFilter());
        } catch (XmppHandlerFactory.UnkownStanza ex) {
            ChatLog.log.debug("Unknown stanza (XMPP): " + ex.getStanza());
        } catch (Throwable ex) {
            ChatLog.log.warn("Exception (XMPP): " + stanza + " " + Utils.exceptionToString(ex));
            mXmppConnectionHandler.close();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        ChatLog.log.debug("exceptionCaught: " + Utils.exceptionToString(cause));
        ctx.close();
    }
}