001: /**
002: * $RCSfile$
003: * $Revision: 3084 $
004: * $Date: 2005-11-15 23:51:41 -0300 (Tue, 15 Nov 2005) $
005: *
006: * Copyright (C) 2004 Jive Software. All rights reserved.
007: *
008: * This software is published under the terms of the GNU Public License (GPL),
009: * a copy of which is included in this distribution.
010: */package org.jivesoftware.openfire.muc.spi;
011:
012: import org.dom4j.Element;
013: import org.jivesoftware.openfire.PacketException;
014: import org.jivesoftware.openfire.PacketRouter;
015: import org.jivesoftware.openfire.auth.UnauthorizedException;
016: import org.jivesoftware.openfire.muc.*;
017: import org.jivesoftware.openfire.user.UserAlreadyExistsException;
018: import org.jivesoftware.util.LocaleUtils;
019: import org.jivesoftware.util.Log;
020: import org.jivesoftware.util.NotFoundException;
021: import org.xmpp.packet.*;
022:
023: import java.util.*;
024: import java.util.concurrent.ConcurrentHashMap;
025:
026: /**
027: * Representation of users interacting with the chat service. A user
028: * may join serveral rooms hosted by the chat service. That means that
029: * we are going to have an instance of this class for the user and several
030: * MUCRoles for each joined room.<p>
031: *
032: * This room occupant is being hosted by this JVM. When the room occupant
033: * is hosted by another cluster node then an instance of {@link RemoteMUCRole}
034: * will be used instead.
035: *
036: * @author Gaston Dombiak
037: */
038: public class LocalMUCUser implements MUCUser {
039:
040: /** The chat server this user belongs to. */
041: private MultiUserChatServer server;
042:
043: /** Real system XMPPAddress for the user. */
044: private JID realjid;
045:
046: /** Table: key roomName.toLowerCase(); value LocalMUCRole. */
047: private Map<String, LocalMUCRole> roles = new ConcurrentHashMap<String, LocalMUCRole>();
048:
049: /** Deliver packets to users. */
050: private PacketRouter router;
051:
052: /**
053: * Time of last packet sent.
054: */
055: private long lastPacketTime;
056:
057: /**
058: * Create a new chat user.
059: *
060: * @param chatserver the server the user belongs to.
061: * @param packetRouter the router for sending packets from this user.
062: * @param jid the real address of the user
063: */
064: LocalMUCUser(MultiUserChatServerImpl chatserver,
065: PacketRouter packetRouter, JID jid) {
066: this .realjid = jid;
067: this .router = packetRouter;
068: this .server = chatserver;
069: }
070:
071: /**
072: * Returns true if the user is currently present in one or more rooms.
073: *
074: * @return true if the user is currently present in one or more rooms.
075: */
076: public boolean isJoined() {
077: return !roles.isEmpty();
078: }
079:
080: /**
081: * Get all roles for this user.
082: *
083: * @return Iterator over all roles for this user
084: */
085: public Collection<LocalMUCRole> getRoles() {
086: return Collections.unmodifiableCollection(roles.values());
087: }
088:
089: /**
090: * Adds the role of the user in a particular room.
091: *
092: * @param roomName The name of the room.
093: * @param role The new role of the user.
094: */
095: public void addRole(String roomName, LocalMUCRole role) {
096: roles.put(roomName, role);
097: }
098:
099: /**
100: * Removes the role of the user in a particular room.<p>
101: *
102: * Note: PREREQUISITE: A lock on this object has already been obtained.
103: *
104: * @param roomName The name of the room we're being removed
105: */
106: public void removeRole(String roomName) {
107: roles.remove(roomName);
108: }
109:
110: /**
111: * Get time (in milliseconds from System currentTimeMillis()) since last packet.
112: *
113: * @return The time when the last packet was sent from this user
114: */
115: public long getLastPacketTime() {
116: return lastPacketTime;
117: }
118:
119: /**
120: * Generate a conflict packet to indicate that the nickname being requested/used is already in
121: * use by another user.
122: *
123: * @param packet the packet to be bounced.
124: * @param error the reason why the operation failed.
125: */
126: private void sendErrorPacket(Packet packet,
127: PacketError.Condition error) {
128: if (packet instanceof IQ) {
129: IQ reply = IQ.createResultIQ((IQ) packet);
130: reply.setChildElement(((IQ) packet).getChildElement()
131: .createCopy());
132: reply.setError(error);
133: router.route(reply);
134: } else {
135: Packet reply = packet.createCopy();
136: reply.setError(error);
137: reply.setFrom(packet.getTo());
138: reply.setTo(packet.getFrom());
139: router.route(reply);
140: }
141: }
142:
143: /**
144: * Obtain the address of the user. The address is used by services like the core
145: * server packet router to determine if a packet should be sent to the handler.
146: * Handlers that are working on behalf of the server should use the generic server
147: * hostname address (e.g. server.com).
148: *
149: * @return the address of the packet handler.
150: */
151: public JID getAddress() {
152: return realjid;
153: }
154:
155: public void process(Packet packet) throws UnauthorizedException,
156: PacketException {
157: if (packet instanceof IQ) {
158: process((IQ) packet);
159: } else if (packet instanceof Message) {
160: process((Message) packet);
161: } else if (packet instanceof Presence) {
162: process((Presence) packet);
163: }
164: }
165:
166: /**
167: * This method does all packet routing in the chat server. Packet routing is actually very
168: * simple:
169: *
170: * <ul>
171: * <li>Discover the room the user is talking to (server packets are dropped)</li>
172: * <li>If the room is not registered and this is a presence "available" packet, try to join the
173: * room</li>
174: * <li>If the room is registered, and presence "unavailable" leave the room</li>
175: * <li>Otherwise, rewrite the sender address and send to the room.</li>
176: * </ul>
177: *
178: * @param packet The packet to route.
179: */
180: public void process(Message packet) {
181: // Ignore messages of type ERROR sent to a room
182: if (Message.Type.error == packet.getType()) {
183: return;
184: }
185: lastPacketTime = System.currentTimeMillis();
186: JID recipient = packet.getTo();
187: String group = recipient.getNode();
188: if (group == null) {
189: // Ignore packets to the groupchat server
190: // In the future, we'll need to support TYPE_IQ queries to the server for MUC
191: Log.info(LocaleUtils
192: .getLocalizedString("muc.error.not-supported")
193: + " " + packet.toString());
194: } else {
195: MUCRole role = roles.get(group);
196: if (role == null) {
197: if (server.hasChatRoom(group)) {
198: boolean declinedInvitation = false;
199: Element userInfo = null;
200: if (Message.Type.normal == packet.getType()) {
201: // An user that is not an occupant could be declining an invitation
202: userInfo = packet.getChildElement("x",
203: "http://jabber.org/protocol/muc#user");
204: if (userInfo != null
205: && userInfo.element("decline") != null) {
206: // A user has declined an invitation to a room
207: // WARNING: Potential fraud if someone fakes the "from" of the
208: // message with the JID of a member and sends a "decline"
209: declinedInvitation = true;
210: }
211: }
212: if (declinedInvitation) {
213: Element info = userInfo.element("decline");
214: server.getChatRoom(group)
215: .sendInvitationRejection(
216: new JID(info
217: .attributeValue("to")),
218: info.elementTextTrim("reason"),
219: packet.getFrom());
220: } else {
221: // The sender is not an occupant of the room
222: sendErrorPacket(packet,
223: PacketError.Condition.not_acceptable);
224: }
225: } else {
226: // The sender is not an occupant of a NON-EXISTENT room!!!
227: sendErrorPacket(packet,
228: PacketError.Condition.recipient_unavailable);
229: }
230: } else {
231: // Check and reject conflicting packets with conflicting roles
232: // In other words, another user already has this nickname
233: if (!role.getUserAddress().equals(packet.getFrom())) {
234: sendErrorPacket(packet,
235: PacketError.Condition.conflict);
236: } else {
237: try {
238: if (packet.getSubject() != null
239: && packet.getSubject().trim().length() > 0
240: && Message.Type.groupchat == packet
241: .getType()
242: && (packet.getBody() == null || packet
243: .getBody().trim().length() == 0)) {
244: // An occupant is trying to change the room's subject
245: role.getChatRoom().changeSubject(packet,
246: role);
247:
248: } else {
249: // An occupant is trying to send a private, send public message,
250: // invite someone to the room or reject an invitation
251: Message.Type type = packet.getType();
252: String resource = packet.getTo()
253: .getResource();
254: if (resource == null
255: || resource.trim().length() == 0) {
256: resource = null;
257: }
258: if (resource == null
259: && Message.Type.groupchat == type) {
260: // An occupant is trying to send a public message
261: role.getChatRoom().sendPublicMessage(
262: packet, role);
263: } else if (resource != null
264: && (Message.Type.chat == type || Message.Type.normal == type)) {
265: // An occupant is trying to send a private message
266: role.getChatRoom().sendPrivatePacket(
267: packet, role);
268: } else if (resource == null
269: && Message.Type.normal == type) {
270: // An occupant could be sending an invitation or declining an
271: // invitation
272: Element userInfo = packet
273: .getChildElement("x",
274: "http://jabber.org/protocol/muc#user");
275: // Real real real UGLY TRICK!!! Will and MUST be solved when
276: // persistence will be added. Replace locking with transactions!
277: LocalMUCRoom room = (LocalMUCRoom) role
278: .getChatRoom();
279: if (userInfo != null
280: && userInfo.element("invite") != null) {
281: // An occupant is sending invitations
282:
283: // Try to keep the list of extensions sent together with the
284: // message invitation. These extensions will be sent to the
285: // invitees.
286: List<Element> extensions = new ArrayList<Element>(
287: packet.getElement()
288: .elements());
289: extensions.remove(userInfo);
290: // Send invitations to invitees
291: for (Iterator it = userInfo
292: .elementIterator("invite"); it
293: .hasNext();) {
294: Element info = (Element) it
295: .next();
296:
297: // Add the user as a member of the room if the room is
298: // members only
299: if (room.isMembersOnly()) {
300: room.lock.writeLock()
301: .lock();
302: try {
303: room
304: .addMember(
305: info
306: .attributeValue("to"),
307: null,
308: role);
309: } finally {
310: room.lock.writeLock()
311: .unlock();
312: }
313: }
314:
315: // Send the invitation to the invitee
316: room
317: .sendInvitation(
318: new JID(
319: info
320: .attributeValue("to")),
321: info
322: .elementTextTrim("reason"),
323: role,
324: extensions);
325: }
326: } else if (userInfo != null
327: && userInfo.element("decline") != null) {
328: // An occupant has declined an invitation
329: Element info = userInfo
330: .element("decline");
331: room
332: .sendInvitationRejection(
333: new JID(
334: info
335: .attributeValue("to")),
336: info
337: .elementTextTrim("reason"),
338: packet.getFrom());
339: } else {
340: sendErrorPacket(
341: packet,
342: PacketError.Condition.bad_request);
343: }
344: } else {
345: sendErrorPacket(
346: packet,
347: PacketError.Condition.bad_request);
348: }
349: }
350: } catch (ForbiddenException e) {
351: sendErrorPacket(packet,
352: PacketError.Condition.forbidden);
353: } catch (NotFoundException e) {
354: sendErrorPacket(
355: packet,
356: PacketError.Condition.recipient_unavailable);
357: } catch (ConflictException e) {
358: sendErrorPacket(packet,
359: PacketError.Condition.conflict);
360: }
361: }
362: }
363: }
364: }
365:
366: public void process(IQ packet) {
367: // Ignore IQs of type ERROR or RESULT sent to a room
368: if (IQ.Type.error == packet.getType()) {
369: return;
370: }
371: lastPacketTime = System.currentTimeMillis();
372: JID recipient = packet.getTo();
373: String group = recipient.getNode();
374: if (group == null) {
375: // Ignore packets to the groupchat server
376: // In the future, we'll need to support TYPE_IQ queries to the server for MUC
377: Log.info(LocaleUtils
378: .getLocalizedString("muc.error.not-supported")
379: + " " + packet.toString());
380: } else {
381: MUCRole role = roles.get(group);
382: if (role == null) {
383: // TODO: send error message to user (can't send packets to group you haven't
384: // joined)
385: } else if (IQ.Type.result == packet.getType()) {
386: // Only process IQ result packet if it's a private packet sent to another
387: // room occupant
388: if (packet.getTo().getResource() != null) {
389: try {
390: // User is sending an IQ result packet to another room occupant
391: role.getChatRoom().sendPrivatePacket(packet,
392: role);
393: } catch (NotFoundException e) {
394: // Do nothing. No error will be sent to the sender of the IQ result packet
395: }
396: }
397: } else {
398: // Check and reject conflicting packets with conflicting roles
399: // In other words, another user already has this nickname
400: if (!role.getUserAddress().equals(packet.getFrom())) {
401: sendErrorPacket(packet,
402: PacketError.Condition.conflict);
403: } else {
404: try {
405: Element query = packet.getElement().element(
406: "query");
407: if (query != null
408: && "http://jabber.org/protocol/muc#owner"
409: .equals(query.getNamespaceURI())) {
410: role.getChatRoom().getIQOwnerHandler()
411: .handleIQ(packet, role);
412: } else if (query != null
413: && "http://jabber.org/protocol/muc#admin"
414: .equals(query.getNamespaceURI())) {
415: role.getChatRoom().getIQAdminHandler()
416: .handleIQ(packet, role);
417: } else {
418: if (packet.getTo().getResource() != null) {
419: // User is sending an IQ packet to another room occupant
420: role.getChatRoom().sendPrivatePacket(
421: packet, role);
422: } else {
423: sendErrorPacket(
424: packet,
425: PacketError.Condition.bad_request);
426: }
427: }
428: } catch (ForbiddenException e) {
429: sendErrorPacket(packet,
430: PacketError.Condition.forbidden);
431: } catch (NotFoundException e) {
432: sendErrorPacket(
433: packet,
434: PacketError.Condition.recipient_unavailable);
435: } catch (ConflictException e) {
436: sendErrorPacket(packet,
437: PacketError.Condition.conflict);
438: } catch (NotAllowedException e) {
439: sendErrorPacket(packet,
440: PacketError.Condition.not_allowed);
441: } catch (Exception e) {
442: sendErrorPacket(
443: packet,
444: PacketError.Condition.internal_server_error);
445: }
446: }
447: }
448: }
449: }
450:
451: public void process(Presence packet) {
452: // Ignore presences of type ERROR sent to a room
453: if (Presence.Type.error == packet.getType()) {
454: return;
455: }
456: lastPacketTime = System.currentTimeMillis();
457: JID recipient = packet.getTo();
458: String group = recipient.getNode();
459: if (group != null) {
460: MUCRole role = roles.get(group);
461: if (role == null) {
462: // If we're not already in a room, we either are joining it or it's not
463: // properly addressed and we drop it silently
464: if (recipient.getResource() != null
465: && recipient.getResource().trim().length() > 0) {
466: if (packet.isAvailable()) {
467: try {
468: // Get or create the room
469: MUCRoom room = server.getChatRoom(group,
470: packet.getFrom());
471: // User must support MUC in order to create a room
472: Element mucInfo = packet.getChildElement(
473: "x",
474: "http://jabber.org/protocol/muc");
475: HistoryRequest historyRequest = null;
476: String password = null;
477: // Check for password & requested history if client supports MUC
478: if (mucInfo != null) {
479: password = mucInfo
480: .elementTextTrim("password");
481: if (mucInfo.element("history") != null) {
482: historyRequest = new HistoryRequest(
483: mucInfo);
484: }
485: }
486: // The user joins the room
487: role = room.joinRoom(recipient
488: .getResource().trim(), password,
489: historyRequest, this , packet
490: .createCopy());
491: // If the client that created the room is non-MUC compliant then
492: // unlock the room thus creating an "instant" room
493: if (mucInfo == null && room.isLocked()
494: && !room.isManuallyLocked()) {
495: room.unlock(role);
496: }
497: } catch (UnauthorizedException e) {
498: sendErrorPacket(
499: packet,
500: PacketError.Condition.not_authorized);
501: } catch (ServiceUnavailableException e) {
502: sendErrorPacket(
503: packet,
504: PacketError.Condition.service_unavailable);
505: } catch (UserAlreadyExistsException e) {
506: sendErrorPacket(packet,
507: PacketError.Condition.conflict);
508: } catch (RoomLockedException e) {
509: sendErrorPacket(
510: packet,
511: PacketError.Condition.recipient_unavailable);
512: } catch (ForbiddenException e) {
513: sendErrorPacket(packet,
514: PacketError.Condition.forbidden);
515: } catch (RegistrationRequiredException e) {
516: sendErrorPacket(
517: packet,
518: PacketError.Condition.registration_required);
519: } catch (ConflictException e) {
520: sendErrorPacket(packet,
521: PacketError.Condition.conflict);
522: } catch (NotAcceptableException e) {
523: sendErrorPacket(
524: packet,
525: PacketError.Condition.not_acceptable);
526: } catch (NotAllowedException e) {
527: sendErrorPacket(packet,
528: PacketError.Condition.not_allowed);
529: }
530: } else {
531: // TODO: send error message to user (can't send presence to group you
532: // haven't joined)
533: }
534: } else {
535: if (packet.isAvailable()) {
536: // A resource is required in order to join a room
537: sendErrorPacket(packet,
538: PacketError.Condition.bad_request);
539: }
540: // TODO: send error message to user (can't send packets to group you haven't
541: // joined)
542: }
543: } else {
544: // Check and reject conflicting packets with conflicting roles
545: // In other words, another user already has this nickname
546: if (!role.getUserAddress().equals(packet.getFrom())) {
547: sendErrorPacket(packet,
548: PacketError.Condition.conflict);
549: } else {
550: if (Presence.Type.unavailable == packet.getType()) {
551: try {
552: // TODO Consider that different nodes can be creating and processing this presence at the same time (when remote node went down)
553: removeRole(group);
554: role.getChatRoom().leaveRoom(role);
555: } catch (Exception e) {
556: Log.error(e);
557: }
558: } else {
559: try {
560: String resource = (recipient.getResource() == null
561: || recipient.getResource().trim()
562: .length() == 0 ? null
563: : recipient.getResource().trim());
564: if (resource == null
565: || role.getNickname()
566: .equalsIgnoreCase(resource)) {
567: // Occupant has changed his availability status
568: role.getChatRoom().presenceUpdated(
569: role, packet);
570: } else {
571: // Occupant has changed his nickname. Send two presences
572: // to each room occupant
573:
574: // Check if occupants are allowed to change their nicknames
575: if (!role.getChatRoom()
576: .canChangeNickname()) {
577: sendErrorPacket(
578: packet,
579: PacketError.Condition.not_acceptable);
580: }
581: // Answer a conflic error if the new nickname is taken
582: else if (role.getChatRoom()
583: .hasOccupant(resource)) {
584: sendErrorPacket(
585: packet,
586: PacketError.Condition.conflict);
587: } else {
588: // Send "unavailable" presence for the old nickname
589: Presence presence = role
590: .getPresence().createCopy();
591: // Switch the presence to OFFLINE
592: presence
593: .setType(Presence.Type.unavailable);
594: presence.setStatus(null);
595: // Add the new nickname and status 303 as properties
596: Element frag = presence
597: .getChildElement("x",
598: "http://jabber.org/protocol/muc#user");
599: frag.element("item").addAttribute(
600: "nick", resource);
601: frag
602: .addElement("status")
603: .addAttribute("code", "303");
604: role.getChatRoom().send(presence);
605:
606: // Send availability presence for the new nickname
607: String oldNick = role.getNickname();
608: role.getChatRoom().nicknameChanged(
609: role, packet, oldNick,
610: resource);
611: }
612: }
613: } catch (Exception e) {
614: Log.error(LocaleUtils
615: .getLocalizedString("admin.error"),
616: e);
617: }
618: }
619: }
620: }
621: }
622: }
623: }
|