Search in sources :

Example 36 with PacketError

use of org.xmpp.packet.PacketError in project Openfire by igniterealtime.

the class ServerDialback method validateRemoteDomain.

/**
 * Returns true if the domain requested by the remote server was validated by the Authoritative
 * Server. To validate the domain a new TCP connection will be established to the
 * Authoritative Server. The Authoritative Server may be the same Originating Server or
 * some other machine in the Originating Server's network.<p>
 *
 * If the domain was not valid or some error occurred while validating the domain then the
 * underlying TCP connection may be closed.
 *
 * @param doc the request for validating the new domain.
 * @param streamID the stream id generated by this server for the Originating Server.
 * @return true if the requested domain is valid.
 */
public boolean validateRemoteDomain(Element doc, StreamID streamID) {
    StringBuilder sb;
    String recipient = doc.attributeValue("to");
    String remoteDomain = doc.attributeValue("from");
    final Logger log = LoggerFactory.getLogger(Log.getName() + "[Acting as Receiving Server: Validate domain: " + recipient + "(id " + streamID + ") for OS: " + remoteDomain + "]");
    log.debug("Validating domain...");
    if (connection.getTlsPolicy() == Connection.TLSPolicy.required && !connection.isSecure()) {
        connection.deliverRawText(new StreamError(StreamError.Condition.policy_violation).toXML());
        // Close the underlying connection
        connection.close();
        return false;
    }
    if (!RemoteServerManager.canAccess(remoteDomain)) {
        connection.deliverRawText(new StreamError(StreamError.Condition.policy_violation).toXML());
        // Close the underlying connection
        connection.close();
        log.debug("Unable to validate domain: Remote domain is not allowed to establish a connection to this server.");
        return false;
    } else if (isHostUnknown(recipient)) {
        dialbackError(recipient, remoteDomain, new PacketError(PacketError.Condition.item_not_found, PacketError.Type.cancel, "Service not hosted here"));
        log.debug("Unable to validate domain: recipient not recognized as a local domain.");
        return false;
    } else {
        log.debug("Check if the remote domain already has a connection to the target domain/subdomain");
        boolean alreadyExists = false;
        for (IncomingServerSession session : sessionManager.getIncomingServerSessions(remoteDomain)) {
            if (recipient.equals(session.getLocalDomain())) {
                alreadyExists = true;
            }
        }
        if (alreadyExists && !sessionManager.isMultipleServerConnectionsAllowed()) {
            dialbackError(recipient, remoteDomain, new PacketError(PacketError.Condition.resource_constraint, PacketError.Type.cancel, "Incoming session already exists"));
            log.debug("Unable to validate domain: An incoming connection already exists from this remote domain, and multiple connections are not allowed.");
            return false;
        } else {
            log.debug("Checking to see if the remote server provides stronger authentication based on SASL. If that's the case, dialback-based authentication can be skipped.");
            if (SASLAuthentication.verifyCertificates(connection.getPeerCertificates(), remoteDomain, true)) {
                log.debug("Host authenticated based on SASL. Weaker dialback-based authentication is skipped.");
                sb = new StringBuilder();
                sb.append("<db:result");
                sb.append(" from=\"").append(recipient).append("\"");
                sb.append(" to=\"").append(remoteDomain).append("\"");
                sb.append(" type=\"valid\"");
                sb.append("/>");
                connection.deliverRawText(sb.toString());
                log.debug("Domain validated successfully!");
                return true;
            }
            log.debug("Unable to authenticate host based on stronger SASL. Proceeding with dialback...");
            String key = doc.getTextTrim();
            final Map.Entry<Socket, Boolean> socketToXmppDomain = SocketUtil.createSocketToXmppDomain(remoteDomain, RemoteServerManager.getPortForServer(remoteDomain));
            if (socketToXmppDomain == null) {
                log.debug("Unable to validate domain: No server available for verifying key of remote server.");
                dialbackError(recipient, remoteDomain, new PacketError(PacketError.Condition.remote_server_not_found, PacketError.Type.cancel, "Unable to connect to authoritative server"));
                return false;
            }
            Socket socket = socketToXmppDomain.getKey();
            boolean directTLS = socketToXmppDomain.getValue();
            VerifyResult result;
            try {
                log.debug("Verifying dialback key...");
                final SocketAddress socketAddress = socket.getRemoteSocketAddress();
                log.debug("Opening a new connection to {} {}.", socketAddress, directTLS ? "using directTLS" : "that is initially not encrypted");
                try {
                    result = verifyKey(key, streamID, recipient, remoteDomain, socket, directTLS, directTLS);
                } catch (SSLHandshakeException e) {
                    result = VerifyResult.error;
                    log.debug("Verification of dialback key failed due to TLS failure.", e);
                    // The receiving entity is expected to close the socket *without* sending any more data (<failure/> nor </stream>).
                    // It is probably (see OF-794) best if we, as the initiating entity, therefor don't send any data either.
                    final SocketAddress oldAddress = socket.getRemoteSocketAddress();
                    socket.close();
                    if (!directTLS) {
                        log.debug("Retry without StartTLS... Re-opening socket (with the same remote peer)...");
                        // Retry, without TLS.
                        socket = new Socket();
                        socket.connect(oldAddress, RemoteServerManager.getSocketTimeout());
                        log.debug("Successfully re-opened socket! Try to validate dialback key again (without TLS this time)...");
                        result = verifyKey(key, streamID, recipient, remoteDomain, socket, true, directTLS);
                    }
                } catch (SSLException ex) {
                    if (JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ON_PLAIN_DETECTION_ALLOW_NONDIRECTTLS_FALLBACK, true) && ex.getMessage().contains("plaintext connection?")) {
                        Log.warn("Plaintext detected on a new connection that is was started in DirectTLS mode (socket address: {}). Attempting to restart the connection in non-DirectTLS mode.", socketAddress);
                        try {
                            // Close old socket
                            socket.close();
                        } catch (Exception e) {
                            Log.debug("An exception occurred (and is ignored) while trying to close a socket that was already in an error state.", e);
                        }
                        socket = new Socket();
                        socket.connect(socketAddress, RemoteServerManager.getSocketTimeout());
                        result = verifyKey(key, streamID, recipient, remoteDomain, socket, false, false);
                        // No error this time? directTLS apparently is 'false'. Change it's value for future usage (if any)
                        directTLS = false;
                        Log.info("Re-established connection to {}. Proceeding without directTLS.", socketAddress);
                    } else {
                        // Do not retry as non-DirectTLS, rethrow the exception.
                        throw ex;
                    }
                }
                switch(result) {
                    case valid:
                    case invalid:
                        boolean valid = (result == VerifyResult.valid);
                        log.debug("Dialback key is " + (valid ? "valid" : "invalid") + ". Sending verification result to remote domain.");
                        sb = new StringBuilder();
                        sb.append("<db:result");
                        sb.append(" from=\"").append(recipient).append("\"");
                        sb.append(" to=\"").append(remoteDomain).append("\"");
                        sb.append(" type=\"");
                        sb.append(valid ? "valid" : "invalid");
                        sb.append("\"/>");
                        connection.deliverRawText(sb.toString());
                        if (!valid) {
                            log.debug("Close the underlying connection as key verification failed.");
                            connection.close();
                            log.debug("Unable to validate domain: dialback key is invalid.");
                            return false;
                        } else {
                            log.debug("Successfully validated domain!");
                            return true;
                        }
                    default:
                        break;
                }
                log.debug("Unable to validate domain: key verification did not complete (the AS likely returned an error or a time out occurred).");
                dialbackError(recipient, remoteDomain, new PacketError(PacketError.Condition.remote_server_timeout, PacketError.Type.cancel, "Authoritative server returned error"));
                return false;
            } catch (Exception e) {
                dialbackError(recipient, remoteDomain, new PacketError(PacketError.Condition.remote_server_timeout, PacketError.Type.cancel, "Authoritative server failed"));
                log.warn("Unable to validate domain: An exception occurred while verifying the dialback key.", e);
                return false;
            }
        }
    }
}
Also used : PacketError(org.xmpp.packet.PacketError) Logger(org.slf4j.Logger) SSLException(javax.net.ssl.SSLException) SSLHandshakeException(javax.net.ssl.SSLHandshakeException) DocumentException(org.dom4j.DocumentException) SSLHandshakeException(javax.net.ssl.SSLHandshakeException) XmlPullParserException(org.xmlpull.v1.XmlPullParserException) SSLException(javax.net.ssl.SSLException) StreamError(org.xmpp.packet.StreamError) SocketAddress(java.net.SocketAddress) Socket(java.net.Socket)

