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;
}
}
}
}
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;
}
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());
}
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());
}
Aggregations