/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * 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 net.java.sip.communicator.impl.protocol.sip; import gov.nist.javax.sip.message.*; import java.io.*; import java.net.*; import java.security.cert.*; import java.text.*; import java.util.*; import javax.net.ssl.*; import javax.sip.*; import javax.sip.address.*; import javax.sip.header.*; import javax.sip.message.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; /** * Contains all functionality that has anything to do with registering and * maintaining registrations with a SIP Registrar. * * @author Emil Ivov */ public class SipRegistrarConnection extends MethodProcessorAdapter { /** * Our class logger. */ private static final Logger logger = Logger.getLogger(SipRegistrarConnection.class); /** * A reference to the sip provider that created us. */ private ProtocolProviderServiceSipImpl sipProvider = null; /** * The SipURI containing the address port and transport of our registrar * server. */ private SipURI registrarURI = null; /** * The registrar address host we are connecting to. */ private String registrarName = null; /** * The name of the property under which the user may specify the number of * seconds that registrations take to expire. */ private static final String REGISTRATION_EXPIRATION = "net.java.sip.communicator.impl.protocol.sip.REGISTRATION_EXPIRATION"; /** * The default amount of time (in seconds) that registration take to * expire or otherwise put - the number of seconds we wait before re- * registering. */ private static final int DEFAULT_REGISTRATION_EXPIRATION = 600; /** * The amount of time (in seconds) that registration take to expire or * otherwise put - the number of seconds we wait before re-registering. */ private int registrationsExpiration = DEFAULT_REGISTRATION_EXPIRATION; /** * Keeps our current registration state. */ private RegistrationState currentRegistrationState = RegistrationState.UNREGISTERED; /** * The timer we use for rescheduling registrations. */ private Timer reRegisterTimer = new Timer(); /** * A copy of our last sent register request. (used when unregistering) */ private Request registerRequest = null; /** * The next long to use as a cseq header value. */ private long nextCSeqValue = 1; /** * The client transaction that we used for sending the last REGISTER * request. */ ClientTransaction regTrans = null; /** * Option for specifying keep-alive method */ private static final String KEEP_ALIVE_METHOD = "KEEP_ALIVE_METHOD"; /** * Option for keep-alive interval */ private static final String KEEP_ALIVE_INTERVAL = "KEEP_ALIVE_INTERVAL"; /** * Default value for keep-alive method - register */ private static final int KEEP_ALIVE_INTERVAL_DEFAULT_VALUE = 25; /** * An account property to force messaging even if MESSAGE method is not * listed in AllowHeader. */ private static final String FORCE_MESSAGING_PROP = "FORCE_MESSAGING"; /** * Specifies whether or not we should be using a route header in register * requests. This field is specified by the REGISTERS_USE_ROUTE account * property. */ private boolean useRouteHeader = false; /** * The sip address that we're currently behind (the one that corresponds to * our account id). ATTENTION!!! This field must remain <tt>null</tt> * when this protocol provider is configured as "No Regsitrar" account and * only be initialized if we actually have a registrar. */ private Address ourSipAddressOfRecord = null; /** * callId must be unique from first register to last one, till we * unregister. */ private CallIdHeader callIdHeader = null; /** * The transport to use. */ private String registrationTransport = null; /** * Registrar port; */ private int registrarPort = -1; /** * We remember the last received address that REGISTER OK is coming from. * For security reasons if we are registered this is the address * that all messages must come from. */ private InetAddress lastRegisterAddressReceived = null; /** * We remember the last received port that REGISTER OK is coming from. * For security reasons if we are registered this is the port * that all messages must come from. */ private int lastRegisterPortReceived = -1; /** * Creates a new instance of this class. * * @param registrarName the FQDN of the registrar we will * be registering with. * @param registrarPort the port of the registrar we will * be registering with. * @param registrationTransport the transport to use when sending our * REGISTER request to the server. * @param sipProviderCallback a reference to the * ProtocolProviderServiceSipImpl instance that created us. */ public SipRegistrarConnection( String registrarName, int registrarPort, String registrationTransport, ProtocolProviderServiceSipImpl sipProviderCallback) { this.registrarPort = registrarPort; this.registrationTransport = registrationTransport; this.registrarName = registrarName; this.sipProvider = sipProviderCallback; //init expiration timeout this.registrationsExpiration = SipActivator.getConfigurationService().getInt( REGISTRATION_EXPIRATION, DEFAULT_REGISTRATION_EXPIRATION); //init our address of record to save time later getAddressOfRecord(); //now let's register ourselves as processor for REGISTER related //messages. sipProviderCallback.registerMethodProcessor(Request.REGISTER, this); } /** * Empty constructor that we only have in order to allow for classes like * SipRegistrarlessConnection to extend this class. */ protected SipRegistrarConnection() { } /** * Changes transport of registrar connection and recreates * registrar URI. * @param newRegistrationTransport the new transport. */ void setTransport(String newRegistrationTransport) { if(newRegistrationTransport.equals(registrationTransport)) return; this.registrationTransport = newRegistrationTransport; if(!registrationTransport.equals(ListeningPoint.UDP)) registrarURI = null; // re-create it } /** * Sends the REGISTER request to the server specified in the constructor. * * @throws OperationFailedException with the corresponding error code * if registration or construction of the Register request fail. */ void register() throws OperationFailedException { // skip REGISTERING event if we are already registered // we are refreshing our registration if (getRegistrationState() != RegistrationState.REGISTERED) setRegistrationState(RegistrationState.REGISTERING, RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, null); Request request; try { //We manage the Call ID Header ourselves.The rest will be handled //by our SipMessageFactory if(callIdHeader == null) callIdHeader = this.getJainSipProvider().getNewCallId(); request = sipProvider.getMessageFactory().createRegisterRequest( getAddressOfRecord(), registrationsExpiration, callIdHeader, getNextCSeqValue()); } catch (Exception exc) { if(exc.getCause() instanceof SocketException || exc.getCause() instanceof IOException || exc.getCause() instanceof SSLHandshakeException) { if(exc.getCause().getCause() instanceof CertificateException || exc.getCause().getMessage() .startsWith("Received fatal alert")) { setRegistrationState(RegistrationState.UNREGISTERED , RegistrationStateChangeEvent.REASON_USER_REQUEST , exc.getMessage()); return; } if(sipProvider.registerUsingNextAddress()) return; } //catches InvalidArgumentException, ParseExeption //this should never happen so let's just log and bail. logger.error("Failed to create a Register request." , exc); setRegistrationState(RegistrationState.CONNECTION_FAILED, RegistrationStateChangeEvent.REASON_INTERNAL_ERROR, exc.getMessage()); if(exc instanceof OperationFailedException) throw (OperationFailedException)exc; else throw new OperationFailedException( "Failed to generate a from header for our register request.", OperationFailedException.INTERNAL_ERROR, exc); } //Transaction try { regTrans = getJainSipProvider().getNewClientTransaction(request); } catch (TransactionUnavailableException ex) { logger.error("Could not create a register transaction!\n" + "Check that the Registrar address is correct!", ex); setRegistrationState(RegistrationState.CONNECTION_FAILED, RegistrationStateChangeEvent.REASON_INTERNAL_ERROR, ex.getMessage()); throw new OperationFailedException( "Could not create a register transaction!\n" + "Check that the Registrar address is correct!", OperationFailedException.NETWORK_FAILURE, ex); } try { regTrans.sendRequest(); } //we sometimes get a null pointer exception here so catch them all catch (Exception ex) { if(ex.getCause() instanceof SocketException || ex.getCause() instanceof IOException) { if(sipProvider.registerUsingNextAddress()) return; } logger.error("Could not send out the register request!", ex); setRegistrationState(RegistrationState.CONNECTION_FAILED , RegistrationStateChangeEvent.REASON_INTERNAL_ERROR , ex.getMessage()); throw new OperationFailedException( "Could not send out the register request!" , OperationFailedException.NETWORK_FAILURE , ex); } this.registerRequest = request; } /** * An ok here means that our registration has been accepted or terminated * (depending on the corresponding REGISTER request). We change state * notify listeners and (in the case of a new registration) schedule * reregistration. * * @param clientTransatcion the ClientTransaction that we created when * sending the register request. * @param response the OK Response that we've just received. */ @SuppressWarnings("unchecked") //legacy jain-sip code for handling multiple //headers of the same type public void processOK(ClientTransaction clientTransatcion, Response response) { //first extract the expires value that we requested int requestedExpiration = 0; Request register = clientTransatcion.getRequest(); //first check for an expires param in the contact header ContactHeader contactHeader = (ContactHeader) register .getHeader(ContactHeader.NAME); if (contactHeader != null) requestedExpiration = contactHeader.getExpires(); else requestedExpiration = 0; //Try the expires header only if there was no expiration interval in //the contact address. (Bug report and fix thereof Frederic Fournier) if(requestedExpiration <= 0) { ExpiresHeader expiresHeader = register.getExpires(); if (expiresHeader != null) requestedExpiration = expiresHeader.getExpires(); } FromHeader fromHeader = (FromHeader) register .getHeader(FromHeader.NAME); if(fromHeader != null && fromHeader.getAddress() != null) { if(sipProvider.setOurDisplayName( fromHeader.getAddress().getDisplayName())) this.ourSipAddressOfRecord = null; } //now check if the registrar has touched our expiration timeout in its //response. Again, check contact headers first. int grantedExpiration; ContactHeader responseContactHdr = (ContactHeader) response.getHeader( ContactHeader.NAME); if (responseContactHdr != null) { grantedExpiration = responseContactHdr.getExpires(); } else { //no luck there, try the expires header. ExpiresHeader expiresHeader = response.getExpires(); if (expiresHeader != null) { grantedExpiration = expiresHeader.getExpires(); } //still no luck - let's be tolerant and reuse the expires timeout we //stated in our last request else { grantedExpiration = requestedExpiration; } } //If this is a response to a REGISTER request ending our registration //then expires would be 0. //fix by Luca Bincoletto <Luca.Bincoletto@tilab.com> //we also take into account the requested expiration since if it was 0 //we don't really care what the server replied (I have an asterisk here //that gives me 3600 even if I request 0). if (grantedExpiration <= 0 || requestedExpiration <= 0) { if(logger.isDebugEnabled()) logger.debug("Account " + sipProvider.getAccountID().getDisplayName() + " unregistered!"); Boolean userRequest = (Boolean)SipApplicationData.getApplicationData( register, SipApplicationData.KEY_USER_REQUEST); boolean userRequestBool = false; if(userRequest != null && userRequest) userRequestBool = true; setRegistrationState(RegistrationState.UNREGISTERED , RegistrationStateChangeEvent.REASON_USER_REQUEST , "Registration terminated.", userRequestBool); } else { int scheduleTime = grantedExpiration; // registration schedule interval can be forced to keep alive // with setting property KEEP_ALIVE_METHOD to register and // setting the interval with property KEEP_ALIVE_INTERVAL // to value in seconds, both properties are account props // this does not change expiration header // If KEEP_ALIVE_METHOD is null we default send registers on // interval of 25 seconds String keepAliveMethod = sipProvider.getAccountID().getAccountPropertyString( KEEP_ALIVE_METHOD); if((keepAliveMethod != null && keepAliveMethod.equalsIgnoreCase("register"))) { int registrationInterval = sipProvider.getAccountID().getAccountPropertyInt( KEEP_ALIVE_INTERVAL, KEEP_ALIVE_INTERVAL_DEFAULT_VALUE); if (registrationInterval < grantedExpiration) { scheduleTime = registrationInterval; } } // remember the address and port from which we received this // message lastRegisterAddressReceived = ((SIPMessage)response).getRemoteAddress(); lastRegisterPortReceived = ((SIPMessage)response).getRemotePort(); //schedule a reregistration. scheduleReRegistration(scheduleTime); //update the set of supported services ListIterator<AllowHeader> headerIter = response.getHeaders(AllowHeader.NAME); if(headerIter != null && headerIter.hasNext()) updateSupportedOperationSets(headerIter); if(logger.isDebugEnabled() && getRegistrationState().equals(RegistrationState.REGISTERING)) logger.debug("Account " + sipProvider.getAccountID().getDisplayName() + " registered!"); setRegistrationState( RegistrationState.REGISTERED , RegistrationStateChangeEvent.REASON_NOT_SPECIFIED , null); } } /** * Checks a particular request is it coming from the same proxy we are * currently using. A check for security reasons, preventing injection * of other messages in the PP. * @param request the request to check. * @return <tt>false</tt> if the request doesn't belong to our register * or if we cannot decide we keep the old behaviour. */ public boolean isRequestFromSameConnection(Request request) { SIPMessage msg = (SIPMessage)request; if(msg.getRemoteAddress() != null && lastRegisterAddressReceived != null) { if(!msg.getRemoteAddress().equals(lastRegisterAddressReceived) || msg.getRemotePort() != lastRegisterPortReceived) { return false; } } return true; } /** * Sends a unregistered request to the registrar thus ending our * registration. * @throws OperationFailedException with the corresponding code if sending * or constructing the request fails. */ public void unregister() throws OperationFailedException { unregisterInternal(true, false); } /** * Sends a unregistered request to the registrar thus ending our * registration. * @throws OperationFailedException with the corresponding code if sending * or constructing the request fails. */ public void unregister(boolean userRequest) throws OperationFailedException { unregisterInternal(true, userRequest); } /** * Sends a unregistered request to the registrar thus ending our * registration. * * @param sendUnregister indicates whether we should actually send an * unREGISTER request or simply set our state to UNREGISTERED. * * @throws OperationFailedException with the corresponding code if sending * or constructing the request fails. */ private void unregisterInternal(boolean sendUnregister, boolean userRequest) throws OperationFailedException { if (getRegistrationState() == RegistrationState.UNREGISTERED) { if (logger.isTraceEnabled()) logger.trace("Trying to unregister when already unresgistered"); return; } cancelPendingRegistrations(); if (this.registerRequest == null) { logger.error("Couldn't find the initial register request"); setRegistrationState(RegistrationState.CONNECTION_FAILED , RegistrationStateChangeEvent.REASON_INTERNAL_ERROR , "Could not find the initial regiest request."); throw new OperationFailedException( "Could not find the initial register request." , OperationFailedException.INTERNAL_ERROR); } setRegistrationState(RegistrationState.UNREGISTERING, RegistrationStateChangeEvent.REASON_USER_REQUEST, ""); if(!sendUnregister) return; //We are apparently registered so send an un-Register request. Request unregisterRequest; try { unregisterRequest = sipProvider.getMessageFactory() .createUnRegisterRequest(registerRequest, getNextCSeqValue()); } catch (InvalidArgumentException ex) { logger.error("Unable to create an unREGISTER request.", ex); //Shouldn't happen setRegistrationState( RegistrationState.CONNECTION_FAILED , RegistrationStateChangeEvent.REASON_INTERNAL_ERROR , "Unable to set Expires Header"); throw new OperationFailedException( "Unable to set Expires Header" , OperationFailedException.INTERNAL_ERROR , ex); } if(userRequest) { SipApplicationData.setApplicationData(unregisterRequest, SipApplicationData.KEY_USER_REQUEST, userRequest); } ClientTransaction unregisterTransaction = null; try { unregisterTransaction = getJainSipProvider() .getNewClientTransaction(unregisterRequest); } catch (TransactionUnavailableException ex) { logger.error("Unable to create a unregister transaction", ex); setRegistrationState( RegistrationState.CONNECTION_FAILED , RegistrationStateChangeEvent.REASON_INTERNAL_ERROR , "Unable to create a unregister transaction"); throw new OperationFailedException( "Unable to create a unregister transaction" , OperationFailedException.INTERNAL_ERROR , ex); } try { // remove current call-id header // on next register we will create new one callIdHeader = null; unregisterTransaction.sendRequest(); //if we're currently registered or in a process of unregistering //we'll wait for an ok response before changing the status. //otherwise we set it immediately. if(!(getRegistrationState().equals(RegistrationState.REGISTERED) || getRegistrationState().equals(RegistrationState.UNREGISTERING))) { if (logger.isInfoEnabled()) logger.info("Setting state to UNREGISTERED."); setRegistrationState( RegistrationState.UNREGISTERED , RegistrationStateChangeEvent.REASON_USER_REQUEST, null); //kill the registration tran in case it is still active if (regTrans != null && regTrans.getState().getValue() <= TransactionState.PROCEEDING.getValue()) { if (logger.isTraceEnabled()) logger.trace("Will try to terminate reg tran ..."); regTrans.terminate(); if (logger.isTraceEnabled()) logger.trace("Transaction terminated!"); } } } catch (SipException ex) { logger.error("Failed to send unregister request", ex); setRegistrationState( RegistrationState.CONNECTION_FAILED , RegistrationStateChangeEvent.REASON_INTERNAL_ERROR , "Unable to create a unregister transaction"); throw new OperationFailedException( "Failed to send unregister request" , OperationFailedException.INTERNAL_ERROR , ex); } } /** * Returns the state of this connection. * @return a RegistrationState instance indicating the state of our * registration with the corresponding registrar. */ public RegistrationState getRegistrationState() { return currentRegistrationState; } /** * Sets our registration state to <tt>newState</tt> and dispatches an event * through the protocol provider service impl. * <p> * @param newState a reference to the RegistrationState that we're currently * detaining. * @param reasonCode one of the REASON_XXX error codes specified in * {@link RegistrationStateChangeEvent}. * @param reason a reason String further explaining the reasonCode. */ public void setRegistrationState(RegistrationState newState, int reasonCode, String reason) { this.setRegistrationState(newState, reasonCode, reason, false); } /** * Sets our registration state to <tt>newState</tt> and dispatches an event * through the protocol provider service impl. * <p> * @param newState a reference to the RegistrationState that we're currently * detaining. * @param reasonCode one of the REASON_XXX error codes specified in * {@link RegistrationStateChangeEvent}. * @param reason a reason String further explaining the reasonCode. */ public void setRegistrationState(RegistrationState newState, int reasonCode, String reason, boolean userRequest) { if( currentRegistrationState.equals(newState) ) return; RegistrationState oldState = currentRegistrationState; this.currentRegistrationState = newState; sipProvider.fireRegistrationStateChanged( oldState, newState, reasonCode, reason, userRequest); } /** * The task is started once a registration has been created. It is * scheduled to run after the expiration timeout has come to an end when * it will resend the REGISTER request. */ private class ReRegisterTask extends TimerTask { /** * Creates a new instance of the ReRegister task prepared to reregister * us after the specified interval. */ public ReRegisterTask() {} /** * Simply calls the register method. */ @Override public void run() { try { if (getRegistrationState() == RegistrationState.REGISTERED) register(); } catch (OperationFailedException ex) { logger.error("Failed to reRegister", ex); setRegistrationState( RegistrationState.CONNECTION_FAILED , RegistrationStateChangeEvent.REASON_INTERNAL_ERROR , "Failed to re register with the SIP server."); } } } /** * Cancels all pending reregistrations. The method is useful when shutting * down. */ private void cancelPendingRegistrations() { reRegisterTimer.cancel(); reRegisterTimer = null; reRegisterTimer = new Timer(); } /** * Schedules a reregistration for after almost <tt>expires</tt> * seconds. The method leaves a margin for all intervals, scheduling * the registration for slightly earlier by reducing with 10% the number * of seconds specified in the expires param. * <p> * @param expires the number of seconds that we specified in the * expires header when registering. */ private void scheduleReRegistration(int expires) { ReRegisterTask reRegisterTask = new ReRegisterTask(); //java.util.Timer thinks in miliseconds and expires header contains //seconds //bug report and fix by Willem Romijn (romijn at lucent.com) //We keep a margin of 10% when sending re-registrations (1000 //becomes 900) reRegisterTimer.schedule(reRegisterTask, expires * 900); } /** * Returns the next long to use as a cseq header value. * @return the next long to use as a cseq header value. */ private long getNextCSeqValue() { return nextCSeqValue++; } /** * Handles a NOT_IMPLEMENTED response sent in reply of our register request. * * @param transatcion the transaction that our initial register request * belongs to. * @param response our initial register request. */ public void processNotImplemented(ClientTransaction transatcion, Response response) { setRegistrationState( RegistrationState.CONNECTION_FAILED , RegistrationStateChangeEvent.REASON_NOT_SPECIFIED , registrarName + " does not appear to be a sip registrar. (Returned a " +"NOT_IMPLEMENTED response to a register request)"); } /** * Analyzes the content of the <tt>allowHeader</tt> and determines whether * some of the operation sets in our provider need to be disabled. * * @param headerIter the list of <tt>AllowHeader</tt>s that we need to * analyze for supported features. */ private void updateSupportedOperationSets( ListIterator<AllowHeader> headerIter) { HashSet<String> set = new HashSet<String>(); while(headerIter.hasNext()) { set.add(headerIter.next().getMethod()); } //Emil XXX: I am not sure how this would work out with most providers //so we are going to only start by implementing this feature for IM. if ( !set.contains(Request.MESSAGE) && !sipProvider.getAccountID() .getAccountPropertyBoolean(FORCE_MESSAGING_PROP, false)) { sipProvider.removeSupportedOperationSet( OperationSetBasicInstantMessaging.class); } } /** * Returns the JAIN SIP provider that should be used for communication with * our current registrar. * * @return the JAIN SIP provider that should be used for communication with * our current registrar. */ public SipProvider getJainSipProvider() { return sipProvider.getJainSipProvider(getTransport()); } /** * Returns the transport that this connection is currently using to * communicate with the Registrar. * * @return the transport that this connection is using. */ public String getTransport() { return this.registrationTransport; } /** * Analyzes the incoming <tt>responseEvent</tt> and then forwards it to the * proper event handler. * * @param responseEvent the responseEvent that we received * ProtocolProviderService. * @return <tt>true</tt> if the specified event has been handled by this * processor and shouldn't be offered to other processors registered * for the same method; <tt>false</tt>, otherwise */ @Override public boolean processResponse(ResponseEvent responseEvent) { ClientTransaction clientTransaction = responseEvent .getClientTransaction(); Response response = responseEvent.getResponse(); SipProvider sourceProvider = (SipProvider)responseEvent.getSource(); boolean processed = false; //OK if (response.getStatusCode() == Response.OK) { processOK(clientTransaction, response); processed = true; } //NOT_IMPLEMENTED else if (response.getStatusCode() == Response.NOT_IMPLEMENTED) { processNotImplemented(clientTransaction, response); processed = true; } //Trying else if (response.getStatusCode() == Response.TRYING) { //do nothing } //401 UNAUTHORIZED, //407 PROXY_AUTHENTICATION_REQUIRED, //403 FORBIDDEN else if (response.getStatusCode() == Response.UNAUTHORIZED || response.getStatusCode() == Response.PROXY_AUTHENTICATION_REQUIRED || response.getStatusCode() == Response.FORBIDDEN) { processAuthenticationChallenge( clientTransaction, response, sourceProvider); processed = true; } else if (response.getStatusCode() == Response.INTERVAL_TOO_BRIEF) { processIntervalTooBrief(response); processed = true; } else if ( response.getStatusCode() >= 400 ) { logger.error("Received an error response (" + response.getStatusCode() + ")" ); int registrationStateReason = RegistrationStateChangeEvent.REASON_NOT_SPECIFIED; if(response.getStatusCode() == Response.NOT_FOUND) registrationStateReason = RegistrationStateChangeEvent.REASON_NON_EXISTING_USER_ID; //tell the others we couldn't register this.setRegistrationState( RegistrationState.CONNECTION_FAILED , registrationStateReason , "Received an error while trying to register. " + "Server returned error:" + response.getReasonPhrase() ); processed = true; } //ignore everything else. return processed; } /** * Attempts to re-generate the corresponding request with the proper * credentials and terminates the call if it fails. * * @param clientTransaction the corresponding transaction * @param response the challenge * @param jainSipProvider the provider that received the challenge */ private void processAuthenticationChallenge( ClientTransaction clientTransaction, Response response, SipProvider jainSipProvider) { try { if (logger.isDebugEnabled()) logger.debug("Authenticating a Register request."); ClientTransaction retryTran; if( response.getStatusCode() == Response.UNAUTHORIZED || response.getStatusCode() == Response.PROXY_AUTHENTICATION_REQUIRED) { //respond to the challenge retryTran = sipProvider.getSipSecurityManager().handleChallenge( response, clientTransaction, jainSipProvider); } else { //if we're in certificate authentication mode, a 403 probably //means that the certificate didn't match. stop connecting. String certId = sipProvider.getAccountID() .getAccountPropertyString( ProtocolProviderFactory.CLIENT_TLS_CERTIFICATE); if(certId != null) { //tell the others we couldn't register this.setRegistrationState( RegistrationState.AUTHENTICATION_FAILED, RegistrationStateChangeEvent .REASON_NON_EXISTING_USER_ID, "We failed to authenticate with the server." ); return; } //we got a BAD PASSWD reply. send a new credential-less request //in order to trigger a new challenge and rerequest a password. retryTran = sipProvider.getSipSecurityManager() .handleForbiddenResponse( response, clientTransaction, jainSipProvider); } if(retryTran == null) { if (logger.isTraceEnabled()) logger.trace("No password supplied or error occured!"); unregister(false); return; } //the security manager has most probably changed the sequence number //so let's make sure we update it here. updateRegisterSequenceNumber(retryTran); retryTran.sendRequest(); return; } catch (OperationFailedException exc) { if(exc.getErrorCode() == OperationFailedException.AUTHENTICATION_CANCELED) { this.setRegistrationState( RegistrationState.UNREGISTERED, RegistrationStateChangeEvent.REASON_USER_REQUEST, "User has canceled the authentication process."); } else if (exc.getErrorCode() == OperationFailedException.FORBIDDEN) { this.setRegistrationState( RegistrationState.CONNECTION_FAILED, RegistrationStateChangeEvent.REASON_AUTHENTICATION_FAILED, exc.getMessage()); } else { //tell the others we couldn't register this.setRegistrationState( RegistrationState.AUTHENTICATION_FAILED, RegistrationStateChangeEvent.REASON_AUTHENTICATION_FAILED, "We failed to authenticate with the server." ); } } catch (Exception exc) { logger.error("We failed to authenticate a Register request.", exc); //tell the others we couldn't register this.setRegistrationState( RegistrationState.AUTHENTICATION_FAILED , RegistrationStateChangeEvent.REASON_AUTHENTICATION_FAILED , "We failed to authenticate with the server." ); } } /** * Processes a Request received on a SipProvider upon which this SipListener * is registered. * <p> * * @param requestEvent requestEvent fired from the SipProvider to the * SipListener representing a Request received from the network. * @return <tt>true</tt> if the specified event has been handled by this * processor and shouldn't be offered to other processors registered * for the same method; <tt>false</tt>, otherwise */ @Override public boolean processRequest(RequestEvent requestEvent) { /** @todo send not implemented */ return false; } /** * Processes a retransmit or expiration Timeout of an underlying * {@link Transaction}handled by this SipListener. * * @param timeoutEvent the timeoutEvent received indicating either the * message retransmit or transaction timed out. * @return <tt>true</tt> if the specified event has been handled by this * processor and shouldn't be offered to other processors registered * for the same method; <tt>false</tt>, otherwise */ @Override public boolean processTimeout(TimeoutEvent timeoutEvent) { if(sipProvider.registerUsingNextAddress()) return false; // don't alert the user if we're already off if (!getRegistrationState().equals(RegistrationState.UNREGISTERED)) { setRegistrationState(RegistrationState.CONNECTION_FAILED, RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, "A timeout occurred while trying to connect to the server."); } return true; } /** * Process an asynchronously reported IO Exception. * * @param exceptionEvent The Exception event that is reported to the * application. * @return <tt>true</tt> if the specified event has been handled by this * processor and shouldn't be offered to other processors registered * for the same method; <tt>false</tt>, otherwise */ @Override public boolean processIOException(IOExceptionEvent exceptionEvent) { setRegistrationState( RegistrationState.CONNECTION_FAILED , RegistrationStateChangeEvent.REASON_NOT_SPECIFIED , "An error occurred while trying to connect to the server." + "[" + exceptionEvent.getHost() + "]:" + exceptionEvent.getPort() + "/" + exceptionEvent.getTransport()); return true; } /** * Process error 423 Interval Too Brief. If there is minimum interval * specified use it. Check the specified interval is greater than the one * we used in our register. * @param response the response containing the min expires header. */ private void processIntervalTooBrief(Response response) { // interval is too brief, if we have specified correct interval // in the response use it and re-register MinExpiresHeader header = (MinExpiresHeader)response.getHeader(MinExpiresHeader.NAME); if(header != null) { int expires = header.getExpires(); if(expires > registrationsExpiration) { registrationsExpiration = expires; try { register(); return; } catch (Throwable e) { logger.error("Cannot send register!", e); setRegistrationState( RegistrationState.CONNECTION_FAILED, RegistrationStateChangeEvent.REASON_NOT_SPECIFIED, "A timeout occurred while trying to " + "connect to the server."); return; } } } //tell the others we couldn't register this.setRegistrationState( RegistrationState.CONNECTION_FAILED , RegistrationStateChangeEvent.REASON_NOT_SPECIFIED , "Received an error while trying to register. " + "Server returned error:" + response.getReasonPhrase() ); } /** * Returns a string representation of this connection instance * instance including information that would permit to distinguish it among * other sip listeners when reading a log file. * <p> * @return a string representation of this operation set. */ @Override public String toString() { String className = getClass().getName(); try { className = className.substring(className.lastIndexOf('.') + 1); } catch (Exception ex) { // we don't want to fail in this method because we've messed up //something with indexes, so just ignore. } return className + "-[dn=" + sipProvider.getOurDisplayName() +" addr="+getAddressOfRecord() + "]"; } /** * Updates our local sequence counter based on the value in the CSeq header * of the request that originated the <tt>lastClientTran</tt> transation. * The method is used after running an authentication challenge through * the security manager. The Security manager would manually increment the * CSeq number of the request so we need to update our local counter or * otherwise the next REGISTER we send would have a wrong CSeq. * * @param lastClientTran the transaction that we should be using to update * our local sequence number */ private void updateRegisterSequenceNumber(ClientTransaction lastClientTran) { Request req = lastClientTran.getRequest(); CSeqHeader cSeqHeader = (CSeqHeader)req.getHeader(CSeqHeader.NAME); long sequenceNumber = cSeqHeader.getSeqNumber(); //sequenceNumber is the value of the CSeq header in the request we just //sent so the next CSeq Value should be set to seqNum + 1. this.nextCSeqValue = sequenceNumber + 1; } /** * Determines whether Register requests should be using a route header. The * return value of this method is specified by the REGISTERS_USE_ROUTE * account property. * * Jeroen van Bemmel: The reason this may needed, is that standards- * compliant registrars check the domain in the request URI. If it contains * an IP address, some registrars are unable to match/process it (they may * forward instead, and get into a forwarding loop) * * @return true if we should be using a route header. */ public boolean isRouteHeaderEnabled() { return useRouteHeader; } /** * Returns true if this is a fake connection that is not actually using * a registrar. This method should be overridden in * <tt>SipRegistrarlessConnection</tt> and return <tt>true</tt> in there. * * @return true if this connection is really using a registrar and * false if it is a fake connection that doesn't really use a registrar. */ public boolean isRegistrarless() { return false; } /** * The registrar URI we use for registrar connection. * @return the registrar URI. * * @throws ParseException in case the specified registrar address is not a * valid registrar address. */ public SipURI getRegistrarURI() throws ParseException { if(registrarURI == null) { registrarURI = sipProvider.getAddressFactory().createSipURI( null, this.registrarName); if(registrarPort != ListeningPoint.PORT_5060) registrarURI.setPort(registrarPort); if(!registrationTransport.equals(ListeningPoint.UDP) && !registrationTransport.equals(ListeningPoint.TLS)) { registrarURI.setTransportParam(registrationTransport); } } return registrarURI; } /** * Returns the address of record that we are using to register against our * registrar or null if this is a fake or "Registrarless" connection. If * our are trying to obtain an address to put in your from header and don't * no what to do in the case of registrarless accounts - think about using * <tt>ProtocolProviderServiceSipImpl.createAddressOfRecord()</tt>. * * @return our Address Of Record */ public Address getAddressOfRecord() { //if we have a registrar we should return our Address of record here. if(this.ourSipAddressOfRecord != null) return this.ourSipAddressOfRecord; // the connection would not have an address of record if it does not // have a registrar. if(isRegistrarless()) return null; //create our own address. String ourUserID = sipProvider.getAccountID().getAccountPropertyString( ProtocolProviderFactory.USER_ID); String sipUriHost = null; if( ourUserID.indexOf("@") != -1 && ourUserID.indexOf("@") < ourUserID.length() -1 ) { //use the domain in the SIP URI if possible. sipUriHost = ourUserID.substring( ourUserID.indexOf("@") + 1 ); ourUserID = ourUserID.substring( 0, ourUserID.indexOf("@") ); } //if there was no domain name in the SIP URI use the registrar address if(sipUriHost == null) sipUriHost = registrarName; SipURI ourSipURI; try { ourSipURI = sipProvider.getAddressFactory().createSipURI( ourUserID, sipUriHost); ourSipAddressOfRecord = sipProvider.getAddressFactory() .createAddress(sipProvider.getOurDisplayName(), ourSipURI); ourSipAddressOfRecord.setDisplayName(sipProvider.getOurDisplayName()); } catch (ParseException ex) { throw new IllegalArgumentException( "Could not create a SIP URI for user " + ourUserID + "@" + sipUriHost + " and registrar " + registrarName); } return ourSipAddressOfRecord; } }