Example 37 with PacketError

use of org.xmpp.packet.PacketError in project Openfire by igniterealtime.

the class IQRosterHandler method manageRoster.

/**
 * The packet is a typical 'set' or 'get' update targeted at the server.
 * Notice that the set could be a roster removal in which case we have to
 * generate a local roster removal update as well as a new roster removal
 * to send to the the roster item's owner.
 *
 * @param packet The packet that triggered this update
 * @return Either a response to the roster update or null if the packet is corrupt and the session was closed down
 */
private IQ manageRoster(org.xmpp.packet.Roster packet) throws UnauthorizedException, UserAlreadyExistsException, SharedGroupException {
    IQ returnPacket = null;
    JID sender = packet.getFrom();
    IQ.Type type = packet.getType();
    try {
        if ((sender.getNode() == null || !RosterManager.isRosterServiceEnabled() || !userManager.isRegisteredUser(sender, false)) && IQ.Type.get == type) {
            // If anonymous user asks for his roster or roster service is disabled then
            // return an empty roster
            IQ reply = IQ.createResultIQ(packet);
            reply.setChildElement("query", "jabber:iq:roster");
            return reply;
        }
        if (!localServer.isLocal(sender)) {
            // Sender belongs to a remote server so discard this IQ request
            Log.warn("Discarding IQ roster packet of remote user: " + packet);
            return null;
        }
        Roster cachedRoster = userManager.getUser(sender.getNode()).getRoster();
        if (IQ.Type.get == type) {
            if (RosterManager.isRosterVersioningEnabled()) {
                String clientVersion = packet.getChildElement().attributeValue("ver");
                String latestVersion = String.valueOf(cachedRoster.hashCode());
                // Whether or not the roster has been modified since the version ID enumerated by the client, ...
                if (!latestVersion.equals(clientVersion)) {
                    // ... the server MUST either return the complete roster
                    // (including a 'ver' attribute that signals the latest version)
                    returnPacket = cachedRoster.getReset();
                    returnPacket.getChildElement().addAttribute("ver", latestVersion);
                } else {
                    // ... or return an empty IQ-result
                    returnPacket = new org.xmpp.packet.IQ();
                }
            } else {
                returnPacket = cachedRoster.getReset();
            }
            returnPacket.setType(IQ.Type.result);
            returnPacket.setTo(sender);
            returnPacket.setID(packet.getID());
            // Force delivery of the response because we need to trigger
            // a presence probe from all contacts
            deliverer.deliver(returnPacket);
            returnPacket = null;
        } else if (IQ.Type.set == type) {
            returnPacket = IQ.createResultIQ(packet);
            // The <query/> element contains more than one <item/> child element.
            if (packet.getItems().size() > 1) {
                returnPacket.setError(new PacketError(PacketError.Condition.bad_request, PacketError.Type.modify, "Query contains more than one item"));
            } else {
                for (org.xmpp.packet.Roster.Item item : packet.getItems()) {
                    if (item.getSubscription() == org.xmpp.packet.Roster.Subscription.remove) {
                        if (removeItem(cachedRoster, packet.getFrom(), item) == null) {
                            // RFC 6121 2.5.3.  Error Cases: If the value of the 'jid' attribute specifies an item that is not in the roster, then the server MUST return an <item-not-found/> stanza error.
                            returnPacket.setError(PacketError.Condition.item_not_found);
                        }
                    } else {
                        PacketError error = checkGroups(item.getGroups());
                        if (error != null) {
                            returnPacket.setError(error);
                        } else {
                            if (cachedRoster.isRosterItem(item.getJID())) {
                                // existing item
                                RosterItem cachedItem = cachedRoster.getRosterItem(item.getJID());
                                cachedItem.setAsCopyOf(item);
                                cachedRoster.updateRosterItem(cachedItem);
                            } else {
                                // new item
                                cachedRoster.createRosterItem(item);
                            }
                        }
                    }
                }
            }
        }
    } catch (UserNotFoundException e) {
        throw new UnauthorizedException(e);
    }
    return returnPacket;
}
Also used : UserNotFoundException(org.jivesoftware.openfire.user.UserNotFoundException) JID(org.xmpp.packet.JID) IQ(org.xmpp.packet.IQ) PacketError(org.xmpp.packet.PacketError) RosterItem(org.jivesoftware.openfire.roster.RosterItem) RosterItem(org.jivesoftware.openfire.roster.RosterItem) Roster(org.jivesoftware.openfire.roster.Roster) UnauthorizedException(org.jivesoftware.openfire.auth.UnauthorizedException) IQ(org.xmpp.packet.IQ)

