KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jivesoftware > messenger > server > ServerDialback


1 /**
2  * $RCSfile: ServerDialback.java,v $
3  * $Revision: 1.10 $
4  * $Date: 2005/06/24 05:08:07 $
5  *
6  * Copyright (C) 2004 Jive Software. All rights reserved.
7  *
8  * This software is published under the terms of the GNU Public License (GPL),
9  * a copy of which is included in this distribution.
10  */

11
12 package org.jivesoftware.messenger.server;
13
14 import org.dom4j.DocumentException;
15 import org.dom4j.Element;
16 import org.dom4j.io.XPPPacketReader;
17 import org.jivesoftware.messenger.*;
18 import org.jivesoftware.messenger.auth.AuthFactory;
19 import org.jivesoftware.messenger.net.DNSUtil;
20 import org.jivesoftware.messenger.net.SocketConnection;
21 import org.jivesoftware.messenger.spi.BasicStreamIDFactory;
22 import org.jivesoftware.util.JiveGlobals;
23 import org.jivesoftware.util.Log;
24 import org.jivesoftware.util.StringUtils;
25 import org.xmlpull.v1.XmlPullParser;
26 import org.xmlpull.v1.XmlPullParserException;
27 import org.xmlpull.v1.XmlPullParserFactory;
28 import org.xmpp.packet.JID;
29 import org.xmpp.packet.StreamError;
30
31 import javax.net.SocketFactory;
32 import java.io.*;
33 import java.net.Socket JavaDoc;
34 import java.net.UnknownHostException JavaDoc;
35 import java.util.concurrent.TimeUnit JavaDoc;
36
37 /**
38  * Implementation of the Server Dialback method as defined by the RFC3920.
39  *
40  * The dialback method follows the following logic to validate the remote server:
41  * <ol>
42  * <li>The Originating Server establishes a connection to the Receiving Server.</li>
43  * <li>The Originating Server sends a 'key' value over the connection to the Receiving
44  * Server.</li>
45  * <li>The Receiving Server establishes a connection to the Authoritative Server.</li>
46  * <li>The Receiving Server sends the same 'key' value to the Authoritative Server.</li>
47  * <li>The Authoritative Server replies that key is valid or invalid.</li>
48  * <li>The Receiving Server informs the Originating Server whether it is authenticated or
49  * not.</li>
50  * </ol>
51  *
52  * By default a timeout of 60 seconds will be used for reading packets from remote servers. Use
53  * the property <b>xmpp.server.read.timeout</b> to change that value. The value should be in
54  * milliseconds.
55  *
56  * @author Gaston Dombiak
57  */

