/*
* Copyright (C) 2009 The Sipdroid Open Source Project
* Copyright (C) 2005 Luca Veltri - University of Parma - Italy
*
* This file is part of Sneakbox (http://sneakbox.googlecode.com)
*
* Sneakbox is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this source code; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.sneakbox.sipua;
import java.util.Vector;
import org.sneakbox.sipua.ui.Receiver;
import org.sneakbox.sipua.ui.Settings;
import org.sneakbox.sipua.ui.Sneakbox;
import org.zoolu.sip.address.NameAddress;
import org.zoolu.sip.authentication.DigestAuthentication;
import org.zoolu.sip.dialog.SubscriberDialog;
import org.zoolu.sip.dialog.SubscriberDialogListener;
import org.zoolu.sip.header.AcceptHeader;
import org.zoolu.sip.header.AuthorizationHeader;
import org.zoolu.sip.header.ContactHeader;
import org.zoolu.sip.header.ExpiresHeader;
import org.zoolu.sip.header.Header;
import org.zoolu.sip.header.ProxyAuthenticateHeader;
import org.zoolu.sip.header.ProxyAuthorizationHeader;
import org.zoolu.sip.header.StatusLine;
import org.zoolu.sip.header.ViaHeader;
import org.zoolu.sip.header.WwwAuthenticateHeader;
import org.zoolu.sip.message.Message;
import org.zoolu.sip.message.MessageFactory;
import org.zoolu.sip.message.SipMethods;
import org.zoolu.sip.provider.SipProvider;
import org.zoolu.sip.provider.SipStack;
import org.zoolu.sip.provider.TransactionIdentifier;
import org.zoolu.sip.transaction.TransactionClient;
import org.zoolu.sip.transaction.TransactionClientListener;
import org.zoolu.tools.Log;
import org.zoolu.tools.LogLevel;
import org.zoolu.tools.Parser;
import android.preference.PreferenceManager;
/**
* Register User Agent. It registers (one time or periodically) a contact
* address with a registrar server.
*/
public class RegisterAgent implements TransactionClientListener, SubscriberDialogListener {
/** Max number of registration attempts. */
static final int MAX_ATTEMPTS = 3;
/* States for the RegisterAgent Module */
public static final int UNREGISTERED = 0;
public static final int REGISTERING = 1;
public static final int REGISTERED = 2;
public static final int DEREGISTERING = 3;
/** RegisterAgent listener */
RegisterAgentListener listener;
/** SipProvider */
SipProvider sip_provider;
/** User's URI with the fully qualified domain name of the registrar server. */
NameAddress target;
/** User name. */
String username;
/** User realm. */
String realm;
/** User's passwd. */
String passwd;
/** Q value for this registration (added by mandrajg)*/
String qvalue;
/** IMS Communication Service Identifier for this registration (currently only one supported)(added by mandrajg) */
String icsi;
/** Nonce for the next authentication. */
String next_nonce;
/** Qop for the next authentication. */
String qop;
/** User's contact address. */
NameAddress contact;
/** Expiration time. */
int expire_time;
/** Whether keep on registering. */
boolean loop;
/** Event logger. */
Log log;
/** Number of registration attempts. */
int attempts,subattempts;
/** Current State of the registrar component */
int CurrentState = UNREGISTERED;
UserAgentProfile user_profile;
SubscriberDialog sd;
boolean alreadySubscribed = false;
Message currentSubscribeMessage;
public final int SUBSCRIPTION_EXPIRES = 184000;
/**
* Creates a new RegisterAgent with authentication credentials (i.e.
* username, realm, and passwd).
*/
public RegisterAgent(SipProvider sip_provider, String target_url,
String contact_url, String username, String realm, String passwd,
RegisterAgentListener listener,UserAgentProfile user_profile,
String qvalue, String icsi) { // modified by mandrajg
init(sip_provider, target_url, contact_url, listener);
// authentication specific parameters
this.username = username;
this.realm = realm;
this.passwd = passwd;
this.user_profile = user_profile;
// IMS specifics (added by mandrajg)
this.qvalue = qvalue;
this.icsi = icsi;
}
public void halt() {
stopMWI();
this.listener = null;
}
/** Inits the RegisterAgent. */
private void init(SipProvider sip_provider, String target_url,
String contact_url, RegisterAgentListener listener) {
this.listener = listener;
this.sip_provider = sip_provider;
this.log = sip_provider.getLog();
this.target = new NameAddress(target_url);
this.contact = new NameAddress(contact_url);
this.expire_time = SipStack.default_expires;
// authentication
this.username = null;
this.realm = null;
this.passwd = null;
this.next_nonce = null;
this.qop = null;
this.attempts = 0;
}
/** Whether it is periodically registering. */
public boolean isRegistered() {
return (CurrentState == REGISTERED || CurrentState == REGISTERING);
}
/** Registers with the registrar server. */
public boolean register() {
return register(expire_time);
}
TransactionClient t;
/** Registers with the registrar server for <i>expire_time</i> seconds. */
public boolean register(int expire_time) {
attempts = 0;
if (expire_time > 0)
{
//Update this to be the default registration duration for next
//instances as well.
if (CurrentState == DEREGISTERING) {
t.terminate();
onTransTimeout(t);
}
if (CurrentState != UNREGISTERED && CurrentState != REGISTERED)
{
return false;
}
this.expire_time = expire_time;
CurrentState = REGISTERING;
}
else
{
if (CurrentState == REGISTERING) {
t.terminate();
onTransTimeout(t);
}
if (CurrentState != REGISTERED)
{
//This is an error condition we must exit, we should not de-register if
//we have not registered at all
return false;
}
//this is the case for de-registration
expire_time = 0;
CurrentState = DEREGISTERING;
}
//Create message re (modified by mandrajg)
Message req = MessageFactory.createRegisterRequest(sip_provider,
target, target, new NameAddress(user_profile.contact_url), qvalue, icsi);
req.setExpiresHeader(new ExpiresHeader(String.valueOf(expire_time)));
//create and fill the authentication params this is done when
//the UA has been challenged by the registrar or intermediate UA
if (next_nonce != null)
{
AuthorizationHeader ah = new AuthorizationHeader("Digest");
ah.addUsernameParam(username);
ah.addRealmParam(realm);
ah.addNonceParam(next_nonce);
ah.addUriParam(req.getRequestLine().getAddress().toString());
ah.addQopParam(qop);
String response = (new DigestAuthentication(SipMethods.REGISTER,
ah, null, passwd)).getResponse();
ah.addResponseParam(response);
req.setAuthorizationHeader(ah);
}
if (expire_time > 0)
{
printLog("Registering contact " + contact + " (it expires in "
+ expire_time + " secs)", LogLevel.HIGH);
}
else
{
printLog("Unregistering contact " + contact, LogLevel.HIGH);
}
t = new TransactionClient(sip_provider, req, this, 30000);
t.request();
return true;
}
/** Unregister with the registrar server */
public boolean unregister() {
stopMWI();
return register(0);
}
public void stopMWI()
{
if (sd != null) {
synchronized (sd) {
sd.notify();
}
}
sd = null;
if (listener != null) listener.onMWIUpdate(false, 0, null);
}
Message getSubscribeMessage(boolean current)
{
String empty = null;
Message req;
// Need to restart subscriber dialogue state engine
if (sd != null) {
synchronized (sd) {
sd.notify();
}
}
sd = new SubscriberDialog(sip_provider, "message-summary", "", this);
sip_provider.removeSipProviderListener(new TransactionIdentifier(
SipMethods.NOTIFY));
sip_provider.addSipProviderListener(new TransactionIdentifier(
SipMethods.NOTIFY), sd);
if (current) {
req = currentSubscribeMessage;
req.setCSeqHeader(req.getCSeqHeader().incSequenceNumber());
} else {
req = MessageFactory.createSubscribeRequest(sip_provider,
target.getAddress(), target, target,
new NameAddress(user_profile.contact_url), sd.getEvent(),
sd.getId(), empty, empty);
}
req.setExpiresHeader(new ExpiresHeader(SUBSCRIPTION_EXPIRES));
req.setHeader(new AcceptHeader("application/simple-message-summary"));
currentSubscribeMessage = req;
return req;
}
public void startMWI()
{
if (alreadySubscribed)
return;
Message req = getSubscribeMessage(false);
if (!PreferenceManager.getDefaultSharedPreferences(Receiver.mContext).getBoolean(Settings.PREF_MWI_ENABLED, Settings.DEFAULT_MWI_ENABLED))
return;
if (sd != null) sd.subscribe(req);
}
void delayStartMWI()
{
if (subattempts < MAX_ATTEMPTS){
subattempts++;
Thread t = new Thread(new Runnable() {
public void run() {
Object o = new Object();
try {
synchronized (o) {
o.wait(10000);
}
} catch (Exception E) {
}
startMWI();
}
});
t.start();
}
}
// **************** Subscription callback functions *****************
public void onDlgSubscriptionSuccess(SubscriberDialog dialog, int code,
String reason, Message resp)
{
final int expires;
/* Can get replays of the subscription notice, so ignore */
if (alreadySubscribed) {
return;
}
alreadySubscribed = true;
if (resp.hasExpiresHeader()) {
if (0 == (expires = resp.getExpiresHeader().getDeltaSeconds()))
return;
} else {
expires = SUBSCRIPTION_EXPIRES;
}
Thread t = new Thread(new Runnable() {
public void run() {
try {
synchronized (sd) {
sd.wait(expires*1000);
}
alreadySubscribed = false;
subattempts = 0;
startMWI();
} catch(Exception E) {
}
}
});
t.start();
}
public void onDlgSubscriptionFailure(SubscriberDialog dialog, int code,
String reason, Message resp)
{
Message req = getSubscribeMessage(true);
if (handleAuthentication(code, resp, req) && subattempts < MAX_ATTEMPTS) {
subattempts++;
sd.subscribe(req);
} else {
delayStartMWI();
}
}
public void onDlgSubscribeTimeout(SubscriberDialog dialog)
{
delayStartMWI();
}
public void onDlgSubscriptionTerminated(SubscriberDialog dialog)
{
alreadySubscribed = false;
startMWI();
}
public void onDlgNotify(SubscriberDialog dialog, NameAddress target,
NameAddress notifier, NameAddress contact, String state,
String content_type, String body, Message msg)
{
if (!PreferenceManager.getDefaultSharedPreferences(Receiver.mContext).getBoolean(Settings.PREF_MWI_ENABLED, Settings.DEFAULT_MWI_ENABLED))
return;
Parser p = new Parser(body);
final char[] propertysep = { ':', '\r', '\n' };
final char[] vmailsep = { '/' };
final char[] vmboxsep = { '@', '\r', '\n' };
String vmaccount = null;
boolean voicemail = false;
int nummsg = 0;
while (p.hasMore()) {
String property = p.getWord(propertysep);
p.skipChar();
p.skipWSP();
String value = p.getWord(Parser.CRLF);
if (property.equalsIgnoreCase("Messages-Waiting") && value.equalsIgnoreCase("yes")) {
voicemail = true;
} else if (property.equalsIgnoreCase("Voice-Message")) {
Parser np = new Parser(value);
String num = np.getWord(vmailsep);
nummsg = Integer.parseInt(num);
} else if (property.equalsIgnoreCase("Message-Account")) {
Parser np = new Parser(value);
// strip the @<pbx> because it may have nat problems
vmaccount = np.getWord(vmboxsep);
}
}
if (listener != null) listener.onMWIUpdate(voicemail, nummsg, vmaccount);
}
// **************** Transaction callback functions *****************
/** Callback function called when client sends back a failure response. */
/** Callback function called when client sends back a provisional response. */
public void onTransProvisionalResponse(TransactionClient transaction,
Message resp) { // do nothing..
}
/** Callback function called when client sends back a success response. */
public void onTransSuccessResponse(TransactionClient transaction,
Message resp)
{
if (transaction.getTransactionMethod().equals(SipMethods.REGISTER)) {
if (resp.hasAuthenticationInfoHeader())
{
next_nonce = resp.getAuthenticationInfoHeader()
.getNextnonceParam();
}
StatusLine status = resp.getStatusLine();
String result = status.getCode() + " " + status.getReason();
int expires = 0;
if (resp.hasExpiresHeader())
{
expires = resp.getExpiresHeader().getDeltaSeconds();
}
else if (resp.hasContactHeader())
{
Vector<Header> contacts = resp.getContacts().getHeaders();
for (int i = 0; i < contacts.size(); i++) {
int exp_i = (new ContactHeader((Header) contacts
.elementAt(i))).getExpires();
if (exp_i > 0 && (expires == 0 || exp_i < expires))
expires = exp_i;
}
}
printLog("Registration success: " + result, LogLevel.HIGH);
if (CurrentState == REGISTERING)
{
CurrentState = REGISTERED;
if (listener != null)
{
listener.onUaRegistrationSuccess(this, target, contact, result);
Receiver.reRegister(expires);
}
}
else
{
CurrentState = UNREGISTERED;
if (listener != null)
{
listener.onUaRegistrationSuccess(this, target, contact, result);
}
}
}
}
/** Callback function called when client sends back a failure response. */
public void onTransFailureResponse(TransactionClient transaction,
Message resp) {
if (transaction.getTransactionMethod().equals(SipMethods.REGISTER)) {
StatusLine status = resp.getStatusLine();
int code = status.getCode();
if (!processAuthenticationResponse(transaction, resp, code)) {
String result = code + " " + status.getReason();
//Since the transactions are atomic, we rollback to the
//previous state
if (CurrentState == REGISTERING)
{
CurrentState = UNREGISTERED;
if (listener != null)
{
listener.onUaRegistrationFailure(this, target, contact,
result);
Receiver.reRegister(1000);
}
}
else
{
CurrentState = REGISTERED;
if (listener != null)
{
listener.onUaRegistrationFailure(this, target, contact,
result);
}
}
printLog("Registration failure: " + result, LogLevel.HIGH);
}
}
}
private boolean generateRequestWithProxyAuthorizationheader(
Message resp, Message req){
if(resp.hasProxyAuthenticateHeader()
&& resp.getProxyAuthenticateHeader().getRealmParam()
.length() > 0){
user_profile.realm = realm = resp.getProxyAuthenticateHeader().getRealmParam();
ProxyAuthenticateHeader pah = resp.getProxyAuthenticateHeader();
String qop_options = pah.getQopOptionsParam();
printLog("DEBUG: qop-options: " + qop_options, LogLevel.MEDIUM);
qop = (qop_options != null) ? "auth" : null;
ProxyAuthorizationHeader ah = (new DigestAuthentication(
req.getTransactionMethod(), req.getRequestLine().getAddress()
.toString(), pah, qop, null, username, passwd))
.getProxyAuthorizationHeader();
req.setProxyAuthorizationHeader(ah);
return true;
}
return false;
}
private boolean generateRequestWithWwwAuthorizationheader(
Message resp, Message req){
if(resp.hasWwwAuthenticateHeader()
&& resp.getWwwAuthenticateHeader().getRealmParam()
.length() > 0){
user_profile.realm = realm = resp.getWwwAuthenticateHeader().getRealmParam();
WwwAuthenticateHeader wah = resp.getWwwAuthenticateHeader();
String qop_options = wah.getQopOptionsParam();
printLog("DEBUG: qop-options: " + qop_options, LogLevel.MEDIUM);
qop = (qop_options != null) ? "auth" : null;
AuthorizationHeader ah = (new DigestAuthentication(
req.getTransactionMethod(), req.getRequestLine().getAddress()
.toString(), wah, qop, null, username, passwd))
.getAuthorizationHeader();
req.setAuthorizationHeader(ah);
return true;
}
return false;
}
private boolean handleAuthentication(int respCode, Message resp,
Message req) {
switch (respCode) {
case 407:
return generateRequestWithProxyAuthorizationheader(resp, req);
case 401:
return generateRequestWithWwwAuthorizationheader(resp, req);
}
return false;
}
private boolean processAuthenticationResponse(TransactionClient transaction,
Message resp, int respCode){
if (attempts < MAX_ATTEMPTS){
attempts++;
Message req = transaction.getRequestMessage();
req.setCSeqHeader(req.getCSeqHeader().incSequenceNumber());
ViaHeader vh=req.getViaHeader();
String newbranch = SipProvider.pickBranch();
vh.setBranch(newbranch);
req.removeViaHeader();
req.addViaHeader(vh);
if (handleAuthentication(respCode, resp, req)) {
t = new TransactionClient(sip_provider, req, this, 30000);
t.request();
return true;
}
}
return false;
}
/** Callback function called when client expires timeout. */
public void onTransTimeout(TransactionClient transaction) {
if (transaction.getTransactionMethod().equals(SipMethods.REGISTER)) {
printLog("Registration failure: No response from server.",
LogLevel.HIGH);
//Since the transactions are atomic, we rollback to the
//previous state
if (CurrentState == REGISTERING)
{
CurrentState = UNREGISTERED;
if (listener != null)
{
listener.onUaRegistrationFailure(this, target, contact,
"Timeout");
Receiver.reRegister(1000);
}
}
else
{
CurrentState = REGISTERED;
if (listener != null)
{
listener.onUaRegistrationFailure(this, target, contact,
"Timeout");
}
}
}
}
// ****************************** Logs *****************************
/** Adds a new string to the default Log */
void printLog(String str, int level) {
if (Sneakbox.release) return;
if (log != null)
log.println("RegisterAgent: " + str, level + SipStack.LOG_LEVEL_UA);
if (level <= LogLevel.HIGH)
System.out.println("RegisterAgent: " + str);
}
/** Adds the Exception message to the default Log */
void printException(Exception e, int level) {
if (Sneakbox.release) return;
if (log != null)
log.printException(e, level + SipStack.LOG_LEVEL_UA);
}
}
|