Example 38 with PacketError

use of org.xmpp.packet.PacketError in project Openfire by igniterealtime.

the class UserManagerTest method isRegisteredUserTrueWillReturnFalseForUnknownRemoteUsers.

@Test
public void isRegisteredUserTrueWillReturnFalseForUnknownRemoteUsers() {
    final AtomicReference<IQResultListener> iqListener = new AtomicReference<>();
    doAnswer(invocationOnMock -> {
        final IQResultListener listener = invocationOnMock.getArgument(1);
        iqListener.set(listener);
        return null;
    }).when(iqRouter).addIQResultListener(any(), any(), anyLong());
    doAnswer(invocationOnMock -> {
        final IQ iq = invocationOnMock.getArgument(0);
        final Element childElement = iq.getChildElement();
        final IQ response = IQ.createResultIQ(iq);
        response.setChildElement(childElement.createCopy());
        response.setError(new PacketError(PacketError.Condition.item_not_found, PacketError.Condition.item_not_found.getDefaultType()));
        iqListener.get().receivedAnswer(response);
        return null;
    }).when(iqRouter).route(any());
    final boolean result = userManager.isRegisteredUser(new JID(USER_ID, REMOTE_XMPP_DOMAIN, null), true);
    assertThat(result, is(false));
    verify(iqRouter).route(any());
}
Also used : IQResultListener(org.xmpp.component.IQResultListener) JID(org.xmpp.packet.JID) Element(org.dom4j.Element) IQ(org.xmpp.packet.IQ) PacketError(org.xmpp.packet.PacketError) AtomicReference(java.util.concurrent.atomic.AtomicReference) Test(org.junit.Test)

