SIP.edu Logo SIP.edu Cookbook Internet2  
MIT  

Contents

Introduction

Getting Started

DNS

Proxies

Gateways

User Agents

Directory Considerations

Security Considerations

Deployments

Glossary


Contacts

Related Links

Configuring SER

Jeremy George <jeremy.george@yale.edu> (November 25, 2003)

I - Architecture

SER (SIP Express Router) is an open-source SIP proxy, redirect and registrar server from Iptel.org, an offshoot of the German national research company FhG Fokus. In addition to voice call signalling, SER includes support for SMS, presence, SIP-based instant messaging and a jabber gateway among other applications.

The design is intended to represent a genuine translation of circuit-switched dial plan configuration into Internet-centric mechanisms rather than the transliteration often seen. This architectural choice lends SER significant flexibility. The following section is intended to help the reader through basic installation and administration only. Further documentation and mailing list information are available at www.iptel.org/ser.

Reader's should note that this section is specific to version 0.8.11. Version 0.8.12 has just been released. Most of the new material consists of bug fixes. Even so some small parts of the following may not apply to this release.

II - Requirements

SER runs on several varieties of linux and SUN Microsystem's Solaris. A generic compressed tarball as well as some linux/Solaris specific packages are available from ftp.berlios.de/pub/ser/0.8.11 . A limited version of the SER core is reported to run on Windows. We do not recommend it at this time.

Gcc or icc, bison or yacc, flex, GNU make or gmake, sed and tr are required for compilation. All are likely already on your machine or are freely available if not. If you want mysql support you'll also need libmysqlclient and libz. Optional modules may similarly require additional library support.

Iptel.org does not specify minimum hardware, though SER will run on a PC with quite modest resources. Proper sizing for expected peak hour loads needs to be determined experientially. The documentation does note, however, that a dual-CPU server would have the transaction processing capacity to handle peak hour voice traffic for the entire San Francisco bay area. As a general guideline then any 100Mb connected PC with reasonable resource should be adequate, particularly for trials. One exception to this is for schools with large alias tables which must be kept in memory. In such cases at least 512 megabytes of memory is suggested and a gigabyte would be better.

This general guideline assumes voice calls only. The additional functionality of presence, instant messaging, etc. would add a load on the machine that would need to be taken into account.

III - Installation

Copy the tarball into some common directory such as /usr/local/src and untar it. Issuing make all followed by make install from the .gz file puts files in the /usr/local tree. Vendor specific packages may place files in other locations. Schools anticipating large alias tables should quadruple the value of PKG_MEM_POOL_SIZE found in config.h prior to compilation.

Binary and script files are installed in /usr/local/sbin. Be sure to run ser_mysql.sh to have the necessary tables added if you plan to use mysql support.

The sample ser.cfg file found in /usr/local/etc/ser that comes with the distribution can be used as is to verify the installation. Any SIP phone, hard or soft, can be used for testing. You should be able to register two (or more) and make calls to/from each. This is a good and satisfying first step, but once completed administrators should be aware that the sample ser.cfg does not use any authentication and should not be employed even for trials without some configuration.

IV - Configuration

SER's configuration file is divided into four main sections: global parameters, external module loading, module parameters and routing blocks, which contain request routing logic. The last is by far the largest section.

# Global Parameters

The global parameters in the sample ser.cfg mostly can be leftalone for normal operation. Only the address to listen on must be set. Debug levels of 4 and above provide considerable debugging information. Fork=yes causes SER to daemonize on startup. Set this to no for debugging. Log_stderror=no should be changed to yes for debugging. SER can listen on multiple IP addresses in normal operation but only one in debugging mode. Comment out the unwanted addresses including the loopback address.

One useful exception is the alias. Listing aliases for all of the ways in which your proxy server can be addressed means you will be able to use the special conditional uri==myself in the routing section. This will allow you to avoid parsing some of the more difficult regular expressions. At minimum it would be helpful to alias the ip address of the server.

# External Module Loading