58 class ServerDialback {
59     /**
60      * The utf-8 charset for decoding and encoding Jabber packet streams.
61      */

62     protected static String JavaDoc CHARSET = "UTF-8";
63     /**
64      * Secret key to be used for encoding and decoding keys used for authentication.
65      */

66     private static final String JavaDoc secretKey = StringUtils.randomString(10);
67
68     private static XmlPullParserFactory FACTORY = null;
69
70     static {
71         try {
72             FACTORY = XmlPullParserFactory.newInstance();
73         }
74         catch (XmlPullParserException e) {
75             Log.error("Error creating a parser factory", e);
76         }
77     }
78
79     private Connection connection;
80     private String JavaDoc serverName;
81     private SessionManager sessionManager = SessionManager.getInstance();
82     private RoutingTable routingTable = XMPPServer.getInstance().getRoutingTable();
83
84     /**
85      * Creates a new instance that will be used for creating {@link IncomingServerSession},
86      * validating subsequent domains or authenticatig new domains. Use
87      * {@link #createIncomingSession(org.dom4j.io.XPPPacketReader)} for creating a new server
88      * session used for receiving packets from the remote server. Use
89      * {@link #validateRemoteDomain(org.dom4j.Element, org.jivesoftware.messenger.StreamID)} for
90      * validating subsequent domains and use
91      * {@link #authenticateDomain(OutgoingServerSocketReader, String, String, String)} for
92      * registering new domains that are allowed to send packets to the remote server.<p>
93      *
94      * For validating domains a new TCP connection will be established to the Authoritative Server.
95      * The Authoritative Server may be the same Originating Server or some other machine in the
96      * Originating Server's network. Once the remote domain gets validated the Originating Server
97      * will be allowed for sending packets to this server. However, this server will need to
98      * validate its domain/s with the Originating Server if this server needs to send packets to
99      * the Originating Server. Another TCP connection will be established for validation this
100      * server domain/s and for sending packets to the Originating Server.
101      *
102      * @param connection the connection created by the remote server.
103      * @param serverName the name of the local server.
104      */

105     ServerDialback(Connection connection, String JavaDoc serverName) {
106         this.connection = connection;
107         this.serverName = serverName;
108     }
109
110     ServerDialback() {
111     }
112
113     /**
114      * Creates a new connection from the Originating Server to the Receiving Server for
115      * authenticating the specified domain.
116      *
117      * @param domain domain of the Originating Server to authenticate with the Receiving Server.
118      * @param hostname IP address or hostname of the Receiving Server.
119      * @param port port of the Receiving Server.
120      * @return an OutgoingServerSession if the domain was authenticated or <tt>null</tt> if none.
121      */

122     public OutgoingServerSession createOutgoingSession(String JavaDoc domain, String JavaDoc hostname, int port) {
123         String JavaDoc realHostname = null;
124         try {
125             // Establish a TCP connection to the Receiving Server
126
Log.debug("OS - Trying to connect to " + hostname + ":" + port);
127             // Get the real hostname to connect to using DNS lookup of the specified hostname
128
DNSUtil.HostAddress address = DNSUtil.resolveXMPPServerDomain(hostname);
129             realHostname = address.getHost();
130             Socket JavaDoc socket = SocketFactory.getDefault().createSocket(realHostname, port);
131             Log.debug("OS - Connection to " + hostname + ":" + port + " successfull");
132             connection =
133                     new SocketConnection(XMPPServer.getInstance().getPacketDeliverer(), socket,
134                             false);
135             // Get a writer for sending the open stream tag
136
// Send to the Receiving Server a stream header
137
StringBuilder JavaDoc stream = new StringBuilder JavaDoc();
138             stream.append("<stream:stream");
139             stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
140             stream.append(" xmlns=\"jabber:server\"");
141             stream.append(" xmlns:db=\"jabber:server:dialback\">");
142             connection.deliverRawText(stream.toString());
143             stream = null;
144
145             XPPPacketReader reader = new XPPPacketReader();
146             reader.setXPPFactory(XmlPullParserFactory.newInstance());
147             reader.getXPPParser().setInput(new InputStreamReader(socket.getInputStream(),
148                     CHARSET));
149             // Get the answer from the Receiving Server
150
XmlPullParser xpp = reader.getXPPParser();
151             for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
152                 eventType = xpp.next();
153             }
154             if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) {
155                 String JavaDoc id = xpp.getAttributeValue("", "id");
156                 OutgoingServerSocketReader socketReader = new OutgoingServerSocketReader(reader);
157                 if (authenticateDomain(socketReader, domain, hostname, id)) {
158                     // Domain was validated so create a new OutgoingServerSession
159
StreamID streamID = new BasicStreamIDFactory().createStreamID(id);
160                     OutgoingServerSession session = new OutgoingServerSession(domain, connection,
161                             socketReader, streamID);
162                     connection.init(session);
163                     // Set the hostname as the address of the session
164
session.setAddress(new JID(null, hostname, null));
165                     return session;
166                 }
167                 else {
168                     // Close the connection
169
connection.close();
170                 }
171             }
172             else {
173                 Log.debug("OS - Invalid namespace in packet: " + xpp.getText());
174                 // Send an invalid-namespace stream error condition in the response
175
StreamError error = new StreamError(StreamError.Condition.invalid_namespace);
176                 StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
177                 sb.append(error.toXML());
178                 connection.deliverRawText(sb.toString());
179                 // Close the connection
180
connection.close();
181             }
182         }
183         catch (UnknownHostException JavaDoc e) {
184             Log.debug("Error connecting to the remote server: " + hostname + "(DNS lookup: " +
185                     realHostname +
186                     ")",
187                     e);
188             // Close the connection
189
if (connection != null) {
190                 connection.close();
191             }
192         }
193         catch (Exception JavaDoc e) {
194             Log.error("Error creating outgoing session to remote server: " + hostname +
195                     "(DNS lookup: " +
196                     realHostname +
197                     ")",
198                     e);
199             // Close the connection
200
if (connection != null) {
201                 connection.close();
202             }
203         }
204         return null;
205     }
206
207     /**
208      * Authenticates the Originating Server domain with the Receiving Server. Once the domain has
209      * been authenticated the Receiving Server will start accepting packets from the Originating
210      * Server.<p>
211      *
212      * The Receiving Server will connect to the Authoritative Server to verify the dialback key.
213      * Most probably the Originating Server machine will be the Authoritative Server too.
214      *
215      * @param socketReader the reader to use for reading the answer from the Receiving Server.
216      * @param domain the domain to authenticate.
217      * @param hostname the hostname of the remote server (i.e. Receiving Server).
218      * @param id the stream id to be used for creating the dialback key.
219      * @return true if the Receiving Server authenticated the domain with the Authoritative Server.
220      */