Example 39 with PacketError

use of org.xmpp.packet.PacketError in project Openfire by igniterealtime.

the class MultiUserChatServiceImpl method isDeliveryRelatedErrorResponse.

/**
 * Determines if a stanza is sent by (on behalf of) an entity that MUC room believes to be an occupant
 * when they've left: a 'ghost' occupant
 *
 * For message and presence stanzas, the delivery errors as defined in section 18.1.2 of XEP-0045 are used.
 *
 * For IQ stanzas, these errors may be due to lack of client support rather than a vanished occupant. Therefore,
 * IQ stanzas will only return 'true' when they are an error response to an IQ Ping request sent by this server.
 *
 * @param stanza The stanza to check
 * @return true if the stanza is a delivery related error response (from a 'ghost user'), otherwise false.
 * @see <a href="https://xmpp.org/extensions/xep-0045.html#impl-service-ghosts">XEP-0045, section 'ghost users'</a>
 */
public boolean isDeliveryRelatedErrorResponse(@Nonnull final Packet stanza) {
    if (stanza instanceof IQ) {
        final IQ iq = ((IQ) stanza);
        if (iq.getType() != IQ.Type.error) {
            return false;
        }
        // Check if this is an error to a ghost-detection ping that we've sent out. Note that clients that are
        // connected but do not support XEP-0199 should send back a 'service-unavailable' error per the XEP, but
        // some clients are known to send 'feature-not-available'. Treat these as indications that the client is
        // still connected.
        final Collection<PacketError.Condition> pingErrorsIndicatingClientConnectivity = Arrays.asList(PacketError.Condition.service_unavailable, PacketError.Condition.feature_not_implemented);
        if (stanza.getError() != null && pingErrorsIndicatingClientConnectivity.contains(stanza.getError().getCondition())) {
            return false;
        }
        final JID jid = PINGS_SENT.get(iq.getID());
        return jid != null && iq.getFrom().equals(jid) && stanza.getError() != null;
    }
    // The remainder of this implementation applies to Message and Presence stanzas.
    // Conditions as defined in XEP-0045, section 'ghost users'.
    final Collection<PacketError.Condition> deliveryRelatedErrorConditions = Arrays.asList(PacketError.Condition.gone, PacketError.Condition.item_not_found, PacketError.Condition.recipient_unavailable, PacketError.Condition.redirect, PacketError.Condition.remote_server_not_found, PacketError.Condition.remote_server_timeout);
    final PacketError error = stanza.getError();
    return error != null && deliveryRelatedErrorConditions.contains(error.getCondition());
}
Also used : GroupJID(org.jivesoftware.openfire.group.GroupJID) JID(org.xmpp.packet.JID) IQ(org.xmpp.packet.IQ) PacketError(org.xmpp.packet.PacketError)

Aggregations

PacketError (org.xmpp.packet.PacketError)39 IQ (org.xmpp.packet.IQ)35 Element (org.dom4j.Element)32 JID (org.xmpp.packet.JID)11 AgentNotFoundException (org.jivesoftware.xmpp.workgroup.AgentNotFoundException)10 AgentSession (org.jivesoftware.xmpp.workgroup.AgentSession)4 Connection (java.sql.Connection)3 PreparedStatement (java.sql.PreparedStatement)3 ResultSet (java.sql.ResultSet)3 AtomicReference (java.util.concurrent.atomic.AtomicReference)3 File (java.io.File)2 SQLException (java.sql.SQLException)2 HashMap (java.util.HashMap)2 UserNotFoundException (org.jivesoftware.openfire.user.UserNotFoundException)2 NotFoundException (org.jivesoftware.util.NotFoundException)2 Agent (org.jivesoftware.xmpp.workgroup.Agent)2 DbProperties (org.jivesoftware.xmpp.workgroup.DbProperties)2 WorkgroupManager (org.jivesoftware.xmpp.workgroup.WorkgroupManager)2 PacketRejectedException (org.jivesoftware.xmpp.workgroup.interceptor.PacketRejectedException)2 UserRequest (org.jivesoftware.xmpp.workgroup.request.UserRequest)2