Openserctl工具是安装在/usr/sbin上的shell脚本。被用来使用命令行的方式来对OpenSER进行管理。可以用来进行:
l 启动,终止,重启OpenSER
l 展示,授权,撤销ACLs
l 添加,删除,列出别名
l 添加,删除,配置AVP
l 管理LCR(low cost routes)
l 管理RPID
l 添加,删除,列出订阅者
l 添加,删除,展示usrloc表“in-ram”
l 监控OpenSER
我们将在下面的章节中学习一些它的选项.下面是openserctl help命令的输出信息:
/etc/openser# openserctl help
database engine 'MYSQL' loaded
Control engine 'FIFO' loaded
/usr/sbin/openserctl 1.2 - $Revision: 1.3 $
Existing commands:
-- command 'start|stop|restart'
restart ............................ restart OpenSER
start .............................. start OpenSER
stop ............................... stop OpenSER
-- command 'acl' - manage access control lists (acl)
acl show [<username>] .............. show user membership
acl grant <username> <group> ....... grant user membership (*)
acl revoke <username> [<group>] .... grant user membership(s) (*)
-- command 'alias_db' - manage database aliases
alias_db show <alias> .............. show alias details
alias_db list <sip-id> ............. list aliases for uri
alias_db add <alias> <sip-id> ...... add an alias (*)
alias_db rm <alias> ................ remove an alias (*)
alias_db help ...................... help message
- <alias> must be an AoR (username@domain)"
- <sip-id> must be an AoR (username@domain)"
-- command 'avp' - manage AVPs
avp list [-T table] [-u <sip-id|uuid>]
[-a attribute] [-v value] [-t type] ... list AVPs
avp add [-T table] <sip-id|uuid>
<attribute> <type> <value> ............ add AVP (*)
avp rm [-T table] [-u <sip-id|uuid>]
[-a attribute] [-v value] [-t type] ... remove AVP (*)
avp help .................................. help message
- -T - table name
- -u - SIP id or unique id
- -a - AVP name
- -v - AVP value
- -t - AVP name and type (0 (str:str), 1 (str:int),
2 (int:str), 3 (int:int))
- <sip-id> must be an AoR (username@domain)
- <uuid> must be a string but not AoR
-- command 'db' - database operations
db exec <query> ..................... execute SQL query
db show <table> ..................... display table content
-- command 'lcr' - manage least cost routes (lcr)
* lcr *
* IP addresses must be entered in dotted quad format e.g. 1.2.3.4 *
* <uri_scheme> and <transport> must be entered in integer or text,*
* e.g. transport '2' is identical to transport 'tcp'. *
* scheme: 1=sip, 2=sips; transport: 1=udp, 2=tcp, 3=tls *
* Examples: lcr addgw_grp usa 1 *
* lcr addgw level3 1.2.3.4 5080 sip tcp 1 *
* lcr addroute +1 % 1 1 *
lcr show ....................................................................
............. show routes, gateways and groups
lcr reload ..................................................................
............. reload lcr gateways
lcr addgw_grp <grp_name> ....................................................
.............. add gateway group, autocreate grp_id
lcr addgw_grp <grp_name> <grp_id> ...........................................
............... add gateway group with grp_id
lcr rmgw_grp <grp_id> ......................................................
............... delete the gw_grp
lcr addgw <gw_name> <ip> <port> <scheme> <transport> <grp_id> ...............
............... add a gateway
lcr addgw <gw_name> <ip> <port> <scheme> <transport> <grp_id> <prefix> ......
............... add a gateway with prefix
lcr addgw <gw_name> <ip> <port> <scheme> <transport> <grp_id> <prefix> <strip>
............... add a gateway with prefix and strip
lcr rmgw <gw_name> .........................................................
............... delete a gateway
lcr addroute <prefix> <from> <grp_id> <prio> ................................
.............. add a route
lcr rmroute <prefix> <from> <grp_id> <prio> ................................
.............. delete a route
-- command 'rpid' - manage Remote-Party-ID (RPID)
rpid add <username> <rpid> ......... add rpid for a user (*)
rpid rm <username> ................. set rpid to NULL for a user (*)
rpid show <username> ............... show rpid of a user
-- command 'speeddial' - manage speed dials (short numbers)
speeddial show <speeddial-id> ....... show speeddial details
speeddial list <sip-id> ............. list speeddial for uri
speeddial add <sip-id> <sd-id> <new-uri> [<desc>] ...
........................... add a speedial (*)
speeddial rm <sip-id> <sd-id> ....... remove a speeddial (*)
speeddial help ...................... help message
- <speeddial-id>, <sd-id> must be an AoR (username@domain)
- <sip-id> must be an AoR (username@domain)
- <new-uri> must be a SIP AoR (sip:username@domain)
- <desc> a description for speeddial
-- command 'add|mail|passwd|rm' - manage 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
-- command 'cisco_restart' - restart CISCO phone (NOTIFY)
cisco_restart <uri> ................ restart phone configured for <uri>
-- command 'online' - dump online users from memory
online ............................. display online users
-- command 'monitor' - show internal status
monitor ............................ show server's internal status
-- command 'ping' - ping a SIP URI (OPTIONS)
ping <uri> ......................... ping <uri> with SIP OPTIONS
-- command 'ul|alias' - manage user location or aliases
ul show [<username>]................ show in-RAM online users
ul rm <username> [<contact URI>].... delete user's UsrLoc entries
ul add <username> <uri> ............ introduce a permanent UrLoc entry
ul add <username> <uri> <expires> .. introduce a temporary UrLoc entry
-- command 'fifo'
fifo ............................... send raw FIFO command
在版本1.1中,我们介绍一个叫做openserctlrc的资源文件。这个脚本可以在/etc/openser中找到。openserctl工具对其进行语法解析然后使用其配置数据库认证和一些进行沟通需要的参数。通常,它使用FIFO机制来向OpenSER守护进程发送命令。
| 安全起见,改变默认的访问数据库的用户名和密码是很重要的 |
To show the file, issue a command:
cat /etc/openser/openserctlrc
# $Id: openserctlrc,v 1.2 2006/07/05 19:37:20 miconda Exp $
#
# openser control tool resource file
#
# here you can set variables used in the openserctl
## your SIP domain
SIP_DOMAIN=voffice.com.br
## database type: MYSQL or PGSQL, by defaulte none is loaded
DBENGINE=MYSQL
## database host
DBHOST=localhost
## database name
DBNAME=openser
## database read/write user
DBRWUSER=openser
## database read only user
DBROUSER=openserro
## password for database read only user
DBROPW=openserro
## database super user
DBROOTUSER="root"
## type of aliases used: DB - database aliases; UL - usrloc aliases
## - default: none
ALIASES_TYPE="DB"
## control engine: FIFO or UNIXSOCK
## - default FIFO
CTLENGINE="FIFO"
## path to FIFO file
OSER_FIFO="FIFO"
## check ACL names; default on (1); off (0)
VERIFY_ACL=1
## ACL names - if VERIFY_ACL is set, only the ACL names from below list
## are accepted
ACL_GROUPS="local ld int voicemail free-pstn"
## verbose - debug purposes - default '0'
VERBOSE=1
## do (1) or don't (0) store plaintext passwords
## in the subscriber table - default '1'
# STORE_PLAINTEXT_PW=0
现在,让我们以一种实践的方式来实现认证。
步骤1:按照上面章节中描述的内容来修改openser.cfg文件。
步骤2:使用命令/etc/init.d/openser restart 来重启OpenSER。
步骤3:使用被openserctl使用的默认的参数来配置openserctlrc。
# $Id: openserctlrc 1827 2007-03-12 15:22:53Z bogdan_iancu $
#
# openser control tool resource file
#
# here you can set variables used in the openserctl
## your SIP domain
SIP_DOMAIN=voffice.com.br
## database type: MYSQL or PGSQL, by defaulte none is loaded
DBENGINE=MYSQL
## database host
DBHOST=localhost
## database name
DBNAME=openser
## database read/write user
DBRWUSER=openser
## database read only user
DBROUSER=openserro
## password for database read only user
DBROPW=openserro
## database super user
DBROOTUSER="root"
## type of aliases used: DB - database aliases; UL - usrloc aliases
## - default: none
ALIASES_TYPE="DB"
## control engine: FIFO or UNIXSOCK
## - default FIFO
CTLENGINE="FIFO"
## path to FIFO file
OSER_FIFO="/tmp/openser_fifo"
## check ACL names; default on (1); off (0)
VERIFY_ACL=1
## ACL names - if VERIFY_ACL is set, only the ACL names from below list
## are accepted
ACL_GROUPS="local ld int voicemail free-pstn"
## presence of serweb tables - default "no"
HAS_SERWEB="yes"
## verbose - debug purposes - default '0'
VERBOSE=1
## do (1) or don't (0) store plaintext passwords
## in the subscriber table - default '1'
STORE_PLAINTEXT_PW=1
步骤4:使用openserctl工具来配置两个用户帐户。
/sbin/openserctl add 1000 password [email protected]
/sbin/openserctl add 1001 password [email protected]
| 如果你碰到“复制关键字(Duplicate Keys)”的问题,请检查你是否 |
| 安装了SerWEB表。如果你已经装了,那么只要将HAS_SERWEB设 |
| 为“yes”即可。 |
| 当你被要求密码时,使用openserrw |
你可以使用openserctl的rm命令来删除用户,使用openserctl的passwd命令来修改密码。
步骤5:使用ngrep工具来观察SIP消息:
#ngrep -p -q -W byline port 5060 >register.pkt
步骤6:使用用户名和密码注册双方话机:
步骤7:验证话机是否注册上,使用下面的命令:
#openserctl ul show
步骤8:你可以使用下面的命令来验证哪些用户在线:
#openserctl online
步骤9:你可以使用下面的命令来ping一个用户:
#openserctl ping 1000
步骤10:使用ngrep工具来验证认证信息
步骤11:从一个用户向另外一个用户打通电话
步骤12:使用下面命令验证在register.pkt文件中的认证
#pg register.pkt
由SIP代理处理的通话可以被分为:
l 内域
l 带外内域
l 带内内域
l 带外到带外
让我们来描述一些目前我们脚本的一些问题:
问题#1:目前,我们没有检查从其他域过来的带外通话的标识。这就使得我们的服务器成为了比较开放的中继。因此,任何人都可以使用我们的服务器了隐藏他们的标识。
问题#2:我们的脚本不接受来自另一个域的来电
问题#3:一个用户可以使用另一个用户的证书来伪造INVITE请求中的FROM头域。
问题#4:一个用户可以使用另一个用户的证书来伪造REGISTER请求中的TO头域。
问题#5:此脚本不会准备对多域(multiple domains)进行管理。
到现在为止,我们已经使用uri==myself这一行指令来对请求进行了校验。然而,这条指令仅仅校验了本地名字和地址。如果我们需要管理多域,我们必须使用域模块和它的is_from_local()和is_uri_host_local()函数。
就像我之前说的那样,域模块导出了两个函数,这两个函数将在我们的脚本中使用。第一个是is_from_local(),用来校验FROM头域是否包含我们代理管理着的域。第二个函数是is_uri_host_local(),用来替代uri==myself指令。这些函数的优点是他们可以检查MySQL表中的域。然后你就可以在你的配置中处理多域了。
| 这个函数需要所有的被管理的域都要被插入数据库中 |
| 对于使用这个功能资源的用户来说,一个相当普遍的 |
| 错误就是在对话机进行注册之前,忘记将域插入到MySQL |
| 数据库。 |
为了简化我们的脚本,我们将创建几个替代路线。我们已经看到脚本可能会变得非常复杂和难懂。为了避免这种情况的发生,我们已经创建了一些和子函数类似的替代路线。使用替代路线,允许我们将代码分片以加强它的可读性。
注册请求(Register Requests)(route[2])
注册请求路线负责处理所有的注册请求。这段代码对用户进行认证并保存UAC的位置信息。
route[2] {
#
# -- Register request handler --
#
if (is_uri_host_local()) {
if (!www_authorize("", "subscriber")) {
www_challenge("", "1");
exit;
};
if (!check_to()) {
sl_send_reply("401", "Unauthorized");
exit;
};
save("location");
exit;
} else if {
sl_send_reply("401", "Unauthorized");
};
}
非注册请求(Non-Register Requests)(route[3])
非注册请求路线处理所有其他的请求。请求需要再次进行认证。我们已经决定把请求分成下面几个部分:
l 带内到带内(route[10])
l 带内到带外(route[11])
l 带外到带内(route[12])
l 带外到带外(route[13])
通常,你会允许带认证的带内到带内的请求。这时比较普通的情况。带内到带外和带外到带内被用来处理内域请求。大多数的VoIP服务提供商不允许内域之间的交互,因为这回减少其潜在收入。带外到带外更是不会被允许的。大多数情况下,这种交互被认为是一种安全漏洞。
route[3] {
#
# -- non-register requests handler --
#
# Verify the source (FROM)
if (is_from_local()){
# From an internal domain -> check the credentials and the FROM
if (!proxy_authorize("","subscriber")) {
proxy_challenge("","1");
exit;
} else if (!check_from()) {
sl_send_reply("403", "Forbidden, use From=ID");
exit;
};
consume_credentials();
# Verify aliases
lookup("aliases");
# Verify the destination (URI)
if (is_uri_host_local()) {
# -- Inbound to Inbound
route(10);
} else {
# -- Inbound to outbound
route(11);
};
} else {
#Verify aliases, if found replace R-URI.
lookup("aliases"); # Verify the destination (URI)
if (is_uri_host_local()) {
#-- Outbound to inbound
route(12);
} else {
# -- Outbound to outbound
route(13);
};
};
}
管理我们域中发起的通话(Managing Calls Coming from Our Domain)
我们的脚本openser.cfg现在使用源地址和目的地址来区分通话。使用函数if(is_uri_host_local())来决定目的地,使用if(is_from_local())来决定源地址。
对于带内发起的通话,我们会首先检查标识并删除其证书以避免发送之。我们在检查目的地和前转请求前会对定义的别名进行解析。
如果通话目的地是我们管理域中之一(使用is_uri_host_local()检查),我们将其送至route(10)否则如果是一个外部域则将其送至route(11)。
带内到带内——route[10](Inbound-to-Inbound------route[10])
带内目的地会被用户位置数据库处理。
route[10] {
#from an internal domain -> inbound
#Native SIP destinations are handled using the location table
append_hf("P-hint: inbound->inbound \r\n");
if (!lookup("location")) {
sl_send_reply("404", "Not Found");
exit;
};
route(1);
}
带内到带外——route[11](Inbound-to-outbound-----route[11])
我们将使用DNS搜索来将通话路由到外部的目的地.
route[11] {
# from an internal domain -> outbound
# Simply route the call outbound using DNS search
append_hf("P-hint: inbound->outbound \r\n");
route(1);
}
带外到带内——route[12](Outbound-to-Inbound------route[12])
我们允许通话从外部域到我们自己的话机。这个配置会让许多垃圾信息传到我们的话机上,但是实际情况却不是这样。我相信这样做的收益远大于风险。以后到底会怎样,谁也不知道。对于我来说比较合理的是,开放的接收通话就应该像开放的接收传统通话,开放的接收emails一样。
route[12] {
# From an external domain -> inbound
# Verify aliases, if found replace R-URI.
lookup("aliases");
if (!lookup("location")) {
sl_send_reply("404", "Not Found");
exit;
};
route(1);
}
带外到带外——route[13](Outbound-to-Outbound------route[13])
我们不想做一个开放的中继器来对外部消息进行中继。如果下面的配置没有进行,那么其他人就能够使用我们的代理来匿名的路由通话。
route[13] {
#From an external domain outbound
#we are not accepting these calls
sl_send_reply("403", "Forbidden");
exit;
}
当操作SIP代理时,你应该确认的是一个有效的帐户不会被一个没有进行认证的用户所使用。Check_to()和check_from()函数被用来将SIP用户通认证用户做映射。SIP用户在FROM和TO头域中,而auth用户仅仅用来进行认证(Authorize头域)并有自己的密码。在当前的例子中,此函数检查SIP用户同auth用户是否相同。这就能够避免一个用户使用另一个用户的认证信息。这些函数通过URI_DB模块进行使能。
有些情况下,你想要允许一个用户拥有多个地址,譬如和一个主地址相关的电话号码。你可以使用别名来实现这个目的。
为了添加别名,使用下面命令:
#openserctl alias add [email protected] sip:[email protected]
database engine 'MYSQL' loaded
Control engine 'FIFO' loaded
MySql password for user 'openser@localhost':
lookup("aliases");
函数lookup(“aliases”)检查数据库中的别名表,如果一个注册者被找到,就将其别名翻译成正规的地址(订阅列表中的地址)。这个特性还能够被用来将DIDs重定向到最终用户。还有一个Alias_db模块。它不是从内存而是数据库中直接对别名进行搜索。牺牲了一点性能的好处就是,它能够简化数据库中别名的直接准备。
按照RFC3261,cancel请求消息和INVITE请求按照同一种方式进行路由。下面的脚本检查CANCEL请求是否与现存的事务相匹配,并且关注所有的必要的路由。有时候,我们需要进行一些与现存事务相关的重传。在这种情况下,函数t_check_trans()将处理之并退出脚本。
#CANCEL processing
if (is_method("CANCEL"))
{
if (t_check_trans())
t_relay();
exit;
}
t_check_trans();
# ------------------ module loading ----------------------------------
#set module path
mpath="//lib/openser/modules/"
# Uncomment this if you want to use SQL database
loadmodule "mysql.so"
loadmodule "sl.so"
loadmodule "tm.so"
loadmodule "rr.so"
loadmodule "maxfwd.so"
loadmodule "usrloc.so"
loadmodule "registrar.so"
loadmodule "textops.so"
loadmodule "mi_fifo.so"
loadmodule "uri.so"
loadmodule "uri_db.so"
loadmodule "domain.so"
# Uncomment this if you want digest authentication
# mysql.so must be loaded !
loadmodule "auth.so"
loadmodule "auth_db.so"
# ----------------- setting module-specific parameters ---------------
# -- mi_fifo params --
modparam("mi_fifo", "fifo_name", "/tmp/openser_fifo")
# -- usrloc params --
#modparam("usrloc", "db_mode", 0)
# Uncomment this if you want to use SQL database
# for persistent storage and comment the previous line
modparam("usrloc", "db_mode", 2)
# -- auth params --
# Uncomment if you are using auth module
#
modparam("auth_db", "calculate_ha1", 0)
#
# If you set "calculate_ha1" parameter to yes,
# uncomment also the following parameter)
#
#modparam("auth_db", "password_column", "password")
# -- rr params --
# add value to ;lr param to make some broken UAs happy
modparam("rr", "enable_full_lr", 1)
# ------------------------- request routing logic -------------------
# main routing logic route{
# initial sanity checks -- messages with
# max_forwards==0, or excessively long requests
if (!mf_process_maxfwd_header("10")) {
sl_send_reply("483","Too Many Hops");
exit;
}; if (msg:len >= 2048 ) {
sl_send_reply("513", "Message too big");
exit;
};
# 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 (!method=="REGISTER")
record_route();
# 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");
route(1);
}; #CANCEL processing
if (is_method("CANCEL")) {
if (t_check_trans()) t_relay();
exit;
}
if (method=="REGISTER") {
route(2);
} else {
route(3);
};
} route[1] {
# send it out now; use stateful forwarding as it works reliably
# even for UDP2TCP
if (!t_relay()) {
sl_reply_error();
};
exit;
}
route[2] {
#
# -- Register request handler --
#
if (is_uri_host_local()) {
if (!www_authorize("", "subscriber")) {
www_challenge("", "1");
exit;
};
if (!check_to()) {
sl_send_reply("40=3", "Forbidden");
exit;
}; save("location");
exit;
} else if {
sl_send_reply("403", "Forbidden");
};
} route[3] {
#
# -- INVITE request handler --
#
if (is_from_local()){
# From an internal domain -> check the credentials and the FROM
if (!proxy_authorize("","subscriber")) {
proxy_challenge("","1");
exit;
} else if (!check_from()) {
sl_send_reply("403", "Forbidden, use From=ID");
exit;
};
consume_credentials();
# Verify aliases
lookup("aliases");
if (is_uri_host_local()) {
# -- Inbound to Inbound route(10);
} else {
# -- Inbound to outbound
route(11);
};
} else {
# From an external domain -> do not check credentials
#Verify aliases, if found replace R-URI.
lookup("aliases");
if (is_uri_host_local()) {
#-- Outbound to inbound
route(12);
} else {
# -- Outbound to outbound
route(13);
};
};
} route[10] {
#from an internal domain -> inbound
#Native SIP destinations are handled using the location table
append_hf("P-hint: inbound->inbound \r\n");
if (!lookup("location")) {
sl_send_reply("404", "Not Found");
exit;
};
route(1);
}
route[11] {
# from an internal domain -> outbound
# Simply route the call outbound using DNS search
append_hf("P-hint: inbound->outbound \r\n");
route(1);
}
route[12] {
# From an external domain -> inbound
# Verify aliases, if found replace R-URI.
lookup("aliases");
if (!lookup("location")) {
sl_send_reply("404", "Not Found");
exit;
};
route(1);
}
route[13] {
#From an external domain outbound
#we are not accepting these calls
append_hf("P-hint: outbound->inbound \r\n");
sl_send_reply("403", "Forbidden");
exit;
}
步骤1:试着用新的配置注册你的话机。你会注意到在你的话机注册时出现了一个错误。
步骤2:上面的配置目前使用的是domain.so模块。现在,为了认证,域必须在MySQL数据库中的域表中。
为了增加一个域,使用openserctl工具。
openserctl domain add your-ip-address
openserctl domain add your-domain
对于每一个域重复上面的过程。
步骤3:再次尝试注册话机。现在注册过程将正常。
步骤1:为订阅者1000添加别名
#openserctl alias add john@youripordomain sip:1000@youripordomain
database engine 'MYSQL' loaded
Control engine 'FIFO' loaded
MySql password for user 'openser@localhost':
| 使用openserrw作为密码 |
步骤2:从注册为1001的软电话打给John
通话完整么?为什么?
在这一章中,你已经学会了如何将MySQL整合进OpenSER中。现在,我们的脚本可以认证用户,检查TO和FROM头域,按照带内和带外来处理通话了。重要的是要牢记域必须被插进数据库中,因为要支持多域。如果你改变了你的域或IP地址,请记着更新你的数据库。