知行合一
全部博文(31)
分类: 系统运维
2009-07-28 21:41:56
在这一章中我们将学到如何使用几种数据库后端来对SIP请求进行鉴定并提供诸如位置和别名表等数据的持续性。最主要的是,我们将使用MySQL来做每一件事。这一章分为两个部分。第一个部分,我们将学到如何实现认证,第二部分我们将学到如何处理不同方向的通话。
这一章的最后,你将能够:
l 配置MySQL来对SIP设备进行认证
l 使用openserctl工具来实现基本的操作,如添加和删除用户
l 改变脚本openser.cfg来配置MySQL认证
l 实现订阅列表的连续性
l 实现位置列表的连续性
l 重启服务器但是不丢失位置记录
l 正确的处理inbound-to-inbound,inbound-to-outbound,outbound-to-inbound,和outbound-to-outbound会话
l 正确的处理CANCEL请求
现在,我们仍然将集中精力于SIP代理上。然而,我们将包含一个新的部件,数据库。OpenSER可以使用MySQL和PostgreSQL。在这本书中,我们选择的是MySQL。到目前为止,它是OpenSER使用最多的数据库。
以数据库为基础的认证是由AUTH_DB模块实现的。其他类型的认证,如radius和diameter可以分别使用AUTH_RADIUS和AUTH_DIAMETER来实现。AUTH_DB和诸如MySQL和PostgreSQL等数据库模块一起工作。AUTH_DB有一些参数没有在脚本中明确的声明。让我们看看AUTH_DB模块的默认参数。
由ATUH_DB模块会导出两个函数。
www_authorize(realm, table)
这个函数在REGISTER的认证中被使用,和RFC2617保持一致。
proxy_authorize(realm, table)
这个函数按照RFC2617对non-REGISTER请求的证书进行验证。如果验证成功,则该证书将被标记为已认证。
如果你的服务器是请求的终点,那么你必须使用www_authorize函数。而当请求的最终目的地不是你的服务器,那么则使用proxy_authorize函数,然后你要对请求进行前转,这时,服务器实际上是作为代理在工作。
www_authorize和proxy_authorize的不同使用之处就在于请求的终点是否是你的服务器(REGISTER)。
脚本应该对REGISTER和INVITE消息进行认证。在修改openser.cfg脚本之前,让我们先展示这是如何发生的。当OpenSER收到REGISTER消息时,它先检查其是否有Authorize头域。如果没有找到,它会请求UAC的证书并退出。
当UAC收到请求后,向其再次发送REGISTER消息,此时的消息中带有Authorize头域。
注册过程可以从下面的抓包中看到:
U 192.168.1.119:29040 -> 192.168.1.155:5060
REGISTER sip:192.168.1.155 SIP/2.0.
Via: SIP/2.0/UDP 192.168.1.119:29040;branch=z9hG4bK-d87543
Max-Forwards: 70.
Contact:
To: "1000"
From: "1000"
Call-ID:
e0739d571d287264NjhiZjM2N2UyMjhmNDViYTgzY2I4ODMxYTVlZTY0NDc..
CSeq: 1 REGISTER.
WWW-Authenticate: Digest realm="192.168.1.155", nonce="46263864b3abb
Server: OpenSER (
Content-Length: 0.
U 192.168.1.119:29040 -> 192.168.1.155:5060
REGISTER sip:192.168.1.155 SIP/2.0.
Via: SIP/2.0/UDP 192.168.1.119:29040;branch=z9hG4bK-d87543-da776d09bd6fcb65-1--d87543-;rport.
Max-Forwards: 70.
Contact:
To: "1000"
From: "1000"
Call-ID: e0739d571d287264NjhiZjM2N2UyMjhmNDViYTgzY2I4ODMxYTVlZTY0NDc..
CSeq: 2 REGISTER.
Expires: 3600.
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO.
User-Agent: X-Lite release
Content-Length: 0.
U 192.168.1.155:5060 -> 192.168.1.119:29040
SIP/2.0 401 Unauthorized.
Via: SIP/2.0/UDP 192.168.1.119:29040;branch=z9hG4bK-d87543
To: "1000"
From: "1000"
Call-ID: e0739d571d287264NjhiZjM2N2UyMjhmNDViYTgzY2I4ODMxYTVlZTY0NDc..
CSeq: 1 REGISTER.
WWW-Authenticate: Digest realm="192.168.1.155",nonce="46263864b3abb
Server: OpenSER (
Content-Length: 0.
U 192.168.1.119:29040 -> 192.168.1.155:5060
REGISTER sip:192.168.1.155 SIP/2.0.
Via: SIP/2.0/UDP 192.168.1.119:29040;branch=z9hG4bK-d87543-da776d09bd6fcb65-1--d87543-;rport.
Max-Forwards: 70.
Contact:
To: "1000"
From: "1000"
Call-ID: e0739d571d287264NjhiZjM2N2UyMjhmNDViYTgzY2I4ODMxYTVlZTY0NDc..
CSeq: 2 REGISTER.
Expires: 3600.
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY, MESSAGE, SUBSCRIBE, INFO.
User-Agent: X-Lite release
Authorization: Digest username="1000",realm="192.168.1.155",nonce="46263864b3abb
Content-Length: 0.
U 192.168.1.155:5060 -> 192.168.1.119:29040
SIP/2.0 200 OK.
Via: SIP/2.0/UDP 192.168.1.119:29040;branch=z9hG4bK-d87543-da776d09bd6fcb65-1--d87543-;rport=29040.
To: "1000"
From: "1000"
Call-ID: e0739d571d287264NjhiZjM2N2UyMjhmNDViYTgzY2I4ODMxYTVlZTY0NDc..
CSeq: 2 REGISTER.
Contact:
Server: OpenSER (
Content-Length: 0.
现在让我们看看这个过程在openser.cfg脚本中是如何写的吧:
if (method=="REGISTER") {
# Uncomment this if you want to use digest authentication
if (!www_authorize("", "subscriber")) {
www_challenge("", "0");
exit;
};
save("location");
exit;
}
在上面的过程中,第一次传的REGISTER包没有被www_authorize函数认证。然后,www_challenge语句被调用。它发送了“401 Unauthorized”包,这个包按照摘要认证方法(digest authentication scheme)包含了认证请求。UAC第二次传的REGISTER包添加了Authorize头域,然后,save(”location”)函数被调用用来保存AOR到MySQL的位置表。
相对的则是一通普通通话的INVITE认证过程。代理服务器总是会对第一个INVITE请求进行响应,使用的是”407
Proxy Authentication Required”消息。这个消息包含了Authorize头域,含有关于摘要认证的信息,如realm和nonce。一旦UAC收到这个消息,它就会立即发送一条新的INVITE消息。现在,Authorize中包含了可以使用MD5算法进行计算的摘要,包括username,password,realm,和nonce。如果在服务器上使用与之相同的参数进行计算的摘要和UAC传递的摘要相符时,用户得到认证。
我们用ngrep抓了一套INVITE认证过程的包。这个过程能够帮助你理解上面的图。为了避免列的过长,我们没有将SDP头的内容一起附上。
U 192.168.1.169:5060
-> 192.168.1.155:5060
INVITE
sip:1000@192.168.1.155 SIP/2.0.
Via: SIP/2.0/UDP
192.168.1.169;branch=z9hG4bKf45d977e65cf40e0.
From:
To:
Contact:
Supported: replaces.
Call-ID: 8acb7ed7fc
CSeq: 39392 INVITE.
User-Agent: TMS320V5000
TI5
Max-Forwards: 70.
Allow:
INVITE,ACK,CANCEL,BYE,NOTIFY,REFER,OPTIONS,INFO,SUBSCRIBE.
Content-Type:
application/sdp.
Content-Length: 386.
(sdp header striped
off).
U 192.168.1.155:5060
-> 192.168.1.169:5060
SIP/2.0 407 Proxy
Authentication Required.
Via: SIP/2.0/UDP 192.168.1.169;branch=z9hG4bKf45d977e65cf40e0.
From:
To:
Call-ID: 8acb7ed7fc
CSeq: 39392 INVITE.
Proxy-Authenticate:
Digest realm="192.168.1.155", nonce="4626420b4b162ef
Server: OpenSER (
Content-Length: 0.
U 192.168.1.169:5060
-> 192.168.1.155:5060
ACK
sip:1000@192.168.1.155 SIP/2.0.
Via: SIP/2.0/UDP
192.168.1.169;branch=z9hG4bKf45d977e65cf40e0.
From:
To:
Contact:
Call-ID: 8acb7ed7fc
CSeq: 39392 ACK.
User-Agent: TMS320V5000
TI5
Max-Forwards: 70.
Allow:
INVITE,ACK,CANCEL,BYE,NOTIFY,REFER,OPTIONS,INFO,SUBSCRIBE.
Content-Length: 0.
U 192.168.1.169:5060
-> 192.168.1.155:5060
INVITE
sip:1000@192.168.1.155 SIP/2.0.
Via: SIP/2.0/UDP
192.168.1.169;branch=z9hG4bKcdb4add5db72d493.
From:
To:
Contact:
Supported: replaces.
Proxy-Authorization:
Digest username="1001", realm="192.168.1.155", algorithm=MD5,
uri="sip:1000@192.168.1.155", nonce="4626420b4b162ef
Call-ID: 8acb7ed7fc
CSeq: 39393 INVITE.
User-Agent: TMS320V5000
TI5
Max-Forwards: 70.
Allow:
INVITE,ACK,CANCEL,BYE,NOTIFY,REFER,OPTIONS,INFO,SUBSCRIBE.
Content-Type:
application/sdp.
Content-Length: 386.
(sdp header striped off)
INVITE Code Snippet
In the code below, the
SIP proxy will challenge the user for
credentials on any
request different from REGISTER. We consume the credentials
after authentication, for security reasons, to avoid sending encrypted material
ahead.
if
(!proxy_authorize("","subscriber")) {
proxy_challenge("","0");
exit;
};
consume_credentials();
lookup("aliases");
if (!uri==myself) {
append_hf("P-hint:
outbound alias\r\n");
route(1);
};
# native SIP
destinations are handled using our USRLOC DB
if
(!lookup("location")) {
sl_send_reply("404",
"Not Found");
exit;
};
append_hf("P-hint:
usrloc applied\r\n");
};
route(1);
摘要认证的基础是RFC2617中的”HTTP
Basic and Digest Access Authentication”。我们这一章节的目的是介绍一个带有摘要认证的系统的要素。它不是对于SIP的所有可能的安全性问题的回答,但确实是一种可以保护在网络传输中的用户名和密码的好方法。
摘要方法是一中简单的请求响应机制(challenge-response mechanism)。使用nonce值向UA发出请求。一个有效的响应包含有所有参数的一个校验和(checksum)。因此,密码从不以简单的文本形式进行传输。
如果服务器没有收到带有有效Authorize头域的REGISTER或INVITE请求,那么它会返回一个带有WWW-Authenticate头域的”401
unauthorized”消息。这个头包含一个realm和一个nonce。
我们希望用户能够再试一次,但是要带上Authorize头域。该头要包含用户名,realm和nonce(由server提供),uri,一个32位的十六进制数,还有一种认证方法(此例中是MD5)。这种响应是用户使用一种特定的算法产生的校验和。
qop参数表明了用户应用到该消息上的保护的质量。如果出现了这个参数,那么它的值要是服务器支持的所有可选值之一。这些选择在WWW-Authenticate头域中表明。这些值会影响摘要的计算。之所以有这条指令,是为了和RFC2809的最小实现保持兼容。
你可以在两个函数中对该参数进行配置,分别是www_challenge(realm, qop)和proxy_challenge(realm, qop)。如果被配成1,那么服务器就会要求有qop参数。总是使用qop=1会帮助你避免’replay”攻击。然而,一些用户对于qop会不兼容。对于摘要认证的详细描述见RFC2617。