KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jivesoftware > messenger > ClientSession


1 /**
2  * $RCSfile: ClientSession.java,v $
3  * $Revision: 1.11 $
4  * $Date: 2005/07/27 18:44:36 $
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;
13
14 import org.jivesoftware.messenger.auth.AuthToken;
15 import org.jivesoftware.messenger.auth.UnauthorizedException;
16 import org.jivesoftware.messenger.user.User;
17 import org.jivesoftware.messenger.user.UserManager;
18 import org.jivesoftware.messenger.user.UserNotFoundException;
19 import org.jivesoftware.messenger.net.SocketConnection;
20 import org.jivesoftware.util.LocaleUtils;
21 import org.jivesoftware.util.Log;
22 import org.jivesoftware.util.JiveGlobals;
23 import org.xmpp.packet.JID;
24 import org.xmpp.packet.Packet;
25 import org.xmpp.packet.Presence;
26 import org.xmpp.packet.StreamError;
27 import org.dom4j.io.XPPPacketReader;
28 import org.xmlpull.v1.XmlPullParserException;
29 import org.xmlpull.v1.XmlPullParser;
30
31 import java.io.Writer JavaDoc;
32 import java.io.IOException JavaDoc;
33 import java.util.Map JavaDoc;
34 import java.util.HashMap JavaDoc;
35 import java.util.Iterator JavaDoc;
36 import java.util.StringTokenizer JavaDoc;
37
38 /**
39  * Represents a session between the server and a client.
40  *
41  * @author Gaston Dombiak
42  */

