/*
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.impl.protocol.sip;
import java.net.*;
import java.text.*;
import java.util.*;
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
implements MethodProcessor
{
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 InetAddress of the registrar we are connecting to.
*/
private InetAddress registrarAddress = null;
/**
* The default amount of time (in seconds) that registration take to
* expire or otherwise put - the number of seconds we wait before re-
* registering.
*/
public static final int DEFAULT_REGISTRATION_EXPIRATION = 3600;
/**
* 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 specifing 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;
/**
* 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;
/**
* Creates a new instance of this class.
*
* @param registrarAddress the ip address or FQDN of the registrar we will
* be registering with.
* @param registrarPort the port on which the specified registrar is
* accepting connections.
* @param registrationTransport the transport to use when sending our
* REGISTER request to the server.
* @param expirationTimeout the number of seconds to wait before
* re-registering.
* @param sipProviderCallback a reference to the
* ProtocolProviderServiceSipImpl instance that created us.
*
* @throws ParseException in case the specified registrar address is not a
* valid reigstrar address.
*/
public SipRegistrarConnection(InetAddress registrarAddress,
int registrarPort,
String registrationTransport,
int expirationTimeout,
ProtocolProviderServiceSipImpl sipProviderCallback)
throws ParseException
{
this.sipProvider = sipProviderCallback;
this.registrarAddress = registrarAddress;
registrarURI = sipProvider.getAddressFactory().createSipURI(
null, registrarAddress.getHostName());
if(registrarPort != ListeningPoint.PORT_5060)
registrarURI.setPort(registrarPort);
registrarURI.setTransportParam(registrationTransport);
this.registrationsExpiration = expirationTimeout;
//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()
{
}
/**
* 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);
//From
FromHeader fromHeader = null;
try
{
fromHeader = sipProvider.getHeaderFactory().createFromHeader(
getAddressOfRecord(),
ProtocolProviderServiceSipImpl.generateLocalTag());
}
catch (ParseException ex)
{
//this should never happen so let's just log and bail.
logger.error(
"Failed to generate a from header for our register request."
, ex);
setRegistrationState(RegistrationState.CONNECTION_FAILED
, RegistrationStateChangeEvent.REASON_INTERNAL_ERROR
, ex.getMessage());
throw new OperationFailedException(
"Failed to generate a from header for our register request."
, OperationFailedException.INTERNAL_ERROR
, ex);
}
//Call ID Header
CallIdHeader callIdHeader
= this.getJainSipProvider().getNewCallId();
//CSeq Header
CSeqHeader cSeqHeader = null;
try
{
cSeqHeader = sipProvider.getHeaderFactory().createCSeqHeader(
getNextCSeqValue(), Request.REGISTER);
}
catch (ParseException ex)
{
//Should never happen
logger.error("Corrupt Sip Stack", ex);
setRegistrationState(RegistrationState.CONNECTION_FAILED
, RegistrationStateChangeEvent.REASON_INTERNAL_ERROR
, ex.getMessage());
throw new OperationFailedException(
"Failed to generate a from header for our register request."
, OperationFailedException.INTERNAL_ERROR
, ex);
}
catch (InvalidArgumentException ex)
{
//Should never happen
logger.error("The application is corrupt", ex);
setRegistrationState(RegistrationState.CONNECTION_FAILED
, RegistrationStateChangeEvent.REASON_INTERNAL_ERROR
, ex.getMessage());
throw new OperationFailedException(
"Failed to generate a from header for our register request."
, OperationFailedException.INTERNAL_ERROR
, ex);
}
//To Header (Equal to the from header in a registration message.)
ToHeader toHeader = null;
try
{
toHeader = sipProvider.getHeaderFactory().createToHeader(
getAddressOfRecord(), null);
}
catch (ParseException ex)
{
logger.error("Could not create a To header for address:"
+ fromHeader.getAddress(),
ex);
//throw was missing - reported by Eero Vaarnas
setRegistrationState(RegistrationState.CONNECTION_FAILED
, RegistrationStateChangeEvent.REASON_INTERNAL_ERROR
, ex.getMessage());
throw new OperationFailedException(
"Could not create a To header for address:"
+ fromHeader.getAddress()
, OperationFailedException.INTERNAL_ERROR
, ex);
}
//MaxForwardsHeader
MaxForwardsHeader maxForwardsHeader = sipProvider.
getMaxForwardsHeader();
//create a host-only uri for the request uri header.
String domain
= ((SipURI) toHeader.getAddress().getURI()).getHost();
//request URI
SipURI requestURI = null;
//Via Headers
ArrayList<ViaHeader> viaHeaders = null;
//Request
Request request = null;
try
{
requestURI
= sipProvider.getAddressFactory().createSipURI(null,domain);
viaHeaders = sipProvider.getLocalViaHeaders(requestURI);
request = sipProvider.getMessageFactory().createRequest(
requestURI
, Request.REGISTER
, callIdHeader
, cSeqHeader
, fromHeader
, toHeader
, viaHeaders
, maxForwardsHeader);
// JvB: use Route header in addition to the request URI
// because some servers loop otherwise
if(isRouteHeaderEnabled())
{
SipURI regURI = (SipURI) registrarURI.clone();
regURI.setLrParam();
RouteHeader route = sipProvider.getHeaderFactory()
.createRouteHeader( sipProvider.getAddressFactory()
.createAddress( null, regURI ));
request.addHeader( route );
}
}
catch (ParseException ex)
{
logger.error("Could not create the register request!", ex);
setRegistrationState(RegistrationState.CONNECTION_FAILED
, RegistrationStateChangeEvent.REASON_INTERNAL_ERROR
, ex.getMessage());
throw new OperationFailedException(
"Could not create the register request!"
, OperationFailedException.INTERNAL_ERROR
, ex);
}
//User Agent
UserAgentHeader userAgentHeader
= sipProvider.getSipCommUserAgentHeader();
if(userAgentHeader != null)
request.addHeader(userAgentHeader);
//Expires Header - try to generate it twice in case the default
//expiration period is null
ExpiresHeader expHeader = null;
for (int retry = 0; retry < 2; retry++)
{
try
{
expHeader = sipProvider.getHeaderFactory().createExpiresHeader(
registrationsExpiration);
}
catch (InvalidArgumentException ex)
{
if (retry == 0)
{
registrationsExpiration = 3600;
continue;
}
logger.error(
"Invalid registrations expiration parameter - "
+ registrationsExpiration,
ex);
setRegistrationState(RegistrationState.CONNECTION_FAILED
, RegistrationStateChangeEvent.REASON_INTERNAL_ERROR
, ex.getMessage());
throw new OperationFailedException(
"Invalid registrations expiration parameter - "
+ registrationsExpiration
, OperationFailedException.INTERNAL_ERROR
, ex);
}
}
request.addHeader(expHeader);
//Contact Header (should contain IP)
ContactHeader contactHeader = sipProvider.getContactHeader(requestURI);
//add expires in the contact header as well in case server likes it
//better there.
try
{
contactHeader.setExpires(registrationsExpiration);
}
catch (InvalidArgumentException exc)
{
logger.error("Failed to add an expires param ("+
registrationsExpiration + ") to a contact header."
+"will ignore error"
,exc);
}
request.addHeader(contactHeader);
//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();
logger.debug("sent request= " + request);
}
//we sometimes get a null pointer exception here so catch them all
catch (Exception ex)
{
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.
*/
public void processOK(ClientTransaction clientTransatcion,
Response response)
{
FromHeader fromHeader =
( (FromHeader) response.getHeader(FromHeader.NAME));
//first extract the expires value that we requested
int requestedExpiration = 0;
Request register = clientTransatcion.getRequest();
ExpiresHeader expiresHeader = register.getExpires();
if (expiresHeader != null)
requestedExpiration = expiresHeader.getExpires();
else
{
//if there is no expires header check the contact header
ContactHeader contactHeader = (ContactHeader) register
.getHeader(ContactHeader.NAME);
if (contactHeader != null)
requestedExpiration = contactHeader.getExpires();
else
requestedExpiration = 0;
}
//now check if the registrar has touched our expiration timeout in its
//response
int grantedExpiration = registrationsExpiration;
expiresHeader = response.getExpires();
if (expiresHeader != null)
{
grantedExpiration = expiresHeader.getExpires();
}
else
{
//if there is no expires header check the contact header
ContactHeader contactHeader = (ContactHeader) response.getHeader(
ContactHeader.NAME);
if (contactHeader != null)
{
grantedExpiration = contactHeader.getExpires();
}
//else - we simply reuse the expires timeout we stated in our last
//request
else
{
grantedExpiration = requestedExpiration;
}
}
//If this is a respond 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)
{
setRegistrationState(RegistrationState.UNREGISTERED
, RegistrationStateChangeEvent.REASON_USER_REQUEST
, "Registration terminated.");
}
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"))
|| keepAliveMethod == null )
{
int registrationInterval =
sipProvider.getAccountID().getAccountPropertyInt(
KEEP_ALIVE_INTERVAL, KEEP_ALIVE_INTERVAL_DEFAULT_VALUE);
if (registrationInterval < grantedExpiration)
{
scheduleTime = registrationInterval;
}
}
//schedule a reregistration.
scheduleReRegistration(scheduleTime);
setRegistrationState(
RegistrationState.REGISTERED
, RegistrationStateChangeEvent.REASON_NOT_SPECIFIED
, null);
}
}
/**
* 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
{
unregister(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.
*/
private void unregister(boolean sendUnregister) throws OperationFailedException
{
if (getRegistrationState() == RegistrationState.UNREGISTERED)
{
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 a un-Register request.
Request unregisterRequest = (Request) registerRequest.clone();
try
{
unregisterRequest.getExpires().setExpires(0);
CSeqHeader cSeqHeader =
(CSeqHeader) unregisterRequest.getHeader(CSeqHeader.NAME);
//[issue 1] - increment registration cseq number
//reported by - Roberto Tealdi <roby.tea@tin.it>
cSeqHeader.setSeqNumber(getNextCSeqValue());
//remove the branch id.
ViaHeader via
= (ViaHeader)unregisterRequest.getHeader(ViaHeader.NAME);
if(via != null)
via.removeParameter("branch");
}
catch (InvalidArgumentException ex)
{
logger.error("Unable to set Expires Header", 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);
}
//also set the expires param in the contact header in case server
//prefers it this way.
ContactHeader contact
= (ContactHeader)unregisterRequest.getHeader(ContactHeader.NAME);
try
{
contact.setExpires(0);
}
catch (InvalidArgumentException exc)
{
logger.error("Failed to add an expires param ("+
registrationsExpiration + ") to a contact header."
+"will ignore error"
,exc);
}
ClientTransaction unregisterTransaction = null;
try
{
unregisterTransaction =
this.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
{
//check whether there's a cached authorization header for this
//call id and if so - attach it to the request.
// add authorization header
CallIdHeader call = (CallIdHeader)unregisterRequest
.getHeader(CallIdHeader.NAME);
String callid = call.getCallId();
AuthorizationHeader authorization = sipProvider
.getSipSecurityManager()
.getCachedAuthorizationHeader(callid);
if(authorization != null)
unregisterRequest.addHeader(authorization);
unregisterTransaction.sendRequest();
logger.info("sent request: " + unregisterRequest);
//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)))
{
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())
{
logger.trace("Will try to terminate reg tran ...");
regTrans.terminate();
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.
*/
void setRegistrationState(RegistrationState newState,
int reasonCode,
String reason)
{
if( currentRegistrationState.equals(newState) )
return;
RegistrationState oldState = currentRegistrationState;
this.currentRegistrationState = newState;
sipProvider.fireRegistrationStateChanged(
oldState, newState, reasonCode, reason);
}
/**
* 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.
*/
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 larger than
* 60 seconds, 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)
if (expires > 60)
{
expires = expires * 900;
}
else{
expires = expires * 1000;
}
reRegisterTimer.schedule(reRegisterTask, expires);
}
/**
* 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
, registrarAddress.getHostAddress()
+ " does not appear to be a sip registrar. (Returned a "
+"NOT_IMPLEMENTED response to a register request)");
}
/**
* Returns the address of this connection's registrar.
*
* @return the InetAddress of our registrar server.
*/
private InetAddress getRegistrarAddress()
{
return registrarAddress;
}
/**
* Returns the listening point that should be used for communication with our
* current registrar.
*
* @return the listening point that should be used for communication with our
* current registrar.
*/
ListeningPoint getListeningPoint()
{
return sipProvider.getListeningPoint(registrarURI.getTransportParam());
}
/**
* 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 registrarURI.getTransportParam();
}
/**
* 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
*/
public boolean processResponse(ResponseEvent responseEvent)
{
ClientTransaction clientTransaction = responseEvent
.getClientTransaction();
Response response = responseEvent.getResponse();
Dialog dialog = clientTransaction.getDialog();
String method = ( (CSeqHeader) response.getHeader(CSeqHeader.NAME)).
getMethod();
Response responseClone = (Response) response.clone();
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
else if (response.getStatusCode() == Response.UNAUTHORIZED
|| response.getStatusCode()
== Response.PROXY_AUTHENTICATION_REQUIRED)
{
processAuthenticationChallenge(clientTransaction
, response
, sourceProvider);
processed = true;
}
//403 FORBIDDEN
else if (response.getStatusCode() == Response.FORBIDDEN)
{
processForbidden(clientTransaction
, response
, sourceProvider);
processed = true;
}
//errors
else if ( response.getStatusCode() / 100 == 4 )
{
logger.error("Received an error response.");
//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()
);
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
{
logger.debug("Authenticating a Register request.");
ClientTransaction retryTran
= sipProvider.getSipSecurityManager().handleChallenge(
response,
clientTransaction,
jainSipProvider);
if(retryTran == null)
{
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
{
//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."
);
}
}
/**
* Makes sure that the last password used is removed from the cache, and
* notifies the user of the authentication failure..
*
* @param clientTransaction the corresponding transaction
* @param response the challenge
* @param jainSipProvider the provider that received the challenge
*/
private void processForbidden(
ClientTransaction clientTransaction,
Response response,
SipProvider jainSipProvider)
{
logger.debug("Authenticating a Register request.");
sipProvider.getSipSecurityManager().handleForbiddenResponse(
response
, clientTransaction
, jainSipProvider);
//tell the others we couldn't register
this.setRegistrationState(
RegistrationState.AUTHENTICATION_FAILED
, RegistrationStateChangeEvent.REASON_AUTHENTICATION_FAILED
, "Received a "+Response.FORBIDDEN+" FORBIDDEN response while "
+"authenticating. Server returned error:"
+ response.getReasonPhrase());
}
/**
* Process an asynchronously reported DialogTerminatedEvent. When a dialog
* transitions to the Terminated state, the stack keeps no further records
* of the dialog. This notification can be used by applications to clean up
* any auxiliary data that is being maintained for the given dialog.
*
* @param dialogTerminatedEvent -- an event that indicates that the dialog
* has transitioned into the terminated state.
* @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
* @since v1.2
*/
public boolean processDialogTerminated(DialogTerminatedEvent
dialogTerminatedEvent)
{
return false;
}
/**
* 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
*/
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
*/
public boolean processTimeout(TimeoutEvent timeoutEvent)
{
// don't alert the user if we're already off
if (getRegistrationState().equals(RegistrationState.UNREGISTERED) == false)
{
setRegistrationState(RegistrationState.CONNECTION_FAILED,
RegistrationStateChangeEvent.REASON_NOT_SPECIFIED,
"A timeout occurred while trying to connect to the server.");
}
return true;
}
/**
* Process an asynchronously reported TransactionTerminatedEvent. When a
* transaction transitions to the Terminated state, the stack keeps no
* further records of the transaction.
*
* @param transactionTerminatedEvent an event that indicates that the
* transaction has transitioned into the terminated state.
* @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
*/
public boolean processTransactionTerminated(TransactionTerminatedEvent
transactionTerminatedEvent)
{
//doesn't mean anything. we do failure handling in processTimeout
return false;
}
/**
* 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
*/
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;
}
/**
* 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.
*/
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;
}
/**
* Specifies whether Register requests should be using a route header.
*
* 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 setRouteHeaderEnabled(boolean useRouteHeader)
{
return this.useRouteHeader = 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;
}
/**
* 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 = getRegistrarAddress().getHostName();
SipURI ourSipURI = null;
try
{
ourSipURI = sipProvider.getAddressFactory().createSipURI(
ourUserID, sipUriHost);
ourSipAddressOfRecord = sipProvider.getAddressFactory()
.createAddress(sipProvider.getOurDisplayName(), ourSipURI);
}
catch (ParseException ex)
{
throw new IllegalArgumentException(
"Could not create a SIP URI for user "
+ ourUserID + "@" + sipUriHost
+ " and registrar "
+ getRegistrarAddress().getHostName());
}
return ourSipAddressOfRecord;
}
}