/*
* Copyright (C) 2005 Luca Veltri - University of Parma - Italy
*
* This source code 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 2 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
*
* Author(s):
* Luca Veltri (luca.veltri@unipr.it)
*/
package local.server;
import org.zoolu.net.SocketAddress;
import org.zoolu.sip.address.*;
import org.zoolu.sip.provider.*;
import org.zoolu.sip.header.SipHeaders;
import org.zoolu.sip.header.Header;
import org.zoolu.sip.header.ToHeader;
import org.zoolu.sip.header.ViaHeader;
import org.zoolu.sip.header.ExpiresHeader;
import org.zoolu.sip.header.StatusLine;
import org.zoolu.sip.header.ContactHeader;
import org.zoolu.sip.header.MultipleHeader;
import org.zoolu.sip.header.WwwAuthenticateHeader;
import org.zoolu.sip.header.AuthorizationHeader;
import org.zoolu.sip.header.AuthenticationInfoHeader;
import org.zoolu.sip.transaction.TransactionServer;
import org.zoolu.sip.message.Message;
import org.zoolu.sip.message.MessageFactory;
import org.zoolu.sip.message.SipResponses;
import org.zoolu.tools.Parser;
import org.zoolu.tools.LogLevel;
import org.zoolu.tools.DateFormat;
import java.util.Date;
//import java.util.Locale;
//import java.text.DateFormat;
//import java.text.SimpleDateFormat;
import java.util.Vector;
import java.util.Enumeration;
/** Class Registrar implements a Registrar SIP Server.
* It extends class ServerEngine.
*/
public class Registrar extends ServerEngine
{
/** LocationService. */
protected LocationService location_service;
/** AuthenticationService (i.e. the repository with authentication credentials). */
protected AuthenticationService authentication_service;
/** AuthenticationServer. */
protected AuthenticationServer as;
/** List of already supported location services */
protected static final String[] LOCATION_SERVICES={ "local", "ldap" };
/** List of location service Classes (ordered as in <i>LOCATION_SERVICES</i>) */
protected static final String[] LOCATION_SERVICE_CLASSES={ "local.server.LocationServiceImpl", "local.ldap.LdapLocationServiceImpl" };
/** List of already supported authentication services */
protected static final String[] AUTHENTICATION_SERVICES={ "local", "ldap" };
/** List of authentication service Classes (ordered as in <i>AUTHENTICATION_SERVICES</i>) */
protected static final String[] AUTHENTICATION_SERVICE_CLASSES={ "local.server.AuthenticationServiceImpl", "local.ldap.LdapAuthenticationServiceImpl" };
/** List of already supported authentication schemes */
protected static final String[] AUTHENTICATION_SCHEMES={ "Digest" };
/** List of authentication server Classes (ordered as in <i>AUTHENTICATION_SCHEMES</i>) */
protected static final String[] AUTHENTICATION_SERVER_CLASSES={ "local.server.AuthenticationServerImpl" };
/** Costructs a void Registrar. */
protected Registrar() {}
/** Costructs a new Registrar. The Location Service is stored within the file <i>db_name</i> */
//public Registrar(SipProvider provider, String db_class, String db_name)
public Registrar(SipProvider provider, ServerProfile profile)
{ super(provider,profile);
printLog("Domains="+getLocalDomains(),LogLevel.HIGH);
// location service
String location_service_class=profile.location_service;
for (int i=0; i<LOCATION_SERVICES.length; i++)
if (LOCATION_SERVICES[i].equalsIgnoreCase(profile.location_service)) { location_service_class=LOCATION_SERVICE_CLASSES[i]; break; }
try
{ Class myclass=Class.forName(location_service_class);
Class[] parameter_types={ Class.forName("java.lang.String") };
Object[] parameters={ profile.location_db };
try
{ java.lang.reflect.Constructor constructor=myclass.getConstructor(parameter_types);
location_service=(LocationService)constructor.newInstance(parameters);
}
catch (NoSuchMethodException e)
{ printException(e,LogLevel.MEDIUM);
location_service=(LocationService)myclass.newInstance();
}
}
catch (Exception e)
{ printException(e,LogLevel.HIGH);
printLog("Error trying to use location service '"+location_service_class+"': use default class.",LogLevel.HIGH);
}
// use default location service
if (location_service==null) location_service=new LocationServiceImpl(profile.location_db);
// do clean all?
if (profile.clean_location_db)
{ printLog("LocationService \""+profile.location_db+"\": cleaned\r\n",LogLevel.MEDIUM);
location_service.removeAllContacts();
location_service.sync();
}
else
{ // remove all expired contacts
boolean changed=false;
for (Enumeration u=location_service.getUsers(); u.hasMoreElements(); )
{ String user=(String)u.nextElement();
for (Enumeration c=location_service.getUserContactURLs(user); c.hasMoreElements(); )
{ String contact=(String)c.nextElement();
if ((changed=location_service.isUserContactExpired(user,contact))==true) location_service.removeUserContact(user,contact);
}
// Note: uncomment the next line, if you want that 'unbound' users (i.e. without registered contacts) are automatically removed
//if (!location_service.getUserContacts(user).hasMoreElements()) location_service.removeUser(user);
}
if (changed) location_service.sync();
}
printLog("LocationService ("+profile.authentication_service+"): size="+location_service.size()+"\r\n"+location_service.toString(),LogLevel.MEDIUM);
// authentication server
if (server_profile.do_authentication || server_profile.do_proxy_authentication)
{ // first, init the proper authentication service
String realm=(server_profile.authentication_realm!=null)? server_profile.authentication_realm : sip_provider.getViaAddress();
String authentication_service_class=profile.authentication_service;
for (int i=0; i<AUTHENTICATION_SERVICES.length; i++)
if (AUTHENTICATION_SERVICES[i].equalsIgnoreCase(profile.authentication_service)) { authentication_service_class=AUTHENTICATION_SERVICE_CLASSES[i]; break; }
try
{ Class myclass=Class.forName(authentication_service_class);
Class[] parameter_types={ Class.forName("java.lang.String") };
Object[] parameters={ profile.authentication_db };
try
{ java.lang.reflect.Constructor constructor=myclass.getConstructor(parameter_types);
authentication_service=(AuthenticationService)constructor.newInstance(parameters);
}
catch (NoSuchMethodException e)
{ printException(e,LogLevel.MEDIUM);
authentication_service=(AuthenticationService)myclass.newInstance();
}
}
catch (Exception e)
{ printException(e,LogLevel.HIGH);
printLog("Error trying to use authentication service '"+authentication_service_class+"': use default class.",LogLevel.HIGH);
}
// use default authentication service
if (authentication_service==null) authentication_service=new AuthenticationServiceImpl(server_profile.authentication_db);
printLog("AuthenticationService ("+profile.authentication_service+"): size="+authentication_service.size()+"\r\n"+authentication_service.toString(),LogLevel.MEDIUM);
// now, init the proper authentication server
String authentication_server_class=profile.authentication_scheme;
for (int i=0; i<AUTHENTICATION_SCHEMES.length; i++)
if (AUTHENTICATION_SCHEMES[i].equalsIgnoreCase(profile.authentication_scheme)) { authentication_server_class=AUTHENTICATION_SERVER_CLASSES[i]; break; }
try
{ Class myclass=Class.forName(authentication_server_class);
Class[] parameter_types={ Class.forName("java.lang.String"), Class.forName("local.server.AuthenticationService"), Class.forName("org.zoolu.tools.Log") };
Object[] parameters={ realm, authentication_service, sip_provider.getLog() };
try
{ java.lang.reflect.Constructor constructor=myclass.getConstructor(parameter_types);
as=(AuthenticationServer)constructor.newInstance(parameters);
}
catch (NoSuchMethodException e)
{ printException(e,LogLevel.MEDIUM);
as=(AuthenticationServer)myclass.newInstance();
}
}
catch (Exception e)
{ printException(e,LogLevel.HIGH);
printLog("Error trying to use authentication server '"+authentication_server_class+"': use default class.",LogLevel.HIGH);
}
// use default authentication service
if (as==null) as=new AuthenticationServerImpl(realm,authentication_service,sip_provider.getLog());
printLog("AuthenticationServer: scheme: "+profile.authentication_scheme,LogLevel.MEDIUM);
printLog("AuthenticationServer: realm: "+profile.authentication_realm,LogLevel.MEDIUM);
}
else as=null;
}
/** When a new request is received for the local server. */
public void processRequestToLocalServer(Message msg)
{
printLog("inside processRequestToLocalServer(msg)",LogLevel.MEDIUM);
if (server_profile.is_registrar && msg.isRegister())
{ TransactionServer t=new TransactionServer(sip_provider,msg,null);
//t.listen();
if (server_profile.do_authentication)
{ // check message authentication
Message err_resp=as.authenticateRequest(msg);
if (err_resp!=null)
{ t.respondWith(err_resp);
return;
}
}
Message resp=updateRegistration(msg);
if (resp==null) return;
if (server_profile.do_authentication)
{ // add Authentication-Info header field
resp.setAuthenticationInfoHeader(as.getAuthenticationInfoHeader());
}
t.respondWith(resp);
}
else
if (!msg.isAck())
{ // send a stateless error response
int result=501; // response code 501 ("Not Implemented")
Message resp=MessageFactory.createResponse(msg,result,SipResponses.reasonOf(result),null);
sip_provider.sendMessage(resp);
}
}
/** When a new request message is received for a local user. */
public void processRequestToLocalUser(Message msg)
{ printLog("inside processRequestToLocalUser(msg)",LogLevel.MEDIUM);
// stateless-response (in order to avoid DoS attacks)
if (!msg.isAck()) sip_provider.sendMessage(MessageFactory.createResponse(msg,404,SipResponses.reasonOf(404),null));
else printLog("message discarded",LogLevel.HIGH);
}
/** When a new request message is received for a remote UA. */
public void processRequestToRemoteUA(Message msg)
{ printLog("inside processRequestToRemoteUA(msg)",LogLevel.MEDIUM);
// stateless-response (in order to avoid DoS attacks)
if (!msg.isAck()) sip_provider.sendMessage(MessageFactory.createResponse(msg,404,SipResponses.reasonOf(404),null));
else printLog("message discarded",LogLevel.HIGH);
}
/** When a new response message is received. */
public void processResponse(Message resp)
{ printLog("inside processResponse(msg)",LogLevel.MEDIUM);
// no actions..
printLog("message discarded",LogLevel.HIGH);
}
// *********************** protected methods ***********************
/** Gets the request's targets as Vector of String.
* @return It returns a Vector of String representing the target URLs. */
protected Vector getTargets(Message msg)
{ printLog("inside getTargets(msg)",LogLevel.LOW);
Vector targets=new Vector();
if (location_service==null)
{ printLog("Location service is not active",LogLevel.HIGH);
return targets;
}
SipURL request_uri=msg.getRequestLine().getAddress();
String username=request_uri.getUserName();
if (username==null)
{ printLog("no username found",LogLevel.HIGH);
return targets;
}
String user=username+"@"+request_uri.getHost();
printLog("user: "+user,LogLevel.MEDIUM);
if (!location_service.hasUser(user))
{ printLog("user "+user+" not found",LogLevel.HIGH);
return targets;
}
SipURL to_url=msg.getToHeader().getNameAddress().getAddress();
Enumeration e=location_service.getUserContactURLs(user);
printLog("message targets: ",LogLevel.LOW);
for (int i=0; e.hasMoreElements(); i++)
{ // if exipred, remove the contact url
String url=(String)e.nextElement();
if (location_service.isUserContactExpired(user,url))
{ location_service.removeUserContact(user,url);
printLog("target"+i+" expired: contact url removed",LogLevel.LOW);
}
// otherwise add the url to the target list
else
{ targets.addElement(url);
printLog("target"+i+"="+targets.elementAt(i),LogLevel.LOW);
}
}
return targets;
}
/** Updates the registration of a local user.
* @return it returns the response message for the registration. */
protected Message updateRegistration(Message msg)
{ ToHeader th=msg.getToHeader();
if (th==null)
{ printLog("ToHeader missed: message discarded",LogLevel.HIGH);
int result=400;
return MessageFactory.createResponse(msg,result,SipResponses.reasonOf(result),null);
}
SipURL dest_uri=th.getNameAddress().getAddress();
String user=dest_uri.getUserName()+"@"+dest_uri.getHost();
int exp_secs=server_profile.expires;
// set the expire value
ExpiresHeader eh=msg.getExpiresHeader();
if (eh!=null)
{ exp_secs=eh.getDeltaSeconds();
}
// limit the expire value
if (exp_secs<0) exp_secs=0;
else
if (exp_secs>server_profile.expires) exp_secs=server_profile.expires;
// known user?
if (!location_service.hasUser(user))
{ if (server_profile.register_new_users)
{ location_service.addUser(user);
printLog("new user '"+user+"' added.",LogLevel.HIGH);
}
else
{ printLog("user '"+user+"' unknown: message discarded.",LogLevel.HIGH);
int result=404;
return MessageFactory.createResponse(msg,result,SipResponses.reasonOf(result),null);
}
}
// Get the "device" parameter. Set device=null if not present or not supported
//String device=null;
// if (msg.hasApplicationHeader()) app=msg.getApplicationHeader().getApplication();
SipURL to_url=msg.getToHeader().getNameAddress().getAddress();
//if (to_url.hasParameter("device")) device=to_url.getParameter("device");
if (!msg.hasContactHeader())
{ //printLog("ContactHeader missed: message discarded",LogLevel.HIGH);
//int result=484;
//return MessageFactory.createResponse(msg,result,SipResponses.reasonOf(result),null,null);
printLog("no contact found: fetching bindings..",LogLevel.MEDIUM);
int result=200;
Message resp=MessageFactory.createResponse(msg,result,SipResponses.reasonOf(result),null);
// add current contacts
Vector v=new Vector();
for (Enumeration e=location_service.getUserContactURLs(user); e.hasMoreElements(); )
{ String url=(String)e.nextElement();
int expires=(int)(location_service.getUserContactExpirationDate(user,url).getTime()-System.currentTimeMillis())/1000;
if (expires>0)
{ // not expired
ContactHeader ch=new ContactHeader(location_service.getUserContactNameAddress(user,url));
ch.setExpires(expires);
v.addElement(ch);
}
}
if (v.size()>0) resp.setContacts(new MultipleHeader(v));
return resp;
}
// else
Vector contacts=msg.getContacts().getHeaders();
int result=200;
Message resp=MessageFactory.createResponse(msg,result,SipResponses.reasonOf(result),null);
ContactHeader contact_0=new ContactHeader((Header)contacts.elementAt(0));
if (contact_0.isStar())
{ printLog("DEBUG: ContactHeader is star",LogLevel.LOW);
Vector resp_contacts=new Vector();
for (Enumeration e=location_service.getUserContactURLs(user); e.hasMoreElements();)
{ String url=(String)(e.nextElement());
NameAddress name_address=location_service.getUserContactNameAddress(user,url);
// update db
location_service.removeUserContact(user,url);
printLog("contact removed: "+url,LogLevel.LOW);
if (exp_secs>0)
{ Date exp_date=new Date(System.currentTimeMillis()+((long)exp_secs)*1000);
location_service.addUserContact(user,name_address,exp_date);
//DateFormat df=new SimpleDateFormat("EEE, dd MMM yyyy hh:mm:ss 'GMT'",Locale.ITALIAN);
//printLog("contact added: "+url+"; expire: "+df.format(location_service.getUserContactExpire(user,url)),LogLevel.LOW);
printLog("contact added: "+url+"; expire: "+DateFormat.formatEEEddMMM(location_service.getUserContactExpirationDate(user,url)),LogLevel.LOW);
}
ContactHeader contact_i=new ContactHeader(name_address.getAddress());
contact_i.setExpires(exp_secs);
resp_contacts.addElement(contact_i);
}
if (resp_contacts.size()>0) resp.setContacts(new MultipleHeader(resp_contacts));
}
else
{ Vector resp_contacts=new Vector();
for (int i=0; i<contacts.size(); i++)
{ ContactHeader contact_i=new ContactHeader((Header)contacts.elementAt(i));
NameAddress name_address=contact_i.getNameAddress();
String url=name_address.getAddress().toString();
int exp_secs_i=exp_secs;
if (contact_i.hasExpires())
{ exp_secs_i=contact_i.getExpires();
}
// limit the expire value
if (exp_secs_i<0) exp_secs_i=0;
else
if (exp_secs_i>server_profile.expires) exp_secs_i=server_profile.expires;
// update db
location_service.removeUserContact(user,url);
if (exp_secs_i>0)
{ Date exp_date=new Date(System.currentTimeMillis()+((long)exp_secs)*1000);
location_service.addUserContact(user,name_address,exp_date);
printLog("registration of user "+user+" updated",LogLevel.HIGH);
}
contact_i.setExpires(exp_secs_i);
resp_contacts.addElement(contact_i);
}
if (resp_contacts.size()>0) resp.setContacts(new MultipleHeader(resp_contacts));
}
location_service.sync();
return resp;
}
// ****************************** Logs *****************************
/** Adds a new string to the default Log. */
private void printLog(String str, int level)
{ if (log!=null) log.println("Registrar: "+str,level+SipStack.LOG_LEVEL_UA);
}
/** Adds the Exception message to the default Log */
private final void printException(Exception e, int level)
{ if (log!=null) log.printException(e,level+SipStack.LOG_LEVEL_UA);
}
// ****************************** MAIN *****************************
/** The main method. */
public static void main(String[] args)
{
String file=null;
for (int i=0; i<args.length; i++)
{ if (args[i].equals("-f") && args.length>(i+1))
{ file=args[++i];
continue;
}
if (args[i].equals("-h"))
{ System.out.println("usage:\n java Registrar [-f <config_file>] \n");
System.exit(0);
}
}
SipStack.init(file);
SipProvider sip_provider=new SipProvider(file);
ServerProfile server_profile=new ServerProfile(file);
new Registrar(sip_provider,server_profile);
}
}