/** * Copyright 2012 Voxbone SA/NV * * 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.ifsoft.sip; import org.jivesoftware.util.*; import java.text.*; import java.util.*; import java.util.concurrent.*; import javax.sip.*; import javax.sip.address.*; import javax.sip.header.*; import javax.sip.message.*; import org.slf4j.*; import org.slf4j.Logger; import org.jivesoftware.openfire.plugin.ofskype.OfSkypePlugin; import org.jivesoftware.util.*; /** * Functions useful for building SIP messages * */ public class SipService { private static final Logger Log = LoggerFactory.getLogger(SipService.class); public static AddressFactory addressFactory; public static HeaderFactory headerFactory; public static MessageFactory messageFactory; public static VideoBridgeSipListener sipListener; public static SipProvider sipProvider; public static ProxyCredentials sipAccount; public static SipService self; private SipStack sipStack; private SipFactory sipFactory; private int localport; private static String localip; private static String remoteip; private static String agentName; private static String clientVersion; public static ConcurrentHashMap<String, CallSession> callSessions = new ConcurrentHashMap<String, CallSession>(); public SipService(Properties properties) { self = this; localip = properties.getProperty("com.voxbone.kelpie.ip"); localport = Integer.parseInt(properties.getProperty("com.voxbone.kelpie.sip_port", "5030")); remoteip = properties.getProperty("com.voxbone.kelpie.sip_gateway"); sipListener = new VideoBridgeSipListener(properties.getProperty("com.voxbone.kelpie.hostname")); agentName = properties.getProperty("com.voxbone.kelpie.sip.user-agent", "TraderLynk SIP User Agent"); clientVersion = properties.getProperty("com.voxbone.kelpie.version"); String nameOS = System.getProperty("os.name"); String versOS = System.getProperty("os.version"); agentName = agentName + " " + clientVersion + " (" + nameOS + "/" + versOS + ")"; sipFactory = SipFactory.getInstance(); sipFactory.setPathName("gov.nist"); try { sipStack = sipFactory.createSipStack(properties); headerFactory = sipFactory.createHeaderFactory(); addressFactory = sipFactory.createAddressFactory(); messageFactory = sipFactory.createMessageFactory(); } catch (PeerUnavailableException e) { Log.error(e.toString(), e); } try { String ipAddress = JiveGlobals.getProperty("skype.sip.hostname", OfSkypePlugin.self.getIpAddress()); ListeningPoint udp = sipStack.createListeningPoint(ipAddress, localport, "udp"); ListeningPoint tcp = sipStack.createListeningPoint(ipAddress, localport, "tcp"); sipProvider = sipStack.createSipProvider(tcp); sipProvider.addListeningPoint(udp); sipProvider.setAutomaticDialogSupportEnabled(false); sipProvider.addSipListener(sipListener); } catch (TransportNotSupportedException e) { Log.error(e.toString(), e); } catch (InvalidArgumentException e) { Log.error(e.toString(), e); } catch (ObjectInUseException e) { Log.error(e.toString(), e); } catch (Exception e) { Log.error(e.toString(), e); } } public static MessageFactory getMessageFactory() { return messageFactory; } public static SipProvider getSipProvider() { return sipProvider; } public static AddressFactory getAddressFactory() { return addressFactory; } public static HeaderFactory getHeaderFactory() { return headerFactory; } public static String getRemoteIP() { return remoteip; } public static String getLocalIP() { ListeningPoint lp = sipProvider.getListeningPoint(); return lp.getIPAddress(); } public void stop() { } public void registerWithAccount(ProxyCredentials sipAccount) { Log.info(String.format("SipService adding SIP registration: %s with user %s host %s", sipAccount.getXmppUserName(), sipAccount.getUserName(), sipAccount.getHost())); String server = JiveGlobals.getProperty("skype.sip.hostname", OfSkypePlugin.self.getIpAddress()); new RegisterProcessing(localip, server, sipAccount); } public void registerWithDefaultProxy() { sipAccount = new ProxyCredentials(); try { String authusername = JiveGlobals.getProperty("voicebridge.default.proxy.sipauthuser", null); String server = JiveGlobals.getProperty("skype.sip.hostname", OfSkypePlugin.self.getIpAddress()); if (authusername != null && authusername.equals("") == false) { String name = JiveGlobals.getProperty("voicebridge.default.proxy.name", "admin"); String username = JiveGlobals.getProperty("voicebridge.default.proxy.username", name); String sipusername = JiveGlobals.getProperty("voicebridge.default.proxy.sipusername", name); String displayname = JiveGlobals.getProperty("voicebridge.default.proxy.sipdisplayname", name); String password = JiveGlobals.getProperty("voicebridge.default.proxy.sippassword", name); String stunServer = JiveGlobals.getProperty("voicebridge.default.proxy.stunserver", localip); String stunPort = JiveGlobals.getProperty("voicebridge.default.proxy.stunport", "3478"); String voicemail = JiveGlobals.getProperty("voicebridge.default.proxy.voicemail", name); String outboundproxy = JiveGlobals.getProperty("voicebridge.default.proxy.outboundproxy", localip); sipAccount.setName(name); sipAccount.setXmppUserName(username); sipAccount.setUserName(sipusername); sipAccount.setAuthUserName(authusername); sipAccount.setUserDisplay(displayname); sipAccount.setPassword(password.toCharArray()); sipAccount.setHost(server); sipAccount.setProxy(outboundproxy); sipAccount.setRealm(server); Log.info(String.format("SipService adding SIP registration: %s with user %s host %s", sipAccount.getXmppUserName(), sipAccount.getUserName(), sipAccount.getHost())); new RegisterProcessing(localip, server, sipAccount); } } catch (Exception e) { Log.error("registerWithDefaultProxy", e); } } public static boolean acceptCall(CallSession cs) { try { Request req = cs.inviteTransaction.getRequest(); Response resp = messageFactory.createResponse(Response.OK, cs.inviteTransaction.getRequest()); ContentTypeHeader cth = headerFactory.createContentTypeHeader("application", "sdp"); Object sdp = cs.buildSDP(false); ToHeader th = (ToHeader) req.getHeader("To"); String dest = ((SipURI) th.getAddress().getURI()).getUser(); ListeningPoint lp = sipProvider.getListeningPoint(); Address localAddress = addressFactory.createAddress("sip:" + dest + "@" + lp.getIPAddress() + ":" + lp.getPort()); ContactHeader ch = headerFactory.createContactHeader(localAddress); AllowHeader allowHeader = headerFactory.createAllowHeader("INVITE, ACK, CANCEL, OPTIONS, BYE, UPDATE, NOTIFY, MESSAGE, SUBSCRIBE, INFO"); resp.addHeader(allowHeader); resp.addHeader(ch); UserAgentHeader userAgent = (UserAgentHeader) headerFactory.createHeader(UserAgentHeader.NAME, agentName); resp.setHeader(userAgent); resp.setContent(sdp, cth); cs.inviteTransaction.sendResponse(resp); } catch (ParseException e) { Log.error("Error accepting call", e); return false; } catch (SipException e) { Log.error("Error accepting call", e); return false; } catch (InvalidArgumentException e) { Log.error("Error accepting call", e); return false; } return true; } public static boolean sendBye(CallSession cs) { Request req; try { if (cs.inviteOutTransaction != null && (cs.sipDialog.getState() == null || cs.sipDialog.getState() == DialogState.EARLY)) { req = cs.inviteOutTransaction.createCancel(); ClientTransaction t = sipProvider.getNewClientTransaction(req); t.sendRequest(); cs.sendBye(); return false; } else { req = cs.sipDialog.createRequest(Request.BYE); ClientTransaction t = sipProvider.getNewClientTransaction(req); cs.sipDialog.sendRequest(t); cs.sendBye(); } } catch (SipException e) { Log.error("Error sending BYE", e); } return true; } public static boolean sendReject(CallSession cs) { try { Request req = cs.inviteTransaction.getRequest(); Response resp = messageFactory.createResponse(Response.TEMPORARILY_UNAVAILABLE, cs.inviteTransaction.getRequest()); ToHeader th = (ToHeader) req.getHeader("To"); String dest = ((SipURI) th.getAddress().getURI()).getUser(); ListeningPoint lp = sipProvider.getListeningPoint(); Address localAddress = addressFactory.createAddress("sip:" + dest + "@" + lp.getIPAddress() + ":" + lp.getPort()); ContactHeader ch = headerFactory.createContactHeader(localAddress); resp.addHeader(ch); cs.inviteTransaction.sendResponse(resp); } catch (Exception e) { Log.error("Error sending Reject", e); } return true; } public static boolean sendDTMFinfo(CallSession cs, char dtmf, int dtmfl) { Request req; try { Log.debug("Sending SIP INFO DTMF - Signal: " + dtmf + " Duration:" + dtmfl); ContentTypeHeader cth = headerFactory.createContentTypeHeader("application", "dtmf-relay"); String body = "Signal=" + dtmf + "\r\nDuration="+dtmfl; req = cs.sipDialog.createRequest(Request.INFO); ClientTransaction t = sipProvider.getNewClientTransaction(req); req.setContent(body, cth); cs.sipDialog.sendRequest(t); } catch (SipException e) { Log.error("Error sending DTMF INFO", e); } catch (ParseException e) { Log.error("Error sending DTMF INFO", e); } return true; } public static boolean sendVideoUpdate(CallSession cs) { Request req; try { ContentTypeHeader cth = headerFactory.createContentTypeHeader("application", "media_control+xml"); String body = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + "<media_control>" + "<vc_primitive>" + "<to_encoder>" + "<picture_fast_update>" + "</picture_fast_update>" + "</to_encoder>" + "</vc_primitive>" + "</media_control>"; req = cs.sipDialog.createRequest(Request.INFO); ClientTransaction t = sipProvider.getNewClientTransaction(req); req.setContent(body, cth); cs.sipDialog.sendRequest(t); } catch (SipException e) { Log.error("Error sending FVR INFO", e); } catch (ParseException e) { Log.error("Error sending FVR INFO", e); } return true; } public static boolean sendInvite(CallSession cs) { FromHeader fromHeader = null; ToHeader toHeader = null; URI requestURI = null; URI fromURI = null; try { ListeningPoint lp = sipProvider.getListeningPoint(); localip = lp.getIPAddress(); requestURI = addressFactory.createURI(cs.to); toHeader = headerFactory.createToHeader(addressFactory.createAddress(requestURI), null); fromURI = addressFactory.createURI(cs.from); fromHeader = headerFactory.createFromHeader(addressFactory.createAddress(fromURI), null); int tag = (int) (Math.random() * 100000); fromHeader.setTag(Integer.toString(tag)); ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(); ViaHeader viaHeader = null; viaHeader = headerFactory.createViaHeader(lp.getIPAddress(), lp.getPort(), lp.getTransport(), null); viaHeaders.add(viaHeader); CallIdHeader callIdHeader = sipProvider.getNewCallId(); CSeqHeader cSeqHeader = headerFactory.createCSeqHeader(1L, Request.INVITE); MaxForwardsHeader maxForwards = headerFactory.createMaxForwardsHeader(70); ContentTypeHeader contentTypeHeader = headerFactory.createContentTypeHeader("application", "sdp"); Request request = messageFactory.createRequest(requestURI, Request.INVITE, callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards, contentTypeHeader, cs.buildSDP(true)); Address localAddress = addressFactory.createAddress(requestURI); ContactHeader ch = headerFactory.createContactHeader(localAddress); request.addHeader(ch); AllowHeader allowHeader = headerFactory.createAllowHeader("INVITE, ACK, CANCEL, OPTIONS, BYE, UPDATE, NOTIFY, MESSAGE, SUBSCRIBE, INFO"); request.addHeader(allowHeader); UserAgentHeader userAgent = (UserAgentHeader) headerFactory.createHeader(UserAgentHeader.NAME, agentName); request.setHeader(userAgent); //t.setApplicationData(new ResponseInfo(listener, transaction)); try { String sipServer = JiveGlobals.getProperty("freeswitch.sip.hostname", OfSkypePlugin.self.getIpAddress()); if (sipServer != null) { SipURI routeURI = (SipURI) addressFactory.createURI("sip:" + sipServer + ";lr"); RouteHeader routeHeader = headerFactory.createRouteHeader(addressFactory.createAddress(routeURI)); request.addHeader(routeHeader); } } catch (Exception e) { Log.error("Creating registration route error " + e); } ClientTransaction t = sipProvider.getNewClientTransaction(request); Dialog d = SipService.sipProvider.getNewDialog(t); cs.sipDialog = d; d.setApplicationData(cs); t.setApplicationData(cs); cs.inviteOutTransaction = t; t.sendRequest(); return true; } catch (ParseException e) { Log.error("Error on SIPTransmitter:deliverMessage", e); } catch (InvalidArgumentException e) { Log.error("Error on SIPTransmitter:deliverMessage", e); } catch (TransactionUnavailableException e) { Log.error("Error on SIPTransmitter:deliverMessage", e); } catch (SipException e) { Log.error("Error on SIPTransmitter:deliverMessage", e); } return false; } public static boolean sendMessageMessage(String to, String from, String body) { FromHeader fromHeader = null; ToHeader toHeader = null; URI requestURI = null; URI fromURI = null; try { requestURI = addressFactory.createURI(to); toHeader = headerFactory.createToHeader(addressFactory.createAddress(requestURI), null); fromURI = addressFactory.createURI(from); fromHeader = headerFactory.createFromHeader(addressFactory.createAddress(fromURI), null); int tag = (int) (Math.random() * 100000); fromHeader.setTag(Integer.toString(tag)); ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>(); ViaHeader viaHeader = null; ListeningPoint lp = sipProvider.getListeningPoint(); viaHeader = headerFactory.createViaHeader(lp.getIPAddress(), lp.getPort(), lp.getTransport(), null); viaHeaders.add(viaHeader); CallIdHeader callIdHeader = sipProvider.getNewCallId(); CSeqHeader cSeqHeader = headerFactory.createCSeqHeader(1L, Request.MESSAGE); MaxForwardsHeader maxForwards = headerFactory.createMaxForwardsHeader(70); ContentTypeHeader contentTypeHeader = headerFactory.createContentTypeHeader("text", "plain"); Request request = messageFactory.createRequest(requestURI, "MESSAGE", callIdHeader, cSeqHeader, fromHeader, toHeader, viaHeaders, maxForwards, contentTypeHeader, body); ClientTransaction t = sipProvider.getNewClientTransaction(request); t.sendRequest(); return true; } catch (ParseException e) { Log.error("Error on SIPTransmitter:deliverMessage", e); } catch (InvalidArgumentException e) { Log.error("Error on SIPTransmitter:deliverMessage", e); } catch (TransactionUnavailableException e) { Log.error("Error on SIPTransmitter:deliverMessage", e); } catch (SipException e) { Log.error("Error on SIPTransmitter:deliverMessage", e); } return false; } public synchronized static ClientTransaction handleChallenge(Response challenge, ClientTransaction challengedTransaction, ProxyCredentials proxyCredentials) { try { String branchID = challengedTransaction.getBranchId(); Request challengedRequest = challengedTransaction.getRequest(); Request reoriginatedRequest = (Request) challengedRequest.clone(); ListIterator authHeaders = null; if (challenge == null || reoriginatedRequest == null) throw new NullPointerException("A null argument was passed to handle challenge."); // CallIdHeader callId = // (CallIdHeader)challenge.getHeader(CallIdHeader.NAME); if (challenge.getStatusCode() == Response.UNAUTHORIZED) authHeaders = challenge.getHeaders(WWWAuthenticateHeader.NAME); else if (challenge.getStatusCode() == Response.PROXY_AUTHENTICATION_REQUIRED) authHeaders = challenge.getHeaders(ProxyAuthenticateHeader.NAME); if (authHeaders == null) throw new SecurityException("Could not find WWWAuthenticate or ProxyAuthenticate headers"); // Remove all authorization headers from the request (we'll re-add // them // from cache) reoriginatedRequest.removeHeader(AuthorizationHeader.NAME); reoriginatedRequest.removeHeader(ProxyAuthorizationHeader.NAME); reoriginatedRequest.removeHeader(ViaHeader.NAME); // rfc 3261 says that the cseq header should be augmented for the // new // request. do it here so that the new dialog (created together with // the new client transaction) takes it into account. // Bug report - Fredrik Wickstrom CSeqHeader cSeq = (CSeqHeader) reoriginatedRequest.getHeader((CSeqHeader.NAME)); cSeq.setSequenceNumber(cSeq.getSequenceNumber() + 1); reoriginatedRequest.setHeader(cSeq); ClientTransaction retryTran = sipProvider.getNewClientTransaction(reoriginatedRequest); WWWAuthenticateHeader authHeader = null; while (authHeaders.hasNext()) { authHeader = (WWWAuthenticateHeader) authHeaders.next(); String realm = authHeader.getRealm(); FromHeader from = (FromHeader) reoriginatedRequest.getHeader(FromHeader.NAME); URI uri = from.getAddress().getURI(); AuthorizationHeader authorization = getAuthorization(reoriginatedRequest.getMethod(), reoriginatedRequest.getRequestURI().toString(), reoriginatedRequest.getContent() == null ? "" : reoriginatedRequest.getContent().toString(), authHeader, proxyCredentials); reoriginatedRequest.addHeader(authorization); // if there was trouble with the user - make sure we fix it if (uri.isSipURI()) { ((SipURI) uri).setUser(proxyCredentials.getUserName()); Address add = from.getAddress(); add.setURI(uri); from.setAddress(add); reoriginatedRequest.setHeader(from); if (challengedRequest.getMethod().equals(Request.REGISTER)) { ToHeader to = (ToHeader) reoriginatedRequest.getHeader(ToHeader.NAME); add.setURI(uri); to.setAddress(add); reoriginatedRequest.setHeader(to); } Log.info("URI: " + uri.toString()); } // if this is a register - fix to as well } return retryTran; } catch (Exception e) { Log.error("ClientTransaction handleChallenge error", e); return null; } } private synchronized static AuthorizationHeader getAuthorization(String method, String uri, String requestBody, WWWAuthenticateHeader authHeader, ProxyCredentials proxyCredentials) throws SecurityException { String response = null; try { Log.info("getAuthorization " + proxyCredentials.getAuthUserName()); response = MessageDigestAlgorithm.calculateResponse(authHeader .getAlgorithm(), proxyCredentials.getAuthUserName(), authHeader.getRealm(), new String(proxyCredentials .getPassword()), authHeader.getNonce(), // TODO we should one day implement those two null-s null,// nc-value null,// cnonce method, uri, requestBody, authHeader.getQop()); } catch (NullPointerException exc) { throw new SecurityException( "The authenticate header was malformatted"); } AuthorizationHeader authorization = null; try { if (authHeader instanceof ProxyAuthenticateHeader) { authorization = headerFactory .createProxyAuthorizationHeader(authHeader.getScheme()); } else { authorization = headerFactory .createAuthorizationHeader(authHeader.getScheme()); } authorization.setUsername(proxyCredentials.getAuthUserName()); authorization.setRealm(authHeader.getRealm()); authorization.setNonce(authHeader.getNonce()); authorization.setParameter("uri", uri); authorization.setResponse(response); if (authHeader.getAlgorithm() != null) authorization.setAlgorithm(authHeader.getAlgorithm()); if (authHeader.getOpaque() != null) authorization.setOpaque(authHeader.getOpaque()); authorization.setResponse(response); } catch (ParseException ex) { throw new SecurityException("Failed to create an authorization header!"); } return authorization; } }