Some of the external modules need to be loaded in a particular order. The following sequence works. If you started with a restricted make parameter, you may need to compile one or more of the optional external modules now. If one or more of the modules are unneeded, you may simply leave them out. Note that some library paths may be different.

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/usrloc.so"
loadmodule "/usr/local/lib/ser/modules/registrar.so"
loadmodule "/usr/local/lib/ser/modules/auth.so"
loadmodule "/usr/local/lib/ser/modules/auth_db.so"
loadmodule "/usr/local/lib/ser/modules/acc.so"
loadmodule "/usr/local/lib/ser/modules/exec.so"
loadmodule "/usr/local/lib/ser/modules/group.so"
loadmodule "/usr/local/lib/ser/modules/msilo.so"
loadmodule "/usr/local/lib/ser/modules/print.so"
loadmodule "/usr/local/lib/ser/modules/textops.so"
loadmodule "/usr/local/lib/ser/modules/jabber.so"
loadmodule "/usr/local/lib/ser/modules/uri.so"
loadmodule "/usr/local/lib/ser/modules/vm.so"

# Modules Parameters

Parameters are passed to the modules via a modparam statement. If you use mysql support you'll need to add:

modparam("usrloc", "db_url", "sql://ser:<password>@localhost/ser")

"heslo" is the default password. It should be changed and if you do you'll also need to change it in the mysql database. Use these mysql commands:

#mysql
mysql> use mysql;
mysql> update user set password='<whatever>' where User='ser';
mysql> quit;

A comprehensive guide to mysql commands can be found at www.mysql.com/documentation/index.html. We recommend a few minutes looking at it for readers unfamiliar with sql.

You may wish to set write-back to mysql to minimize runtime delay with this parameter:

modparam("usrloc", "db_mode", 2)

Some authentication method should be enabled. For basic digest with mysql you'll need to set these parameters.

#A random secret string.
modparam("auth", "secret", "<random string>")
#Yes, allows clear text passwords in mysql. No, disables them.
modparam("auth", "calculate_ha1", "yes")
#Name of password column in mysql database. Unless you've customized
mysql this can be added as is.
modparam("auth", "password_column", "password")

# Routing Blocks

Documentation on the configuration language used in the routing blocks is available in the Admin Guide and provided with the distribution and online at Iptel.org. Further documentation is also available throughout the distribution. Spending some time looking through the directories is highly encouraged. Even though the code is fairly complex, readers with programming backgrounds may find the comments in some code segments helpful.

The initial routing block (route[0]) is the point of entry to SER for all requests regardless of method. One major function of route[0] is to process registration requests. A second major function is to parse requests and transfer program control to a destination specific route block such as one for traffic headed to a PSTN gateway. Various transformations (e.g., canonicalizing request-URIs) and checks are also performed here. Finally, remaining requests are forwarded to directly registered clients.

In the conditional block for the REGISTER method you should check for infinite loops with code like this:

if (search("^(Contact|m): .*@(10\.1\.2\.3\|sip-proxy\.bigu\.edu)")) {
  log("LOG: alert: someone trying to set aor==contact\n");
  sl_send_reply("476", "No Server Address in Contacts Allowed" );
  break;
};

Substitute the correct IP address and FQDN. This is a good example of the kind of text parsing SER uses regular expressions for. There is a guide to regular expressions at www.uky.edu/ArtsSciences/Classics/regex.html for people wanting a short refresher course. The O'Reilly Owl book on "Mastering Regular Expressions" is available for comprehensive study.

Administrators wishing to use digest authentication will need to add code similar to this:

# challenge/response
if (!www_authorize("bigu.edu", "subscriber")) {
  www_challenge("bigu.edu", "0");
  break;
};

The second parameter in the www_challenge function call is the qop parameter (as specified in rfc2617). Absent a specific need it should be turned off by sending a "0" because some user agent clients do not process it properly.

The following code snippet might be inserted after the REGISTER conditional block to send calls destined for the Internet to route block route[2] for processing appropriate to those destinations.

