/*
* Copyright (C) 2004-2008 Jive Software. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.openfire.plugin;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.dom4j.Element;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.openfire.group.Group;
import org.jivesoftware.openfire.group.GroupManager;
import org.jivesoftware.openfire.group.GroupNotFoundException;
import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.component.Component;
import org.xmpp.component.ComponentException;
import org.xmpp.component.ComponentManager;
import org.xmpp.component.ComponentManagerFactory;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import org.xmpp.packet.Packet;
import org.xmpp.packet.PacketError;
import org.xmpp.packet.Presence;
/**
* Broadcast service plugin. It accepts messages and broadcasts them out to
* groups of connected users. The address <tt>all@[serviceName].[server]</tt> is
* reserved for sending a broadcast message to all connected users. Otherwise,
* broadcast messages can be sent to groups of users by using addresses
* in the form <tt>[group]@[serviceName].[server]</tt>.
*
* @author Matt Tucker
*/
public class BroadcastPlugin implements Plugin, Component, PropertyEventListener {
private static final Logger Log = LoggerFactory.getLogger(BroadcastPlugin.class);
private String serviceName;
private SessionManager sessionManager;
private GroupManager groupManager;
private List<JID> allowedUsers;
private boolean groupMembersAllowed;
private boolean disableGroupPermissions;
private boolean all2ofline;
private String messagePrefix;
private ComponentManager componentManager;
private PluginManager pluginManager;
private UserManager userManager;
/**
* Constructs a new broadcast plugin.
*/
public BroadcastPlugin() {
serviceName = JiveGlobals.getProperty("plugin.broadcast.serviceName", "broadcast");
disableGroupPermissions = JiveGlobals.getBooleanProperty(
"plugin.broadcast.disableGroupPermissions");
groupMembersAllowed = JiveGlobals.getBooleanProperty(
"plugin.broadcast.groupMembersAllowed", true);
allowedUsers = stringToList(JiveGlobals.getProperty("plugin.broadcast.allowedUsers", ""));
all2ofline = JiveGlobals.getBooleanProperty("plugin.broadcast.all2offline", false);
messagePrefix = JiveGlobals.getProperty("plugin.broadcast.messagePrefix", null);
}
// Plugin Interface
public void initializePlugin(PluginManager manager, File pluginDirectory) {
pluginManager = manager;
sessionManager = SessionManager.getInstance();
groupManager = GroupManager.getInstance();
userManager = UserManager.getInstance();
// Register as a component.
componentManager = ComponentManagerFactory.getComponentManager();
try {
componentManager.addComponent(serviceName, this);
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
PropertyEventDispatcher.addListener(this);
}
public void destroyPlugin() {
PropertyEventDispatcher.removeListener(this);
// Unregister component.
if (componentManager != null) {
try {
componentManager.removeComponent(serviceName);
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
componentManager = null;
userManager = null;
pluginManager = null;
sessionManager = null;
groupManager = null;
allowedUsers.clear();
}
public void initialize(JID jid, ComponentManager componentManager) {
}
public void start() {
}
public void shutdown() {
}
// Component Interface
public String getName() {
// Get the name from the plugin.xml file.
return pluginManager.getName(this);
}
public String getDescription() {
// Get the description from the plugin.xml file.
return pluginManager.getDescription(this);
}
public void processPacket(Packet packet) {
boolean canProceed = false;
Group group = null;
String toNode = packet.getTo().getNode();
// Check if user is allowed to send packet to this service[+group]
boolean targetAll = "all".equals(toNode);
if (targetAll) {
// See if the user is allowed to send the packet.
JID address = new JID(packet.getFrom().toBareJID());
if (allowedUsers.isEmpty() || allowedUsers.contains(address)) {
canProceed = true;
}
}
else {
try {
if (toNode != null) {
group = groupManager.getGroup(toNode);
boolean isGroupUser = group.isUser(packet.getFrom()) ||
group.isUser(new JID(packet.getFrom().toBareJID()));
if (disableGroupPermissions || (groupMembersAllowed && isGroupUser) ||
allowedUsers.contains(new JID(packet.getFrom().toBareJID()))) {
canProceed = true;
}
}
}
catch (GroupNotFoundException e) {
// Ignore.
}
}
if (packet instanceof Message) {
// Respond to incoming messages
Message message = (Message)packet;
processMessage(message, targetAll, group, canProceed);
}
else if (packet instanceof Presence) {
// Respond to presence subscription request or presence probe
Presence presence = (Presence) packet;
processPresence(canProceed, presence);
}
else if (packet instanceof IQ) {
// Handle disco packets
IQ iq = (IQ) packet;
// Ignore IQs of type ERROR or RESULT
if (IQ.Type.error == iq.getType() || IQ.Type.result == iq.getType()) {
return;
}
processIQ(iq, targetAll, group, canProceed);
}
}
private void processMessage(Message message, boolean targetAll, Group group,
boolean canProceed) {
// Check to see if trying to broadcast to all connected users.
if (targetAll) {
if (!canProceed) {
Message error = new Message();
if (message.getID() != null) {
error.setID(message.getID());
}
error.setError(PacketError.Condition.not_allowed);
error.setTo(message.getFrom());
error.setFrom(message.getTo());
error.setSubject("Error sending broadcast message");
error.setBody("Not allowed to send a broadcast message to " +
message.getTo());
try {
componentManager.sendPacket(this, error);
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
return;
}
if ( ( messagePrefix != null ) && ( message.getBody() != null ) ) {
message.setBody(messagePrefix + " " + message.getBody());
}
if (all2ofline==false) {
// send to online users
sessionManager.broadcast(message);
} else {
// send to all users
Collection<User> users = userManager.getUsers();
String xmppdomain = "@" + JiveGlobals.getProperty("xmpp.domain");
for (User u : users)
{
Message newMessage = message.createCopy();
newMessage.setTo(u.getUsername() + xmppdomain);
try {
componentManager.sendPacket(this, newMessage);
} catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
}
}
// See if the name is a group.
else {
if (group == null) {
// The address is not recognized so send an error message back.
Message error = new Message();
if (message.getID() != null) {
error.setID(message.getID());
}
error.setTo(message.getFrom());
error.setFrom(message.getTo());
error.setError(PacketError.Condition.not_allowed);
error.setSubject("Error sending broadcast message");
error.setBody("Address not valid: " +
message.getTo());
try {
componentManager.sendPacket(this, error);
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
else if (canProceed) {
// Broadcast message to group users. Users that are offline will get
// the message when they come back online
if ( ( messagePrefix != null ) && ( message.getBody() != null ) ) {
message.setBody(messagePrefix + " " + message.getBody());
}
for (JID userJID : group.getMembers()) {
Message newMessage = message.createCopy();
newMessage.setTo(userJID);
try {
componentManager.sendPacket(this, newMessage);
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
}
else {
// Otherwise, the address is recognized so send an error message back.
Message error = new Message();
if (message.getID() != null) {
error.setID(message.getID());
}
error.setTo(message.getFrom());
error.setFrom(message.getTo());
error.setError(PacketError.Condition.not_allowed);
error.setSubject("Error sending broadcast message");
error.setBody("Not allowed to send a broadcast message to " +
message.getTo());
try {
componentManager.sendPacket(this, error);
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
}
}
private void processPresence(boolean canProceed, Presence presence) {
try {
if (Presence.Type.subscribe == presence.getType()) {
// Accept all presence requests if user has permissions
// Reply that the subscription request was approved or rejected
Presence reply = new Presence();
reply.setTo(presence.getFrom());
reply.setFrom(presence.getTo());
reply.setType(canProceed ? Presence.Type.subscribed : Presence.Type.unsubscribed);
componentManager.sendPacket(this, reply);
}
else if (Presence.Type.unsubscribe == presence.getType()) {
// Send confirmation of unsubscription
Presence reply = new Presence();
reply.setTo(presence.getFrom());
reply.setFrom(presence.getTo());
reply.setType(Presence.Type.unsubscribed);
componentManager.sendPacket(this, reply);
if (!canProceed) {
// Send unavailable presence of the service
reply = new Presence();
reply.setTo(presence.getFrom());
reply.setFrom(presence.getTo());
reply.setType(Presence.Type.unavailable);
componentManager.sendPacket(this, reply);
}
}
else if (Presence.Type.probe == presence.getType()) {
// Send that the service is available
Presence reply = new Presence();
reply.setTo(presence.getFrom());
reply.setFrom(presence.getTo());
if (!canProceed) {
// Send forbidden error since user is not allowed
reply.setError(PacketError.Condition.forbidden);
}
componentManager.sendPacket(this, reply);
}
}
catch (ComponentException e) {
Log.error(e.getMessage(), e);
}
}
private void processIQ(IQ iq, boolean targetAll, Group group,
boolean canProceed) {
IQ reply = IQ.createResultIQ(iq);
Element childElement = iq.getChildElement();
String namespace = childElement.getNamespaceURI();
Element childElementCopy = iq.getChildElement().createCopy();
reply.setChildElement(childElementCopy);
if ("http://jabber.org/protocol/disco#info".equals(namespace)) {
if (iq.getTo().getNode() == null) {
// Return service identity and features
Element identity = childElementCopy.addElement("identity");
identity.addAttribute("category", "component");
identity.addAttribute("type", "generic");
identity.addAttribute("name", "Broadcast service");
childElementCopy.addElement("feature")
.addAttribute("var", "http://jabber.org/protocol/disco#info");
childElementCopy.addElement("feature")
.addAttribute("var", "http://jabber.org/protocol/disco#items");
}
else {
if (targetAll) {
// Return identity and features of the "all" group
Element identity = childElementCopy.addElement("identity");
identity.addAttribute("category", "component");
identity.addAttribute("type", "generic");
identity.addAttribute("name", "Broadcast all connected users");
childElementCopy.addElement("feature")
.addAttribute("var", "http://jabber.org/protocol/disco#info");
}
else if (group != null && canProceed) {
// Return identity and features of the "all" group
Element identity = childElementCopy.addElement("identity");
identity.addAttribute("category", "component");
identity.addAttribute("type", "generic");
identity.addAttribute("name", "Broadcast " + group.getName());
childElementCopy.addElement("feature")
.addAttribute("var", "http://jabber.org/protocol/disco#info");
}
else {
// Group not found or not allowed to use that group so
// answer item_not_found error
reply.setError(PacketError.Condition.item_not_found);
}
}
}
else if ("http://jabber.org/protocol/disco#items".equals(namespace)) {
if (iq.getTo().getNode() == null) {
// Return the list of groups hosted by the service that can be used by the user
Collection<Group> groups;
JID address = new JID(iq.getFrom().toBareJID());
if (allowedUsers.contains(address)) {
groups = groupManager.getGroups();
}
else {
groups = groupManager.getGroups(iq.getFrom());
}
for (Group userGroup : groups) {
try {
JID groupJID = new JID(userGroup.getName() + "@" + serviceName + "." +
componentManager.getServerName());
childElementCopy.addElement("item")
.addAttribute("jid", groupJID.toString());
}
catch (Exception e) {
// Group name is not valid to be used as a JID
}
}
if (allowedUsers.isEmpty() || allowedUsers.contains(address)) {
// Add the "all" group to the list
childElementCopy.addElement("item").addAttribute("jid",
"all@" + serviceName + "." + componentManager.getServerName());
}
}
}
else {
// Answer an error since the server can't handle the requested namespace
reply.setError(PacketError.Condition.service_unavailable);
}
try {
componentManager.sendPacket(this, reply);
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
}
// Other Methods
/**
* Returns the service name of this component, which is "broadcast" by default.
*
* @return the service name of this component.
*/
public String getServiceName() {
return serviceName;
}
/**
* Sets the service name of this component, which is "broadcast" by default.
*
* @param serviceName the service name of this component.
*/
public void setServiceName(String serviceName) {
JiveGlobals.setProperty("plugin.broadcast.serviceName", serviceName);
}
/**
* Returns a collection of the addresses of users allowed to send broadcast
* messages. If no users are defined, anyone can send broadcast messages to
* all users. Additional users may also be allowed to send broadcast messages
* to specific groups depending on the group settings.
*
* @return the users allowed to send broadcast messages.
*/
public Collection<JID> getGlobalAllowedUsers() {
return allowedUsers;
}
/**
* Sets the collection of addresses of users allowed to send broadcast
* messages. If the collection is empty, anyone can send broadcast messages.
* Additional users may also be allowed to send broadcast messages to
* specific groups depending on the group settings.
*
* @param allowedUsers collection of users allowed to send broadcast messages
* to all users.
*/
public void setGlobalAllowedUsers(Collection<String> allowedUsers) {
StringBuilder buf = new StringBuilder();
for (String jid : allowedUsers) {
buf.append(jid).append(",");
}
JiveGlobals.setProperty("plugin.broadcast.allowedUsers", buf.toString());
}
/**
* Returns true if all permission checking on sending messages to groups is disabled
* (enabled by default). When disabled, any user in the system can send a message to
* a group.
*
* @return true if group permission checking is disabled.
*/
public boolean isGroupPermissionsDisabled() {
return disableGroupPermissions;
}
/**
* Enables or disables permission checking when sending messages to a group. When
* disabled, any user in the system can send a message to a group.
*
* @param disableGroupPermissions true if group permission checking should be disabled.
*/
public void setGroupPermissionsDisabled(boolean disableGroupPermissions) {
this.disableGroupPermissions = disableGroupPermissions;
JiveGlobals.setProperty("plugin.broadcast.disableGroupPermissions",
Boolean.toString(disableGroupPermissions));
}
/**
* Returns true if normal group members are allowed to send broadcast messages
* to groups they belong to. Otherwise, only group administrators can send
* broadcast messages to groups. Global allowed users can also send messages to
* groups.
*
* @return true if group members are allowed to broadcast messages; otherwise only
* group admins are allowed.
*/
public boolean isGroupMembersAllowed() {
return groupMembersAllowed;
}
/**
* Sets whether normal group members are allowed to send broadcast messages
* to groups they belong to. Otherwise, only group administrators can send
* broadcast messages to groups. Global allowed users can also send messages to
* groups.
*
* @param allowed true if group members are allowed to broadcast messages; otherwise only
* group admins are allowed.
*/
public void setGroupMembersAllowed(boolean allowed) {
this.groupMembersAllowed = allowed;
JiveGlobals.setProperty("plugin.broadcast.groupMembersAllowed", Boolean.toString(allowed));
}
// PropertyEventListener Methods
public void propertySet(String property, Map<String, Object> params) {
if (property.equals("plugin.broadcast.groupMembersAllowed")) {
this.groupMembersAllowed = Boolean.parseBoolean((String)params.get("value"));
}
else if (property.equals("plugin.broadcast.disableGroupPermissions")) {
this.disableGroupPermissions = Boolean.parseBoolean((String)params.get("value"));
}
else if (property.equals("plugin.broadcast.allowedUsers")) {
this.allowedUsers = stringToList((String)params.get("value"));
}
else if (property.equals("plugin.broadcast.serviceName")) {
changeServiceName((String)params.get("value"));
}
}
public void propertyDeleted(String property, Map<String, Object> params) {
if (property.equals("plugin.broadcast.groupMembersAllowed")) {
this.groupMembersAllowed = true;
}
else if (property.equals("plugin.broadcast.disableGroupPermissions")) {
this.disableGroupPermissions = false;
}
else if (property.equals("plugin.broadcast.allowedUsers")) {
this.allowedUsers = Collections.emptyList();
}
else if (property.equals("plugin.broadcast.serviceName")) {
changeServiceName("broadcast");
}
}
public void xmlPropertySet(String property, Map<String, Object> params) {
// Ignore.
}
public void xmlPropertyDeleted(String property, Map<String, Object> params) {
// Ignore.
}
/**
* Changes the service name to a new value.
*
* @param serviceName the service name.
*/
private void changeServiceName(String serviceName) {
if (serviceName == null) {
throw new NullPointerException("Service name cannot be null");
}
if (this.serviceName.equals(serviceName)) {
return;
}
// Re-register the service.
try {
componentManager.removeComponent(this.serviceName);
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
try {
componentManager.addComponent(serviceName, this);
}
catch (Exception e) {
Log.error(e.getMessage(), e);
}
this.serviceName = serviceName;
}
/**
* Returns a comma-delimitted list of strings into a Collection of Strings.
*
* @param str the String.
* @return a list.
*/
private List<JID> stringToList(String str) {
List<JID> values = new ArrayList<JID>();
StringTokenizer tokens = new StringTokenizer(str, ",");
while (tokens.hasMoreTokens()) {
String value = tokens.nextToken().trim();
if (!value.equals("")) {
// See if this is a full JID or just a username.
if (value.contains("@")) {
values.add(new JID(value));
}
else {
values.add(XMPPServer.getInstance().createJID(value, null));
}
}
}
return values;
}
}