221     public boolean authenticateDomain(OutgoingServerSocketReader socketReader, String JavaDoc domain,
222             String JavaDoc hostname, String JavaDoc id) {
223         String JavaDoc key = AuthFactory.createDigest(id, secretKey);
224         Log.debug("OS - Sent dialback key to host: " + hostname + " id: " + id + " from domain: " +
225                 domain);
226
227         synchronized (socketReader) {
228             // Send a dialback key to the Receiving Server
229
StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
230             sb.append("<db:result");
231             sb.append(" from=\"" + domain + "\"");
232             sb.append(" to=\"" + hostname + "\">");
233             sb.append(key);
234             sb.append("</db:result>");
235             connection.deliverRawText(sb.toString());
236             sb = null;
237
238             // Process the answer from the Receiving Server
239
try {
240                 Element doc = socketReader.getElement(JiveGlobals.getIntProperty("xmpp.server.read.timeout", 60000),
241                         TimeUnit.MILLISECONDS);
242                 if (doc == null) {
243                     Log.debug("OS - Time out waiting for answer in validation from: " + hostname +
244                             " id: " +
245                             id +
246                             " for domain: " +
247                             domain);
248                     return false;
249                 }
250                 else if ("db".equals(doc.getNamespacePrefix()) && "result".equals(doc.getName())) {
251                     boolean success = "valid".equals(doc.attributeValue("type"));
252                     Log.debug("OS - Validation " + (success ? "GRANTED" : "FAILED") + " from: " +
253                             hostname +
254                             " id: " +
255                             id +
256                             " for domain: " +
257                             domain);
258                     return success;
259                 }
260                 else {
261                     Log.debug("OS - Unexpected answer in validation from: " + hostname + " id: " +
262                             id +
263                             " for domain: " +
264                             domain +
265                             " answer:" +
266                             doc.asXML());
267                     return false;
268                 }
269             }
270             catch (InterruptedException JavaDoc e) {
271                 Log.debug("OS - Validation FAILED from: " + hostname +
272                         " id: " +
273                         id +
274                         " for domain: " +
275                         domain, e);
276                 return false;
277             }
278         }
279     }
280
281     /**
282      * Returns a new {@link IncomingServerSession} with a domain validated by the Authoritative
283      * Server. New domains may be added to the returned IncomingServerSession after they have
284      * been validated. See
285      * {@link IncomingServerSession#validateSubsequentDomain(org.dom4j.Element)}. The remote
286      * server will be able to send packets through this session whose domains were previously
287      * validated.<p>
288      *
289      * When acting as an Authoritative Server this method will verify the requested key
290      * and will return null since the underlying TCP connection will be closed after sending the
291      * response to the Receiving Server.<p>
292      *
293      * @param reader reader of DOM documents on the connection to the remote server.
294      * @return an IncomingServerSession that was previously validated against the remote server.
295      * @throws IOException if an I/O error occurs while communicating with the remote server.
296      * @throws XmlPullParserException if an error occurs while parsing XML packets.
297      */