43 public class ClientSession extends Session {
44
45     private static final String JavaDoc ETHERX_NAMESPACE = "http://etherx.jabber.org/streams";
46     private static final String JavaDoc FLASH_NAMESPACE = "http://www.jabber.com/streams/flash";
47     private static final String JavaDoc TLS_NAMESPACE = "urn:ietf:params:xml:ns:xmpp-tls";
48
49     /**
50      * Version of the XMPP spec supported as MAJOR_VERSION.MINOR_VERSION (e.g. 1.0).
51      */

52     private static final int MAJOR_VERSION = 0;
53     private static final int MINOR_VERSION = 0;
54
55     /**
56      * Keep the list of IP address that are allowed to connect to the server. If the list is
57      * empty then anyone is allowed to connect to the server.<p>
58      *
59      * Note: Key = IP address or IP range; Value = empty string. A hash map is being used for
60      * performance reasons.
61      */

62     private static Map JavaDoc<String JavaDoc,String JavaDoc> allowedIPs = new HashMap JavaDoc<String JavaDoc,String JavaDoc>();
63
64     /**
65      * The authentication token for this session.
66      */

67     protected AuthToken authToken;
68
69     /**
70      * Flag indicating if this session has been initialized yet (upon first available transition).
71      */

72     private boolean initialized;
73
74     /**
75      * Flag indicating if the user requested to not receive offline messages when sending
76      * an available presence. The user may send a disco request with node
77      * "http://jabber.org/protocol/offline" so that no offline messages are sent to the
78      * user when he becomes online. If the user is connected from many resources then
79      * if one of the sessions stopped the flooding then no session should flood the user.
80      */

81     private boolean offlineFloodStopped = false;
82
83     private Presence presence = null;
84
85     private int conflictCount = 0;
86
87     static {
88         // Fill out the allowedIPs with the system property
89
String JavaDoc allowed = JiveGlobals.getProperty("xmpp.client.login.allowed", "");
90         StringTokenizer JavaDoc tokens = new StringTokenizer JavaDoc(allowed, ", ");
91         while (tokens.hasMoreTokens()) {
92             String JavaDoc address = tokens.nextToken().trim();
93             allowedIPs.put(address, "");
94         }
95     }
96
97     /**
98      * Returns a newly created session between the server and a client. The session will
99      * be created and returned only if correct name/prefix (i.e. 'stream' or 'flash')
100      * and namespace were provided by the client.
101      *
102      * @param serverName the name of the server where the session is connecting to.
103      * @param reader the reader that is reading the provided XML through the connection.
104      * @param connection the connection with the client.
105      * @return a newly created session between the server and a client.
106      */

107     public static Session createSession(String JavaDoc serverName, XPPPacketReader reader,
108             SocketConnection connection) throws XmlPullParserException, UnauthorizedException,
109             IOException JavaDoc
110     {
111         XmlPullParser xpp = reader.getXPPParser();
112
113         boolean isFlashClient = xpp.getPrefix().equals("flash");
114         connection.setFlashClient(isFlashClient);
115
116         // Conduct error checking, the opening tag should be 'stream'
117
// in the 'etherx' namespace
118
if (!xpp.getName().equals("stream") && !isFlashClient) {
119             throw new XmlPullParserException(
120                     LocaleUtils.getLocalizedString("admin.error.bad-stream"));
121         }
122
123         if (!xpp.getNamespace(xpp.getPrefix()).equals(ETHERX_NAMESPACE) &&
124                 !(isFlashClient && xpp.getNamespace(xpp.getPrefix()).equals(FLASH_NAMESPACE)))
125         {
126             throw new XmlPullParserException(LocaleUtils.getLocalizedString(
127                     "admin.error.bad-namespace"));
128         }
129
130         if (!allowedIPs.isEmpty()) {
131             // The server is using a whitelist so check that the IP address of the client
132
// is authorized to connect to the server
133
if (!allowedIPs.containsKey(connection.getInetAddress().getHostAddress())) {
134                 byte[] address = connection.getInetAddress().getAddress();
135                 String JavaDoc range1 = (address[0] & 0xff) + "." + (address[1] & 0xff) + "." +
136                         (address[2] & 0xff) +
137                         ".*";
138                 String JavaDoc range2 = (address[0] & 0xff) + "." + (address[1] & 0xff) + ".*.*";
139                 String JavaDoc range3 = (address[0] & 0xff) + ".*.*.*";
140                 if (!allowedIPs.containsKey(range1) && !allowedIPs.containsKey(range2) &&
141                         !allowedIPs.containsKey(range3)) {
142                     // Client cannot connect from this IP address so end the stream and
143
// TCP connection
144
Log.debug("Closed connection to client attempting to connect from: " +
145                             connection.getInetAddress().getHostAddress());
146                     // Include the not-authorized error in the response
147
StreamError error = new StreamError(StreamError.Condition.not_authorized);
148                     StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
149                     sb.append(error.toXML());
150                     connection.deliverRawText(sb.toString());
151                     // Close the underlying connection
152
connection.close();
153                     return null;
154                 }
155             }
156         }
157
158         // Default language is English ("en").
159
String JavaDoc language = "en";
160         // Default to a version of "0.0". Clients written before the XMPP 1.0 spec may
161
// not report a version in which case "0.0" should be assumed (per rfc3920
162
// section 4.4.1).
163
int majorVersion = 0;
164         int minorVersion = 0;
165         for (int i = 0; i < xpp.getAttributeCount(); i++) {
166             if ("lang".equals(xpp.getAttributeName(i))) {
167                 language = xpp.getAttributeValue(i);
168             }
169             if ("version".equals(xpp.getAttributeName(i))) {
170                 try {
171                     String JavaDoc [] versionString = xpp.getAttributeValue(i).split("\\.");
172                     majorVersion = Integer.parseInt(versionString[0]);
173                     minorVersion = Integer.parseInt(versionString[1]);
174                 }
175                 catch (Exception JavaDoc e) {
176                     Log.error(e);
177                 }
178             }
179         }
180
181         // If the client supports a greater major version than the server,
182
// set the version to the highest one the server supports.
183
if (majorVersion > MAJOR_VERSION) {
184             majorVersion = MAJOR_VERSION;
185             minorVersion = MINOR_VERSION;
186         }
187         else if (majorVersion == MAJOR_VERSION) {
188             // If the client supports a greater minor version than the
189
// server, set the version to the highest one that the server
190
// supports.
191
if (minorVersion > MINOR_VERSION) {
192                 minorVersion = MINOR_VERSION;
193             }
194         }
195
196         // Store language and version information in the connection.
197
connection.setLanaguage(language);
198         connection.setXMPPVersion(majorVersion, minorVersion);
199
200         // Create a ClientSession for this user.
201
Session session = SessionManager.getInstance().createClientSession(connection);
202
203         Writer JavaDoc writer = connection.getWriter();
204         // Build the start packet response
205
StringBuilder JavaDoc sb = new StringBuilder JavaDoc();
206         sb.append("<?xml version='1.0' encoding='");
207         sb.append(CHARSET);
208         sb.append("'?>");
209         if (isFlashClient) {
210             sb.append("<flash:stream xmlns:flash=\"http://www.jabber.com/streams/flash\" ");
211         }
212         else {
213             sb.append("<stream:stream ");
214         }
215         sb.append("xmlns:stream=\"http://etherx.jabber.org/streams\" xmlns=\"jabber:client\" from=\"");
216         sb.append(serverName);
217         sb.append("\" id=\"");
218         sb.append(session.getStreamID().toString());
219         sb.append("\" xml:lang=\"");
220         sb.append(language);
221         // Don't include version info if the version is 0.0.
222
if (majorVersion != 0) {
223             sb.append("\" version=\"");
224             sb.append(majorVersion).append(".").append(minorVersion);
225         }
226         sb.append("\">");
227         writer.write(sb.toString());
228
229         // If this is a "Jabber" connection, the session is now initialized and we can
230
// return to allow normal packet parsing.
231
if (majorVersion == 0) {
232             // If this is a flash client append a special caracter to the response.
233
if (isFlashClient) {
234                 writer.write('\0');
235             }
236             writer.flush();
237
238             return session;
239         }
240         // Otherwise, this is at least XMPP 1.0 so we need to announce stream features.
241

242         sb = new StringBuilder JavaDoc();
243         sb.append("<stream:features>");
244         sb.append("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">");
245         // sb.append("<required/>");
246
sb.append("</starttls></stream:features>");
247
248         writer.write(sb.toString());
249
250         if (isFlashClient) {
251             writer.write('\0');
252         }
253         writer.flush();
254
255         boolean done = false;
256         while (!done) {
257             if (xpp.next() == XmlPullParser.START_TAG) {
258                 done = true;
259                 if (xpp.getName().equals("starttls") &&
260                         xpp.getNamespace(xpp.getPrefix()).equals(TLS_NAMESPACE))
261                 {
262                     writer.write("<proceed xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>");
263                     if (isFlashClient) {
264                         writer.write('\0');
265                     }
266                     writer.flush();
267
268                     // TODO: setup SSLEngine and negotiate TLS.
269
}
270             }
271         }
272
273         return session;
274     }
275
276     /**
277      * Returns the list of IP address that are allowed to connect to the server. If the list is
278      * empty then anyone is allowed to connect to the server.
279      *
280      * @return the list of IP address that are allowed to connect to the server.
281      */

282     public static Map JavaDoc<String JavaDoc, String JavaDoc> getAllowedIPs() {
283         return allowedIPs;
284     }
285
286     /**
287      * Sets the list of IP address that are allowed to connect to the server. If the list is
288      * empty then anyone is allowed to connect to the server.
289      *
290      * @param allowed the list of IP address that are allowed to connect to the server.
291      */

292     public static void setAllowedIPs(Map JavaDoc<String JavaDoc, String JavaDoc> allowed) {
293         allowedIPs = allowed;
294         if (allowedIPs.isEmpty()) {
295             JiveGlobals.deleteProperty("xmpp.client.login.allowed");
296         }
297         else {
298             // Iterate through the elements in the map.
299
StringBuilder JavaDoc buf = new StringBuilder JavaDoc();
300             Iterator JavaDoc<String JavaDoc> iter = allowedIPs.keySet().iterator();
301             if (iter.hasNext()) {
302                 buf.append(iter.next());
303             }
304             while (iter.hasNext()) {
305                 buf.append(", ").append((String JavaDoc)iter.next());
306             }
307             JiveGlobals.setProperty("xmpp.client.login.allowed", buf.toString());
308         }
309     }
310
311     /**
312      * Creates a session with an underlying connection and permission protection.
313      *
314      * @param connection The connection we are proxying
315      */

316     public ClientSession(String JavaDoc serverName, Connection connection, StreamID streamID) {
317         super(serverName, connection, streamID);
318         // Set an unavailable initial presence
319
presence = new Presence();
320         presence.setType(Presence.Type.unavailable);
321     }
322
323     /**
324      * Returns the username associated with this session. Use this information
325      * with the user manager to obtain the user based on username.
326      *
327      * @return the username associated with this session
328      * @throws UserNotFoundException if a user is not associated with a session
329      * (the session has not authenticated yet)
330      */

331     public String JavaDoc getUsername() throws UserNotFoundException {
332         if (authToken == null) {
333             throw new UserNotFoundException();
334         }
335         return getAddress().getNode();
336     }
337
338
339     /**
340      * Initialize the session with a valid authentication token and
341      * resource name. This automatically upgrades the session's
342      * status to authenticated and enables many features that are not
343      * available until authenticated (obtaining managers for example).
344      *
345      * @param auth the authentication token obtained from the AuthFactory.
346      * @param resource the resource this session authenticated under.
347      * @param userManager the user manager this authentication occured under.
348      */

349     public void setAuthToken(AuthToken auth, UserManager userManager, String JavaDoc resource)
350             throws UserNotFoundException
351     {
352         User user = userManager.getUser(auth.getUsername());
353         setAddress(new JID(user.getUsername(), getServerName(), resource));
354         authToken = auth;
355
356         sessionManager.addSession(this);
357         setStatus(Session.STATUS_AUTHENTICATED);
358     }
359
360     /**
361      * <p>Initialize the session as an anonymous login.</p>
362      * <p>This automatically upgrades the session's
363      * status to authenticated and enables many features that are not
364      * available until authenticated (obtaining managers for example).</p>
365      */

366     public void setAnonymousAuth() {
367         sessionManager.addAnonymousSession(this);
368         setStatus(Session.STATUS_AUTHENTICATED);
369     }
370
371     /**
372      * Returns the authentication token associated with this session.
373      *
374      * @return the authentication token associated with this session (can be null).
375      */

376     public AuthToken getAuthToken() {
377         return authToken;
378     }
379
380     /**
381      * Flag indicating if this session has been initialized once coming
382      * online. Session initialization occurs after the session receives
383      * the first "available" presence update from the client. Initialization
384      * actions include pushing offline messages, presence subscription requests,
385      * and presence statuses to the client. Initialization occurs only once
386      * following the first available presence transition.
387      *
388      * @return True if the session has already been initializsed
389      */

390     public boolean isInitialized() {
391         return initialized;
392     }
393
394     /**
395      * Sets the initialization state of the session.
396      *
397      * @param isInit True if the session has been initialized
398      * @see #isInitialized
399      */

400     public void setInitialized(boolean isInit) {
401         initialized = isInit;
402     }
403
404     /**
405      * Returns true if the offline messages of the user should be sent to the user when
406      * the user becomes online. If the user sent a disco request with node
407      * "http://jabber.org/protocol/offline" before the available presence then do not
408      * flood the user with the offline messages. If the user is connected from many resources
409      * then if one of the sessions stopped the flooding then no session should flood the user.
410      *
411      * @return true if the offline messages of the user should be sent to the user when the user
412      * becomes online.
413      */

414     public boolean canFloodOfflineMessages() {
415         for (ClientSession session : sessionManager.getSessions()) {
416             if (session.isOfflineFloodStopped()) {
417                 return false;
418             }
419         }
420         return true;
421     }
422
423     /**
424      * Returns true if the user requested to not receive offline messages when sending
425      * an available presence. The user may send a disco request with node
426      * "http://jabber.org/protocol/offline" so that no offline messages are sent to the
427      * user when he becomes online. If the user is connected from many resources then
428      * if one of the sessions stopped the flooding then no session should flood the user.
429      *
430      * @return true if the user requested to not receive offline messages when sending
431      * an available presence.
432      */

433     public boolean isOfflineFloodStopped() {
434         return offlineFloodStopped;
435     }
436
437     /**
438      * Sets if the user requested to not receive offline messages when sending
439      * an available presence. The user may send a disco request with node
440      * "http://jabber.org/protocol/offline" so that no offline messages are sent to the
441      * user when he becomes online. If the user is connected from many resources then
442      * if one of the sessions stopped the flooding then no session should flood the user.
443      *
444      * @param offlineFloodStopped if the user requested to not receive offline messages when
445      * sending an available presence.
446      */

447     public void setOfflineFloodStopped(boolean offlineFloodStopped) {
448         this.offlineFloodStopped = offlineFloodStopped;
449     }
450
451     /**
452      * Obtain the presence of this session.
453      *
454      * @return The presence of this session or null if not authenticated
455      */

456     public Presence getPresence() {
457         return presence;
458     }
459
460     /**
461      * Set the presence of this session
462      *
463      * @param presence The presence for the session
464      * @return The old priority of the session or null if not authenticated
465      */

466     public Presence setPresence(Presence presence) {
467         Presence oldPresence = this.presence;
468         this.presence = presence;
469         if (oldPresence.isAvailable() && !this.presence.isAvailable()) {
470             // The client is no longer available
471
sessionManager.sessionUnavailable(this);
472             // Mark that the session is no longer initialized. This means that if the user sends
473
// an available presence again the session will be initialized again thus receiving
474
// offline messages and offline presence subscription requests
475
setInitialized(false);
476         }
477         else if (!oldPresence.isAvailable() && this.presence.isAvailable()) {
478             // The client is available
479
sessionManager.sessionAvailable(this);
480         }
481         else if (oldPresence.getPriority() != this.presence.getPriority()) {
482             // The client has changed the priority of his presence
483
sessionManager.changePriority(getAddress(), this.presence.getPriority());
484         }
485         return oldPresence;
486     }
487
488     /**
489      * Returns the number of conflicts detected on this session.
490      * Conflicts typically occur when another session authenticates properly
491      * to the user account and requests to use a resource matching the one
492      * in use by this session. Administrators may configure the server to automatically
493      * kick off existing sessions when their conflict count exceeds some limit including
494      * 0 (old sessions are kicked off immediately to accommodate new sessions). Conflicts
495      * typically signify the existing (old) session is broken/hung.
496      *
497      * @return The number of conflicts detected for this session
498      */

499     public int getConflictCount() {
500         return conflictCount;
501     }
502
503     /**
504      * Increments the conflict by one.
505      */

506     public void incrementConflictCount() {
507         conflictCount++;
508     }
509
510     public void process(Packet packet) {
511         deliver(packet);
512     }
513
514     private void deliver(Packet packet) {
515         if (conn != null && !conn.isClosed()) {
516             try {
517                 conn.deliver(packet);
518             }
519             catch (Exception JavaDoc e) {
520                 Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
521             }
522         }
523     }
524
525     public String JavaDoc toString() {
526         return super.toString() + " presence: " + presence;
527     }
528 }
529
Popular Tags