/* * Copyright (C) 2005-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.sip.tester.stack; import org.jivesoftware.openfire.sip.tester.comm.CommunicationsException; import org.jivesoftware.openfire.sip.tester.comm.CommunicationsListener; import org.jivesoftware.openfire.sip.tester.Log; import org.jivesoftware.openfire.sip.tester.security.UserCredentials; import org.jivesoftware.openfire.sip.tester.security.SipSecurityManager; import javax.sip.*; import javax.sip.address.Address; import javax.sip.address.AddressFactory; import javax.sip.address.SipURI; import javax.sip.header.*; import javax.sip.message.MessageFactory; import javax.sip.message.Request; import javax.sip.message.Response; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.text.ParseException; import java.util.ArrayList; import java.util.Iterator; import java.util.TooManyListenersException; /** * Title: SIP Register Tester * * @author Thiago Rocha Camargo (thiago@jivesoftware.com) */ public class SipManager implements SipListener { protected static final int RETRY_OBJECT_DELETES = 10; protected static final long RETRY_OBJECT_DELETES_AFTER = 500; protected static final String DEFAULT_TRANSPORT = "udp"; protected InetAddress localAddress = null; public SipFactory sipFactory; public AddressFactory addressFactory; public HeaderFactory headerFactory; public MessageFactory messageFactory; SipStack sipStack; public boolean isBusy = true; ListeningPoint listeningPoint; public SipProvider sipProvider; private InetSocketAddress publicIpAddress = null; protected String sipStackPath = "gov.nist"; protected String currentlyUsedURI = null; protected String displayName = null; protected String transport = null; protected String registrarAddress = null; protected int localPort = -1; protected int registrarPort = -1; protected int registrationsExpiration = -1; protected String registrarTransport = null; //private int registerRetries = 0; protected String stackAddress = null; protected String stackName = "JiveSIP"; protected FromHeader fromHeader = null; protected ContactHeader contactHeader = null; protected ArrayList<ViaHeader> viaHeaders = null; protected static final int MAX_FORWARDS = 70; protected MaxForwardsHeader maxForwardsHeader = null; protected long registrationTransaction = -1; protected ArrayList<CommunicationsListener> listeners = new ArrayList<CommunicationsListener>(); protected boolean isStarted = false; private RegisterProcessing registerProcessing = null; public SipSecurityManager sipSecurityManager = null; /** * Constructor. It only creates a SipManager instance without initializing * the stack itself. * * @param localAddress localAddress */ public SipManager(InetAddress localAddress) { this.localAddress = localAddress; registerProcessing = new RegisterProcessing(this); sipSecurityManager = new SipSecurityManager(); //registerRetries = 0; } /** * Creates and initializes JAIN SIP objects (factories, stack, listening * point and provider). Once this method is called the application is ready * to handle (incoming and outgoing) sip messages. * * @throws CommunicationsException if an axception should occur during the initialization * process */ public void start() throws CommunicationsException { initProperties(); SIPConfig.setSystemProperties(); this.sipFactory = SipFactory.getInstance(); sipFactory.setPathName(sipStackPath); try { addressFactory = sipFactory.createAddressFactory(); headerFactory = sipFactory.createHeaderFactory(); messageFactory = sipFactory.createMessageFactory(); } catch (PeerUnavailableException ex) { Log.error("start", ex); throw new CommunicationsException( "Could not create factories!", ex); } try { sipStack = sipFactory.createSipStack(System.getProperties()); ((SipCommRouter) sipStack.getRouter()) .setOutboundProxy(SIPConfig.getOutboundProxy()); } catch (PeerUnavailableException ex) { Log.error("start", ex); throw new CommunicationsException( "Cannot connect!\n" + "Cannot reach proxy.\nCheck your connection." + "(Syntax:<proxy_address:port/transport>)", ex); } try { boolean successfullyBound = false; while (!successfullyBound) { try { publicIpAddress = new InetSocketAddress(localAddress, localPort); listeningPoint = sipStack.createListeningPoint( localPort, transport); } catch (InvalidArgumentException ex) { // choose another port between 1024 and 65000 localPort = (int) ((65000 - 1024) * Math.random()) + 1024; try { Thread.sleep(1000); } catch (Exception e) { // Do Nothing } continue; } successfullyBound = true; } } catch (TransportNotSupportedException ex) { throw new CommunicationsException( "Transport " + transport + " is not suppported by the stack!\n Try specifying another" + " transport in Mais property files.\n", ex); } try { sipProvider = sipStack.createSipProvider(listeningPoint); } catch (ObjectInUseException ex) { Log.error("start", ex); throw new CommunicationsException( "Could not create factories!\n", ex); } try { sipProvider.addSipListener(this); } catch (TooManyListenersException exc) { throw new CommunicationsException( "Could not register SipManager as a sip listener!", exc); } sipSecurityManager.setHeaderFactory(headerFactory); sipSecurityManager.setTransactionCreator(sipProvider); sipSecurityManager.setSipManCallback(this); // Make sure prebuilt headers are nulled so that they get reinited // if this is a restart contactHeader = null; fromHeader = null; viaHeaders = null; maxForwardsHeader = null; isStarted = true; } /** * Unregisters listening points, deletes sip providers, and generally * prepares the stack for a re-start(). This method is meant to be used when * properties are changed and should be reread by the stack. * * @throws CommunicationsException CommunicationsException */ synchronized public void stop() throws CommunicationsException { if (sipStack == null) return; // Delete SipProvider int tries; for (tries = 0; tries < SipManager.RETRY_OBJECT_DELETES; tries++) { try { sipStack.deleteSipProvider(sipProvider); } catch (ObjectInUseException ex) { SipManager.sleep(SipManager.RETRY_OBJECT_DELETES_AFTER); continue; } break; } if (sipStack == null) return; if (tries >= SipManager.RETRY_OBJECT_DELETES) throw new CommunicationsException( "Failed to delete the sipProvider!"); if (sipStack == null) return; // Delete RI ListeningPoint for (tries = 0; tries < SipManager.RETRY_OBJECT_DELETES; tries++) { try { sipStack.deleteListeningPoint(listeningPoint); } catch (ObjectInUseException ex) { // Log.debug("Retrying delete of riListeningPoint!"); SipManager.sleep(SipManager.RETRY_OBJECT_DELETES_AFTER); continue; } break; } if (sipStack != null) { for (Iterator<SipProvider> it = sipStack.getSipProviders(); it.hasNext();) { SipProvider element = it.next(); try { sipStack.deleteSipProvider(element); } catch (Exception e) { // Do nothing } } } if (tries >= SipManager.RETRY_OBJECT_DELETES) throw new CommunicationsException( "Failed to delete a listeningPoint!"); listeningPoint = null; addressFactory = null; messageFactory = null; headerFactory = null; sipStack = null; registrarAddress = null; viaHeaders = null; contactHeader = null; fromHeader = null; } /** * Waits during _no_less_ than sleepFor milliseconds. Had to implement it on * top of Thread.sleep() to guarantee minimum sleep time. * * @param sleepFor the number of miliseconds to wait */ protected static void sleep(long sleepFor) { long startTime = System.currentTimeMillis(); long haveBeenSleeping = 0; while (haveBeenSleeping < sleepFor) { try { Thread.sleep(sleepFor - haveBeenSleeping); } catch (InterruptedException ex) { // we-ll have to wait again! } haveBeenSleeping = (System.currentTimeMillis() - startTime); } } /** * @param uri the currentlyUsedURI to set. */ public void setCurrentlyUsedURI(String uri) { this.currentlyUsedURI = uri; } public void register(String publicAddress) { try { if (publicAddress == null || publicAddress.trim().length() == 0) { Log.debug("PUBLIC NOT FOUND!"); return; // maybe throw an exception? } if (!publicAddress.trim().toLowerCase().startsWith("sip:")) { publicAddress = "sip:" + publicAddress; } this.currentlyUsedURI = publicAddress; registerProcessing.register(registrarAddress, registrarPort, registrarTransport, registrationsExpiration); } catch (Exception e) { Log.error("register", e); } } public void startRegisterProcess(String userName, String authUserName, String password) throws CommunicationsException { try { checkIfStarted(); // Obtain initial credentials String realm = SIPConfig.getAuthenticationRealm(); realm = realm == null ? "" : realm; // put the returned user name in the properties file // so that it appears as a default one next time user is prompted // for pass SIPConfig.setUserName(userName); SIPConfig.setAuthUserName(authUserName); UserCredentials initialCredentials = new UserCredentials(); initialCredentials.setUserName(userName); initialCredentials.setAuthUserName(authUserName); initialCredentials.setPassword(password.toCharArray()); register(initialCredentials.getUserName() + "@" + realm); // at this point a simple register request has been sent and the // global // from header in SipManager has been set to a valid value by the // RegisterProcesing // class. Use it to extract the valid user name that needs to be // cached by // the security manager together with the user provided password. initialCredentials.setUserName(((SipURI) getFromHeader() .getAddress().getURI()).getUser()); // JOptionPane.showMessageDialog(null,( (SipURI) // getFromHeader().getAddress().getURI()).getUser()); cacheCredentials(realm, initialCredentials); } catch (Exception ee) { Log.error("startRegisterProcess", ee); } } /** * Causes the PresenceAgent object to notify all subscribers of our brand * new offline status and the RegisterProcessing object to send a * registration request with a 0 "expires" interval to the registrar defined * in net.java.mais.sip.REGISTRAR_ADDRESS. * * @throws CommunicationsException if an exception is thrown by the underlying stack. The * exception that caused this CommunicationsException may be * extracted with CommunicationsException.getCause() */ public void unregister() throws CommunicationsException { try { checkIfStarted(); registerProcessing.unregister(); fireUnregistered(registrarAddress == null ? "" : registrarAddress); } catch (Exception e) { Log.error("unregister", e); } } private void registrationFailed(RegistrationEvent.Type type) { try { fireRegistrationFailed(registrarAddress == null ? "" : registrarAddress, type); } catch (Exception e) { Log.error("unregister", e); } } /** * Queries the RegisterProcessing object whether the application is * registered with a registrar. * * @return true if the application is registered with a registrar. */ public boolean isRegistered() { return (registerProcessing != null && registerProcessing.isRegistered()); } /** * Determines whether the SipManager was started. * * @return true if the SipManager was started. */ public boolean isStarted() { return isStarted; } /** * Sends a NOT_IMPLEMENTED response through the specified transaction. * * @param serverTransaction the transaction to send the response through. * @param request the request that is being answered. */ void sendNotImplemented(ServerTransaction serverTransaction, Request request) { Response notImplemented; try { notImplemented = messageFactory.createResponse( Response.NOT_IMPLEMENTED, request); attachToTag(notImplemented, serverTransaction.getDialog()); } catch (ParseException ex) { fireCommunicationsError(new CommunicationsException( "Failed to create a NOT_IMPLEMENTED response to a " + request.getMethod() + " request!", ex)); return; } try { serverTransaction.sendResponse(notImplemented); } catch (SipException ex) { fireCommunicationsError(new CommunicationsException( "Failed to create a NOT_IMPLEMENTED response to a " + request.getMethod() + " request!", ex)); } } public void fireCommunicationsError(Throwable throwable) { } public FromHeader getFromHeader() throws CommunicationsException { return this.getFromHeader(false); } public FromHeader getFromHeader(boolean isNew) throws CommunicationsException { if (fromHeader != null && !isNew) { return fromHeader; } try { SipURI fromURI = (SipURI) addressFactory .createURI(currentlyUsedURI); fromURI.setTransportParam(listeningPoint.getTransport()); fromURI.setPort(listeningPoint.getPort()); Address fromAddress = addressFactory.createAddress(fromURI); if (displayName != null && displayName.trim().length() > 0) { fromAddress.setDisplayName(displayName); } else { fromAddress .setDisplayName(UserCredentials.getUserDisplay());// UserCredentials.getUser()); // JOptionPane.showMessageDialog(null,currentlyUsedURI); } fromHeader = headerFactory.createFromHeader(fromAddress, Integer.toString(hashCode())); } catch (ParseException ex) { throw new CommunicationsException( "A ParseException occurred while creating From Header!", ex); } return fromHeader; } /** * Same as calling getContactHeader(true) * * @return the result of getContactHeader(true) * @throws CommunicationsException if an exception is thrown while calling * getContactHeader(false) */ public ContactHeader getContactHeader() throws CommunicationsException { return getContactHeader(true); } /** * Same as calling getContactHeader(true). * * @return the result of calling getContactHeader(true). * @throws CommunicationsException if an exception occurs while executing * getContactHeader(true). */ ContactHeader getRegistrationContactHeader() throws CommunicationsException { return getContactHeader(true); } /** * Initialises SipManager's contactHeader field in accordance with * javax.sip.IP_ADDRESS net.java.mais.sip.DISPLAY_NAME * net.java.mais.sip.TRANSPORT net.java.mais.sip.PREFERRED_LOCAL_PORT and * returns a reference to it. * * @param useLocalHostAddress specifies whether the SipURI in the contact header should * contain the value of javax.sip.IP_ADDRESS (true) or that of * net.java.mais.sip.PUBLIC_ADDRESS (false). * @return a reference to SipManager's contactHeader field. * @throws CommunicationsException if a ParseException occurs while initially composing the * FromHeader. */ public ContactHeader getContactHeader(boolean useLocalHostAddress) throws CommunicationsException { if (contactHeader != null) { return contactHeader; } try { SipURI contactURI; if (useLocalHostAddress) { contactURI = addressFactory.createSipURI(null, UserCredentials.getUserDisplay() + "@" + publicIpAddress.getAddress() .getHostAddress()); } else { contactURI = (SipURI) addressFactory .createURI(currentlyUsedURI); } contactURI.setPort(publicIpAddress.getPort()); Address contactAddress = addressFactory .createAddress(contactURI); if (displayName != null && displayName.trim().length() > 0) { contactAddress.setDisplayName(displayName); } contactHeader = headerFactory .createContactHeader(contactAddress); } catch (ParseException ex) { throw new CommunicationsException( "A ParseException occurred while creating From Header!", ex); } return contactHeader; } /** * Initializes (if null) and returns an ArrayList with a single ViaHeader * containing localhost's address. This ArrayList may be used when sending * requests. * * @return ViaHeader-s list to be used when sending requests. * @throws CommunicationsException if a ParseException is to occur while initializing the array * list. */ public ArrayList<ViaHeader> getLocalViaHeaders() throws CommunicationsException { if (viaHeaders != null) { return viaHeaders; } ListeningPoint lp = sipProvider.getListeningPoint(); viaHeaders = new ArrayList<ViaHeader>(); try { ViaHeader viaHeader = headerFactory.createViaHeader(SIPConfig .getIPAddress(), lp.getPort(), lp.getTransport(), null); viaHeader.setParameter("rport", null); viaHeaders.add(viaHeader); return viaHeaders; } catch (ParseException ex) { throw new CommunicationsException( "A ParseException occurred while creating Via Headers!"); } catch (InvalidArgumentException ex) { throw new CommunicationsException( "Unable to create a via header for port " + lp.getPort(), ex); } } /** * Initializes and returns SipManager's maxForwardsHeader field using the * value specified by MAX_FORWARDS. * * @return an instance of a MaxForwardsHeader that can be used when sending * requests * @throws CommunicationsException if MAX_FORWARDS has an invalid value. */ public MaxForwardsHeader getMaxForwardsHeader() throws CommunicationsException { if (maxForwardsHeader != null) { return maxForwardsHeader; } try { maxForwardsHeader = headerFactory .createMaxForwardsHeader(SipManager.MAX_FORWARDS); return maxForwardsHeader; } catch (InvalidArgumentException ex) { throw new CommunicationsException( "A problem occurred while creating MaxForwardsHeader", ex); } } /** * Returns the user used to create the From Header URI. * * @return the user used to create the From Header URI. */ public String getLocalUser() { try { return ((SipURI) getFromHeader().getAddress().getURI()).getUser(); } catch (CommunicationsException ex) { return ""; } } /** * Generates a ToTag (the containingDialog's hashCode())and attaches it to * response's ToHeader. * * @param response the response that is to get the ToTag. * @param containingDialog the Dialog instance that is to extract a unique Tag value * (containingDialog.hashCode()) */ public void attachToTag(Response response, Dialog containingDialog) { ToHeader to = (ToHeader) response.getHeader(ToHeader.NAME); if (to == null) { fireCommunicationsError(new CommunicationsException( "No TO header found in, attaching a to tag is therefore impossible")); } try { if (to.getTag() == null || to.getTag().trim().length() == 0) { int toTag = containingDialog != null ? containingDialog .hashCode() : (int) System.currentTimeMillis(); to.setTag(Integer.toString(toTag)); } } catch (ParseException ex) { fireCommunicationsError(new CommunicationsException( "Failed to attach a TO tag to an outgoing response")); } } protected void initProperties() { try { stackAddress = getLocalHostAddress(); // Add the host address to the properties that will pass the stack SIPConfig.setIPAddress(stackAddress); SIPConfig.setSystemProperties(); // ensure IPv6 address compliance if (stackAddress.indexOf(':') != stackAddress.lastIndexOf(':') && stackAddress.charAt(0) != '[') { stackAddress = '[' + stackAddress.trim() + ']'; } stackName = SIPConfig.getStackName(); if (stackName == null) { stackName = "SIPark@" + Integer.toString(hashCode()); } currentlyUsedURI = SIPConfig.getPublicAddress(); if (currentlyUsedURI == null) { currentlyUsedURI = SIPConfig.getUserName() + "@" + stackAddress; } if (!currentlyUsedURI.trim().toLowerCase().startsWith("sip:")) { currentlyUsedURI = "sip:" + currentlyUsedURI.trim(); } registrarAddress = SIPConfig.getRegistrarAddress(); try { registrarPort = SIPConfig.getRegistrarPort(); } catch (NumberFormatException ex) { registrarPort = 5060; } registrarTransport = SIPConfig.getRegistrarTransport(); if (registrarTransport == null) { registrarTransport = SipManager.DEFAULT_TRANSPORT; } try { registrationsExpiration = SIPConfig.getRegistrationExpiration(); } catch (NumberFormatException ex) { registrationsExpiration = 3600; } sipStackPath = SIPConfig.getStackPath(); if (sipStackPath == null) { sipStackPath = "gov.nist"; } transport = SIPConfig.getTransport(); if (transport.equals("")) { transport = SipManager.DEFAULT_TRANSPORT; } try { localPort = SIPConfig.getLocalPort(); } catch (NumberFormatException exc) { localPort = 5060; } displayName = SIPConfig.getDisplayName(); } catch (Exception e) { Log.error(e.getMessage(), e); } } /** * Adds the specified credentials to the security manager's credentials * cache so that they get tried next time they're needed. * * @param realm the realm these credentials should apply for. * @param credentials a set of credentials (username and pass) */ public void cacheCredentials(String realm, UserCredentials credentials) { sipSecurityManager.cacheCredentials(realm, credentials); } /** * Adds a CommunicationsListener to SipManager. * * @param listener The CommunicationsListener to be added. */ public void addCommunicationsListener(CommunicationsListener listener) { try { listeners.add(listener); } catch (Exception e) { Log.error("addCommunicationsListener", e); } } // ------------ registerred void fireRegistered(String address) { RegistrationEvent evt = new RegistrationEvent(address); for (int i = listeners.size() - 1; i >= 0; i--) { (listeners.get(i)).registered(evt); } } // call received // ------------ registering void fireRegistering(String address) { RegistrationEvent evt = new RegistrationEvent(address); for (int i = listeners.size() - 1; i >= 0; i--) { (listeners.get(i)).registering(evt); } } // call received // ------------ unregistered public void fireUnregistered(String address) { RegistrationEvent evt = new RegistrationEvent(address); for (int i = listeners.size() - 1; i >= 0; i--) { (listeners.get(i)).unregistered(evt); } } void fireRegistrationFailed(String address, RegistrationEvent.Type type) { RegistrationEvent evt = new RegistrationEvent(address, type); for (int i = listeners.size() - 1; i >= 0; i--) { (listeners.get(i)).registrationFailed(evt); } } void fireUnregistering(String address) { RegistrationEvent evt = new RegistrationEvent(address); for (int i = listeners.size() - 1; i >= 0; i--) { (listeners.get(i)).unregistering(evt); } } public void processRequest(RequestEvent requestEvent) { } // -------------------- PROCESS RESPONSE public void processResponse(ResponseEvent responseReceivedEvent) { Log.debug("RESPONSE [" + responseReceivedEvent.getResponse().getStatusCode() + "]"); ClientTransaction clientTransaction = responseReceivedEvent .getClientTransaction(); if (clientTransaction == null) { return; } Response response = responseReceivedEvent.getResponse(); String method = ((CSeqHeader) response.getHeader(CSeqHeader.NAME)) .getMethod(); // OK if (response.getStatusCode() == Response.OK) { // REGISTER if (method.equals(Request.REGISTER)) { registerProcessing.processOK(clientTransaction, response); } } // NOT_FOUND else if (response.getStatusCode() == Response.NOT_FOUND) { if (method.equals(Request.REGISTER)) { try { unregister(); registrationFailed(RegistrationEvent.Type.NotFound); } catch (CommunicationsException e) { Log.error("NOT FOUND", e); } Log.debug("REGISTER NOT FOUND"); } } // NOT_IMPLEMENTED else if (response.getStatusCode() == Response.NOT_IMPLEMENTED) { if (method.equals(Request.REGISTER)) { // Fixed typo issues - Reported by pizarro registerProcessing.processNotImplemented(clientTransaction, response); } } // REQUEST_TERMINATED // 401 UNAUTHORIZED else if (response.getStatusCode() == Response.UNAUTHORIZED || response.getStatusCode() == Response.PROXY_AUTHENTICATION_REQUIRED) { if (method.equals(Request.REGISTER)) { CSeqHeader cseq = (CSeqHeader) response.getHeader(CSeqHeader.NAME); if (cseq.getSequenceNumber() < 2) registerProcessing.processAuthenticationChallenge( clientTransaction, response); else registrationFailed(RegistrationEvent.Type.WrongPass); } } // 403 Wrong Authorization user for this account else if(response.getStatusCode() == Response.FORBIDDEN){ registrationFailed(RegistrationEvent.Type.Forbidden); } } // process response public void processTimeout(TimeoutEvent timeoutEvent) { } String getLocalHostAddress() { return localAddress.getHostAddress(); } protected void checkIfStarted() throws CommunicationsException { if (!isStarted) { throw new CommunicationsException( "The underlying SIP Stack had not been" + "properly initialised! Impossible to continue"); } } public static void main(String args[]) { SIPConfig.setRegistrarAddress("apollo"); SIPConfig.setAuthenticationRealm("apollo"); SIPConfig.setDefaultDomain("apollo"); InetAddress address = null; try { address = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); } SipManager sipManager = new SipManager(address); try { sipManager.start(); } catch (CommunicationsException e) { e.printStackTrace(); } try { sipManager.startRegisterProcess("7512", "7512", "7512"); } catch (CommunicationsException e) { e.printStackTrace(); } try { sipManager.unregister(); } catch (CommunicationsException e) { e.printStackTrace(); } } }