/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.vysper.xmpp.protocol; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.vysper.xmpp.addressing.Entity; import org.apache.vysper.xmpp.modules.extension.xep0077_inbandreg.InBandRegistrationHandler; import org.apache.vysper.xmpp.protocol.exception.TLSException; import org.apache.vysper.xmpp.protocol.worker.AuthenticatedProtocolWorker; import org.apache.vysper.xmpp.protocol.worker.EncryptedProtocolWorker; import org.apache.vysper.xmpp.protocol.worker.EncryptionStartedProtocolWorker; import org.apache.vysper.xmpp.protocol.worker.EndOrClosedProtocolWorker; import org.apache.vysper.xmpp.protocol.worker.InitiatedProtocolWorker; import org.apache.vysper.xmpp.protocol.worker.StartedProtocolWorker; import org.apache.vysper.xmpp.protocol.worker.UnconnectedProtocolWorker; import org.apache.vysper.xmpp.server.ServerRuntimeContext; import org.apache.vysper.xmpp.server.SessionContext; import org.apache.vysper.xmpp.server.SessionState; import org.apache.vysper.xmpp.server.response.ServerErrorResponses; import org.apache.vysper.xmpp.stanza.Stanza; import org.apache.vysper.xmpp.stanza.StanzaBuilder; import org.apache.vysper.xmpp.stanza.StanzaErrorCondition; import org.apache.vysper.xmpp.stanza.StanzaErrorType; import org.apache.vysper.xmpp.stanza.XMPPCoreStanza; import org.apache.vysper.xmpp.writer.DenseStanzaLogRenderer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * responsible for high-level XMPP protocol logic for client-server sessions * determines start, end and jabber conditions. * reads the stream and cuts it into stanzas, * holds state and invokes stanza execution, * separates stream reading from actual execution. * stateless. * * @author The Apache MINA Project (dev@mina.apache.org) */ public class ProtocolWorker implements StanzaProcessor { final Logger logger = LoggerFactory.getLogger(ProtocolWorker.class); private final Map<SessionState, StateAwareProtocolWorker> stateWorker = new HashMap<SessionState, StateAwareProtocolWorker>(); private final ResponseWriter responseWriter = new ResponseWriter(); public ProtocolWorker() { stateWorker.put(SessionState.UNCONNECTED, new UnconnectedProtocolWorker()); stateWorker.put(SessionState.INITIATED, new InitiatedProtocolWorker()); stateWorker.put(SessionState.STARTED, new StartedProtocolWorker()); stateWorker.put(SessionState.ENCRYPTION_STARTED, new EncryptionStartedProtocolWorker()); stateWorker.put(SessionState.ENCRYPTED, new EncryptedProtocolWorker()); stateWorker.put(SessionState.AUTHENTICATED, new AuthenticatedProtocolWorker()); stateWorker.put(SessionState.ENDED, new EndOrClosedProtocolWorker()); stateWorker.put(SessionState.CLOSED, new EndOrClosedProtocolWorker()); } /** * executes the handler for a stanza, handles Protocol exceptions. * also writes a response, if the handler implements ResponseStanzaContainer * @param serverRuntimeContext * @param sessionContext * @param stanza * @param sessionStateHolder */ public void processStanza(ServerRuntimeContext serverRuntimeContext, SessionContext sessionContext, Stanza stanza, SessionStateHolder sessionStateHolder) { if (stanza == null) throw new RuntimeException("cannot process NULL stanzas"); StanzaHandler stanzaHandler = serverRuntimeContext.getHandler(stanza); if (stanzaHandler == null) { responseWriter.handleUnsupportedStanzaType(sessionContext, stanza); return; } if (sessionContext == null && stanzaHandler.isSessionRequired()) { throw new IllegalStateException("handler requires session context"); } StateAwareProtocolWorker stateAwareProtocolWorker = stateWorker.get(sessionContext.getState()); if (stateAwareProtocolWorker == null) { throw new IllegalStateException("no protocol worker for state " + sessionContext.getState().toString()); } // check as of RFC3920/4.3: if (sessionStateHolder.getState() != SessionState.AUTHENTICATED) { // is not authenticated... if (XMPPCoreStanza.getWrapper(stanza) != null && !(stanzaHandler instanceof InBandRegistrationHandler)) { // ... and is a IQ/PRESENCE/MESSAGE stanza! responseWriter.handleNotAuthorized(sessionContext, stanza); return; } } Entity from = stanza.getFrom(); if(sessionContext.isServerToServer()) { XMPPCoreStanza coreStanza = XMPPCoreStanza.getWrapper(stanza); if(coreStanza != null) { // stanza must come from the origin server if(from == null) { Stanza errorStanza = ServerErrorResponses.getStanzaError(StanzaErrorCondition.UNKNOWN_SENDER, coreStanza, StanzaErrorType.MODIFY, "Missing from attribute", null, null); ResponseWriter.writeResponse(sessionContext, errorStanza); return; } else if(!from.getDomain().equals(sessionContext.getInitiatingEntity().getDomain())) { // make sure the from attribute refers to the correct remote server Stanza errorStanza = ServerErrorResponses.getStanzaError(StanzaErrorCondition.UNKNOWN_SENDER, coreStanza, StanzaErrorType.MODIFY, "Incorrect from attribute", null, null); ResponseWriter.writeResponse(sessionContext, errorStanza); return; } Entity to = stanza.getTo(); if(to == null) { // TODO what's the appropriate error? StreamErrorCondition.IMPROPER_ADDRESSING? Stanza errorStanza = ServerErrorResponses.getStanzaError(StanzaErrorCondition.BAD_REQUEST, coreStanza, StanzaErrorType.MODIFY, "Missing to attribute", null, null); ResponseWriter.writeResponse(sessionContext, errorStanza); return; } else if(!to.getDomain().equals(serverRuntimeContext.getServerEnitity().getDomain())) { // TODO what's the appropriate error? StreamErrorCondition.IMPROPER_ADDRESSING? Stanza errorStanza = ServerErrorResponses.getStanzaError(StanzaErrorCondition.BAD_REQUEST, coreStanza, StanzaErrorType.MODIFY, "Invalid to attribute", null, null); ResponseWriter.writeResponse(sessionContext, errorStanza); return; } // rewrite namespace stanza = StanzaBuilder.rewriteNamespace(stanza, NamespaceURIs.JABBER_SERVER, NamespaceURIs.JABBER_CLIENT); } } else { // make sure that 'from' (if present) matches the bare authorized entity // else respond with a stanza error 'unknown-sender' // see rfc3920_draft-saintandre-rfc3920bis-04.txt#8.5.4 if (from != null && sessionContext.getInitiatingEntity() != null) { Entity fromBare = from.getBareJID(); Entity initiatingEntity = sessionContext.getInitiatingEntity(); if (!initiatingEntity.equals(fromBare)) { responseWriter.handleWrongFromJID(sessionContext, stanza); return; } } // make sure that there is a bound resource entry for that from's resource id attribute! if (from != null && from.getResource() != null) { List<String> boundResources = sessionContext.getServerRuntimeContext().getResourceRegistry() .getBoundResources(from, false); if (boundResources.size() == 0) { responseWriter.handleWrongFromJID(sessionContext, stanza); return; } } // make sure that there is a full from entity given in cases where more than one resource is bound // in the same session. // see rfc3920_draft-saintandre-rfc3920bis-04.txt#8.5.4 if (from != null && from.getResource() == null) { List<String> boundResources = sessionContext.getServerRuntimeContext().getResourceRegistry() .getResourcesForSession(sessionContext); if (boundResources.size() > 1) { responseWriter.handleWrongFromJID(sessionContext, stanza); return; } } } try { stateAwareProtocolWorker.processStanza(sessionContext, sessionStateHolder, stanza, stanzaHandler); } catch (Exception e) { logger.error("error executing handler {} with stanza {}", stanzaHandler.getClass().getName(), DenseStanzaLogRenderer.render(stanza)); logger.debug("error executing handler exception: ", e); } } public void processTLSEstablished(SessionContext sessionContext, SessionStateHolder sessionStateHolder) { processTLSEstablishedInternal(sessionContext, sessionStateHolder, responseWriter); } static void processTLSEstablishedInternal(SessionContext sessionContext, SessionStateHolder sessionStateHolder, ResponseWriter responseWriter) { if (sessionContext.getState() != SessionState.ENCRYPTION_STARTED) { responseWriter.handleProtocolError(new TLSException(), sessionContext, null); return; } sessionStateHolder.setState(SessionState.ENCRYPTED); sessionContext.setIsReopeningXMLStream(); } }