298     public IncomingServerSession createIncomingSession(XPPPacketReader reader) throws IOException,
299             XmlPullParserException {
300         XmlPullParser xpp = reader.getXPPParser();
301         StringBuilder JavaDoc sb;
302         StreamError error;
303         if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) {
304
305             StreamID streamID = sessionManager.nextStreamID();
306
307             sb = new StringBuilder JavaDoc();
308             sb.append("<stream:stream");
309             sb.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
310             sb.append(" xmlns=\"jabber:server\" xmlns:db=\"jabber:server:dialback\"");
311             sb.append(" id=\"");
312             sb.append(streamID.toString());
313             sb.append("\">");
314             connection.deliverRawText(sb.toString());
315
316             try {
317                 Element doc = reader.parseDocument().getRootElement();
318                 if ("db".equals(doc.getNamespacePrefix()) && "result".equals(doc.getName())) {
319                     if (validateRemoteDomain(doc, streamID)) {
320                         String JavaDoc hostname = doc.attributeValue("from");
321                         // Create a server Session for the remote server
322
IncomingServerSession session = sessionManager.
323                                 createIncomingServerSession(connection, streamID);
324                         // Set the first validated domain as the address of the session
325
session.setAddress(new JID(null, hostname, null));
326                         // Add the validated domain as a valid domain
327
session.addValidatedDomain(hostname);
328                         return session;
329                     }
330                 }
331                 else if ("db".equals(doc.getNamespacePrefix()) && "verify".equals(doc.getName())) {
332                     // When acting as an Authoritative Server the Receiving Server will send a
333
// db:verify packet for verifying a key that was previously sent by this
334
// server when acting as the Originating Server
335
verifyReceivedKey(doc, connection);
336                     // Close the underlying connection
337
connection.close();
338                     String JavaDoc verifyFROM = doc.attributeValue("from");
339                     String JavaDoc id = doc.attributeValue("id");
340                     Log.debug("AS - Connection closed for host: " + verifyFROM + " id: " + id);
341                     sb = null;
342                     return null;
343                 }
344                 else {
345                     // The remote server sent an invalid/unknown packet
346
error = new StreamError(StreamError.Condition.invalid_xml);
347                     sb = new StringBuilder JavaDoc();
348                     sb.append(error.toXML());
349                     connection.deliverRawText(sb.toString());
350                     // Close the underlying connection
351
connection.close();
352                     return null;
353                 }
354             }
355             catch (Exception JavaDoc e) {
356                 Log.error("An error occured while creating a server session", e);
357                 // Close the underlying connection
358
connection.close();
359                 return null;
360             }
361
362         }
363         else {
364             // Include the invalid-namespace stream error condition in the response
365
error = new StreamError(StreamError.Condition.invalid_namespace);
366             sb = new StringBuilder JavaDoc();
367             sb.append(error.toXML());
368             connection.deliverRawText(sb.toString());
369             // Close the underlying connection
370
connection.close();
371             return null;
372         }
373         return null;
374     }
375
376     /**
377      * Returns true if the domain requested by the remote server was validated by the Authoritative
378      * Server. To validate the domain a new TCP connection will be established to the
379      * Authoritative Server. The Authoritative Server may be the same Originating Server or
380      * some other machine in the Originating Server's network.<p>
381      *
382      * If the domain was not valid or some error occured while validating the domain then the
383      * underlying TCP connection will be closed.
384      *
385      * @param doc the request for validating the new domain.
386      * @param streamID the stream id generated by this server for the Originating Server.
387      * @return true if the requested domain is valid.
388      */

