1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
/**
* $RCSfile: ServerDialback.java,v $
* $Revision: 3188 $
* $Date: 2005-12-12 00:28:19 -0300 (Mon, 12 Dec 2005) $
*
* Copyright (C) 2007 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.wildfire.server;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.XMPPPacketReader;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.StringUtils;
import org.jivesoftware.wildfire.*;
import org.jivesoftware.wildfire.auth.AuthFactory;
import org.jivesoftware.wildfire.net.DNSUtil;
import org.jivesoftware.wildfire.net.MXParser;
import org.jivesoftware.wildfire.net.ServerTrafficCounter;
import org.jivesoftware.wildfire.net.SocketConnection;
import org.jivesoftware.wildfire.session.IncomingServerSession;
import org.jivesoftware.wildfire.session.OutgoingServerSession;
import org.jivesoftware.wildfire.spi.BasicStreamIDFactory;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmpp.packet.JID;
import org.xmpp.packet.StreamError;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.TimeUnit;
/**
* Implementation of the Server Dialback method as defined by the RFC3920.
*
* The dialback method follows the following logic to validate the remote server:
* <ol>
* <li>The Originating Server establishes a connection to the Receiving Server.</li>
* <li>The Originating Server sends a 'key' value over the connection to the Receiving
* Server.</li>
* <li>The Receiving Server establishes a connection to the Authoritative Server.</li>
* <li>The Receiving Server sends the same 'key' value to the Authoritative Server.</li>
* <li>The Authoritative Server replies that key is valid or invalid.</li>
* <li>The Receiving Server informs the Originating Server whether it is authenticated or
* not.</li>
* </ol>
*
* By default a timeout of 20 seconds will be used for reading packets from remote servers. Use
* the property <b>xmpp.server.read.timeout</b> to change that value. The value should be in
* milliseconds.
*
* @author Gaston Dombiak
*/
public class ServerDialback {
/**
* The utf-8 charset for decoding and encoding Jabber packet streams.
*/
protected static String CHARSET = "UTF-8";
/**
* Secret key to be used for encoding and decoding keys used for authentication.
*/
private static final String secretKey = StringUtils.randomString(10);
private static XmlPullParserFactory FACTORY = null;
static {
try {
FACTORY = XmlPullParserFactory.newInstance(MXParser.class.getName(), null);
}
catch (XmlPullParserException e) {
Log.error("Error creating a parser factory", e);
}
}
private Connection connection;
private String serverName;
private SessionManager sessionManager = SessionManager.getInstance();
private RoutingTable routingTable = XMPPServer.getInstance().getRoutingTable();
/**
* Returns true if server dialback is enabled. When enabled remote servers may connect to this
* server using the server dialback method and this server may try the server dialback method
* to connect to remote servers.<p>
*
* When TLS is enabled between servers and server dialback method is enabled then TLS is going
* to be tried first, when connecting to a remote server, and if TLS fails then server dialback
* is going to be used as a last resort.
*
* @return true if server dialback is enabled.
*/
public static boolean isEnabled() {
return JiveGlobals.getBooleanProperty("xmpp.server.dialback.enabled", true);
}
/**
* Creates a new instance that will be used for creating {@link IncomingServerSession},
* validating subsequent domains or authenticatig new domains. Use
* {@link #createIncomingSession(org.dom4j.io.XMPPPacketReader)} for creating a new server
* session used for receiving packets from the remote server. Use
* {@link #validateRemoteDomain(org.dom4j.Element, org.jivesoftware.wildfire.StreamID)} for
* validating subsequent domains and use
* {@link #authenticateDomain(OutgoingServerSocketReader, String, String, String)} for
* registering new domains that are allowed to send packets to the remote server.<p>
*
* For validating domains 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. Once the remote domain gets validated the Originating Server
* will be allowed for sending packets to this server. However, this server will need to
* validate its domain/s with the Originating Server if this server needs to send packets to
* the Originating Server. Another TCP connection will be established for validation this
* server domain/s and for sending packets to the Originating Server.
*
* @param connection the connection created by the remote server.
* @param serverName the name of the local server.
*/
public ServerDialback(Connection connection, String serverName) {
this.connection = connection;
this.serverName = serverName;
}
public ServerDialback() {
}
/**
* Creates a new connection from the Originating Server to the Receiving Server for
* authenticating the specified domain.
*
* @param domain domain of the Originating Server to authenticate with the Receiving Server.
* @param hostname IP address or hostname of the Receiving Server.
* @param port port of the Receiving Server.
* @return an OutgoingServerSession if the domain was authenticated or <tt>null</tt> if none.
*/
public OutgoingServerSession createOutgoingSession(String domain, String hostname, int port) {
String realHostname = null;
int realPort = port;
try {
// Establish a TCP connection to the Receiving Server
// Get the real hostname to connect to using DNS lookup of the specified hostname
DNSUtil.HostAddress address = DNSUtil.resolveXMPPServerDomain(hostname, port);
realHostname = address.getHost();
realPort = address.getPort();
Log.debug("OS - Trying to connect to " + hostname + ":" + port +
"(DNS lookup: " + realHostname + ":" + realPort + ")");
// Connect to the remote server
Socket socket = new Socket();
socket.connect(new InetSocketAddress(realHostname, realPort),
RemoteServerManager.getSocketTimeout());
Log.debug("OS - Connection to " + hostname + ":" + port + " successful");
connection =
new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket,
false);
// Get a writer for sending the open stream tag
// Send to the Receiving Server a stream header
StringBuilder stream = new StringBuilder();
stream.append("<stream:stream");
stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
stream.append(" xmlns=\"jabber:server\"");
stream.append(" xmlns:db=\"jabber:server:dialback\">");
connection.deliverRawText(stream.toString());
// Set a read timeout (of 5 seconds) so we don't keep waiting forever
int soTimeout = socket.getSoTimeout();
socket.setSoTimeout(RemoteServerManager.getSocketTimeout());
XMPPPacketReader reader = new XMPPPacketReader();
reader.setXPPFactory(FACTORY);
reader.getXPPParser().setInput(new InputStreamReader(
ServerTrafficCounter.wrapInputStream(socket.getInputStream()), CHARSET));
// Get the answer from the Receiving Server
XmlPullParser xpp = reader.getXPPParser();
for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
eventType = xpp.next();
}
if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) {
// Restore default timeout
socket.setSoTimeout(soTimeout);
String id = xpp.getAttributeValue("", "id");
OutgoingServerSocketReader socketReader = new OutgoingServerSocketReader(reader);
if (authenticateDomain(socketReader, domain, hostname, id)) {
// Domain was validated so create a new OutgoingServerSession
StreamID streamID = new BasicStreamIDFactory().createStreamID(id);
OutgoingServerSession session = new OutgoingServerSession(domain, connection,
socketReader, streamID);
connection.init(session);
// Set the hostname as the address of the session
session.setAddress(new JID(null, hostname, null));
return session;
}
else {
// Close the connection
connection.close();
}
}
else {
Log.debug("OS - Invalid namespace in packet: " + xpp.getText());
// Send an invalid-namespace stream error condition in the response
connection.deliverRawText(
new StreamError(StreamError.Condition.invalid_namespace).toXML());
// Close the connection
connection.close();
}
}
catch (IOException e) {
Log.debug("Error connecting to the remote server: " + hostname + "(DNS lookup: " +
realHostname + ":" + realPort + ")", e);
// Close the connection
if (connection != null) {
connection.close();
}
}
catch (Exception e) {
Log.error("Error creating outgoing session to remote server: " + hostname +
"(DNS lookup: " +
realHostname +
")",
e);
// Close the connection
if (connection != null) {
connection.close();
}
}
return null;
}
/**
* Authenticates the Originating Server domain with the Receiving Server. Once the domain has
* been authenticated the Receiving Server will start accepting packets from the Originating
* Server.<p>
*
* The Receiving Server will connect to the Authoritative Server to verify the dialback key.
* Most probably the Originating Server machine will be the Authoritative Server too.
*
* @param socketReader the reader to use for reading the answer from the Receiving Server.
* @param domain the domain to authenticate.
* @param hostname the hostname of the remote server (i.e. Receiving Server).
* @param id the stream id to be used for creating the dialback key.
* @return true if the Receiving Server authenticated the domain with the Authoritative Server.
*/
public boolean authenticateDomain(OutgoingServerSocketReader socketReader, String domain,
String hostname, String id) {
String key = AuthFactory.createDigest(id, secretKey);
Log.debug("OS - Sent dialback key to host: " + hostname + " id: " + id + " from domain: " +
domain);
synchronized (socketReader) {
// Send a dialback key to the Receiving Server
StringBuilder sb = new StringBuilder();
sb.append("<db:result");
sb.append(" from=\"").append(domain).append("\"");
sb.append(" to=\"").append(hostname).append("\">");
sb.append(key);
sb.append("</db:result>");
connection.deliverRawText(sb.toString());
// Process the answer from the Receiving Server
try {
Element doc = socketReader.getElement(RemoteServerManager.getSocketTimeout(),
TimeUnit.MILLISECONDS);
if (doc == null) {
Log.debug("OS - Time out waiting for answer in validation from: " + hostname +
" id: " +
id +
" for domain: " +
domain);
return false;
}
else if ("db".equals(doc.getNamespacePrefix()) && "result".equals(doc.getName())) {
boolean success = "valid".equals(doc.attributeValue("type"));
Log.debug("OS - Validation " + (success ? "GRANTED" : "FAILED") + " from: " +
hostname +
" id: " +
id +
" for domain: " +
domain);
return success;
}
else {
Log.debug("OS - Unexpected answer in validation from: " + hostname + " id: " +
id +
" for domain: " +
domain +
" answer:" +
doc.asXML());
return false;
}
}
catch (InterruptedException e) {
Log.debug("OS - Validation FAILED from: " + hostname +
" id: " +
id +
" for domain: " +
domain, e);
return false;
}
}
}
/**
* Returns a new {@link IncomingServerSession} with a domain validated by the Authoritative
* Server. New domains may be added to the returned IncomingServerSession after they have
* been validated. See
* {@link IncomingServerSession#validateSubsequentDomain(org.dom4j.Element)}. The remote
* server will be able to send packets through this session whose domains were previously
* validated.<p>
*
* When acting as an Authoritative Server this method will verify the requested key
* and will return null since the underlying TCP connection will be closed after sending the
* response to the Receiving Server.<p>
*
* @param reader reader of DOM documents on the connection to the remote server.
* @return an IncomingServerSession that was previously validated against the remote server.
* @throws IOException if an I/O error occurs while communicating with the remote server.
* @throws XmlPullParserException if an error occurs while parsing XML packets.
*/
public IncomingServerSession createIncomingSession(XMPPPacketReader reader) throws IOException,
XmlPullParserException {
XmlPullParser xpp = reader.getXPPParser();
StringBuilder sb;
if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) {
StreamID streamID = sessionManager.nextStreamID();
sb = new StringBuilder();
sb.append("<stream:stream");
sb.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
sb.append(" xmlns=\"jabber:server\" xmlns:db=\"jabber:server:dialback\"");
sb.append(" id=\"");
sb.append(streamID.toString());
sb.append("\">");
connection.deliverRawText(sb.toString());
try {
Element doc = reader.parseDocument().getRootElement();
if ("db".equals(doc.getNamespacePrefix()) && "result".equals(doc.getName())) {
if (validateRemoteDomain(doc, streamID)) {
String hostname = doc.attributeValue("from");
String recipient = doc.attributeValue("to");
// Create a server Session for the remote server
IncomingServerSession session = sessionManager.
createIncomingServerSession(connection, streamID);
// Set the first validated domain as the address of the session
session.setAddress(new JID(null, hostname, null));
// Add the validated domain as a valid domain
session.addValidatedDomain(hostname);
// Set the domain or subdomain of the local server used when
// validating the session
session.setLocalDomain(recipient);
return session;
}
}
else if ("db".equals(doc.getNamespacePrefix()) && "verify".equals(doc.getName())) {
// When acting as an Authoritative Server the Receiving Server will send a
// db:verify packet for verifying a key that was previously sent by this
// server when acting as the Originating Server
verifyReceivedKey(doc, connection);
// Close the underlying connection
connection.close();
String verifyFROM = doc.attributeValue("from");
String id = doc.attributeValue("id");
Log.debug("AS - Connection closed for host: " + verifyFROM + " id: " + id);
return null;
}
else {
// The remote server sent an invalid/unknown packet
connection.deliverRawText(
new StreamError(StreamError.Condition.invalid_xml).toXML());
// Close the underlying connection
connection.close();
return null;
}
}
catch (Exception e) {
Log.error("An error occured while creating a server session", e);
// Close the underlying connection
connection.close();
return null;
}
}
else {
// Include the invalid-namespace stream error condition in the response
connection.deliverRawText(
new StreamError(StreamError.Condition.invalid_namespace).toXML());
// Close the underlying connection
connection.close();
return null;
}
return null;
}
/**
* 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 occured while validating the domain then the
* underlying TCP connection will 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 hostname = doc.attributeValue("from");
Log.debug("RS - Received dialback key from host: " + hostname + " to: " + recipient);
if (!RemoteServerManager.canAccess(hostname)) {
// Remote server is not allowed to establish a connection to this server
connection.deliverRawText(new StreamError(StreamError.Condition.host_unknown).toXML());
// Close the underlying connection
connection.close();
Log.debug("RS - Error, hostname is not allowed to establish a connection to " +
"this server: " +
recipient);
return false;
}
else if (isHostUnknown(recipient)) {
// address does not match a recognized hostname
connection.deliverRawText(new StreamError(StreamError.Condition.host_unknown).toXML());
// Close the underlying connection
connection.close();
Log.debug("RS - Error, hostname not recognized: " + recipient);
return false;
}
else {
// Check if the remote server already has a connection to the target domain/subdomain
boolean alreadyExists = false;
for (IncomingServerSession session : sessionManager
.getIncomingServerSessions(hostname)) {
if (recipient.equals(session.getLocalDomain())) {
alreadyExists = true;
}
}
if (alreadyExists && !sessionManager.isMultipleServerConnectionsAllowed()) {
// Remote server already has a IncomingServerSession created
connection.deliverRawText(
new StreamError(StreamError.Condition.not_authorized).toXML());
// Close the underlying connection
connection.close();
Log.debug("RS - Error, incoming connection already exists from: " + hostname);
return false;
}
else {
String key = doc.getTextTrim();
DNSUtil.HostAddress address = DNSUtil.resolveXMPPServerDomain(hostname,
RemoteServerManager.getPortForServer(hostname));
try {
boolean valid = verifyKey(key, streamID.toString(), recipient, hostname,
address.getHost(), address.getPort());
Log.debug("RS - Sending key verification result to OS: " + hostname);
sb = new StringBuilder();
sb.append("<db:result");
sb.append(" from=\"").append(recipient).append("\"");
sb.append(" to=\"").append(hostname).append("\"");
sb.append(" type=\"");
sb.append(valid ? "valid" : "invalid");
sb.append("\"/>");
connection.deliverRawText(sb.toString());
if (!valid) {
// Close the underlying connection
connection.close();
}
return valid;
}
catch (Exception e) {
Log.warn("Error verifying key of remote server: " + hostname, e);
// Send a <remote-connection-failed/> stream error condition
// and terminate both the XML stream and the underlying
// TCP connection
connection.deliverRawText(new StreamError(
StreamError.Condition.remote_connection_failed).toXML());
// Close the underlying connection
connection.close();
return false;
}
}
}
}
private boolean isHostUnknown(String recipient) {
boolean host_unknown = !serverName.equals(recipient);
// If the recipient does not match the serverName then check if it matches a subdomain. This
// trick is useful when subdomains of this server are registered in the DNS so remote
// servers may establish connections directly to a subdomain of this server
if (host_unknown && recipient.contains(serverName)) {
RoutableChannelHandler route = routingTable.getRoute(new JID(recipient));
if (route == null || route instanceof OutgoingSessionPromise) {
host_unknown = true;
}
else {
host_unknown = false;
}
}
return host_unknown;
}
/**
* Verifies the key with the Authoritative Server.
*/
private boolean verifyKey(String key, String streamID, String recipient, String hostname,
String host, int port) throws IOException, XmlPullParserException,
RemoteConnectionFailedException {
XMPPPacketReader reader;
Writer writer = null;
// Establish a TCP connection back to the domain name asserted by the Originating Server
Log.debug("RS - Trying to connect to Authoritative Server: " + hostname + ":" + port +
"(DNS lookup: " + host + ":" + port + ")");
// Connect to the Authoritative server
Socket socket = new Socket();
socket.connect(new InetSocketAddress(host, port), RemoteServerManager.getSocketTimeout());
// Set a read timeout
socket.setSoTimeout(RemoteServerManager.getSocketTimeout());
Log.debug("RS - Connection to AS: " + hostname + ":" + port + " successful");
try {
reader = new XMPPPacketReader();
reader.setXPPFactory(FACTORY);
reader.getXPPParser().setInput(new InputStreamReader(socket.getInputStream(),
CHARSET));
// Get a writer for sending the open stream tag
writer =
new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),
CHARSET));
// Send the Authoritative Server a stream header
StringBuilder stream = new StringBuilder();
stream.append("<stream:stream");
stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
stream.append(" xmlns=\"jabber:server\"");
stream.append(" xmlns:db=\"jabber:server:dialback\">");
writer.write(stream.toString());
writer.flush();
// Get the answer from the Authoritative Server
XmlPullParser xpp = reader.getXPPParser();
for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
eventType = xpp.next();
}
if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) {
Log.debug("RS - Asking AS to verify dialback key for id" + streamID);
// Request for verification of the key
StringBuilder sb = new StringBuilder();
sb.append("<db:verify");
sb.append(" from=\"").append(recipient).append("\"");
sb.append(" to=\"").append(hostname).append("\"");
sb.append(" id=\"").append(streamID).append("\">");
sb.append(key);
sb.append("</db:verify>");
writer.write(sb.toString());
writer.flush();
try {
Element doc = reader.parseDocument().getRootElement();
if ("db".equals(doc.getNamespacePrefix()) && "verify".equals(doc.getName())) {
if (!streamID.equals(doc.attributeValue("id"))) {
// Include the invalid-id stream error condition in the response
writer.write(new StreamError(StreamError.Condition.invalid_id).toXML());
writer.flush();
// Thrown an error so <remote-connection-failed/> stream error
// condition is sent to the Originating Server
throw new RemoteConnectionFailedException("Invalid ID");
}
else if (isHostUnknown(doc.attributeValue("to"))) {
// Include the host-unknown stream error condition in the response
writer.write(
new StreamError(StreamError.Condition.host_unknown).toXML());
writer.flush();
// Thrown an error so <remote-connection-failed/> stream error
// condition is sent to the Originating Server
throw new RemoteConnectionFailedException("Host unknown");
}
else if (!hostname.equals(doc.attributeValue("from"))) {
// Include the invalid-from stream error condition in the response
writer.write(
new StreamError(StreamError.Condition.invalid_from).toXML());
writer.flush();
// Thrown an error so <remote-connection-failed/> stream error
// condition is sent to the Originating Server
throw new RemoteConnectionFailedException("Invalid From");
}
else {
boolean valid = "valid".equals(doc.attributeValue("type"));
Log.debug("RS - Key was " + (valid ? "" : "NOT ") +
"VERIFIED by the Authoritative Server for: " +
hostname);
return valid;
}
}
else {
Log.debug("db:verify answer was: " + doc.asXML());
}
}
catch (DocumentException e) {
Log.error("An error occured connecting to the Authoritative Server", e);
// Thrown an error so <remote-connection-failed/> stream error condition is
// sent to the Originating Server
throw new RemoteConnectionFailedException("Error connecting to the Authoritative Server");
}
}
else {
// Include the invalid-namespace stream error condition in the response
writer.write(new StreamError(StreamError.Condition.invalid_namespace).toXML());
writer.flush();
// Thrown an error so <remote-connection-failed/> stream error condition is
// sent to the Originating Server
throw new RemoteConnectionFailedException("Invalid namespace");
}
}
finally {
try {
Log.debug("RS - Closing connection to Authoritative Server: " + hostname);
// Close the stream
StringBuilder sb = new StringBuilder();
sb.append("</stream:stream>");
writer.write(sb.toString());
writer.flush();
// Close the TCP connection
socket.close();
}
catch (IOException ioe) {
// Do nothing
}
}
return false;
}
/**
* Verifies the key sent by a Receiving Server. This server will be acting as the
* Authoritative Server when executing this method. The remote server may have established
* a new connection to the Authoritative Server (i.e. this server) for verifying the key
* or it may be reusing an existing incoming connection.
*
* @param doc the Element that contains the key to verify.
* @param connection the connection to use for sending the verification result
* @return true if the key was verified.
*/
public static boolean verifyReceivedKey(Element doc, Connection connection) {
String verifyFROM = doc.attributeValue("from");
String verifyTO = doc.attributeValue("to");
String key = doc.getTextTrim();
String id = doc.attributeValue("id");
Log.debug("AS - Verifying key for host: " + verifyFROM + " id: " + id);
// TODO If the value of the 'to' address does not match a recognized hostname,
// then generate a <host-unknown/> stream error condition
// TODO If the value of the 'from' address does not match the hostname
// represented by the Receiving Server when opening the TCP connection, then
// generate an <invalid-from/> stream error condition
// Verify the received key
// Created the expected key based on the received ID value and the shared secret
String expectedKey = AuthFactory.createDigest(id, secretKey);
boolean verified = expectedKey.equals(key);
// Send the result of the key verification
StringBuilder sb = new StringBuilder();
sb.append("<db:verify");
sb.append(" from=\"").append(verifyTO).append("\"");
sb.append(" to=\"").append(verifyFROM).append("\"");
sb.append(" type=\"");
sb.append(verified ? "valid" : "invalid");
sb.append("\" id=\"").append(id).append("\"/>");
connection.deliverRawText(sb.toString());
Log.debug("AS - Key was: " + (verified ? "VALID" : "INVALID") + " for host: " +
verifyFROM +
" id: " +
id);
return verified;
}
}