/* * 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.sip.address.SipURL; import org.zoolu.sip.provider.*; import org.zoolu.sip.header.MultipleHeader; import org.zoolu.sip.header.ViaHeader; import org.zoolu.sip.header.Header; import org.zoolu.sip.header.RouteHeader; import org.zoolu.sip.header.RequestLine; import org.zoolu.sip.header.MaxForwardsHeader; import org.zoolu.sip.header.MultipleHeader; import org.zoolu.sip.message.Message; import org.zoolu.sip.message.SipResponses; import org.zoolu.sip.message.MessageFactory; import org.zoolu.tools.Log; import org.zoolu.tools.LogLevel; import org.zoolu.tools.SimpleDigest; import java.util.Vector; /** Class ServerEngine implement a stateless abstract SIP Server. * The ServerEngine can act as SIP Proxy Server, SIP Registrar Server or both. * <p> For each incoming message, the ServerEngine fires one of the following * abstract methods: * <ul> * <li>public abstract processRequestToRemoteUA(Message),</li> * <li>public abstract processRequestToLocalServer(Message),</li> * <li>public abstract processRequestToLocalServer(Message),</li> * <li>public abstract processResponse(Message).</li> * </ul> * depending of the type of received message. */ public abstract class ServerEngine implements SipProviderListener { /** Name of the Loop-Tag header field. * It is used as temporary filed for carry loop detection information, * added to the via branch parameter of the forwarded requests. */ protected static final String Loop_Tag="Loop-Tag"; /** Event logger. */ protected Log log=null; /** ServerProfile of the server. */ protected ServerProfile server_profile=null; /** SipProvider used by the server. */ protected SipProvider sip_provider=null; /** Costructs a void ServerEngine */ protected ServerEngine() {} // *************************** abstract methods *************************** /** When a new request message is received for a remote UA */ public abstract void processRequestToRemoteUA(Message req); /** When a new request message is received for a locally registered user */ public abstract void processRequestToLocalUser(Message req); /** When a new request request is received for the local server */ public abstract void processRequestToLocalServer(Message req); /** When a new response message is received */ public abstract void processResponse(Message resp); // **************************** public methods **************************** /** Costructs a new ServerEngine on SipProvider <i>provider</i>, * and adds it as SipProviderListener. */ public ServerEngine(SipProvider provider, ServerProfile profile) { server_profile=profile; sip_provider=provider; log=sip_provider.getLog(); sip_provider.addSipProviderListener(SipProvider.ANY,this); } /** When a new message is received by the SipProvider. * If the received message is a request, it cheks for loops, */ public void onReceivedMessage(SipProvider provider, Message msg) { printLog("message received",LogLevel.MEDIUM); if (msg.isRequest()) // it is an INVITE or ACK or BYE or OPTIONS or REGISTER or CANCEL { printLog("message is a request",LogLevel.MEDIUM); // validate the message Message err_resp=validateRequest(msg); if (err_resp!=null) { // for non-ACK requests respond with an error message if (!msg.isAck()) sip_provider.sendMessage(err_resp); return; } // target SipURL target=msg.getRequestLine().getAddress(); // check if this server is the target //boolean this_is_target=isResponsibleFor(target.getHost(),target.getPort()); // look if the msg sent by the previous UA is compliant with the RFC2543 Strict Route rule.. if (isResponsibleFor(target.getHost(),target.getPort()) && msg.hasRouteHeader()) { //SipURL route_url=msg.getRouteHeader().getNameAddress().getAddress(); SipURL route_url=(new RouteHeader(msg.getRoutes().getBottom())).getNameAddress().getAddress(); if (!route_url.hasLr()) { printLog("probably the message was compliant to RFC2543 Strict Route rule: message is updated to RFC3261",LogLevel.MEDIUM); // the message has been sent to this server according with RFC2543 Strict Route // the proxy MUST replace the Request-URI in the request with the last // value from the Route header field, and remove that value from the // Route header field. The proxy MUST then proceed as if it received // this modified request. msg.rfc2543toRfc3261RouteUpdate(); // update the target target=msg.getRequestLine().getAddress(); printLog("new recipient: "+target.toString(),LogLevel.LOW); // check again if this server is the target //this_is_target=matchesDomainName(target.getHost(),target.getPort()); } } // removes the local Route value, if present /*if (msg.hasRouteHeader()) { MultipleHeader mr=msg.getRoutes(); SipURL top_route=(new RouteHeader(mr.getTop())).getNameAddress().getAddress(); if (matchesDomainName(top_route.getHost(),top_route.getPort())) { mr.removeTop(); if (mr.size()>0) msg.setRoutes(mr); else msg.removeRoutes(); } }*/ // check whether the request is for a domain the server is responsible for if (isResponsibleFor(msg)) { printLog("the request is for the local server",LogLevel.LOW); if (target.hasUserName()) { printLog("the request is for a local user",LogLevel.LOW); processRequestToLocalUser(msg); } else { printLog("no username: the request is for the local server",LogLevel.LOW); processRequestToLocalServer(msg); } } else // the request is NOT for the "local" server { printLog("the request is not for the local server",LogLevel.LOW); processRequestToRemoteUA(msg); } } else // the message may be a response { if (msg.isResponse()) { printLog("message is a response",LogLevel.LOW); processResponse(msg); } else printWarning("received message is not recognized as a request nor a response: discarded",LogLevel.HIGH); } } /** Relays the massage. * Called after a received message has been successful processed for being relayed */ //protected void sendMessage(Message msg) //{ printLog("sending the successfully processed message",LogLevel.MEDIUM); // sip_provider.sendMessage(msg); //} /** Whether the server is responsible for the given <i>domain</i> * (i.e. the <i>domain</i> is included in the local domain names list) * and <i>port</i> (if >0) matches the local server port. */ protected boolean isResponsibleFor(String domain, int port) { // check port if (!server_profile.domain_port_any && port>0 && port!=sip_provider.getPort()) return false; // check host address if (domain.equals(sip_provider.getViaAddress())) return true; // check domain name boolean it_is=false; for (int i=0; i<server_profile.domain_names.length; i++) { if (server_profile.domain_names[i].equals(domain)) { it_is=true; break; } } return it_is; } /** Whether the server is responsible for the request-uri of the request <i>req</i>. */ protected boolean isResponsibleFor(Message req) { SipURL target=req.getRequestLine().getAddress(); return isResponsibleFor(target.getHost(),target.getPort()); } /** Whether the request is for the local server */ /*protected boolean isTargetOf(Message req) { SipURL target=req.getRequestLine().getAddress(); if (!isResponsibleFor(target.getHost(),target.getPort())) return false; // else, request-uri matches a domain the server is responsible for if (!req.hasRouteHeader()) return true; // else, has route.. MultipleHeader route=req.getRoutes(); if (route.size()>1) return false; // else, only 1 route, check it target=(new RouteHeader(route.getTop())).getNameAddress().getAddress(); if (!isResponsibleFor(target.getHost(),target.getPort())) return false; // else return true; }*/ /** Gets a String of the list of local domain names. */ protected String getLocalDomains() { if (server_profile.domain_names.length>0) { String str=""; for (int i=0; i<server_profile.domain_names.length-1; i++) { str+=server_profile.domain_names[i]+", "; } return str+server_profile.domain_names[server_profile.domain_names.length-1]; } else return ""; } /** Validates the message. * @return It returns 0 if the message validation successes, otherwise return the error code. */ protected Message validateRequest(Message msg) { printLog("inside validateRequest(msg)",LogLevel.LOW); int err_code=0; // Max-Forwads if (err_code==0) { MaxForwardsHeader mfh=msg.getMaxForwardsHeader(); if (mfh!=null && mfh.getNumber()==0) err_code=483; } // Loops // Insert also a temporary Loop-Tag header field in order to correctly compose // the branch field when forwarding the message. // This behaviour has been choosen since the message validation is done // when receiving the message while the information used for loop detection // (the branch parameter) is calculated and added when sending the message. // Note that the RFC suggests to calculate the branch parameter based on // the original request-uri, but the request-uri has been already replaced // and forgotten when processing the message for calculating the branch! ;) if (err_code==0 && server_profile.loop_detection) { String loop_tag=pickLoopTag(msg); // add temporary Loop-Tag header field msg.setHeader(new Header(Loop_Tag,loop_tag)); // check for loop if (!msg.hasRouteHeader()) { Vector v=msg.getVias().getHeaders(); for (int i=0; i<v.size(); i++) { ViaHeader vh=new ViaHeader((Header)v.elementAt(i)); if (sip_provider.getViaAddress().equals(vh.getHost()) && sip_provider.getPort()==vh.getPort()) { // possible loop if (!vh.hasBranch()) err_code=482; else { // check branch String branch=vh.getBranch(); if (branch.indexOf(loop_tag,branch.length()-loop_tag.length())>=0) err_code=482; } } } } } // Proxy-Require // Proxy-Authorization if (err_code>0) { String reason=SipResponses.reasonOf(err_code); printLog("Message validation failed ("+reason+"), message discarded",LogLevel.HIGH); return MessageFactory.createResponse(msg,err_code,reason,null); } else return null; } /** Picks an unique branch value based on a SIP message. * This value could also be used for loop detection. */ /*public String pickBranch(Message msg) { String branch=sip_provider.pickBranch(msg); if (server_profile.loop_detection) branch+=pickLoopTag(msg); return branch; }*/ /** Picks the token used for loop detection. */ private String pickLoopTag(Message msg) { StringBuffer sb=new StringBuffer(); sb.append(msg.getToHeader().getTag()); sb.append(msg.getFromHeader().getTag()); sb.append(msg.getCallIdHeader().getCallId()); sb.append(msg.getRequestLine().getAddress().toString()); sb.append(msg.getCSeqHeader().getSequenceNumber()); MultipleHeader rr=msg.getRoutes(); if (rr!=null) sb.append(rr.size()); return (new SimpleDigest(7,sb.toString())).asHex(); } // ********************************* logs ********************************* /** Adds a new string to the default Log */ private void printLog(String str, int level) { if (log!=null) log.println("ServerEngine: "+str,level+SipStack.LOG_LEVEL_UA); } /** Adds a Warning message to the default Log */ private final void printWarning(String str, int level) { printLog("WARNING: "+str,level); } }