/**
* $Revision $
* $Date $
*
* Copyright (C) 2005-2010 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.ofskype;
import java.sql.*;
import java.io.File;
import java.util.*;
import java.net.*;
import java.util.concurrent.*;
import org.apache.tomcat.InstanceManager;
import org.apache.tomcat.SimpleInstanceManager;
import org.jivesoftware.util.*;
import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.openfire.http.HttpBindManager;
import org.jivesoftware.openfire.cluster.ClusterEventListener;
import org.jivesoftware.openfire.cluster.ClusterManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.sip.sipaccount.*;
import org.jivesoftware.openfire.handler.IQHandler;
import org.jivesoftware.openfire.IQHandlerInfo;
import org.jivesoftware.database.DbConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.eclipse.jetty.apache.jsp.JettyJasperInitializer;
import org.eclipse.jetty.plus.annotation.ContainerInitializer;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.webapp.WebAppContext;
import org.xmpp.packet.*;
import org.dom4j.*;
import net.sf.json.*;
import org.ifsoft.skype.SkypeClient;
import org.ifsoft.sip.*;
import javax.sip.*;
import javax.sip.message.*;
import javax.sdp.SdpFactory;
import javax.sdp.SessionDescription;
import javax.sdp.MediaDescription;
import javax.sdp.Attribute;
public class OfSkypePlugin implements Plugin, ClusterEventListener, PropertyEventListener {
private static final Logger Log = LoggerFactory.getLogger(OfSkypePlugin.class);
private XMPPServer server;
private boolean sipPluginAvailable;
private boolean freeswitchPluginAvailable;
public static OfSkypePlugin self;
private ExecutorService executor;
public ConcurrentHashMap<String, SkypeClient> clients = new ConcurrentHashMap<String, SkypeClient>();
public ConcurrentHashMap<String, CallSession> callSessions = new ConcurrentHashMap<String, CallSession>();
public SipService sipService = null;
private OfSkypeIQHandler ofskypeIQHandler = null;
public String getName() {
return "ofskype";
}
public String getDescription() {
return "OfSkype Plugin";
}
public void initializePlugin(final PluginManager manager, final File pluginDirectory)
{
ContextHandlerCollection contexts = HttpBindManager.getInstance().getContexts();
self = this;
server = XMPPServer.getInstance();
sipPluginAvailable = server.getPluginManager().getPlugin("sip") != null;
freeswitchPluginAvailable = server.getPluginManager().getPlugin("ofswitch") != null;
try {
ClusterManager.addListener(this);
PropertyEventDispatcher.addListener(this);
Log.info("OfSkype Plugin - Initialize Webservice");
// Ensure the JSP engine is initialized correctly (in order to be able to cope with Tomcat/Jasper precompiled JSPs).
final List<ContainerInitializer> initializers2 = new ArrayList<>();
initializers2.add(new ContainerInitializer(new JettyJasperInitializer(), null));
WebAppContext context2 = new WebAppContext(contexts, pluginDirectory.getPath(), "/ofskype");
context2.setClassLoader(this.getClass().getClassLoader());
context2.setAttribute("org.eclipse.jetty.containerInitializers", initializers2);
context2.setAttribute(InstanceManager.class.getName(), new SimpleInstanceManager());
context2.setWelcomeFiles(new String[]{"index.html"});
boolean skypeEnabled = JiveGlobals.getBooleanProperty("skype.enabled", true);
if (skypeEnabled)
{
Log.info("OfSkype Plugin - Scanning for skype accounts");
executor = Executors.newCachedThreadPool();
executor.submit(new Callable<Boolean>()
{
public Boolean call() throws Exception
{
startSipService(pluginDirectory.getAbsolutePath());
List<String> properties = JiveGlobals.getPropertyNames();
for (String propertyName : properties)
{
if (propertyName.indexOf("skype.password.") == 0)
{
startClient(propertyName, JiveGlobals.getProperty(propertyName), null);
}
}
return true;
}
});
Log.info("OfSkype Plugin - Initialize IQ handler ");
ofskypeIQHandler = new OfSkypeIQHandler();
server.getIQRouter().addHandler(ofskypeIQHandler);
}
} catch (Exception e) {
Log.error("Could NOT start openfire skype", e);
}
}
public void destroyPlugin() {
PropertyEventDispatcher.removeListener(this);
server.getIQRouter().removeHandler(ofskypeIQHandler);
ofskypeIQHandler = null;
try {
for (SkypeClient client : clients.values())
{
client.close();
}
for (CallSession callSession : callSessions.values())
{
callSession.sendBye();
callSession.sendBye();
}
callSessions.clear();
if (sipService != null) sipService.stop();
executor.shutdown();
} catch (Exception e) {
}
}
public String getDomain()
{
return server.getServerInfo().getXMPPDomain();
}
public String getHostname()
{
return server.getServerInfo().getHostname();
}
public String getIpAddress()
{
String ourHostname = server.getServerInfo().getHostname();
String ourIpAddress = ourHostname;
try {
ourIpAddress = InetAddress.getByName(ourHostname).getHostAddress();
} catch (Exception e) {
}
return ourIpAddress;
}
private void startSipService(String pluginDirectoryPath)
{
Log.info("OfSkype Plugin - Starting SIP client service");
Properties properties = new Properties();
String logDir = pluginDirectoryPath + File.separator + ".." + File.separator + ".." + File.separator + "logs" + File.separator;
String port = JiveGlobals.getProperty("skype.sip.port", "5030");
String ipAddress = JiveGlobals.getProperty("skype.sip.hostname", getIpAddress());
properties.setProperty("com.voxbone.kelpie.hostname", ipAddress);
properties.setProperty("com.voxbone.kelpie.ip", ipAddress);
properties.setProperty("com.voxbone.kelpie.sip_port", port);
properties.setProperty("javax.sip.IP_ADDRESS", ipAddress);
properties.setProperty("javax.sip.STACK_NAME", "Openfire Skype SIP");
properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "99");
properties.setProperty("gov.nist.javax.sip.SERVER_LOG", logDir + "sip_server.log");
properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", logDir + "sip_debug.log");
sipService = new SipService(properties);
Log.info("OfSkype Plugin - Initialized SIP stack at " + ipAddress + ":" + port);
}
private String startClient(String propertyName, String propertyValue, JSONObject requestJSON)
{
String response = null;
String presence = "Online";
String note = "Ready";
boolean contacts = false;
boolean groups = false;
if (requestJSON != null)
{
if (requestJSON.has("presence")) presence = requestJSON.getString("presence");
if (requestJSON.has("note")) note = requestJSON.getString("note");
if (requestJSON.has("contacts")) contacts = "true".equals(requestJSON.getString("contacts"));
if (requestJSON.has("groups")) groups = "true".equals(requestJSON.getString("groups"));
}
int pos = propertyName.indexOf("skype.password.");
if (pos == 0)
{
try {
String username = propertyName.substring(pos + 15);
Log.info("OfSkype Plugin - Starting skype account " + username);
String password = propertyValue;
String[] user = username.split("@");
String domain = user[1];
String userid = user[0];
String userName = (user[0] + "_" + user[1]).replaceAll("\\.", "_");
if (clients.containsKey(propertyName))
{
SkypeClient client = clients.remove(propertyName);
if (client.registerProcessing != null)
{
client.registerProcessing.unregister();
}
client.close();
client = null;
}
SkypeClient skypeClient = new SkypeClient(username, password, domain, username);
skypeClient.setClientId(new JID(JID.escapeNode(userName) + "@" + server.getServerInfo().getXMPPDomain()));
skypeClient.doLogin();
skypeClient.makeMeAvailable(presence);
skypeClient.setNote(note);
skypeClient.getMyLinks();
SipAccount sipAccount = SipAccountDAO.getAccountByUser(JID.escapeNode(username));
if (sipAccount == null) sipAccount = SipAccountDAO.getAccountByUser(userName);
if (sipAccount != null)
{
Log.info("OfSkype Plugin - Starting sip account " + sipAccount.getSipUsername());
ProxyCredentials sip = new ProxyCredentials();
sip.setName(skypeClient.myName);
sip.setXmppUserName(sipAccount.getUsername());
sip.setUserName(sipAccount.getSipUsername());
sip.setAuthUserName(sipAccount.getAuthUsername());
sip.setUserDisplay(skypeClient.myName);
sip.setPassword(sipAccount.getPassword().toCharArray());
sip.setHost(sipAccount.getServer());
sip.setProxy(sipAccount.getOutboundproxy());
sip.setRealm(sipAccount.getServer());
skypeClient.myConferenceNumber = sipAccount.getVoiceMailNumber();
skypeClient.registerProcessing = new RegisterProcessing(sipAccount.getServer(), sipAccount.getServer(), sip);
}
clients.put(propertyName, skypeClient);
}
catch (Exception e) {
Log.error("OfSkype error handling profile " + propertyName + " = " + propertyValue, e);
response = e.toString();
}
} else response = "skype.password. prefix missing";
return response;
}
//-------------------------------------------------------
//
//
//
//-------------------------------------------------------
@Override
public void joinedCluster()
{
Log.info("OfSkype Plugin - joinedCluster");
}
@Override
public void joinedCluster(byte[] arg0)
{
}
@Override
public void leftCluster()
{
Log.info("OfSkype Plugin - leftCluster");
}
@Override
public void leftCluster(byte[] arg0)
{
}
@Override
public void markedAsSeniorClusterMember()
{
Log.info("OfSkype Plugin - markedAsSeniorClusterMember");
}
//-------------------------------------------------------
//
//
//
//-------------------------------------------------------
public void propertySet(final String property, final Map params)
{
executor.submit(new Callable<Boolean>()
{
public Boolean call() throws Exception
{
if (property.indexOf("skype.password.") == 0)
{
startClient(property, (String)params.get("value"), null);
}
return true;
}
});
}
public void propertyDeleted(String property, Map<String, Object> params)
{
if (clients.containsKey(property))
{
SkypeClient client = clients.remove(property);
client.close();
client = null;
}
}
public void xmlPropertySet(String property, Map<String, Object> params)
{
}
public void xmlPropertyDeleted(String property, Map<String, Object> params)
{
}
//-------------------------------------------------------
//
//
//
//-------------------------------------------------------
public void makeCall(String sipUrl, String sdp, JSONObject json)
{
Log.info("OfSkype Plugin - makeCall " + sipUrl + "\n" + sdp + "\n" + json);
sdp = sdp.replace("RTP/AVP","RTP/SAVP");
try {
SessionDescription sd = SdpFactory.getInstance().createSessionDescription(sdp);
MediaDescription md = ((MediaDescription) sd.getMediaDescriptions(false).get(0));
Vector<Attribute> attributes = (Vector<Attribute>) md.getAttributes(false);
String ssrc = null;
Vector<Attribute> deletes = new Vector<Attribute>();
boolean rtcpMux = false;
String[] ssrcs = null;
try {
for (Attribute attrib : attributes)
{
if (attrib.getName().equals("rtcp-mux")) rtcpMux = true;
}
for (Attribute attrib : attributes)
{
Log.info("makeCall attribute " + attrib.getName() + "=" + attrib.getValue());
if (attrib.getName().equals("crypto"))
{
String crypto = attrib.getValue();
attrib.setValue(crypto.substring(0, crypto.indexOf("|")));
if (crypto.indexOf("1:1") > -1) deletes.add(attrib);
}
if (attrib.getName().equals("x-ssrc-range"))
{
ssrcs = attrib.getValue().split("-");
deletes.add(attrib);
}
if (attrib.getName().equals("candidate"))
{
attrib.setValue(attrib.getValue().replace("UDP","udp").replace("TCP-PASS","tcp").replace("TCP-ACT","tcp") + " generation 0");
//if (attrib.getValue().indexOf("typ host") > -1) deletes.add(attrib);
}
if (attrib.getName().equals("cryptoscale")) deletes.add(attrib);
if (attrib.getName().equals("x-candidate-ipv6")) deletes.add(attrib);
if (attrib.getName().equals("rtcp-fb")) deletes.add(attrib);
}
for (Attribute attrib : deletes)
{
attributes.remove(attrib);
}
if (ssrcs != null)
{
attributes.add(SdpFactory.getInstance().createAttribute("ssrc", ssrcs[0] + " cname:CYWnkHxGZPAENJW9"));
attributes.add(SdpFactory.getInstance().createAttribute("ssrc", ssrcs[0] + " msid:YOGW0gxEFBdOm7AsbjxMDJwlLTKNkBId a0"));
attributes.add(SdpFactory.getInstance().createAttribute("ssrc", ssrcs[0] + " mslabel:YOGW0gxEFBdOm7AsbjxMDJwlLTKNkBId"));
attributes.add(SdpFactory.getInstance().createAttribute("ssrc", ssrcs[0] + " label:YOGW0gxEFBdOm7AsbjxMDJwlLTKNkBIda0"));
}
attributes.add(SdpFactory.getInstance().createAttribute("sendrecv", null));
} catch (Exception ec) {
Log.error("acceptWithAnswer error", ec);
}
String key = "skype.password." + sipUrl;
if (clients.containsKey(key))
{
SkypeClient client = clients.get(key);
String newSDP = sd.toString();
Log.info("OfSkype Plugin - makeCall sendInvite " + client.myName);
String callId = json.getJSONObject("_links").getJSONObject("conversation").getString("href");
String sip = json.getJSONObject("_embedded").getJSONObject("from").getString("uri");
ProxyCredentials sipAccount = client.registerProcessing.proxyCredentials;
SipService.sipAccount = sipAccount;
String from = "sip:" + sipAccount.getUserName() + "@" + sipAccount.getRealm();
String to = "sip:" + client.myConferenceNumber + "@" + sipAccount.getRealm();
CallSession callSession = new CallSession(newSDP, callId, from, to, client, json);
callSessions.put(callId, callSession);
SipService.callSessions.put(sipAccount.getUserName() + client.myConferenceNumber, callSession);
SipService.sendInvite(callSession);
} else Log.warn("OfSkype Plugin - makeCall - cant find client for " + key);
} catch (Exception e) {
Log.error("OfSkype Plugin - makeCall", e);
}
}
//-------------------------------------------------------
//
// custom IQ handler for JSON request/response
//
//-------------------------------------------------------
public class OfSkypeIQHandler extends IQHandler
{
public OfSkypeIQHandler()
{
super("Openfire Skype IQ Handler");
}
@Override public IQ handleIQ(IQ iq)
{
IQ reply = IQ.createResultIQ(iq);
try {
Log.info("Openfire Skype handleIQ \n" + iq.toString());
final Element element = iq.getChildElement();
JID from = iq.getFrom();
JSONObject requestJSON = new JSONObject(element.getText());
String action = requestJSON.getString("action");
Log.info("Openfire Skype handleIQ action " + action);
if ("start_skype_user".equals(action)) startSkypeUser(iq.getFrom().getNode(), reply, requestJSON);
if ("stop_skype_user".equals(action)) stopSkypeUser(iq.getFrom().getNode(), reply, requestJSON);
return reply;
} catch(Exception e) {
Log.error("Openfire Skype handleIQ", e);
reply.setError(new PacketError(PacketError.Condition.internal_server_error, PacketError.Type.modify, e.toString()));
return reply;
}
}
@Override public IQHandlerInfo getInfo()
{
return new IQHandlerInfo("request", "http://igniterealtime.org/protocol/ofskype");
}
private void startSkypeUser(String username, IQ reply, JSONObject requestJSON)
{
Element childElement = reply.setChildElement("response", "http://igniterealtime.org/protocol/ofskype");
try {
String password = requestJSON.getString("password");
String sipuri = requestJSON.getString("sipuri");
Log.info("Openfire Skype startSkypeUser " + sipuri);
String property = "skype.password." + sipuri;
String response = startClient(property, password, requestJSON);
if (response != null)
{
reply.setError(new PacketError(PacketError.Condition.not_allowed, PacketError.Type.modify, "User " + username + " " + " " + response));
}
} catch (Exception e) {
reply.setError(new PacketError(PacketError.Condition.not_allowed, PacketError.Type.modify, "User " + username + " " + " " + e));
}
}
private void stopSkypeUser(String username, IQ reply, JSONObject requestJSON)
{
Element childElement = reply.setChildElement("response", "http://igniterealtime.org/protocol/ofskype");
try {
String sipuri = requestJSON.getString("sipuri");
String property = "skype.password." + sipuri;
Log.info("Openfire Skype stopSkypeUser " + sipuri);
if (clients.containsKey(property))
{
SkypeClient client = clients.remove(property);
client.close();
client = null;
} else {
reply.setError(new PacketError(PacketError.Condition.not_allowed, PacketError.Type.modify, "User " + username + " skype session not found"));
}
} catch (Exception e1) {
reply.setError(new PacketError(PacketError.Condition.not_allowed, PacketError.Type.modify, requestJSON.toString() + " " + e1));
}
}
private String removeNull(String s)
{
if (s == null)
{
return "";
}
return s.trim();
}
}
//-------------------------------------------------------
//
//
//
//-------------------------------------------------------
}