1 /* 2 * Copyright (C) 2005-2008 Jive Software. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.jivesoftware.openfire; 18 19 import java.util.Collections; 20 import java.util.Iterator; 21 import java.util.List; 22 import java.util.concurrent.CopyOnWriteArrayList; 23 24 import org.jivesoftware.openfire.container.BasicModule; 25 import org.jivesoftware.openfire.disco.ServerFeaturesProvider; 26 import org.jivesoftware.openfire.privacy.PrivacyList; 27 import org.jivesoftware.openfire.privacy.PrivacyListManager; 28 import org.jivesoftware.openfire.user.UserManager; 29 import org.jivesoftware.util.JiveGlobals; 30 import org.slf4j.Logger; 31 import org.slf4j.LoggerFactory; 32 import org.xmpp.packet.JID; 33 import org.xmpp.packet.Message; 34 import org.xmpp.packet.PacketError; 35 36 /** 37 * Controls what is done with offline messages. 38 * 39 * @author Iain Shigeoka 40 */ 41 public class OfflineMessageStrategy extends BasicModule implements ServerFeaturesProvider { 42 43 private static final Logger Log = LoggerFactory.getLogger(OfflineMessageStrategy.class); 44 45 private static int quota = 100*1024; // Default to 100 K. 46 private static Type type = Type.store_and_bounce; 47 48 private static List<OfflineMessageListener> listeners = new CopyOnWriteArrayList<>(); 49 50 private OfflineMessageStore messageStore; 51 private JID serverAddress; 52 private PacketRouter router; 53 OfflineMessageStrategy()54 public OfflineMessageStrategy() { 55 super("Offline Message Strategy"); 56 } 57 getQuota()58 public int getQuota() { 59 return quota; 60 } 61 setQuota(int quota)62 public void setQuota(int quota) { 63 OfflineMessageStrategy.quota = quota; 64 JiveGlobals.setProperty("xmpp.offline.quota", Integer.toString(quota)); 65 } 66 getType()67 public OfflineMessageStrategy.Type getType() { 68 return type; 69 } 70 setType(OfflineMessageStrategy.Type type)71 public void setType(OfflineMessageStrategy.Type type) { 72 if (type == null) { 73 throw new IllegalArgumentException(); 74 } 75 OfflineMessageStrategy.type = type; 76 JiveGlobals.setProperty("xmpp.offline.type", type.toString()); 77 } 78 storeOffline(Message message)79 public void storeOffline(Message message) { 80 if (message != null) { 81 // Do nothing if the message was sent to the server itself, an anonymous user or a non-existent user 82 // Also ignore message carbons 83 JID recipientJID = message.getTo(); 84 if (recipientJID == null || serverAddress.equals(recipientJID) || 85 recipientJID.getNode() == null || 86 message.getExtension("received", "urn:xmpp:carbons:2") != null || 87 !UserManager.getInstance().isRegisteredUser(recipientJID, false)) { 88 return; 89 } 90 91 // Do not store messages if communication is blocked 92 PrivacyList list = 93 PrivacyListManager.getInstance().getDefaultPrivacyList(recipientJID.getNode()); 94 if (list != null && list.shouldBlockPacket(message)) { 95 if (message.getType() == Message.Type.error){ 96 Log.debug("Avoid generating an error in response to a stanza that itself is an error (to avoid the chance of entering an endless back-and-forth of exchanging errors). Suppress sending an {} error in response to: {}", PacketError.Condition.service_unavailable, message); 97 return; 98 } 99 Message result = message.createCopy(); 100 result.setTo(message.getFrom()); 101 result.setFrom(message.getTo()); 102 result.setError(PacketError.Condition.service_unavailable); 103 104 XMPPServer.getInstance().getPacketRouter().route(result); 105 return; 106 } 107 108 // 8.5.2. localpart@domainpart 109 // 8.5.2.2. No Available or Connected Resources 110 if (recipientJID.getResource() == null) { 111 if (message.getType() == Message.Type.headline || message.getType() == Message.Type.error) { 112 // For a message stanza of type "headline" or "error", the server MUST silently ignore the message. 113 return; 114 } 115 // // For a message stanza of type "groupchat", the server MUST return an error to the sender, which SHOULD be <service-unavailable/>. 116 else if (message.getType() == Message.Type.groupchat) { 117 bounce(message); 118 return; 119 } 120 } else { 121 // 8.5.3. localpart@domainpart/resourcepart 122 // 8.5.3.2.1. Message 123 124 // For a message stanza of type "normal", "groupchat", or "headline", the server MUST either (a) silently ignore the stanza 125 // or (b) return an error stanza to the sender, which SHOULD be <service-unavailable/>. 126 if (message.getType() == Message.Type.normal || message.getType() == Message.Type.groupchat || message.getType() == Message.Type.headline) { 127 // Depending on the OfflineMessageStragey, we may silently ignore or bounce 128 if (type == Type.bounce) { 129 bounce(message); 130 } 131 // Either bounce or silently ignore, never store such messages 132 return; 133 } 134 // For a message stanza of type "error", the server MUST silently ignore the stanza. 135 else if (message.getType() == Message.Type.error) { 136 return; 137 } 138 } 139 140 switch (type) { 141 case bounce: 142 bounce(message); 143 break; 144 case store: 145 store(message); 146 break; 147 case store_and_bounce: 148 if (underQuota(message)) { 149 store(message); 150 } 151 else { 152 Log.debug( "Unable to store, as user is over storage quota. Bouncing message instead: " + message.toXML() ); 153 bounce(message); 154 } 155 break; 156 case store_and_drop: 157 if (underQuota(message)) { 158 store(message); 159 } else { 160 Log.debug( "Unable to store, as user is over storage quota. Silently dropping message: " + message.toXML() ); 161 } 162 break; 163 case drop: 164 // Drop essentially means silently ignore/do nothing 165 break; 166 } 167 } 168 } 169 170 /** 171 * Registers a listener to receive events. 172 * 173 * @param listener the listener. 174 */ addListener(OfflineMessageListener listener)175 public static void addListener(OfflineMessageListener listener) { 176 if (listener == null) { 177 throw new NullPointerException(); 178 } 179 listeners.add(listener); 180 } 181 182 /** 183 * Unregisters a listener to receive events. 184 * 185 * @param listener the listener. 186 */ removeListener(OfflineMessageListener listener)187 public static void removeListener(OfflineMessageListener listener) { 188 listeners.remove(listener); 189 } 190 underQuota(Message message)191 private boolean underQuota(Message message) { 192 return quota > messageStore.getSize(message.getTo().getNode()) + message.toXML().length(); 193 } 194 store(Message message)195 private void store(Message message) { 196 final OfflineMessage offlineMessage = messageStore.addMessage(message); 197 // Inform listeners that an offline message was stored 198 if (offlineMessage != null && !listeners.isEmpty()) { 199 for (OfflineMessageListener listener : listeners) { 200 try { 201 listener.messageStored(offlineMessage); 202 } catch (Exception e) { 203 Log.warn("An exception occurred while dispatching a 'messageStored' event!", e); 204 } 205 } 206 } 207 } 208 bounce(Message message)209 private void bounce(Message message) { 210 // Do nothing if the sender was the server itself 211 if (message.getFrom() == null || message.getFrom().equals( serverAddress )) { 212 return; 213 } 214 215 try { 216 if (message.getType() == Message.Type.error){ 217 Log.debug("Avoid generating an error in response to a stanza that itself is an error (to avoid the chance of entering an endless back-and-forth of exchanging errors). Suppress sending an {} error in response to: {}", PacketError.Condition.service_unavailable, message); 218 } else { 219 // Generate a rejection response to the sender 220 Message errorResponse = message.createCopy(); 221 // return an error stanza to the sender, which SHOULD be <service-unavailable/> 222 errorResponse.setError(PacketError.Condition.service_unavailable); 223 errorResponse.setFrom(message.getTo()); 224 errorResponse.setTo(message.getFrom()); 225 // Send the response 226 router.route(errorResponse); 227 // Inform listeners that an offline message was bounced 228 } 229 if (!listeners.isEmpty()) { 230 for (OfflineMessageListener listener : listeners) { 231 try { 232 listener.messageBounced(message); 233 } catch (Exception e) { 234 Log.warn("An exception occurred while dispatching a 'messageBounced' event!", e); 235 } 236 } 237 } 238 } 239 catch (Exception e) { 240 Log.error(e.getMessage(), e); 241 } 242 } 243 244 @Override initialize(XMPPServer server)245 public void initialize(XMPPServer server) { 246 super.initialize(server); 247 messageStore = server.getOfflineMessageStore(); 248 router = server.getPacketRouter(); 249 serverAddress = new JID(server.getServerInfo().getXMPPDomain()); 250 251 JiveGlobals.migrateProperty("xmpp.offline.quota"); 252 JiveGlobals.migrateProperty("xmpp.offline.type"); 253 254 String quota = JiveGlobals.getProperty("xmpp.offline.quota"); 255 if (quota != null && quota.length() > 0) { 256 OfflineMessageStrategy.quota = Integer.parseInt(quota); 257 } 258 String type = JiveGlobals.getProperty("xmpp.offline.type"); 259 if (type != null && type.length() > 0) { 260 OfflineMessageStrategy.type = Type.valueOf(type); 261 } 262 } 263 264 @Override getFeatures()265 public Iterator<String> getFeatures() { 266 switch (type) { 267 case store: 268 case store_and_bounce: 269 case store_and_drop: 270 // http://xmpp.org/extensions/xep-0160.html#disco 271 return Collections.singleton("msgoffline").iterator(); 272 } 273 return Collections.emptyIterator(); 274 } 275 276 /** 277 * Strategy types. 278 */ 279 public enum Type { 280 281 /** 282 * All messages are bounced to the sender. 283 */ 284 bounce, 285 286 /** 287 * All messages are silently dropped. 288 */ 289 drop, 290 291 /** 292 * All messages are stored. 293 */ 294 store, 295 296 /** 297 * Messages are stored up to the storage limit, and then bounced. 298 */ 299 store_and_bounce, 300 301 /** 302 * Messages are stored up to the storage limit, and then silently dropped. 303 */ 304 store_and_drop 305 } 306 } 307