# # $Id$ # # # Applicability of this Configuration File # ---------------------------------------- # # This is default SER script as used for example at the iptel.org # SIP service; it can deal with NATs, terminate calls to a PSTN # gateway, and it implements a couple of basic signaling features # (few types of call forwarding). In this scenario you may have # multiple SIP proxies sharing one database for accessing provisioned # data, which are maintained for example using serweb. The proxy # servers also share write-access to user location database (and # keeps a full cache of all usrloc entries synchronized using # multicast). # # If you look for a simpler version with a lot less dependencies # please refer to the sip-router-basic.cfg file in your SER distribution. # # If you look for documentation, try http://sip-router.org/wiki/. # The right mailing lists for questions about this file is # . # # Requirements: # --------------- # running DB, running RTP proxy, one public IP address # for SIP service, one private IP address for administrative purposes; # optional: IP address of a PSTN gateway # # HOWTOs: # --------- # To get this config running you need to execute the following commands # with the new serctl (the capital word are just place holders): # # $ ser_ctl domain add DOMAINNAME # $ ser_ctl user add USERNAME@DOMAINNAME -p PASSWORD # # (ser_ctl can be obtained from # http://ftp.iptel.org/pub/serctl/daily-snapshots/ ) # # If you want to have P-Asserted-ID header for your user # # $ ser_attr add uid=UID asserted_id="PID" # # If you want to have (PSTN) gateway support: # # $ ser_db add attr_types name=gw_ip rich_type=string raw_type=2 \ # description="The gateway IP for the default ser.cfg" default_flags=33 # $ ser_attr add global gw_ip=GATEWAY-IP # # Alternatively, you can simple uncomment the relevant line in this file # right at the beginning of the main route. # # You can also use serweb to set all the values above # (http://ftp.iptel.org/pub/serweb/daily-snapshots/ or # http://developer.berlios.de/projects/serweb). # # Users with permission to call PSTN using this script must have # the $gw_acl attribute set properly, and shall have $asserted_id # set to indicate their caller-id for calls to PSTN. For inbound # calls from PSTN, additional aliases may be also set. # # Warning: # ----------- # If this file is installed on Debian from package 'ser-oob' then some # options in this configuration file may be set by post-installation # script, according to values entered by user at installation time in # debconf configuration. These values are then applied automatically to # this file each time the 'ser-oob' package is upgraded or reconfigured by # calling 'dpkg-reconfigure sip-router-oob'. # # The parts of this configuration file that may be altered by debconf are # enclosed between '#DEBCONF-something-START' and '#DEBCONF-something-END' # comment marks. Please do not remove them. # # # TODO (Future possible improvements): # --------------------------------------- # * protocol tuning # - AVP-based diversion for call-forwarding (as opposed to specialized # module) # - add Date header in 200s to REGISTERs (to be packaged with NTP!) # * more security: # - pike/rate-limit # - identity # - TLS # - permissions # - Re-name all internal headers so that they start with a common prefix, # such as P-SER and then wipe all such headers from requests received # from untrusted sources, such as the user agents or foreign proxy # servers # * refined DB use (e.g., flatstore for acc) # * miscellanous: # - dialog module for monitoring purposes # - more extensive logging using xlog (controlled by gflags/gAVPs) # * leveraging 2.1 features: # - removal of private IP address (it takes a multicast-specific # command which will allow OS to determine source IP address) # - timer route: # * don't use exec (it takes domain.reload as script command) # * compare last-stored timestamp with current timestamp (it takes # assignment of gAVPs) # * check multicast REGISTERs for their TTL (this is a simple and # effective security check to prevent remote multicast messages # to damage our traffic) # - numerous fine-tuning parameters which are only available in 2.1 # (mlock_pages, dns_try_naptr, etc.) # - better support for preloaded routes with domain name # # Security considerations: # ------------------------ # The script has been tested against security leaks, but it comes # under terms of GPL "as is" without any warranties; better check # yourself that: # - IP based authentication of PSTN gateway and multicast REGISTERs # is compliant to your network setup and security policy. # - Multiple gateway IPs can't be provisioned as security checks # are applied only to one. # # Licensing # ---------- # Copyright (C) 2005-2008 iptelorg GmbH # This file is part of SER, a free SIP server. It is available under the # terms of the GNU General Public License. # Numerous folks have contributed to this file, including but not limited # to Andrei, Jan, Jiri, Michal, Miklos, Nils. # # # .... that's it, enough of yadiyada, here the real config begins! # ----------- Global Defines / Extra Features ------------------------------- # (can be enabled either by uncommenting the corresponding #!define # statement or by starting with -A WITH_, e.g. # ser -A WITH_TLS -f /etc/ser/ser-oob.cfg ) # enable TLS ##!define WITH_TLS #enable xmlrpc support ##!define WITH_XMLRPC # xmlrpc allowed only if it comes on TLS from a client with a valid cert ##!define XMLRPC_TLS_ONLY # xmlrpc allowed subnets (if defined XMLRPC requests with source ip matching # this network addresses will be allowed, if no XMLRPC_ALLOWED_SUBNETx is # defined only requests coming from localhost will be allowed). # E.g.: ser -A XMLRPC_ALLOW_NET1=192.168.1.0/24 -f ser-oob.cfg ##!define XMLRPC_ALLOW_NET1 192.168.0.0/16 ##!define XMLRPC_ALLOW_NET2 10.0.0.0/255.0.0.0 ##!define XMLRPC_ALLOW_NET3 172.16.0.0/12 # started from compile directory (not installed) ##!define LOCAL_TEST_RUN # ----------- Global Configuration Parameters ------------------------------- #debug=3 # debug level (cmd line: -ddd) #memdbg=10 # memory debug log level #memlog=10 # memory statistics log level #log_facility=LOG_LOCAL0 # the facility used for logging (see syslog(3)) #DEBCONF-SERVERID-START server_id=0 #DEBCONF-SERVERID-END # Uncomment these lines to enter debugging mode or start SER with # sip-router -ED # #fork=no #log_stderror=yes check_via=no # (cmd. line: -v) dns=no # (cmd. line: -r) rev_dns=no # (cmd. line: -R) #port=5060 #children=4 #user=sip-router #group=sip-router #disable_core=yes # disables core dumping #open_files_limit=20480 # sets the open file descriptors limit #mhomed=yes # usefull for multihomed hosts, small performance # penalty disable_tcp=no # be conservative about enabling TCP -- it can # degrade performance a lot #tcp_accept_aliases=yes # accepts the tcp alias via option phone2tel=no # ignore user=phone in request-URIs -- otherwise # these URIs would be interpreted as equivalent # to TEL URIs, and their lookup would fail in URI # database reply_to_via=no sip_warning=yes # public IP address #DEBCONF-LISTEN-START listen=127.0.0.1 #DEBCONF-LISTEN-END # sip.mcast.net for REGISTER replication #DEBCONF-LISTEN_REPL-START listen=udp:224.0.1.75 #DEBCONF-LISTEN_REPL-END # administrative interface -- needed for example for multicast source # or XML-RPC #DEBCONF-LISTEN_ADMIN-START listen=udp:127.0.0.1 #DEBCONF-LISTEN_ADMIN-END #listen=tls:127.0.0.1:5061 mlock_pages=yes shm_force_alloc=yes real_time=7 # ------------------- DNS Parameters ---------------------------------------- # (see doc/dns.txt for more details) # # minimum timeouts dns_retr_time=1 dns_retr_no=1 dns_servers_no=1 dns_use_search_list=no dns_try_ipv6=no # dns cache & failover use_dns_cache=on use_dns_failover=on # dns_cache_flags=0 dns_cache_negative_ttl=300 dns_cache_min_ttl=60 dns_cache_max_ttl=86400 # 1 day dns_cache_mem=2048 # 2 MB dns_cache_gc_interval=60 # garbage collection every minute # ser 2.1 specific options dns_try_naptr=yes dns_srv_lb=yes # srv based load balancing dns_udp_pref=3 # prefer udp (when resolving naptr record) dns_tcp_pref=2 # if no udp available accept tcp (for naptr) dns_sctp_pref=2 # same preference as tcp #!ifdef WITH_TLS dns_tls_pref=1 # low preference (heavy resource use) #!else dns_tls_pref=-1 # ignore / don't accept tls (for naptr) #!endif # dns_cache_delete_nonexpired=no # ------------------- Blacklist Parameters ---------------------------------- # (see doc/dst_blacklist.txt for more details) # use_dst_blacklist=on dst_blacklist_mem=1024 # 1 MB dst_blacklist_expire=300 # blacklist default time dst_blacklist_gc_interval=150 # 2.5 min # for sip-router 2.1 to the above add tm blst_503* parameters and/or use the # blst module (see NEWS) # ------------------- TCP Parameters ---------------------------------------- # (see NEWS for more details) tcp_connection_lifetime=3600 #tcp_max_connections=10240 # default is 2048 tcp_connect_timeout=1 tcp_async=yes # ------------------- TLS Parameters ---------------------------------------- #!ifdef WITH_TLS # Enable TLS hooks so that the TLS module can be used tls_enable=yes #!endif # -------------------- Custom Parameters ------------------------------------ # These parameters can be modified runtime via RPC interface, # read the documentation of cfg_rpc module. # Session Timer parameters, RFC 4028 # # Default session interval used by the proxy if the UAC does not support # session timer. Set it to "0" to disable session timer proxy support. # session_timer.default = "1800" desc "default session interval (in s)" # # Minimum session interval accepted by the proxy, it must not be less # than 90 seconds. # session_timer.min_se = "90" desc "minimum session interval (in s)" # RTP Proxy options # # Whether to enable or disable the rtp proxy. Possible values are: # "0" -- always disable # "1" -- always enable regardless of whether UAC or UAS is behind NAT # "detect" -- detect whether the UAC or the UAS is behind NAT, # and enable the rtp proxy when necessary # #DEBCONF-RTP_ENABLE-START rtp_proxy.enabled = "detect" desc "indicates whether the RTP Proxy is enabled or not (0/1/detect)" #DEBCONF-RTP_ENABLE-END # ------------------ Module Loading ----------------------------------------- #!ifdef LOCAL_TEST_RUN loadpath "modules:modules_s" #!else loadpath "/usr/lib/sip-router/modules:/usr/lib/sip-router/modules_s" #!endif # load a SQL database for authentication, domains, user AVPs etc. loadmodule "db_mysql" #loadmodule "postgres" loadmodule "tm" loadmodule "sl" loadmodule "rr" loadmodule "maxfwd" loadmodule "usrloc" loadmodule "registrar" loadmodule "xlog" loadmodule "textops" loadmodule "ctl" loadmodule "auth" loadmodule "auth_db" loadmodule "gflags" loadmodule "domain" loadmodule "uri_db" loadmodule "avp" loadmodule "avp_db" loadmodule "acc_db" #!ifdef WITH_XMLRPC loadmodule "xmlrpc" #!endif loadmodule "options" loadmodule "sanity" loadmodule "nathelper" loadmodule "uri" loadmodule "speeddial" loadmodule "timer" loadmodule "db_ops" loadmodule "exec" loadmodule "cfg_rpc" loadmodule "eval" loadmodule "enum" #!ifdef WITH_TLS loadmodule "tls" #!endif # ----------------- Declaration of Script Flags ----------------------------- flags FLAG_ACC : 1, # the request will be recorded by ACC FLAG_FAILUREROUTE : 2, # we are operating from the failure route FLAG_NAT : 3, # the UAC is behind a NAT FLAG_REPL_ENABLED : 4, # REGISTER replication is enabled if set FLAG_TOTAG : 5, # request has a To tag FLAG_PSTN_ALLOWED : 6, # the user is allowed to use the PSTN FLAG_DONT_RM_CRED : 7, # do not remove the credentials FLAG_AUTH_OK : 8, # authentication succeeded FLAG_SERWEB_RSVD1 : 9, # bit reserved for use with serweb FLAG_SERWEB_RSVD2 : 10, # bit reserved for use with serweb FLAG_SESSIONTIMER : 11, # indicates that the UAC supports Session Timer FLAG_RR_DONE : 12, # the request got already one RR header FLAG_RTP_PROXY : 13, # the RTP proxy is turned on FLAG_NAT_REG : 14, # the UAC behind NAT, stored in location record FLAG_INIT_DLG : 15, # init INVITE dialog FLAG_REVERSE_DIR : 16, # set if request goes callee -> caller direction, requires rr.append_fromtag=1 FLAG_ACC_MISSED : 17, # the missed call will be recorded by ACC FLAG_USRLOC_FWD : 18, # usrloc based forward FLAG_NEXT_ROUTE : 19; # there is a route remaining avpflags dialog_cookie; # attribute will be stored in Route headers # ----------------- Module-specific Parameters ------------------------------ # path to the database # #DEBCONF-DBURL-START modparam("speeddial|auth_db|usrloc|domain|uri_db|gflags|avp_db|db_ops", "db_url", "mysql://ser:heslo@localhost/ser") #DEBCONF-DBURL-END # specify the path to your database for accounting #DEBCONF-DBURLACC-START modparam("acc_db", "db_url", "mysql://ser:heslo@localhost/ser") #DEBCONF-DBURLACC-END # -- usrloc -- # Database access mode: 0 -- memory cached, 1 -- write through, # 2 -- delayed write. 1 is generally safer than 2. 2 can help # to survive peaks in load. However, it creates delayed peaks that can # impair request-processing latency later (usrloc would have to be # re-redesigned more lock-free to avoid it). #DEBCONF-DBMODE-START modparam("usrloc", "db_mode", 1) #DEBCONF-DBMODE-END # Don't delete expired records from database on a per-contact basis -- that # results in bulky DB operations and can lead to synchronization issues # in server farm when for a time a server doesn't obtain re-reregistrations modparam("usrloc","db_skip_delete",1) # -- registrar -- # Maximum expires time. Forces users to re-register every 10 min. modparam("registrar", "max_expires", 600) # Minimum expires time. Even if they try, clients cannot register # for a shorter time than this. modparam("registrar", "min_expires", 240) # Identify natted contacts using a flag. modparam("registrar", "load_nat_flag", "FLAG_NAT_REG") modparam("registrar", "save_nat_flag", "FLAG_NAT_REG") # Maximum number of contacts. modparam("registrar", "max_contacts", 10) # -- auth -- #modparam("auth_db", "calculate_ha1", yes) #modparam("auth_db", "password_column", "password") # Minimize replay-attack window. modparam("auth", "nonce_expire", 10) # Enable/disable extra authentication checks using the following modparams. # The values are: 1 -- Request-URI, 2 -- Call-ID, 4 -- From tag, # 8 -- source IP. The options are disabled by default. # For REGISTER requests we hash the Request-URI, Call-ID, and source IP of the # request into the nonce string. This ensures that the generated credentials # cannot be used with another registrar, user agent with another source IP # address or Call-ID. Note that user agents that change Call-ID with every # REGISTER message will not be able to register if you enable this. #modparam("auth", "auth_checks_register", 11) # For dialog-establishing requests (such as the original INVITE, OPTIONS, etc) # we hash the Request-URI and source IP. Hashing Call-ID and From tags takes # some extra precaution, because these checks could render some UA unusable. #modparam("auth", "auth_checks_no_dlg", 9) # For mid-dialog requests, such as re-INVITE, we can hash source IP and # Request-URI just like in the previous case. In addition to that we can hash # Call-ID and From tag because these are fixed within a dialog and are # guaranteed not to change. This settings effectively restrict the usage of # generated credentials to a single user agent within a single dialog. #modparam("auth", "auth_checks_in_dlg", 15) # Deal with clients who can't do qop properly modparam("auth", "qop", "") #DEBCONF-AUTHSECRET-START modparam("auth", "secret", "aqwedrftredswqwddcft") #DEBCONF-AUTHSECRET-END # -- rr -- # Add value to lr param to make some broken UAs happy. modparam("rr", "enable_full_lr", 1) # Limit the length of the AVP cookie to necessary attributes only modparam("rr", "cookie_filter", "(account|rproxy|stimer|dialog_id)") # You probably do not want that someone can simply read and change # the AVP cookie in your Routes, thus should really change this # secret value below modparam("rr", "cookie_secret", "sgsatewgdbsnmpoiewh") # The ftag Route parameter may be used to easily determine if a BYE # is coming from caller or callee, but we prefer shorter messages # Enable when FLAG_REVERSE_DIR is to be used modparam("rr", "append_fromtag", 0) # -- gflags -- # Load global attributes. modparam("gflags", "load_global_attrs", 1) # -- domain -- # Load domain attributes. modparam("domain", "load_domain_attrs", 1) # -- ctl -- # By default, ctl listens on unixs:/tmp/sip-router_ctl if no other address is # specified in modparams; this is also the default for sercmd. modparam("ctl", "binrpc", "unixs:/tmp/ser_ctl") # Listen on the "standard" fifo for backward compatibility. modparam("ctl", "fifo", "fifo:/tmp/ser_fifo") # Listen on tcp on localhost. modparam("ctl", "binrpc", "tcp:127.0.0.1:2046") # -- acc_db -- # Failed transactions (those with negative responses) should be logged, too. modparam("acc_db", "failed_transactions", 1) # If you don't want to have accounting entries written into the database, # comment the next line out. modparam("acc_db", "log_flag", "FLAG_ACC") # seems "log_flag" and "log_flag_missed" cannot share the same flag! modparam("acc_db", "log_missed_flag", "FLAG_ACC_MISSED") # if you would like to customize your CDRs, do it here.... #modparam("acc_db", "attrs", # "$f.sop_billing_category,$f.isPrepaidCustomer,$f.sop_cf_orig_uid") # -- tm -- # Do not restart the resend timer with each reply. (See INBOUND route # below.) modparam("tm", "restart_fr_on_each_reply", 0) # -- xmlrpc -- #!ifdef WITH_XMLRPC # Use a sub-route. This is a lot safer then relying on the request method # to distinguish HTTP from SIP modparam("xmlrpc", "route", "XMLRPC"); #!endif # -- nathelper -- # RTP Proxy address #DEBCONF-RTTPPROXY-START modparam("nathelper", "rtpproxy_sock", "udp:127.0.0.1:22222") #DEBCONF-RTTPPROXY-END # TCP keepalives as simple as CRLF modparam("nathelper", "natping_crlf", 0) # How often to send a NAT ping. Set this to 0 to turn NAT ping off. #DEBCONF-NATPING_INTERVAL-START modparam("nathelper", "natping_interval", 15) #DEBCONF-NATPING_INTERVAL-END # Only ping contacts that have the NAT flag set. modparam("nathelper", "ping_nated_only", 1) # Send an OPTIONS SIP request as NAT ping. If this is not set, a simple # 4-byte ping is used. modparam("nathelper", "natping_method", "OPTIONS") # Temporary statefull natping test (only in future versions) #modparam("nathelper", "natping_stateful", 1) # -- exec -- modparam("exec", "time_to_kill", 200); modparam("exec", "setvars", 0); # -- timer -- # Register route ON_1MIN_TIMER to be called every minute. modparam("timer", "declare_timer", "ON_1MIN_TIMER=ON_1MIN_TIMER,60000,slow,enable"); #!ifdef WITH_TLS # -- tls -- #!ifdef LOCAL_TEST_RUN modparam("tls", "config", "./modules/tls/tls.cfg"); #!else modparam("tls", "config", "tls.cfg"); #!endif #!endif # -- db_ops -- modparam("db_ops", "declare_handle", "reload") modparam("db_ops", "declare_handle", "gattr_reload") # ------------------------- Request Routing Logic -------------------------- # Main request route. # # Each request starts here. # route { # if you have a PSTN gateway just un-comment the follwoing line and # specify the IP address of it to route calls to it. #$gw_ip = "1.2.3.4" # Alternatively (even better), set it as global persistent parameter # using serweb or ser_attrs). If using a PSTN GW, per-subscriber # options must ($gw_acl) or may (asserted_id) be set to enable calls # to PSTN. If email-like URIs are used, having a URI alias for # processing incoming PSTN-to-ip requests may be useful, too. # Important: the script is assuming one global pstn-gw for all # domains! Failure to allow gw_ip to be a domain-specic attribute # would result in security gaps (onsend_route checks only for one # gateway). # First, do some initial sanity checks. route(INIT); # Bypass the rest of the script for CANCELs if possible. route(CATCH_CANCEL); # Check if the request is routed via Route header. route(PROCESS_ROUTES); # Look up domain IDs route(DOMAIN); # Answer OPTIONS requests to our system. route(OPTIONS_REPLY); # Enforce domain policy. route(DOMAIN_POLICY); # Handle REGISTER requests. route(REGISTRAR); # From here on we want to know who is calling. route(AUTHENTICATION); # We are finished with all the precaution work -- let's # try to locate the callee. The first route that matches # "wins" and relays the request. If none matches, SER will # send a 404. # Check if we should be outbound proxy for a local user. route(OUTBOUND); # Redirect in case user dialed a speed dial entry. route(SPEEDDIAL); # Place various site-specific routes here. route(SITE_SPECIFIC); # Check if the request is for a local user. route(INBOUND); # There is SIP user for the called address. Before trying PSTN, # you may have to convert the adress, for instance by using # ENUM. #route(ENUM); # Last resort: if none of the previous route has found # the recepient, try PSTN. route(PSTN); # nothing matched sl_reply("404", "No route matched"); } # Forward a request to the destination set. # route[FORWARD] { # If this is called from the failure route we need to add a new # branch. if (isflagset(FLAG_FAILUREROUTE)) { if (!append_branch()) { t_reply("500", "Too many branches"); drop; } } # If this is an initial INVITE (without a To-tag) we might try # another target (call forwarding or voicemail) after receiving # an error. if (isflagset(FLAG_INIT_DLG)) { t_on_failure("FAILURE_ROUTE"); } # Always use the reply route to check for NATed UAS. t_on_reply("REPLY_ROUTE"); # Remove credentials to keep requests shorter if (isflagset(FLAG_AUTH_OK) && !isflagset(FLAG_DONT_RM_CRED) ) { consume_credentials(); } # Activate the RTP proxy as the second last step because it modifies the # body but also sets an dialog AVP cookie. route(RTPPROXY); # Insert a Record-Route header into all requests. # This has to be done as one of the last steps to include all the # RR cookies which might have been created during the script run. route(RECORD_ROUTE); # Send it out now. if (!t_relay()) { if (isflagset(FLAG_FAILUREROUTE)) { # XXX This should be replaced with # t_reply_error() similar to sl_reply_error() # in order to return the proper failure code. # Only, there is no such function yet. t_reply("500", "Request cannot be forwarded"); } else { sl_reply_error(); } } drop; } # Perform initial checks on an incoming request. # # Rejects the request if it fails any of the checks. # route[INIT] { # Messages with a Max-Forwards header of zero. if (!mf_process_maxfwd_header("10")) { sl_reply("483","Too Many Hops"); drop; } # Set flag for use in the onsend route (because it does not # allow to use "select" statements) if (@to.tag != "") { setflag(FLAG_TOTAG); } # Check if the UAC is NATed and fix the message accordingly route(UAC_NAT_DETECTION); # Activate accounting for all initial INVITEs. In-dialog requests # are accounted by a RR cookie (see below). # It should work also when the call has been already forked at a previous router if (method == "INVITE" && !isflagset(FLAG_TOTAG)) { $dialog_id = @sys.unique; # make unique dialogid setflag(FLAG_ACC); setflag(FLAG_ACC_MISSED); setflag(FLAG_INIT_DLG); } else if (isflagset(FLAG_TOTAG) && @hf_value.route[0].params.ftag != @from.tag) { setflag(FLAG_REVERSE_DIR); # callee -> caller } # if needed then we MUST put after force_rport() which is located in NAT_DETECTION!!! # also must be called after FLAG_ACC is set !!! # Check t_reply() vs. sl_reply() usage in script #if (!t_newtran()) { # sl_reply("500", "Internal tm error"); # drop; #} # Set flag and use it instead of the attribute. if ($replicate==1) { setflag(FLAG_REPL_ENABLED); } } # Reply OPTIONS requests sent to the proxy itself. # route[OPTIONS_REPLY] { # OPTIONS requests without a username in the Request-URI but one # of our domains or IPs are addressed to the proxy itself and # can be answered statelessly. if (method == "OPTIONS" && strempty(@ruri.user) && (uri == myself || $t.did != "")) { options_reply(); drop; } } # Check if the sender of the request is behind a NAT device. If so, # fix the request so that other devices can talk to the sender nonetheless. # route[UAC_NAT_DETECTION] { # Lots of UAs do not include the rport parameter in there Via # header, so we put it there regardless. force_rport(); # If a reliable transport was used store the connection internally # so that SERs core can re-use the connection later. if (proto==TCP || proto == TLS) { force_tcp_alias(); } # Check if the request contains hints for a NATed UAC. Also, try to # rewrite contacts using maddr. Using maddr is a really dubious # technique and we better replace such with transport address. # Downside: it fails for clients fronted by another server, in # which case a valid contact we dislike because of maddr will be # substituted inapproprietely (e.g., WM from other domains will # fail). If you are worried about that, remove tests for maddr and # recompile SER using HONOR_MADDR. Also note that rewriting # contacts may possibly lead to client denying subseqent requests # to them because they don't recognized fixed contacts as their # own. Should you encounter such a case, a possible solution # would be to store the original information as a contact parameter # and restore it on its way back. # In case of UDP we test for # - private IPs in Contact # - mismatch of transport IP and IP in Via # - mismatch of transport port and port in Via # in all other cases we skip the port test, because lots of clients # do not correctly advertise their emphemeral port number in their Via # header in case of reliable transports (although they are not behind # a NAT). # Warning: if you are dealing with SIP implementations which are # running on public IP and do as-symmertic signaling for whatever # reason the following check will make their signaling symmetric. # If you need to support as-symmertic signaling reduce the following # nat_uac_test for UDP to "3" or even "1". if ((proto == UDP && nat_uac_test("19")) || (nat_uac_test("3")) || (@hf_value["contact"] != "" && @contact.uri.params.maddr != "")) { setflag(FLAG_NAT); if (method == "REGISTER") { # Prepare the Contact so that the registrar module # saves the source address and port as well. fix_nated_register(); } else { # Overwrite the Contact to allow proper in-dialog # routing. # but do not override if there is already a proxy in the path, we'll route by record-route, # RURI responsibility takes to previous proxy # TODO: shouldn't we rather limit to methods which are dialog aware (INVITE, UPDATE, SUBSCRIBE, ..) if (strempty(@hf_value.record_route) || (@hf_value["contact"]!="" && @contact.uri.params.maddr!="")) { fix_nated_contact(); } } } } # Check if the receiver of the request is behind a NAT device. If so, # fix the Contact header to allow proper routing of in-dialog requests. route[UAS_NAT_DETECTION] { # Fix the Contact in the reply if it contains a private IP to # allow proper routing of in-dialog messages. # Do the same if the contact is maddred. # But skip 3XX responses, because we do not know the right IP for that, # even if they contain private IPs. if (status=~"(3[0-9][0-9])") { break; } # prevent contact overwriting when a proxy between ser and UAS. # We get it from record-route but it's rather difficult or # do it only for UAS which is registered in usrloc database and has no # proxy on path. # Note: destination forced by $fwd_always_target is not NAT detected and contact left untouched! if (isflagset(FLAG_INIT_DLG) && !isflagset(FLAG_USRLOC_FWD)) { break; } # for in-dialog requests we get it easily because it provides loose_route() if (!isflagset(FLAG_INIT_DLG) && isflagset(FLAG_NEXT_ROUTE)) { break; } # Prevent that we over-write the Contact with the IP of our proxy when # the reply loops through ourself. if (src_ip == myself) { break; } # In this case we check only if the Contact URI contains a private # IP, because the Via header contains only informations from the UAC. # Additionally we check if the port in the Contact URI differs from # the port of the transport to catch UAS or ALG which put the public # IP address into the Contact header, but "forget" about the port. # Warning: if you are dealing with SIP implementations which are # running on public IP and do as-symmertic signaling for whatever # reason the following check will make their signaling symmetric. # If you need to support as-symmertic signaling reduce the following # nat_uac_test for UDP to just "1". if ( (proto == UDP && nat_uac_test("33")) || (nat_uac_test("1") || (@hf_value["contact"] != "" && @contact.uri.params.maddr != ""))) { # TODO: check if no proxy between UAS&myself fix_nated_contact(); } } # Activates RTP proxy if necessary. # route[RTPPROXY] { if (@cfg_get.rtp_proxy.enabled == "0") { # RTP Proxy is disabled break; } else if (@cfg_get.rtp_proxy.enabled == "detect") { if (!isflagset(FLAG_NAT)) { # If no NAT is involved we don't have to do here anything. break; } } else if (@cfg_get.rtp_proxy.enabled != "1") { # This is not a valid setting xlog("L_ERR", "Unknown option for rtp_proxy.enabled: %@cfg_get.rtp_proxy.enabled\n"); break; } # else rtp proxy is permanently enabled # If the message terminates a dialog for which the RTP proxy # was turned on, turn it off again. if ((method == "BYE" && isflagset(FLAG_RTP_PROXY)) || (method == "CANCEL")) { unforce_rtp_proxy(); append_hf("P-RTP-Proxy: Off\r\n"); break; } # Turn the RTP proxy on for INVITEs and UPDATEs, if they # have a body if (((method=="INVITE" || method == "UPDATE") && @msg.body != "") && !isflagset(FLAG_RTP_PROXY)) { force_rtp_proxy('r'); append_hf("P-RTP-Proxy: On\r\n"); setflag(FLAG_RTP_PROXY); $rproxy = 1; setavpflag($rproxy, "dialog_cookie"); } } # Handling of Route headers # route[PROCESS_ROUTES] { # subsequent messages withing a dialog should take the # path determined by the Route headers. if (loose_route()) { if (!defined $dialog_id) { $dialog_id = $t.dialog_id; # there is only 1 dialog_id } if (@rr.next_route != "") { setflag("FLAG_NEXT_ROUTE"); } xlog("L_DEBUG", "\n%mb\n\ndialogid -/from/to=%$dialog_id/%$f.dialog_id/%$t.dialog_id"); if (method == "INVITE" || method == "UPDATE" || method == "ACK" || method == "BYE") { if (!defined $dialog_id) { sl_reply("400", "Missing cookie"); drop; } } # Mark routing logic in request. append_hf("P-hint: rr-enforced\r\n"); # If the Route contained the accounting AVP cookie we # set the accounting flag for the acc_db module. # This is more for demonstration purpose as this could # also be solved without RR cookies. # Note: this means all in-dialog request will show up in # the accounting tables, so prepare your accounting software # for this. if ($account == "yes") { setflag(FLAG_ACC); setflag(FLAG_ACC_MISSED); } # Restore the RTP proxy flag if present if ($rproxy == "1") { setflag(FLAG_RTP_PROXY); } # Restore Session Timer flag and headers. if ( defined $stimer && ($stimer != "0")) { route(SESSION_TIMER); } # Some broken devices overide the dialog route set with the # Record-Route headers from each in-dialog request. So, we # better add Record-Route headers again. If we call # record_route() after loose_route(), the AVP cookies are # restored automatically. Additionally, there is a scenario # where Record-Route headers are necessary if an initial # SUBSCRIBE is forked. # # Note that here we forward before authentication checks # are executed. Generally, we only authenticate # out-of-dialog requests. Some in-dialog requests can't be # authenticated at all, see the call-forwarding example in # route[DOMAIN]. route(RECORD_ROUTE); route(FORWARD); } } # Add a Record-Route header # route[RECORD_ROUTE] { if (!isflagset(FLAG_RR_DONE) && method != "REGISTER") { # We record-route all messages to make sure that # subsequent messages will go through our proxy. This is # particularly good if upstream and downstream entities # use different transport protocols. # If the ACC flag is set, store this in a Record-Route # AVP cookie. This is more for demonstration purposes. if (isflagset(FLAG_ACC)) { $account = "yes"; setavpflag($account, "dialog_cookie"); } setavpflag("$f.dialog_id", "dialog_cookie"); # Insert the RR header. record_route(); # This flag allows to call this route several times # without inserting several RR headers. setflag(FLAG_RR_DONE); } } # Look up the domains of the caller and the callee. # route[DOMAIN] { # Check whether the caller is from a local domain. lookup_domain("$fd", "@from.uri.host"); # Check whether the callee is at a local domain lookup_domain("$td", "@ruri.host"); } # Check domain usage policies and reject illegal requests. # route[DOMAIN_POLICY] { # If we don't know the domain of the caller nor the domain of the # callee, somone tries to use our proxy as a relay. However, we # can only apply this check out-of-dialog requests without a To # tag. In some cases such as call-forwarding, subsequent requests # may not include served domain neither as origination nor # destination (a@A calls b@B who forwards to c@C. A BYE by c@C is # then From b@B and To a@A. There is no mentioning of c@C despite # legitimate behaviour of c@C). if (!isflagset(FLAG_TOTAG) && strempty($t.did) && strempty($f.did)) { sl_reply("403", "Relaying Forbidden"); drop; } } # The Registrar # route[REGISTRAR] { # Process only REGISTERs here. if (method != "REGISTER") { break; } # If this is a replica (sent to the multicast address), trust it to # be secure and store it in usrloc if (dst_ip==224.0.1.75) { if (!isflagset(FLAG_REPL_ENABLED)) { # Multicast replication administratively disabled. # Ignore. drop; } # Read marker from master if (search("^Repl-Marker: nated")) { setflag(FLAG_NAT); } # If the replicating server added its own server id to the # request, obtain the value and store it in an attribute. # This is used by registrar. $server_id = @msg.header["SER-Server-ID"]; # Assume URI in form of UID@mydomain and store contacts # under this UID. Note that this only works if local policy # causes UIDs to have form compliant to RFC3261 URI # usernames. if (@ruri.user!="") $tu.uid = @ruri.user; if (isflagset(FLAG_NAT)) { setflag(FLAG_NAT_REG); } if (!save_mem_nr("location")) { log(1, "Error while saving replicated REGISTER.\n"); } drop; } else { # This is a REGISTER request received from the UA. Remove # our internal header fields if they are present. The may # have been added maliciously. remove_hf("SER-Server-ID"); remove_hf("Repl-Marker"); } # Check if the REGISTER if for one of our local domains. if (strempty($t.did)) { sl_reply("403", "Register Forwarding Forbidden"); drop; } # The REGISTER target is in the To header, so reload the domain. if (!lookup_domain("$td", "@to.uri.host")) { sl_reply("404", "Unknown Domain"); drop; } # Useful for clients that ignore expires in 200 (OK). This is an # attempt to keep them sticking to our value of 600. append_to_reply("Expires: 600\r\n"); append_to_reply("Min-Expires: 240\r\n"); # We want only authenticated users to be registered. if (!www_authenticate("$fd.digest_realm", "credentials")) { if ($? == -2) { sl_reply("500", "Internal Server Error"); } else if ($? == -3) { sl_reply("400", "Bad Request"); } else { if ($digest_challenge != "") { append_to_reply("%$digest_challenge"); } sl_reply("401", "Unauthorized"); } drop; } # Check if the authenticated user is the same as the target user. if (!lookup_user("$tu.uid", "@to.uri")) { sl_reply("404", "Unknown user in To"); drop; } # the authentication ID does not match the ID in the To header if ($f.uid != $t.uid) { sl_reply("403", "Authentication and To-Header mismatch"); drop; } # Check if the authenticated user is the same as the request # originator. You may uncomment it if you care, which URI is in # the From header. #if (!lookup_user("$fr.uid", "@from.uri")) { # sl_reply("404", "Unknown user in From"); # drop; #} #if ($fu.uid != $fr.uid) { # sl_reply("403", "Authentication and From-Header mismatch"); # drop; #} if (isflagset(FLAG_NAT)) { setflag(FLAG_NAT_REG); } # Everything is fine. Store the binding. if (!save_contacts("location")) { sl_reply("400", "Invalid REGISTER Request"); drop; } # do not delete the following 3 lines, they are used by debconf #DEBCONF-REPLICATION1-START # #DEBCONF-REPLICATION1-END if (isflagset(FLAG_REPL_ENABLED)) { if (isflagset(FLAG_NAT)) { append_hf("Repl-Marker: nated\r\n"); } # Append this server's unique ID to the request append_hf_value("SER-Server-ID", "%@sys.server_id"); # We are multicasting a successful REGISTER to all proxies # on the multicast network to replicate the contact # addresses to all of them. In case they share the same IP # address (VIP) it is important to set the sending IP # address to an unshared one (in the future a special mcast # module may use unbound sockets for sending and leave # the source IP address decision up to kernel routing # tables). #DEBCONF-REPL_SEND_ADDR-START force_send_socket(udp:127.0.0.1); #DEBCONF-REPL_SEND_ADDR-END # Put the UID in the Request-URI so that it doesn't have to # be looked up in the database by all multicast receivers. attr2uri("$tu.uid","user"); forward_udp(224.0.1.75,5060); } #DEBCONF-REPLICATION2-START # #DEBCONF-REPLICATION2-END drop; } # Authentication of request originators claiming to belong to one of our # domains. # route[AUTHENTICATION] { # CANCELs and ACKs cannot be challenged. if (method=="CANCEL" || method=="ACK") { break; } # Requests from non-local to local domains should be permitted. # Remove this if you want a walled garden. if (strempty($f.did)) { break; } # Gateways are usually not able to authenticate for their requests. # You have to trust them base on some other information such as the # source IP address. # WARNING: If at all this is only safe in a local network! if (src_ip == $gw_ip) { break; } if (!proxy_authenticate("$fd.digest_realm", "credentials")) { if ($? == -2) { sl_reply("500", "Internal Server Error"); } else if ($? == -3) { sl_reply("400", "Bad Request"); } else { if (defined $digest_challenge && $digest_challenge != "") { append_to_reply("%$digest_challenge"); } sl_reply("407", "Proxy Authentication Required"); } drop; } # Check if the UID derived from authentication matches that from # the From header. if (!lookup_user("$fr.uid", "@from.uri")) { sl_reply("403", "Fake Identity"); drop; } if ($fu.uid != $fr.uid) { sl_reply("403", "Fake Identity"); drop; } setflag(FLAG_AUTH_OK); # Load the user attributes of the caller. load_attrs("$fu", "$f.uid"); } # Process request targeted to non-local domains. # route[OUTBOUND] { # If a local user calls to a foreign domain we play outbound # proxy for them. # Comment this out if you want a walled garden. if ($f.did != "" && strempty($t.did)) { append_hf("P-hint: outbound\r\n"); route(FORWARD); } } # Process speeddial addresses. # route[SPEEDDIAL] { # If the caller is local and uses two digits only, we redirect the # UA to the real target. if ($fd.did != "" && uri =~ "sip:[0-9][0-9]@") { if (sd_lookup("speed_dial")) { sl_reply("302", "Speed Dial Redirect"); } else { sl_reply("404", "Speed Dial Not Found"); } drop; } } # Process requests targeted to a local user. # route[INBOUND] { # lets see if know the callee if (!lookup_user("$tu.uid", "@ruri")) { break; } # Load the attributes of the callee. load_attrs("$tu", "$t.uid"); # You can check if the called URI is in fact an alias like this. #if (! $tu.uri_canonical) { # # If the alias URI has different attributes, you can load # # them into the URI track like this. # load_attrs("$tr", "@ruri"); #} # Check for call forwarding of the callee. # Note: The forwarding target has to be full routable URI # in this example. if (defined $tu.fwd_always_target && $tu.fwd_always_target != "") { attr2uri("$tu.fwd_always_target"); # If we are forwarding to ourselves, don't remove # credentials. Otherwise the request would be challenged # again. # Note: This doesn't apply to failure_route which may # still be problematic -- credentials are already # removed when we forward. Consider using a 3xx. lookup_domain("$td", "@ruri.host"); if (defined $t.did && $t.did != "") { setflag(FLAG_DONT_RM_CRED); } route(FORWARD); } # Native SIP destinations are handled using the usrloc database. if (lookup_contacts("location")) { append_hf("P-hint: usrloc applied\r\n"); # destination is behind NAT if (isflagset(FLAG_NAT_REG)) { setflag(FLAG_NAT); /* client was behind NAT when made registration */ } # We set the tm module timers according to the prefences # of the callee (avoid too long ringing of his phones). # Note1: Timer values have to be in ms now! # Note2: This makes even more sense if you switch to a # voicemail from the FAILURE_ROUTE below. if ($t.fr_inv_timer) { if ($t.fr_timer) { t_set_fr("$t.fr_inv_timer", "$t.fr_timer"); } else { t_set_fr("$t.fr_inv_timer"); } } # This enables session timer support as long as one side # supports it. If you want to have session timmer support # only for calls from your PSTN gateway but not between pure # VoIP calls you can remove the comment marks from the if # clause in the next line and closing bracket below. # WARNING: If at all you should trust IP addresses only in # your local network! #if (src_ip == $gw_ip) { route(SESSION_TIMER); #} route(FORWARD); } else { sl_reply("480", "Temporarily unavailable"); drop; } } # Process calls for PSTN. # route[PSTN] { # Check some conditions first: # PSTN is available for our own users only. if (strempty($f.did)) { break; } # If the attribute $gw_ip isn't set, there is no PSTN service # active. if (!defined $gw_ip) { break; } # And finally, the username of the Request-URI must look like # a phone number. if (!uri =~ "^sips?:\+?[0-9]{3,18}@") { break; } # You may have to convert the number in the Request-URI into a # format that is accepted by your gateway here. # Check permissions of the caller for initial INVITEs. if (isflagset(FLAG_INIT_DLG)) { if ($f.gw_acl != "1") { sl_reply("403", "PSTN Not Permitted"); drop; } } # If the attribute $asserted_id is set, we add its contents as a # Remote-Party-ID header. # Depending on your gateway, you may have to add a # P-Asserted-Identity header here instead. if (defined $asserted_id) { xlset_attr("$rpidheader", ";screen=yes"); replace_attr_hf("Remote-Party-ID", "$rpidheader"); } # Enable Session Timer support with the gateway. route(SESSION_TIMER); # Replace the domain part of the Request-URI with the value from # the attribute and send it out. attr2uri("$gw_ip", "domain"); # Set the PSTN_ALLOWED flag. This will be checked on the # onsend_route. setflag(FLAG_PSTN_ALLOWED); route(FORWARD); } # Try to process CANCEL requests quickly. # route[CATCH_CANCEL] { if (method == CANCEL) { # t_relay_cancel() will stop processing if a matching # INVITE was found. xlog("L_DEBUG", "catching cancel dialogid=%$dialog_id\n"); if (!t_relay_cancel()) { # An INVITE was found but some error occurred. sl_reply("500", "Internal Server Error"); drop; } # Bad luck, no corresponding INVITE was found, we have to # continue with the script. } } # Site specific policy. # route[SITE_SPECIFIC] { # This is only relevant for requests for one of our domains. if (strempty($t.did)) { break; } # Do site specific routing such as peering. # For example: if (uri=~"^sip:000777") { rewritehostport("sems01.iptel.org:5074"); route(FORWARD); } } # Process Session-Timer. # route[SESSION_TIMER] { # We are only interested in session establishment or session # refreshing. # if (method != "INVITE" && method != "UPDATE") { break; } # Let's check if the Session-Expires header is already present. if (@hf_value.session_expires != "") { # Compare the Session-Expires header value with the # configured Min-SE. eval_push("x:%@hf_value.session_expires.uri"); eval_oper("(int)", -1); eval_push("x:%@cfg_get.session_timer.min_se"); eval_oper("(int)", -1); eval_oper(">=", -2); # Let's check for the Suported header. if (hf_value_exists("Supported", "timer")) { # The UAC supports Session-Timer, so we # only need to take a look at the values if (@eval.pop[-1] == "0") { # Session interval is lower than the # configured Min-SE append_to_reply("Min-SE: %@cfg_get.session_timer.min_se\r\n"); sl_reply("422", "Session Interval Too Small"); drop; } # We store the session expires value for the reply # route and mark the attribute for inserting as # Record-Route cookie. $stimer = @hf_value.session_expires.uri; setavpflag($stimer, "dialog_cookie"); # Set the session timer flag that indicates the # UAC supports the extension. setflag(FLAG_SESSIONTIMER); } else { # Session epxires was already inserted by some other # proxy if (@eval.pop[-1] == "0") { # Session interval is lower than the # configured Min-SE. There is no point in # sending 422 response, because the UAC # does not support the extension, the values # can be corrected instead. assign_hf_value("Session-Expires", "%@cfg_get.session_timer.min_se"); remove_hf_value("Min-SE"); append_hf_value("Min-SE", "%@cfg_get.session_timer.min_se"); } } } else { # No Session Timer is requested yet, neither by UAC nor by # proxy if (@cfg_get.session_timer.default != "0") { # Add a Session Expires header to see if the UAS # supports Session Timer. We do not insert a # Required header because then the call might fail. append_hf_value("Session-Expires", "%@cfg_get.session_timer.default"); if (@cfg_get.session_timer.min_se != "90") { append_hf_value("Min-SE", "%@cfg_get.session_timer.min_se"); } # Mark the attribute to be inserted as a # Record-Route cookie $stimer = @cfg_get.session_timer.default; setavpflag($stimer, "dialog_cookie"); } } } # Route which checks and performs ENUM queries # # route[ENUM] { # perform ENUM query only if the RURI contains an E.164 # number as uer part if (uri =~ "sip:\+[0-9]?@") { # if the ENUM query was successful send it right # away of to the new target, otherwise do nothing if (enum_query()) { route(FORWARD); } } } # Failure route for initial INVITEs. # failure_route[FAILURE_ROUTE] { if (isflagset(FLAG_INIT_DLG)) { # Mark that we are operating from a failure route. setflag(FLAG_FAILUREROUTE); if (t_check_status("486|600")) { # If we received a busy and a busy target is set, forward # it there. # Note: Again, the forwarding target has to be a routeable # URI. We redirect using 3xx to avoid possible issues with # credentials (if we consumed them, they may be missing in # a loop, if we don't consume them, messages are bigger and # more vulnerable) if ($tu.fwd_busy_target != "") { attr2uri("$tu.fwd_busy_target"); #attr_destination("$tu.fwd_busy_target"); #route(FORWARD); t_reply("302", "Redirect On Busy"); } # Alternatively, you could forward the request to # SEMS/voicemail here } else if (t_check_status("408|480")) { # If we received no answer and the noanswer target is set, # forward it there. # Note: See above. if ($tu.fwd_noanswer_target != "") { attr2uri("$tu.fwd_noanswer_target"); #attr_destination("$tu.fwd_noanswer_target"); #route(FORWARD); t_reply("302", "Redirect On No Answer"); } } } # if (isflagset... } # Onreply route that fixes NAT in responses. # onreply_route[REPLY_ROUTE] { # Check and fix the Contact in the reply to # allow proper routing of in-dialog messages. route(UAS_NAT_DETECTION); # If RTP proxy was activated and this is a 18x or 2xx reply with a # body, inform RTP proxy. if (isflagset(FLAG_RTP_PROXY) && status=~"(18[03])|(2[0-9][0-9])" && @msg.body != "") { force_rtp_proxy('r'); } # Let's check for session timer support. if (isflagset(FLAG_SESSIONTIMER) && status =~ "2[0-9][0-9]") { # The UAC wanted to have a session timer. if (strempty(@hf_value.session_expires)) { # But the UAS does not support it, so we will try # to convince the UAC to do it. append_hf_value("Session-Expires", "%$stimer;refresher=uac"); if (!hf_value_exists("Require", "timer")) { include_hf_value("Require", "timer"); } } } } # Do some final checks before a request is sent out. onsend_route { # Bypass check: Eliminate requests to the PSTN gateway if they have # not passed ACL checks and are not marked with FLAG_PSTN_ALLOWED # but are dialog-initiating requests (no to-tag, no CANCEL, no ACK). # This helps to stop policy bypasses (gateway IP uploaded as a # forked contact, or a call-forwarding destination, or a DNS name, # or a preloaded route, or something else possibly) if (defined $g.gw_ip && to_ip==$g.gw_ip && !isflagset(FLAG_PSTN_ALLOWED) && !isflagset(FLAG_TOTAG) && method != "ACK" && method != "CANCEL") { log(1, "ALERT: non authorized packet for PSTN, dropping...\n%mb\n"); # You can't use advanced features from onsend_route. # xlog("L_ALERT", "non authorized packet for PSTN, dropping...\n%mb\n"); drop; } # RFC 1918 relay protection: Useful if SER is attached to an # administrative network using private IP address space and you # wish to prevent UACs from relaying their packets there. # # You will have to comment this out, if you are regularly serving # an RFC 1918 address space. if (to_ip==10.0.0.0/8 || to_ip==172.16.0.0/12 || to_ip==192.168.0.0/16) { log(1, "ALERT: Packet targeted to an RFC1918 address dropped\n"); drop; } } # Run every minute by the timer module. # route[ON_1MIN_TIMER] { # Cleanup expired location records # MySQL version: db_query("delete from location where expires= 8192) { xmlrpc_reply("513", "request too big"); return; } #!ifdef XMLRPC_TLS_ONLY # allow xmlrpc only on TLS and only if the client certificate is valid if (proto!=TLS){ xmlrpc_reply("400", "xmlrpc allowed only over TLS"); return; } if (@tls.peer.verified!=""){ xmlrpc_reply("400", "Unauthorized"); return; } #!endif # close connection only for xmlrpclib user agents (there is a bug in # xmlrpclib: it waits for EOF before interpreting the response). if (search("^User-Agent:.*xmlrpclib")) set_reply_close(); set_reply_no_connect(); # optional dispatch_rpc(); }