389     public boolean validateRemoteDomain(Element doc, StreamID streamID) {
390         StreamError error;
391         StringBuilder JavaDoc sb;
392         String JavaDoc recipient = doc.attributeValue("to");
393         String JavaDoc hostname = doc.attributeValue("from");
394         Log.debug("RS - Received dialback key from host: " + hostname + " to: " + recipient);
395         if (!RemoteServerManager.canAccess(hostname)) {
396             // Remote server is not allowed to establish a connection to this server
397
error = new StreamError(StreamError.Condition.host_unknown);
398             sb = new StringBuilder JavaDoc();
399             sb.append(error.toXML());
400             connection.deliverRawText(sb.toString());
401             // Close the underlying connection
402
connection.close();
403             Log.debug("RS - Error, hostname is not allowed to establish a connection to " +
404                     "this server: " +
405                     recipient);
406             return false;
407         }
408         else if (isHostUnknown(recipient)) {
409             // address does not match a recognized hostname
410
error = new StreamError(StreamError.Condition.host_unknown);
411             sb = new StringBuilder JavaDoc();
412             sb.append(error.toXML());
413             connection.deliverRawText(sb.toString());
414             // Close the underlying connection
415
connection.close();
416             Log.debug("RS - Error, hostname not recognized: " + recipient);
417             return false;
418         }
419         else {
420             if (sessionManager.getIncomingServerSession(hostname) != null) {
421                 // Remote server already has a IncomingServerSession created
422
error = new StreamError(StreamError.Condition.not_authorized);
423                 sb = new StringBuilder JavaDoc();
424                 sb.append(error.toXML());
425                 connection.deliverRawText(sb.toString());
426                 // Close the underlying connection
427
connection.close();
428                 Log.debug("RS - Error, incoming connection already exists from: " + hostname);
429                 return false;
430             }
431             else {
432                 String JavaDoc key = doc.getTextTrim();
433
434                 DNSUtil.HostAddress address = DNSUtil.resolveXMPPServerDomain(hostname);
435
436                 try {
437                     boolean valid = verifyKey(key, streamID.toString(), recipient, hostname,
438                             address.getHost(), address.getPort());
439
440                     Log.debug("RS - Sending key verification result to OS: " + hostname);
441                     sb = new StringBuilder JavaDoc();
442                     sb.append("<db:result");
443                     sb.append(" from=\"" + recipient + "\"");
444                     sb.append(" to=\"" + hostname + "\"");
445                     sb.append(" type=\"");
446                     sb.append(valid ? "valid" : "invalid");
447                     sb.append("\"/>");
448                     connection.deliverRawText(sb.toString());
449
450                     if (!valid) {
451                         // Close the underlying connection
452
connection.close();
453                     }
454                     return valid;
455                 }
456                 catch (Exception JavaDoc e) {
457                     Log.warn("Error verifying key", e);
458                     // Send a <remote-connection-failed/> stream error condition
459
// and terminate both the XML stream and the underlying
460
// TCP connection
461
error =
462                             new StreamError(StreamError.Condition.remote_connection_failed);
463                     sb = new StringBuilder JavaDoc();
464                     sb.append(error.toXML());
465                     connection.deliverRawText(sb.toString());
466                     // Close the underlying connection
467
connection.close();
468                     return false;
469                 }
470             }
471         }
472     }
473
474     private boolean isHostUnknown(String JavaDoc recipient) {
475         boolean host_unknown = !serverName.equals(recipient);
476         // If the recipient does not match the serverName then check if it matches a subdomain. This
477
// trick is useful when subdomains of this server are registered in the DNS so remote
478
// servers may establish connections directly to a subdomain of this server
479
if (host_unknown && recipient.contains(serverName)) {
480             try {
481                 routingTable.getRoute(new JID(recipient));
482                 host_unknown = false;
483             }
484             catch (NoSuchRouteException e) {
485                 host_unknown = true;
486             }
487         }
488         return host_unknown;
489     }
490
491     /**
492      * Verifies the key with the Authoritative Server.
493      */