if (!(uri==myself) {
  route(2);
  break;
}:

The configuration above is equivalent to the one below using a regular expression provided you listed aliases for 10.1.2.3 and bigu.edu.

if (!(uri=~"^sip:(.+@)?(10\.1\.2\.3|(sip-proxy\.)?bigu\.edu)([:;\?].*)?$")) {
  route(2);
  break;
};

In route[2] you probably would want to authenticate outbound calls to avoid spoofing and unauthorized relaying. But you'd only want the authentication done for the initial INVITE, not subsequent methods. You might use a code snippet like this:

if (!(src_ip==10.1.2.3) & !(proxy_authorize("bigu.edu", "subscriber"))) {

  # ACK and CANCEL have no security mechanisms so they are just
  # noted
  if (method=="ACK" | method=="BYE") {
    log("LOG: failed outbound authentication for ACK granted\n");
  } else if (method=="CANCEL") {
    log("LOG: failed outbound authentication for CANCEL granted\n");
  } else {
    proxy_challenge("bigu.edu", "0");
    break;
  };
};

The assumption is often made that all-numeric usernames not directly registered are really PSTN numbers and so they are forwarded to a SIP-PSTN gateway. The parsing in route[0] might look like this for a site assigning local SIP usernames in the 60000-60099 range.

if (!(uri=~"sip:600[0-9]{2}@.*")) {
  if ( (uri=~"^sip:911@.*") | (uri=~"sip:[0-9]{5,20}@.*") ) {

    # ALL calls through the gateway MUST be Record Routed
    addRecordRoute();
    route(3);
    break;
  };
};

Emergency services call requirements are currently an unresolved issue in VoIP. At minimum such calls should be forwarded without delay.

The assumption in requiring calls destined for a gateway to be Record Routed is that the gateway will be limited via an ACL to port 5060 traffic with the local proxy to prevent an arbitrary caller globally from sending calls through it. Thus, the final ACK in the handshake must be sent through the local proxy and not use a direct shortcut from UAC to gateway. Record Routing is explained in detail in RFC3261.

Notice that the break statement is required to prevent continued processing in route[0] when control is returned from route[3].

In the code block specific to calls headed to the PSTN the first task likely will be actually to forward emergency services calls. A code snippet like this might be used where the IP address of the gateway is 10.1.2.5:

if (uri=~"^sip:911@.*") {
  rewritehostport("10.1.2.5:5060");
  forward(uri:host, uri:port);
  break;
};

Similarly, to enable circuit-switched phones directly connected to the local PBX to be contacted via the Internet, five digit numeric addresses might be sent to the gateway without authentication. This assumes, of course, that five digit phone numbers are used locally.

if (uri=~"^sip:[0-9]{5}@(10.1.2.3|bigu\.edu)") {
  rewritehostport("130.132.1.5:5060");
  forward(uri:host, uri:port);
  break;
};

The examples that follow assume that an initial "9" is used to signal an offcampus call. The first code snippet also assumes that that BigU's policy is to authenticate all calls leaving the campus for the PSTN.

if (uri=~"^sip:9[0-9]*@(10.1.2.3|bigu\.edu)") {
  if (!(src_ip==10.1.2.3 | method==ACK | method=="CANCEL" | method=="BYE")) {
    if (!proxy_authorize("bigu.edu", "subscriber")) {
      proxy_challenge("bigu.edu","0");
      break;
    };
  };
};

In addition to authentication, an appropriate class of service may be required for calls that will result in a toll charge. This code snippet restricts domestic long distance calls to users in the "ld" group.

if (method=="INVITE") {
  if (uri=~"sip:91[0-9]*@.*") {
    if (!is_in_group("ld")) {
      sl_send_reply("403", "Local calls only");
      break;
    };
  };
};

Users may be placed in a particular group either by direct entries in mysql or by a utility named serctl which will be discussed later. These are the mysql commands that might be used for a user id of 60000 into the ld group:

#mysql
mysql> use ser;
mysql> insert into grp (username, grp) values ('60000', 'ld');
mysql> select * from grp where user='60000';

The last command will show you the entries for 60000 in the grp table so you can check what you've just done.

SER compiles the configuration file into a binary form at startup time to minimize runtime signaling delay. Syntactic errors prevent the compilation and SER will not start. This results in an efficient and timely means of guaranteeing the configuration file is at least syntactically correct. Note that such correctness does not necessarily mean that calls will forward as expected. Routing logic errors may require additional debugging.

The actual configuration file Iptel.org uses can be found in /etc/iptel.cfg. While not many sites will use a configuration this complex or fully featured, it is a very helpful example.

V - User Accounts

The following assumes you are using mysql but ldap and other methods are possible. User accounts can be manipulated a number of ways: direct use of mysql, serctl and multiple Iptel.org and third party applications. The latter are documented on www.iptel.org/ser and in the serusers and serdev mail archives.

The mysql command sequence to add a user might be:

#mysql
mysql> insert into subscriber (username,password,email_address) values
mysql> ('6000','secret','user@bigu.edu');

Serctl could be used to accomplish the same goal from the command prompt with this line:

#serctl add 6000 <secret> user@bigu.edu

Serctl will return "MySql Password: " to which you would respond with the password you had previously set for mysql in the modparam statement. If you haven't changed it, the correct response is "heslo" . You can set the environment variable PW to satisfy the challenge automatically.

Serctl can also be used to associate a user with a group:

#serctl acl grant 60000 ld

VI - Debugging and Server Monitoring with Serctl

This is the full list of commands available with serctl:

# serctl -h
/usr/local/sbin/serctl $Revision: 1.59.2.3 $
parameter usage:
* subscribers *
add <username> <password> <email> ......... add a new subscriber (*)
passwd <username> <passwd> ................ change user's password (*)
rm <username> ............................. delete a user (*)
mail <username> ........................... send an email to a user
alias show [<alias>] ...................... show aliases
alias rm <alias> .......................... remove an alias
alias add <alias> <uri> ................... add an aliases

* access control lists *

acl show [<username>] ..................... show user membership
acl grant <username> <group> .............. grant user membership (*)
acl revoke <username> [<group>] ........... grant user membership(s) (*)

* usrloc *

ul show [<username>]....................... show in-RAM online users
ul rm <username> .......................... delete user's UsrLoc entries
ul add <username> <uri> ....................introduce a permanent UrLoc entry
showdb [<username>] ........................show online users flushed in DB

* control and diagnostics *

moni ................. show internal status     start ...... start ser
ps ................... show running processes   stop ....... stop ser
fifo ................. send raw FIFO commands   restart .... restart ser
ping <uri> ........... ping a URI (OPTIONS)
cisco_restart <uri> .. restart a Cisco phone (NOTIFY)

Commands labeled with (*) will prompt for a MySQL password. If the variable PW is set, the password will not be prompted. ACL privileges are: local ld int voicemail free-pstn

A correctly running SER listening on IP address 10.1.2.3 and the loopback interface configured to use four processes will return this:

# serctl ps
0 25254 attendant
1 25258 receiver child=0 sock=0 @ 10.1.2.3:5060
2 25259 receiver child=1 sock=0 @ 10.1.2.3:5060
3 25261 receiver child=2 sock=0 @ 10.1.2.3:5060
4 25263 receiver child=3 sock=0 @ 10.1.2.3:5060
5 25265 receiver child=0 sock=1 @ 127.0.0.1:5060
6 25267 receiver child=1 sock=1 @ 127.0.0.1:5060
7 25274 receiver child=2 sock=1 @ 127.0.0.1:5060
8 25280 receiver child=3 sock=1 @ 127.0.0.1:5060
9 25282 fifo server
10 25312 timer
11 25314 tcp receiver
12 25316 tcp receiver
13 25318 tcp receiver
14 25320 tcp receiver
15 25323 tcp main process

You can continuously monitor SER's behavior with:

# serctl moni
[cycle #: 1; if constant make sure server lives and fifo is on]
Server: Sip EXpress router (0.8.11 (i386/linux))
Now: Tue Nov 25 11:32:51 2003
Up Since: Tue Nov 25 11:30:57 2003
Up time: 114 [sec]

Transaction Statistics
Current: 0 (0 waiting) Total: 0 (0 local)
Replied localy: 0
Completion status 6xx: 0, 5xx: 0, 4xx: 0, 3xx: 0,2xx: 0

Stateless Server Statistics
200: 0 202: 0 2xx: 0
300: 0 301: 0 302: 0 3xx: 0
400: 0 401: 0 403: 0 404: 0 407: 0 408: 0 483: 0 4xx: 0
500: 0 5xx: 0
6xx: 0
xxx: 0
failures: 0

UsrLoc Stats
Domain Registered Expired
'aliases' 15008 0
'location' 1 0

The cycle number and current time should update every few seconds.

VII - MODERATELY COMPLEX SAMPLE

N.B. the following is an example only for a hypothetical machine named proxy.bigu.edu at ip address 10.1.2.3 running on a RedHat Linux server

# ------------- version 0.8.11-0
# ------------- Initial global variables

debug=3
fork=yes
log_stderror=no

listen=10.1.2.3
listen=127.0.0.1

# hostname matching an alias will satisfy the condition uri==myself".
alias=bigu.edu
alias=10.1.2.3

# dns - Uses dns to check if it is necessary to add a "received=" field
# to a via. Default is no.
# rev_dns - Same as dns but use reverse DNS.

dns=no
rev_dns=no

port=5060
children=4

# check_via - Turn on or off Via host checking when forwarding replies.
# Default is no. arcane. looks for discrepancy between name and
# ip address when forwarding replies.

check_via=yes

# syn_branch - Shall the server use stateful synonym branches? It is
# faster but not reboot-safe. Default is yes.

syn_branch=yes

# memlog - Debugging level for final memory statistics report. Default
# is L_DBG -- memory statistics are dumped only if debug is set high.

memlog=3

# sip_warning - Should replies include extensive warnings? By default
# yes, it is good for trouble-shooting.

sip_warning=yes

# fifo - FIFO special file pathname

fifo="/tmp/ser_fifo"

# server_signature - Should locally-generated messages include server's
# signature? By default yes, it is good for trouble-shooting.

server_signature=yes

# reply_to_via - A hint to reply modules whether they should send reply
# to IP advertised in Via. Turned off by default, which means that
# replies are sent to IP address from which requests came.

reply_to_via=no

# user | uid - uid to be used by the server. 99 = nobody.

uid="nobody"

# group | gid - gid to be used by the server. 99 = nobody.

gid="nobody"

# mhomed -- enable calculation of outbound interface; useful on
# multihomed servers.

mhomed=0

# ------------- external 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/usrloc.so"
loadmodule "/usr/local/lib/ser/modules/registrar.so"
loadmodule "/usr/local/lib/ser/modules/auth.so"
loadmodule "/usr/local/lib/ser/modules/auth_db.so"
loadmodule "/usr/local/lib/ser/modules/acc.so"
loadmodule "/usr/local/lib/ser/modules/exec.so"
loadmodule "/usr/local/lib/ser/modules/group.so"
loadmodule "/usr/local/lib/ser/modules/print.so"
loadmodule "/usr/local/lib/ser/modules/textops.so"
loadmodule "/usr/local/lib/ser/modules/uri.so"

# ------------- tm parameters

modparam("tm", "fr_timer", 12)
modparam("tm", "fr_inv_timer", 24)

# ------------- rr parameters

# set ";lr" tag to “;lr=true”
modparam("rr", "enable_full_lr", 1)

# ------------- accounting parameters

modparam("acc", "log_missed_flag", 3)
modparam("acc", "log_level", 1)
modparam("acc", "log_flag", 1)

# ------------- usrloc parameters

# 2 enables write-back to persistent mysql storage for speed
# disable=0, write-through=1
modparam("usrloc", "db_mode", 2)

# minimize write back window - default is 60 seconds
modparam("usrloc", "timer_interval", 10)

# database location
modparam("usrloc", "db_url", "sql://ser:>password>@localhost/ser")

# ------------- auth parameters

# database location
modparam("auth_db", "db_url", "sql://ser:>password>@localhost/ser")

# allows clear text passwords in the mysql database
modparam("auth_db", "calculate_ha1", yes)

# name of password column in mysql database
modparam("auth_db", "password_column", "password")

# ------------- routing logic
route {

  # ------------- routine checks

  # stop forwarding at 10 hops to prevent infinite loops
  if (!mf_process_maxfwd_header("10")) {
    log(1, "LOG: Too many hops\n");
    sl_send_reply("483", "Too many hops");
    break;
  };

  # prevents private ip space from being used
  if (search("^(Contact|m): .*@(192\.168\.|10\.|172\.16)")) {
    if (method=="REGISTER") {
      log(1, "LOG: Someone trying to register from private IP\n");
      sl_send_reply("479", "Please don't use private IP addresses" );
      break;
    };
  };

  # separate the destination r-uri from the set of proxies that must be traversed
  loose_route();

  # if the host portion of the request uri is not local, send it directly
  # to route processing.
  if (!(uri==myself)) {
    route(2);
    break;
  };

  # All REGISTER attempts are processed and must always be authenticated
  if (method=="REGISTER") {

    # make sure that users don't register infinite loops
    if (search("^(Contact|m): .*@(10\.1\.2\.3|(proxy\.)?bigu\.edu)")) {
      log(1, "LOG: alert: someone trying to set aor==contact\n");
      sl_send_reply("476", "No Server Address in Contacts Allowed" );
      break;
    };

    # challenge/response
    if (!www_authorize("bigu.edu", "subscriber")) {
      www_challenge("bigu.edu", "0");
      break;
    };

    # only registered users are allowed
    if (!is_user("replicator") & !check_to()) {
      log(1, "LOG: unregistered user registration attempt\n");
      sl_send_reply("403", "Only registered users are allowed");
      break;
    };

    # it is an authenticated request, update Contact database now
    if (!save("location")) {
      sl_reply_error();
    };
    break;
  };

  # process traffic local to BigU and the PSTN
  # Find the canonical username
  lookup("aliases");

  # check domain again, if it is not still local after the alias
  # table lookup, just send it on its way. We do not authenticate
  # traffic we forward
  if (!(uri=~"^sip:(.+@)?(10\.1\.2\.3|(proxy\.)?bigu\.edu)([:;\?].*)?$")) {
    route(5);
    break;
  };

  # now check for destinations through the gateway. 911 and 9911
  # are always sent to the gateway. The assumption is that other all
  # numeric usernames between 5 and 20 digits are really pstn numbers
  # and so they are routed to the gateway
  if ( (uri=~"^sip:911@.*") | (uri=~"^sip:9911@.*") | (uri=~"sip:[0-9]{5,20}@.*") ) {
    route(3);
    break;
  };

  # does the user wish redirection on no availability? (i.e., is he
  # in the voicemail (ser->grp) group?)
  if (is_user_in("Request-URI", "voicemail")) {
    t_on_failure("4");
    setflag(4);
  };

  # handle local SIP destinations not found in usrloc db

  # mostly offline or non-existent users
  if (!lookup("location")) {
    route(4);
    break;
  };

  # check whether some inventive user has uploaded gateway
  # contacts to usrloc to bypass authorization logic
  if (uri=~"@10\.1\.2\.5([;:].*)*" ) {
    log(1, "LOG: Gateway address in UsrLoc\n");
    route(3);
    break;
  };

  # this flag is used with the acc module to report missed calls
  # to syslog.
  setflag(3);

  # do it (words to live by)
  append_hf("P-hint: USRLOC\r\n");
  if (!t_relay()) {
    sl_reply_error();
    break;
  };

} /* end of initial routing logic */


# ------------- process traffic leaving BigU for Internet

route[2] {

  # outbound requests are allowed only for registered BigU users
  if (!(src_ip==10.1.2.3) &
    !(proxy_authorize("bigu.edu", "subscriber"))) {

    # ACK and CANCEL have no security mechanisms so they are just
    # noted
    if (method=="ACK" | method=="BYE") {
      log(1, "LOG: failed outbound authentication for ACK granted\n");
    } else if (method=="CANCEL") {
      log(1, "LOG: failed outbound authentication for CANCEL granted\n");
    } else {
      proxy_challenge("bigu.edu", "0");
      break;
    };
  };

  # to maintain credibility of our proxy, we check From in INVITEs
  if (!src_ip==10.1.2.3 & method=="INVITE" & !check_from()) {
    log(1, "LOG: Spoofed from attempt\n");
    sl_send_reply("403", "Use From=id next time");
    break;
  };

  append_hf("P-hint: OUTBOUND ON INTERNET\r\n");
  if (!t_relay()) {
    sl_reply_error();
    break;
  };

}


# ------------- process traffic leaving Internet for PSTN

route[3] {

  # all calls through the gateway must be record routed to assure
  # acl acceptance on the gateway
  record_route();

  # send out emergency calls to pstn gateway immediately
  if ( (uri=~"^sip:911@.*") | (uri=~"^sip:9911@.*") ) {
    rewritehostport("10.1.2.5:5060");
    forward(uri:host, uri:port);
    break;
  };

  # five digit numeric addresses are internal freebies sent to the pbx
  # without authentication
  if (uri=~"^sip:[0-9]{5}@(10.1.2.3|(proxy\,)?\.bigu\.edu)") {
    rewritehostport("10.1.2.5:5060");
    forward(uri:host, uri:port);
    break;
  };

  # all numeric addresses beginning with 9 go to the pbx on the way
  # to the PSTN

  # first the caller needs to be authenticated
  if (uri=~"^sip:9[0-9]*@(10.1.2.3|(proxy\.)?bigu\.edu)") {
    if (!(src_ip==10.1.2.3 | method==ACK | method=="CANCEL" | method=="BYE")) {
      if (!proxy_authorize("bigu.edu", "subscriber")) {
        proxy_challenge( "bigu.edu","0");
        break;
      } else if (method=="INVITE" & !check_from()) {
        log(1, "LOG: Spoofed from attempt\n");
        sl_send_reply("403", "Use From=id next time");
        break;
      };
    };

    if (method=="INVITE") {

      # if the r-uri begins 91, does the authenticated user have
      # permission for long distance
      if (uri=~"sip:91[0-9]*@.*") {
        if (!is_user_in("credentials", "ld")) {
          sl_send_reply("403", "Local calls only");
          break;
        };
      };
    };

    # authenticated and authorized, now accounting is set
    setflag(1);
  };

  rewritehostport("10.1.2.5:5060");
  append_hf("P-hint: GATEWAY\r\n");
  if (!t_relay()) {
    sl_reply_error();
    break;
  };
}


# ------------- process calls for users offline

route[4] {

  if (!t_newtran()) {
    sl_reply_error();
  };

  if (!t_reply("404", "Not Found")) {
    sl_reply_error();
  };
  break;
}


# ------------- process aliased outbound traffic
# inbound requests that have been aliased to a non-BigU domain
# are not authenticated by BigU

route[5] {

  append_hf("P-hint: ALIASED-OUTBOUND\r\n");
  if (!t_relay()) {
    sl_reply_error();
    break;
  };
}


# ------------- CC-Diversion to voicemail

failure_route[4] {

  append_branch("sip:80000@10.1.2.5");
  append_urihf("CC-Diversion: ", "\r\n");
  append_hf("P-hint: OFFLINE-VOICEMAIL\r\n");
  t_relay();
}