# # ser.cfg # # Copyright (C) 2006 - 2007 Alfred E. Heggestad # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # ----------- global configuration parameters ------------------------ debug=3 # debug level (cmd line: -dddddddddd) #memdbg=10 # memory debug log level #memlog=10 # memory statistics log level #log_facility=LOG_LOCAL0 # sets the facility used for logging (see syslog(3)) /* Uncomment these lines to enter debugging mode fork=no log_stderror=yes */ check_via=no # (cmd. line: -v) dns=no # (cmd. line: -r) rev_dns=no # (cmd. line: -R) listen=ser listen=tls:ser:5061 listen=localhost port=5060 children=2 #user=ser #group=ser #disable_core=yes #disables core dumping #open_fd_limit=1024 # sets the open file descriptors limit #mhomed=yes # usefull for multihomed hosts, small performance penalty #disable_tcp=yes tcp_accept_aliases=yes # accepts the tcp alias via option (see NEWS) enable_tls=1 # For STUN testing stun_refresh_interval=25000 # number in millisecond (default 0); value for attribute REFRESH INTERVAL stun_allow_stun=1 # 0 | 1 (off | on - default 1); use STUN or not if compiled stun_allow_fp=0 # 0 | 1 (off | on - default 1); use FINGERPRINT attribute # ------------------ module loading ---------------------------------- loadmodule "/usr/local/lib/ser/modules/mysql.so" loadmodule "/usr/local/lib/ser/modules/sl.so" loadmodule "/usr/local/lib/ser/modules/tm.so" loadmodule "/usr/local/lib/ser/modules/rr.so" loadmodule "/usr/local/lib/ser/modules/maxfwd.so" loadmodule "/usr/local/lib/ser/modules/msilo.so" loadmodule "/usr/local/lib/ser/modules/usrloc.so" loadmodule "/usr/local/lib/ser/modules/registrar.so" loadmodule "/usr/local/lib/ser/modules/xlog.so" loadmodule "/usr/local/lib/ser/modules/textops.so" loadmodule "/usr/local/lib/ser/modules/ctl.so" loadmodule "/usr/local/lib/ser/modules/fifo.so" loadmodule "/usr/local/lib/ser/modules/auth.so" loadmodule "/usr/local/lib/ser/modules/auth_db.so" loadmodule "/usr/local/lib/ser/modules/gflags.so" loadmodule "/usr/local/lib/ser/modules/domain.so" loadmodule "/usr/local/lib/ser/modules/uri.so" loadmodule "/usr/local/lib/ser/modules/uri_db.so" loadmodule "/usr/local/lib/ser/modules/avp.so" loadmodule "/usr/local/lib/ser/modules/avp_db.so" loadmodule "/usr/local/lib/ser/modules/avpops.so" loadmodule "/usr/local/lib/ser/modules/acc_db.so" loadmodule "/usr/local/lib/ser/modules/xmlrpc.so" loadmodule "/usr/local/lib/ser/modules/eval.so" loadmodule "/usr/local/lib/ser/modules/nathelper.so" loadmodule "/usr/local/lib/ser/modules/acc_syslog.so" loadmodule "/usr/local/lib/ser/modules/tls.so" loadmodule "/usr/local/lib/ser/modules/sanity.so" # ----------------- setting script FLAGS ----------------------------- flags FLAG_ACC : 1, # include message in accouting FLAG_FAILUREROUTE : 2, # we are operating from a failure route FLAG_NAT_UAC : 3, # NAT flags FLAG_NAT_UAS : 4; avpflags dialog_cookie; # handled by rr module # ----------------- setting module-specific parameters --------------- # specify the path to you database here modparam("acc_db|auth_db|avp_db|domain|gflags|usrloc|uri_db", "db_url", "mysql://ser:heslo@127.0.0.1/ser") # -- usrloc params -- modparam("usrloc", "db_mode", 1) # -- auth params -- modparam("auth_db", "calculate_ha1", yes) modparam("auth_db", "password_column", "password") # -- rr params -- modparam("rr", "enable_full_lr", 1) modparam("rr", "cookie_filter", "(account)") modparam("rr", "cookie_secret", "b9bEg61ExAOrMsASsdsdS19asj") # -- gflags params -- modparam("gflags", "load_global_attrs", 1) # -- domain params -- modparam("domain", "load_domain_attrs", 1) # -- ctl params -- modparam("ctl", "binrpc", "unixs:/tmp/ser_ctl") modparam("ctl", "fifo", "fifo:/tmp/ser_fifo") modparam("ctl", "binrpc", "tcp:127.0.0.1:2046") # -- acc_db params -- modparam("acc_db", "failed_transactions", 1) # comment the next line if you dont want to have accouting to DB modparam("acc_db", "log_flag", "FLAG_ACC") # -- registrar params -- modparam("registrar", "save_nat_flag", "FLAG_NAT_UAC") modparam("registrar", "load_nat_flag", "FLAG_NAT_UAS") # -- nathelper params -- modparam("nathelper", "natping_interval", 30) modparam("nathelper", "natping_method", "OPTIONS") modparam("nathelper", "ping_nated_only", 1) modparam("nathelper", "rtpproxy_sock", "udp:127.0.0.1:22222") # -- tm params -- # uncomment the following line if you want to avoid that each new reply # restarts the resend timer (see INBOUND route below) #modparam("tm", "restart_fr_on_each_reply", "0") # -- xmlrpc params -- # using a sub-route from the module is a lot safer then relying on the # request method to distinguish HTTP from SIP modparam("xmlrpc", "route", "RPC"); # -- acc_syslog params -- modparam("acc_syslog", "early_media", no) modparam("acc_syslog", "failed_transactions", no) modparam("acc_syslog", "report_ack", no) modparam("acc_syslog", "report_cancels", no) modparam("acc_syslog", "log_flag", "acc.log") modparam("acc_syslog", "log_missed_flag", "acc.missed"); # -- tls params -- modparam("tls", "config", "tls.cfg") # ------------------------- request routing logic ------------------- # main routing logic 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" xlog("L_NOTICE", "%rm From <%fu> To <%tu> (%ua)\n"); # first do some initial sanity checks route(INIT); # check if the request is routed via Route header or # needs a Record-Route header route(RR); # check if the request belongs to our proxy route(DOMAIN); # check if any UA participating in the call is behind NAT route(NAT_DETECT); # handle REGISTER requests route(REGISTRAR); # from here on we want to know you is calling route(AUTHENTICATION); # check if we should be outbound proxy for a local user route(OUTBOUND); # check if the request is for a local user route(INBOUND); # here you could for example try to do an ENUM lookup before # the call gets routed to the PSTN #route(ENUM); # lets see if someone wants to call a PSTN number route(PSTN); # nothing matched, reject it finally sl_reply("404", "No route matched"); } route[FORWARD] { # here you could decide wether this call needs a RTP relay or not route(NAT_MANGLE); t_on_reply("NAT_MANGLE"); # for NATed clients record route following cookies except it has already been loose routed if ($record_route_nated && !$loose_route) { setavpflag("$nated_caller", "dialog_cookie"); setavpflag("$nated_callee", "dialog_cookie"); } # avoids double record routing if loose routed if (!$loose_route) record_route(); # if this is called from the failure route we need to open a new branch if (isflagset(FLAG_FAILUREROUTE)) { append_branch(); } # if this is an initial INVITE (without a To-tag) we might try another # (forwarding or voicemail) target after receiving an error if (method=="INVITE" && @to.tag=="") { t_on_failure("FAILURE_ROUTE"); } # if the downstream UA does not support MESSAGE requests # go to failure_route[1] if (method=="MESSAGE") { t_on_failure("MESSAGE"); } # send it out now; use stateful forwarding as it works reliably # even for UDP2TCP if (!t_relay()) { sl_reply_error(); } drop; } route[INIT] { # MUST be done *after* route(RPC) sanity_check(); # initial sanity checks -- messages with # max_forwards==0, or excessively long requests if (!mf_process_maxfwd_header("10")) { sl_reply("483","Too Many Hops"); drop; } if (msg:len >= max_len ) { sl_reply("513", "Message too big"); drop; } # you could add some NAT detection here for example #route(NAT_DETECT); # or you cuold call here some of the check from the sanity module # lets account all initial INVITEs # further in-dialog requests are accounted by a RR cookie (see below) if (method=="INVITE" && @to.tag=="") { setflag(FLAG_ACC); } } route[RPC] { # allow XMLRPC from localhost if ((method=="POST" || method=="GET") && src_ip==127.0.0.1) { if (msg:len >= 8192) { sl_reply("513", "Request to big"); drop; } # lets see if a module wants to answer this dispatch_rpc(); drop; } } route[RR] { # subsequent messages withing a dialog should take the # path determined by record-routing if (loose_route()) { # 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 # accouting tables, so prepare your accounting software for this ;-) if ($account == "yes") { setflag(FLAG_ACC); } # this attribute is used for check in route[FORWARD] $loose_route = true; # for broken devices which overwrite their Route's with each # (not present) RR from within dialog requests it is better # to repeat the RRing # and if we call rr after loose_route the AVP cookies are restored # automatically :) record_route(); route(FORWARD); } else if (!method=="REGISTER") { # we record-route all messages -- to make sure that # subsequent messages will go through our proxy; that's # particularly good if upstream and downstream entities # use different transport protocol # if the inital INVITE got the ACC flag store this in # an RR AVP cookie. this is more for demonstration purpose if (isflagset(FLAG_ACC)) { $account = "yes"; setavpflag($account, "dialog_cookie"); } } } route[DOMAIN] { # check if the caller is from a local domain lookup_domain("$fd", "@from.uri.host"); # check if the callee is at a local domain lookup_domain("$td", "@ruri.host"); # we dont know the domain of the caller and also not # the domain of the callee -> somone uses our proxy as # a relay if (!$t.did && !$f.did) { sl_reply("403", "Relaying Forbidden"); drop; } } route[REGISTRAR] { # if the request is a REGISTER lets take care of it if (method=="REGISTER") { # check if the REGISTER if for one of our local domains if (!$t.did) { xlog("L_ERR", "%rm From <%fu> 403 Register forwarding forbidden\n"); sl_reply("403", "Register forwarding forbidden"); drop; } # we want only authenticated users to be registered if (!www_authenticate("$fd.digest_realm", "credentials")) { if ($? == -2) { xlog("L_ERR", "%rm From <%fu> 500 Internal Server Error\n"); sl_reply("500", "Internal Server Error"); } else if ($? == -3) { xlog("L_ERR", "%rm From <%fu> 400 Bad Request\n"); 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; } 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, what uri is in From header if (!lookup_user("$fu.uid", "@from.uri")) { sl_reply("404", "Unknown user in From"); drop; } if ($fu.uid != $tu.uid) { sl_reply("403", "Authentication and From-Header mismatch"); drop; } # everyhting is fine so lets store the binding if (!save_contacts("location")) { sl_reply("400", "Invalid REGISTER Request"); drop; } # MSILO - dumping user's offline messages if (m_dump("")) { xlog("L_DBG", "MSILO: offline messages dumped - if they were\n"); } else { xlog("L_DBG", "MSILO: no offline messages dumped\n"); }; drop; } } route[AUTHENTICATION] { if (method=="CANCEL" || method=="ACK") { # you are not allowed to challenge these methods break; } # requests from non-local to local domains should be permitted # remove this if you want a walled garden if (! $f.did) { break; } # as gateways are usually not able to authenticate for their # requests you will have trust them base on some other information # like the source IP address. WARNING: if at all this is only safe # in a local network!!! #if (src_ip==a.b.c.d) { # 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 ($digest_challenge) { append_to_reply("%$digest_challenge"); } sl_reply("407", "Proxy Authentication Required"); } drop; } # check if the UID from the authentication meets the From header $authuid = $uid; if (!lookup_user("$fu.uid", "@from.uri")) { del_attr("$uid"); } if ($fu.uid != $fr.authuid) { sl_reply("403", "Fake Identity"); drop; } # load the user AVPs (preferences) of the caller, e.g. for RPID header load_attrs("$fu", "$f.uid"); } route[NAT_DETECT] { # xlog("L_NOTICE","route[NAT_DETECT] entered NAT_DETECT route"); # Skip spiralling requets if (src_ip == 127.0.0.1) return; # Skip requests that already passed a proxy if (is_present_hf("^Record-Route:")) { return; } if (nat_uac_test("3")) { force_rport(); setflag("FLAG_NAT_UAC"); $nated_caller = true; } return; } route[NAT_MANGLE] { # Rewrite Contact if the UAC is behind NAT if ($nated_caller || $t.nated_caller) { if (method == "REGISTER") { xlog("L_NOTICE","NAT_MANGLE: Fix NAT-ted REGISTER"); fix_nated_register(); } else { xlog("L_NOTICE","NAT_MANGLE: Fix NAT-ted %rm"); fix_nated_contact(); } } if (method == "BYE" || method == "CANCEL") { unforce_rtp_proxy(); } else if (method == "INVITE") { # If either UAC or UAS is behind NAT then go on # if (!$f.nated_caller && !$t.nated_callee) return; # Force record routing if either party is behind NAT $record_route_nated = true; # rewrites IP address and port in SDP body force_rtp_proxy(); } return 1; } onreply_route["NAT_MANGLE"] { xlog("L_NOTICE","onreply_route[NAT_MANGLE] %rs %rr"); # Rewrite Contact in 200 OK if UAS is behind NAT if ($nated_callee || $t.nated_callee) { fix_nated_contact(); } # Apply RTP proxy if necessary, but only for INVITE transactions # and 183 or 2xx replies if (!$f.nated_caller && !$t.nated_callee) return; if (@cseq.method != "INVITE") return; if ((status =~ "(183)|2[0-9][0-9]") && search("^(Content-Type|c):.*application/sdp")) { xlog("L_NOTICE","onreply_route[NAT_MANGLE] force rtp proxy"); force_rtp_proxy(); } return; } route[OUTBOUND] { # if a local user calls to a foreign domain we play outbound proxy for him # comment this out if you want a walled garden if ($f.did && ! $t.did) { append_hf("P-hint: outbound\r\n"); route(FORWARD); } } route[INBOUND] { # lets see if know the callee if (lookup_user("$tu.uid", "@ruri")) { # load the preferences of the callee to have his timeout values loaded load_attrs("$tu", "$t.uid"); # if you want to know if the callee username was an alias # check it like this #if (! $tu.uri_canonical) { # if the alias URI has different AVPs/preferences # 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 ($tu.fwd_always_target) { attr2uri("$tu.fwd_always_target"); route(FORWARD); } # native SIP destinations are handled using our USRLOC DB if (lookup_contacts("location")) { append_hf("P-hint: usrloc applied\r\n"); # 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"); } } if (isflagset("FLAG_NAT_UAS")) { xlog("L_NOTICE","route[INBOUND] callee is behind NAT - setting $nated_callee = true"); $nated_callee = true; } route(FORWARD); } else { # User is offline # we care about MESSAGEs if (method == "MESSAGE") { if (!t_newtran()) { sl_reply_error(); break; } # store only text messages NOT isComposing... ! if (search("^(Content-Type|c):.*application/im-iscomposing\+xml.*")) { t_reply("202", "Ignored"); break; } xlog("L_NOTICE", "MESSAGE received -> storing using MSILO\n"); # MSILO - storing as offline message if (m_store("0", "")) { xlog("L_NOTICE", "MSILO: offline message stored\n"); if (!t_reply("202", "Accepted")) { sl_reply_error(); }; } else { xlog("L_NOTICE", "MSILO: offline message NOT stored\n"); if (!t_reply("503", "Service Unavailable")) { sl_reply_error(); }; }; drop; } xlog("L_NOTICE","route[INBOUND] 480 User Offline"); sl_reply("480", "User Offline"); drop; } } } route[PSTN] { # Only if the AVP 'gw_ip' is set and the request URI contains # only a number we consider sending this to the PSTN GW. # Only users from a local domain are permitted to make calls. # Additionally you might want to check the acl AVP to verify # that the user is allowed to make such expensives calls. if ($f.did && $gw_ip && uri=~"sips?:\+?[0-9]{3,18}@.*") { # probably you need to convert the number in the request # URI according to the requirements of your gateway here # if an AVP 'asserted_id' is set we insert an RPID header if ($asserted_id) { xlset_attr("$rpidheader", ";screen=yes"); replace_attr_hf("Remote-Party-ID", "$rpidheader"); } # just replace the domain part of the RURI with the # value from the AVP and send it out attr2uri("$gw_ip", "domain"); route(FORWARD); } } failure_route[FAILURE_ROUTE] { # mark for the other routes that we are operating from here on 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 if ($tu.fwd_busy_target) { attr2uri("$tu.fwd_busy_target"); route(FORWARD); } # 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: again the target has to be a routeable URI if ($tu.fwd_noanswer_target) { attr2uri("$tu.fwd_noanswer_target"); route(FORWARD); } # alternatively you could forward the request to SEMS/voicemail here } } failure_route[MESSAGE] { xlog("L_ERR", "failure route MESSAGE\n"); # forwarding failed -- check if the request was a MESSAGE if (method == "MESSAGE") { xlog("L_NOTICE", "MSILO: the downstream UA doesn't support MESSAGEs\n"); # we have changed the R-URI with the contact address, ignore it now if (m_store("1", "")) { xlog("L_NOTICE", "MSILO: offline message stored\n"); t_reply("202", "Accepted"); } else { xlog("L_NOTICE", "MSILO: offline message NOT stored\n"); t_reply("503", "Service Unavailable"); } } } ### EOF ser.cfg ###