494     private boolean verifyKey(String JavaDoc key, String JavaDoc streamID, String JavaDoc recipient, String JavaDoc hostname,
495             String JavaDoc host, int port) throws IOException, XmlPullParserException,
496             RemoteConnectionFailedException {
497         XPPPacketReader reader = null;
498         Writer writer = null;
499         StreamError error;
500         // Establish a TCP connection back to the domain name asserted by the Originating Server
501
Log.debug("RS - Trying to connect to Authoritative Server: " + hostname + ":" + port);
502         Socket JavaDoc socket = SocketFactory.getDefault().createSocket(host, port);
503         // Set a read timeout of 60 seconds.
504
socket.setSoTimeout(JiveGlobals.getIntProperty("xmpp.server.read.timeout", 60000));
505         Log.debug("RS - Connection to AS: " + hostname + ":" + port + " successfull");
506         try {
507             reader = new XPPPacketReader();
508             reader.setXPPFactory(FACTORY);
509
510             reader.getXPPParser().setInput(new InputStreamReader(socket.getInputStream(),
511                     CHARSET));
512             // Get a writer for sending the open stream tag
513
writer =
514                     new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),
515                             CHARSET));
516             // Send the Authoritative Server a stream header
517
StringBuilder JavaDoc stream = new StringBuilder JavaDoc();
518             stream.append("<stream:stream");
519             stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
520             stream.append(" xmlns=\"jabber:server\"");
521             stream.append(" xmlns:db=\"jabber:server:dialback\">");
522             writer.write(stream.toString());
523             writer.flush();
524             stream = null;
525
526             // Get the answer from the Authoritative Server
527
XmlPullParser xpp = reader.getXPPParser();
528             for (int eventType = xpp.getEventType(); eventType != XmlPullParser.START_TAG;) {
529                 eventType = xpp.next();
530             }
531             if ("jabber:server:dialback".equals(xpp.getNamespace("db"))) {
532                 Log.debug("RS - Asking AS to verify dialback key for id" + streamID);
533                 // Request for verification of the key
534
StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
535                 sb.append("<db:verify");
536                 sb.append(" from=\"" + recipient + "\"");
537                 sb.append(" to=\"" + hostname + "\"");
538                 sb.append(" id=\"" + streamID + "\">");
539                 sb.append(key);
540                 sb.append("</db:verify>");
541                 writer.write(sb.toString());
542                 writer.flush();
543                 sb = null;
544
545                 try {
546                     Element doc = reader.parseDocument().getRootElement();
547                     if ("db".equals(doc.getNamespacePrefix()) && "verify".equals(doc.getName())) {
548                         if (!streamID.equals(doc.attributeValue("id"))) {
549                             // Include the invalid-id stream error condition in the response
550
error = new StreamError(StreamError.Condition.invalid_id);
551                             sb = new StringBuilder JavaDoc();
552                             sb.append(error.toXML());
553                             writer.write(sb.toString());
554                             writer.flush();
555                             // Thrown an error so <remote-connection-failed/> stream error
556
// condition is sent to the Originating Server
557
throw new RemoteConnectionFailedException("Invalid ID");
558                         }
559                         else if (isHostUnknown(doc.attributeValue("to"))) {
560                             // Include the host-unknown stream error condition in the response
561
error = new StreamError(StreamError.Condition.host_unknown);
562                             sb = new StringBuilder JavaDoc();
563                             sb.append(error.toXML());
564                             writer.write(sb.toString());
565                             writer.flush();
566                             // Thrown an error so <remote-connection-failed/> stream error
567
// condition is sent to the Originating Server
568
throw new RemoteConnectionFailedException("Host unknown");
569                         }
570                         else if (!hostname.equals(doc.attributeValue("from"))) {
571                             // Include the invalid-from stream error condition in the response
572
error = new StreamError(StreamError.Condition.invalid_from);
573                             sb = new StringBuilder JavaDoc();
574                             sb.append(error.toXML());
575                             writer.write(sb.toString());
576                             writer.flush();
577                             // Thrown an error so <remote-connection-failed/> stream error
578
// condition is sent to the Originating Server
579
throw new RemoteConnectionFailedException("Invalid From");
580                         }
581                         else {
582                             boolean valid = "valid".equals(doc.attributeValue("type"));
583                             Log.debug("RS - Key was " + (valid ? "" : "NOT ") +
584                                     "VERIFIED by the Authoritative Server for: " +
585                                     hostname);
586                             return valid;
587                         }
588                     }
589                     else {
590                         Log.debug("db:verify answer was: " + doc.asXML());
591                     }
592                 }
593                 catch (DocumentException e) {
594                     Log.error("An error occured connecting to the Authoritative Server", e);
595                     // Thrown an error so <remote-connection-failed/> stream error condition is
596
// sent to the Originating Server
597
throw new RemoteConnectionFailedException("Error connecting to the Authoritative Server");
598                 }
599
600             }
601             else {
602                 // Include the invalid-namespace stream error condition in the response
603
error = new StreamError(StreamError.Condition.invalid_namespace);
604                 StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
605                 sb.append(error.toXML());
606                 writer.write(sb.toString());
607                 writer.flush();
608                 // Thrown an error so <remote-connection-failed/> stream error condition is
609
// sent to the Originating Server
610
throw new RemoteConnectionFailedException("Invalid namespace");
611             }
612         }
613         finally {
614             try {
615                 Log.debug("RS - Closing connection to Authoritative Server: " + hostname);
616                 // Close the stream
617
StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
618                 sb.append("</stream:stream>");
619                 writer.write(sb.toString());
620                 writer.flush();
621                 // Close the TCP connection
622
socket.close();
623             }
624             catch (IOException ioe) {
625             }
626         }
627         return false;
628     }
629
630     /**
631      * Verifies the key sent by a Receiving Server. This server will be acting as the
632      * Authoritative Server when executing this method. The remote server may have established
633      * a new connection to the Authoritative Server (i.e. this server) for verifying the key
634      * or it may be reusing an existing incoming connection.
635      *
636      * @param doc the Element that contains the key to verify.
637      * @param connection the connection to use for sending the verification result
638      * @return true if the key was verified.
639      */

640     public static boolean verifyReceivedKey(Element doc, Connection connection) {
641         String JavaDoc verifyFROM = doc.attributeValue("from");
642         String JavaDoc verifyTO = doc.attributeValue("to");
643         String JavaDoc key = doc.getTextTrim();
644         String JavaDoc id = doc.attributeValue("id");
645         Log.debug("AS - Verifying key for host: " + verifyFROM + " id: " + id);
646
647         // TODO If the value of the 'to' address does not match a recognized hostname,
648
// then generate a <host-unknown/> stream error condition
649
// TODO If the value of the 'from' address does not match the hostname
650
// represented by the Receiving Server when opening the TCP connection, then
651
// generate an <invalid-from/> stream error condition
652

653         // Verify the received key
654
// Created the expected key based on the received ID value and the shared secret
655
String JavaDoc expectedKey = AuthFactory.createDigest(id, secretKey);
656         boolean verified = expectedKey.equals(key);
657
658         // Send the result of the key verification
659
StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
660         sb.append("<db:verify");
661         sb.append(" from=\"" + verifyTO + "\"");
662         sb.append(" to=\"" + verifyFROM + "\"");
663         sb.append(" type=\"");
664         sb.append(verified ? "valid" : "invalid");
665         sb.append("\" id=\"" + id + "\"/>");
666         connection.deliverRawText(sb.toString());
667         Log.debug("AS - Key was: " + (verified ? "VALID" : "INVALID") + " for host: " +
668                 verifyFROM +
669                 " id: " +
670                 id);
671         return verified;
672     }
673 }
674
675
Popular Tags