diff --git a/.gitignore b/.gitignore index 2081111..4f4dd06 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ -b2bua_simple -b2bua_radius -call_transfer -rfc8760 -stir_shaken +/b2bua_simple +/b2bua_radius +/call_transfer +/rfc8760 +/stir_shaken *~ diff --git a/GNUmakefile b/GNUmakefile index f18bd12..9a4987e 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -24,3 +24,4 @@ clean: test: go test ./sippy + go test ./cmd/b2bua_radius diff --git a/README.md b/README.md index 7b26fa5..a893c5d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,3 @@ - All available CPU cores are utilized. - Runs faster (approx 4x per one CPU core). - The configuration object is not global thus allowing to run several B2BUA instances inside one executable. - -## Known problems: - -- No RADIUS support. diff --git a/cmd/b2bua_radius/b2broute.go b/cmd/b2bua_radius/b2broute.go index 1a5fc38..4504483 100644 --- a/cmd/b2bua_radius/b2broute.go +++ b/cmd/b2bua_radius/b2broute.go @@ -1,5 +1,5 @@ // Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved. -// Copyright (c) 2006-2016 Sippy Software, Inc. All rights reserved. +// Copyright (c) 2006-2024 Sippy Software, Inc. All rights reserved. // // All rights reserved. // @@ -69,20 +69,8 @@ type B2BRoute struct { rtpp bool outbound_proxy *sippy_net.HostPort rnum int + params map[string]string } -/* -from sippy.SipHeader import SipHeader -from sippy.SipConf import SipConf - -from urllib import unquote -from socket import getaddrinfo, SOCK_STREAM, AF_INET, AF_INET6 - -class B2BRoute(object): - rnum = nil - addrinfo = nil - params = nil - ainfo = nil -*/ func NewB2BRoute(sroute string, global_config sippy_conf.Config) (*B2BRoute, error) { var hostport []string @@ -96,6 +84,9 @@ func NewB2BRoute(sroute string, global_config sippy_conf.Config) (*B2BRoute, err cli_set : false, extra_headers : []sippy_header.SipHeader{}, rtpp : true, + params : make(map[string]string), + credit_time : -1, + expires : -1, } route := strings.Split(sroute, ";") if strings.IndexRune(route[0], '@') != -1 { @@ -141,27 +132,31 @@ func NewB2BRoute(sroute string, global_config sippy_conf.Config) (*B2BRoute, err } self.ainfo = append(self.ainfo, &ainfo_item{ ip, port.String() }) } - //self.params = []string{} for _, x := range route[1:] { av := strings.SplitN(x, "=", 2) - switch av[0] { + if len(av) != 2 { + continue + } + a := av[0] + s_v := av[1] + switch a { case "credit-time": - v, err := strconv.Atoi(av[1]) + v, err := strconv.Atoi(s_v) if err != nil { - return nil, errors.New("Error parsing credit-time '" + av[1] + "': " + err.Error()) + return nil, errors.New("Error parsing credit-time '" + s_v + "': " + err.Error()) } if v < 0 { v = 0 } self.credit_time = time.Duration(v * int(time.Second)) self.crt_set = true case "expires": - v, err := strconv.Atoi(av[1]) + v, err := strconv.Atoi(s_v) if err != nil { - return nil, errors.New("Error parsing the expires '" + av[1] + "': " + err.Error()) + return nil, errors.New("Error parsing the expires '" + s_v + "': " + err.Error()) } if v < 0 { v = 0 } self.expires = time.Duration(v * int(time.Second)) case "hs_scodes": - for _, s := range strings.Split(av[1], ",") { + for _, s := range strings.Split(s_v, ",") { s = strings.TrimSpace(s) if s == "" { continue } scode, err := strconv.Atoi(s) @@ -171,54 +166,53 @@ func NewB2BRoute(sroute string, global_config sippy_conf.Config) (*B2BRoute, err self.huntstop_scodes = append(self.huntstop_scodes, scode) } case "np_expires": - v, err := strconv.Atoi(av[1]) + v, err := strconv.Atoi(s_v) if err != nil { - return nil, errors.New("Error parsing the no_progress_expires '" + av[1] + "': " + err.Error()) + return nil, errors.New("Error parsing the no_progress_expires '" + s_v + "': " + err.Error()) } if v < 0 { v = 0 } self.no_progress_expires = time.Duration(v * int(time.Second)) case "forward_on_fail": self.forward_on_fail = true case "auth": - tmp := strings.SplitN(av[1], ":", 2) + tmp := strings.SplitN(s_v, ":", 2) if len(tmp) != 2 { - return nil, errors.New("Error parsing the auth (no colon) '" + av[1] + "': " + err.Error()) + return nil, errors.New("Error parsing the auth (no colon) '" + s_v + "': " + err.Error()) } self.user, self.passw = tmp[0], tmp[1] case "cli": - self.cli = av[1] + self.cli = s_v self.cli_set = true case "cnam": - self.caller_name, err = url.QueryUnescape(av[1]) + self.caller_name, err = url.QueryUnescape(s_v) if err != nil { - return nil, errors.New("Error parsing the cnam '" + av[1] + "': " + err.Error()) + return nil, errors.New("Error parsing the cnam '" + s_v + "': " + err.Error()) } case "ash": - var v string var ash []sippy_header.SipHeader - v, err = url.QueryUnescape(av[1]) + s_v, err = url.QueryUnescape(s_v) if err == nil { - ash, err = sippy.ParseSipHeader(v) + ash, err = sippy.ParseSipHeader(s_v) } if err != nil { - return nil, errors.New("Error parsing the ash '" + av[1] + "': " + err.Error()) + return nil, errors.New("Error parsing the ash '" + s_v + "': " + err.Error()) } self.extra_headers = append(self.extra_headers, ash...) case "rtpp": - v, err := strconv.Atoi(av[1]) + v, err := strconv.Atoi(s_v) if err != nil { - return nil, errors.New("Error parsing the rtpp '" + av[1] + "': " + err.Error()) + return nil, errors.New("Error parsing the rtpp '" + s_v + "': " + err.Error()) } self.rtpp = (v != 0) case "op": - host_port := strings.SplitN(av[1], ":", 2) + host_port := strings.SplitN(s_v, ":", 2) if len(host_port) == 1 { - self.outbound_proxy = sippy_net.NewHostPort(av[1], "5060") + self.outbound_proxy = sippy_net.NewHostPort(s_v, "5060") } else { self.outbound_proxy = sippy_net.NewHostPort(host_port[0], host_port[1]) } - //default: - // self.params[a] = v + default: + self.params[a] = s_v } } return self, nil @@ -235,10 +229,6 @@ func (self *B2BRoute) customize(rnum int, default_cld, default_cli string, defau if ! self.crt_set { self.credit_time = default_credit_time } - //if self.params.has_key("gt") { - // timeout, skip = self.params["gt"].split(",", 1) - // self.params["group_timeout"] = (int(timeout), rnum + int(skip)) - //} self.extra_headers = append(self.extra_headers, pass_headers...) if max_credit_time != 0 { if self.credit_time == 0 || self.credit_time > max_credit_time { diff --git a/cmd/b2bua_radius/call_controller.go b/cmd/b2bua_radius/call_controller.go index 76a6287..317dfe0 100644 --- a/cmd/b2bua_radius/call_controller.go +++ b/cmd/b2bua_radius/call_controller.go @@ -1,6 +1,6 @@ // // Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved. -// Copyright (c) 2006-2014 Sippy Software, Inc. All rights reserved. +// Copyright (c) 2006-2024 Sippy Software, Inc. All rights reserved. // // All rights reserved. // @@ -27,9 +27,12 @@ package main import ( + "crypto/md5" "fmt" + "strconv" "strings" "sync" + "time" "github.com/sippy/go-b2bua/sippy" "github.com/sippy/go-b2bua/sippy/headers" @@ -40,6 +43,7 @@ import ( type callController struct { id int64 + username string uaA sippy_types.UA uaO sippy_types.UA global_config *myConfigParser @@ -54,26 +58,19 @@ type callController struct { cli string cld string caller_name string + challenge *sippy_header.SipWWWAuthenticate rtp_proxy_session *sippy.Rtp_proxy_session eTry *sippy.CCEventTry huntstop_scodes []int - acctA *fakeAccounting + acctA Accounting + acctO *RadiusAccounting sip_tm sippy_types.SipTransactionManager proxied bool sdp_session *sippy.SdpSession cmap *CallMap + auth_proc Cancellable } -/* -class CallController(object): - cld = nil - acctA = nil - acctO = nil - global_config = nil - rtp_proxy_session = nil - huntstop_scodes = nil - auth_proc = nil - challenge = nil -*/ + func NewCallController(id int64, remote_ip *sippy_net.MyAddress, source *sippy_net.HostPort, global_config *myConfigParser, pass_headers []sippy_header.SipHeader, sip_tm sippy_types.SipTransactionManager, cguid *sippy_header.SipCiscoGUID, cmap *CallMap) *callController { @@ -94,7 +91,7 @@ func NewCallController(id int64, remote_ip *sippy_net.MyAddress, source *sippy_n cmap : cmap, } self.uaA = sippy.NewUA(sip_tm, global_config, nil, self, self.lock, nil) - self.uaA.SetKaInterval(self.global_config.keepalive_ans) + self.uaA.SetKaInterval(self.global_config.Keepalive_ans_dur) self.uaA.SetLocalUA(sippy_header.NewSipUserAgent(self.global_config.GetMyUAName())) self.uaA.SetConnCb(self.aConn) self.uaA.SetDiscCb(self.aDisc) @@ -115,33 +112,42 @@ func (self *callController) RecvEvent(event sippy_types.CCEvent, ua sippy_types. self.cId = ev_try.GetSipCallId() self.cli = ev_try.GetCLI() self.cld = ev_try.GetCLD() - //, body, auth, self.caller_name = ev_try.GetCallerName() if self.cld == "" { self.uaA.RecvEvent(sippy.NewCCEventFail(500, "Internal Server Error (1)", event.GetRtime(), "")) self.state = CCStateDead return } -/* - if body != nil && self.global_config.has_key('_allowed_pts') { - try: - body.parse() - except: - self.uaA.RecvEvent(CCEventFail((400, "Malformed SDP Body"), rtime = event.rtime)) + body := ev_try.GetBody() + if body != nil && len(self.global_config.Allowed_pts) > 0 { + sdp_body, err := body.GetSdp() + if err != nil { + self.uaA.RecvEvent(sippy.NewCCEventFail(400, "Malformed SDP Body", event.GetRtime(), "")) self.state = CCStateDead return - allowed_pts = self.global_config['_allowed_pts'] - mbody = body.content.sections[0].m_header - if mbody.transport.lower() == "rtp/avp" { - old_len = len(mbody.formats) - mbody.formats = [x for x in mbody.formats if x in allowed_pts] - if len(mbody.formats) == 0 { - self.uaA.RecvEvent(CCEventFail((488, "Not Acceptable Here"))) - self.state = CCStateDead - return - if old_len > len(mbody.formats) { - body.content.sections[0].optimize_a() -*/ + } + sections := sdp_body.GetSections() + if len(sections) > 0 { + mbody := sections[0].GetMHeader() + if strings.ToLower(mbody.GetTransport()) == "rtp/avp" { + formats := []string{} + old_len := len(mbody.GetFormats()) + for _, pt := range mbody.GetFormats() { + if _, ok := self.global_config.Allowed_pts_map[pt]; ok { + formats = append(formats, pt) + } + } + if len(formats) == 0 { + self.uaA.RecvEvent(sippy.NewCCEventFail(488, "Not Acceptable Here", event.GetRtime(), "")) + self.state = CCStateDead + return + } + if old_len > len(formats) { + sections[0].SetFormats(formats) + } + } + } + } if strings.HasPrefix(self.cld, "nat-") { self.cld = self.cld[4:] if ev_try.GetBody() != nil { @@ -149,12 +155,16 @@ func (self *callController) RecvEvent(event sippy_types.CCEvent, ua sippy_types. } event, _ = sippy.NewCCEventTry(self.cId, self.cli, self.cld, ev_try.GetBody(), ev_try.GetSipAuthorizationHF(), self.caller_name, nil, "") } -/* - if self.global_config.has_key('static_tr_in') { - self.cld = re_replace(self.global_config['static_tr_in'], self.cld) - event = sippy.NewCCEventTry(self.cId, self.cGUID, self.cli, self.cld, body, auth, self.caller_name) + if self.global_config.Static_tr_in != "" { + var err error + self.cld, err = re_replace(self.global_config.Static_tr_in, self.cld) + if err != nil { + self.uaA.RecvEvent(sippy.NewCCEventFail(500, "Internal Server Error (5)", event.GetRtime(), "")) + self.state = CCStateDead + return + } + event, _ = sippy.NewCCEventTry(self.cId, self.cli, self.cld, ev_try.GetBody(), ev_try.GetSipAuthorizationHF(), self.caller_name, nil, "") } -*/ if len(self.cmap.rtp_proxy_clients) > 0 { var err error self.rtp_proxy_session, err = sippy.NewRtp_proxy_session(self.global_config, self.cmap.rtp_proxy_clients, self.cId.CallId, "", "", self.global_config.B2bua_socket, /*notify_tag*/ fmt.Sprintf("r%%20%d", self.id), self.lock) @@ -168,18 +178,31 @@ func (self *callController) RecvEvent(event sippy_types.CCEvent, ua sippy_types. } self.eTry = ev_try self.state = CCStateWaitRoute - //if ! self.global_config['auth_enable'] { - //self.username = self.remote_ip - self.rDone() - //} else if auth == nil || auth.username == nil || len(auth.username) == 0 { - // self.username = self.remote_ip - // self.auth_proc = self.global_config['_radius_client'].do_auth(self.remote_ip, self.cli, self.cld, self.cGUID, \ - // self.cId, self.remote_ip, self.rDone) - //} else { - // self.username = auth.username - // self.auth_proc = self.global_config['_radius_client'].do_auth(auth.username, self.cli, self.cld, self.cGUID, - // self.cId, self.remote_ip, self.rDone, auth.realm, auth.nonce, auth.uri, auth.response) - //} + if ! self.global_config.Auth_enable { + self.username = self.remote_ip.String() + self.rDone_nolock(NewRadiusResult()) + return + } + var auth *sippy_header.SipAuthorizationBody + var err error + auth_hf := ev_try.GetSipAuthorizationHF() + if auth_hf != nil { + auth, err = auth_hf.GetBody() + if err != nil { + self.uaA.RecvEvent(sippy.NewCCEventFail(500, "Internal Server Error (6)", event.GetRtime(), "")) + self.state = CCStateDead + return + } + } + if auth == nil || auth.GetUsername() == "" { + self.username = self.remote_ip.String() + self.auth_proc = self.cmap.radius_auth.Do_auth(self.remote_ip.String(), self.cli, self.cld, self.cGUID, + self.cId, self.remote_ip, self.rDone, "", "", "", "") + } else { + self.username = auth.GetUsername() + self.auth_proc = self.cmap.radius_auth.Do_auth(auth.GetUsername(), self.cli, self.cld, self.cGUID, + self.cId, self.remote_ip, self.rDone, auth.GetRealm(), auth.GetNonce(), auth.GetUri(), auth.GetResponse()) + } return } if (self.state != CCStateARComplete && self.state != CCStateConnected && self.state != CCStateDisconnecting) || self.uaO == nil { @@ -213,68 +236,89 @@ func (self *callController) RecvEvent(event sippy_types.CCEvent, ua sippy_types. } } -func (self *callController) rDone(/*results*/) { -/* +func (self *callController) rDone(results *RadiusResult) { + self.lock.Lock() + defer self.lock.Unlock() + self.rDone_nolock(results) +} + +func (self *callController) rDone_nolock(results *RadiusResult) { // Check that we got necessary result from Radius - if len(results) != 2 || results[1] != 0: - if isinstance(self.uaA.state, UasStateTrying): - if self.challenge != nil: - event = CCEventFail((401, "Unauthorized")) - event.extra_header = self.challenge - else: - event = CCEventFail((403, "Auth Failed")) + if results == nil || results.Rcode != 0 { + if self.uaA.GetState() == sippy_types.UAS_STATE_TRYING { + var event sippy_types.CCEvent + if self.challenge != nil { + event = sippy.NewCCEventFail(401, "Unauthorized", nil, "") + event.AppendExtraHeader(self.challenge) + } else { + event = sippy.NewCCEventFail(403, "Auth Failed", nil, "") + } self.uaA.RecvEvent(event) self.state = CCStateDead + } return - if self.global_config['acct_enable']: - self.acctA = RadiusAccounting(self.global_config, "answer", \ - send_start = self.global_config['start_acct_enable'], lperiod = \ - self.global_config.getdefault('alive_acct_int', nil)) - self.acctA.ms_precision = self.global_config.getdefault('precise_acct', false) - self.acctA.setParams(self.username, self.cli, self.cld, self.cGUID, self.cId, self.remote_ip) - else: -*/ + } + if self.global_config.Acct_enable { + acctA := NewRadiusAccounting(self.global_config, "answer", self.cmap.radius_client) + acctA.SetParams(self.username, self.cli, self.cld, self.cGUID.StringBody(), self.cId.StringBody(), self.remote_ip.String(), "") + self.acctA = acctA + } else { self.acctA = NewFakeAccounting() + } // Check that uaA is still in a valid state, send acct stop if self.uaA.GetState() != sippy_types.UAS_STATE_TRYING { - //self.acctA.disc(self.uaA, time(), "caller") + rtime, _ := sippy_time.NewMonoTime() + self.acctA.Disc(self.uaA, rtime, "caller", 0) return } -/* - cli = [x[1][4:] for x in results[0] if x[0] == "h323-ivr-in" && x[1].startswith("CLI:")] - if len(cli) > 0: - self.cli = cli[0] - if len(self.cli) == 0: - self.cli = nil - caller_name = [x[1][5:] for x in results[0] if x[0] == "h323-ivr-in" && x[1].startswith("CNAM:")] - if len(caller_name) > 0: - self.caller_name = caller_name[0] - if len(self.caller_name) == 0: - self.caller_name = nil - credit_time = [x for x in results[0] if x[0] == "h323-credit-time"] - if len(credit_time) > 0: - credit_time = int(credit_time[0][1]) - else: - credit_time := time.Duration(0) - if ! self.global_config.has_key('_static_route'): - routing = [x for x in results[0] if x[0] == "h323-ivr-in" && x[1].startswith("Routing:")] - if len(routing) == 0: - self.uaA.RecvEvent(CCEventFail((500, "Internal Server Error (2)"))) + cli := "" + caller_name := "" + credit_time := time.Duration(0) + credit_time_found := false + for _, avp := range results.Avps { + if avp.name == "h323-ivr-in" { + if cli == "" && strings.HasPrefix(avp.value, "CLI:") { + cli = avp.value[4:] + } + if caller_name == "" && strings.HasPrefix(avp.value, "CNAM:") { + caller_name = avp.value[5:] + } + if ! credit_time_found { + credit_time_found = true + val, err := strconv.Atoi(avp.value) + if err == nil { + credit_time = time.Duration(val) * time.Second + } + } + } + } + routing := []*B2BRoute{} + + if self.cmap.static_route == nil { + for _, avp := range results.Avps { + if avp.name == "h323-ivr-in" && strings.HasPrefix(avp.value, "Routing:") { + b2br, err := NewB2BRoute(avp.value[8:], self.global_config) + if err == nil { + routing = append(routing, b2br) + } + } + } + if len(routing) == 0 { + self.uaA.RecvEvent(sippy.NewCCEventFail(500, "Internal Server Error (2)", nil, "")) self.state = CCStateDead return - routing = [B2BRoute(x[1][8:]) for x in routing] - else { -*/ - routing := []*B2BRoute{ self.cmap.static_route.getCopy() } -// } + } + } else { + routing = []*B2BRoute{ self.cmap.static_route.getCopy() } + } rnum := 0 for _, oroute := range routing { rnum += 1 oroute.customize(rnum, self.cld, self.cli, 0, self.pass_headers, 0) - //oroute.customize(rnum, self.cld, self.cli, credit_time, self.pass_headers, self.global_config.max_credit_time) - //if oroute.credit_time == 0 || oroute.expires == 0 { - // continue - //} + oroute.customize(rnum, self.cld, self.cli, credit_time, self.pass_headers, time.Duration(self.global_config.Max_credit_time) * time.Second) + if oroute.credit_time == 0 || oroute.expires == 0 { + continue + } self.routes = append(self.routes, oroute) //println "Got route:", oroute.hostport, oroute.cld } @@ -293,36 +337,59 @@ func (self *callController) placeOriginate(oroute *B2BRoute) { //cId, cGUID, cli, cld, body, auth, caller_name = self.eTry.getData() cld := oroute.cld self.huntstop_scodes = oroute.huntstop_scodes - //if self.global_config.has_key('static_tr_out') { - // cld = re_replace(self.global_config['static_tr_out'], cld) - //} + if self.global_config.Static_tr_out != "" { + var err error + cld, err = re_replace(self.global_config.Static_tr_out, cld) + if err != nil { + self.uaA.RecvEvent(sippy.NewCCEventFail(500, "Internal Server Error (7)", nil, "")) + self.state = CCStateDead + return + } + } var nh_address *sippy_net.HostPort + var host string if oroute.hostport == "sip-ua" { - //host = self.source[0] + host = self.source.Host.String() nh_address = self.source } else { - //host = oroute.hostonly + host = oroute.hostonly nh_address = oroute.getNHAddr(self.source) } - //if ! oroute.forward_on_fail && self.global_config['acct_enable'] { - // self.acctO = RadiusAccounting(self.global_config, "originate", - // send_start = self.global_config['start_acct_enable'], /*lperiod*/ - // self.global_config.getdefault('alive_acct_int', nil)) - // self.acctO.ms_precision = self.global_config.getdefault('precise_acct', false) - // self.acctO.setParams(oroute.params.get('bill-to', self.username), oroute.params.get('bill-cli', oroute.cli), \ - // oroute.params.get('bill-cld', cld), self.cGUID, self.cId, host) - //else { - // self.acctO = nil - //} + if ! oroute.forward_on_fail && self.global_config.Acct_enable { + self.acctO = NewRadiusAccounting(self.global_config, "originate", self.cmap.radius_client) + bill_to := self.username + if v, ok := oroute.params["bill-to"]; ok { + bill_to = v + } + cli := oroute.cli + if v, ok := oroute.params["bill-cli"]; ok { + cli = v + } + cld := oroute.cld + if v, ok := oroute.params["bill-cld"]; ok { + cld = v + } + self.acctO.SetParams(bill_to, cli, cld, self.cGUID.StringBody(), self.cId.StringBody(), host, "") + } else { + self.acctO = nil + } //self.acctA.credit_time = oroute.credit_time - //disc_handlers = [] - //if ! oroute.forward_on_fail && self.global_config['acct_enable'] { - // disc_handlers.append(self.acctO.disc) - //} self.uaO = sippy.NewUA(self.sip_tm, self.global_config, nh_address, self, self.lock, nil) - // oroute.user, oroute.passw, nh_address, oroute.credit_time, - // /*expire_time*/ oroute.expires, /*no_progress_time*/ oroute.no_progress_expires, /*extra_headers*/ oroute.extra_headers) - //self.uaO.SetConnCbs([]sippy_types.OnConnectListener{ self.oConn }) + self.uaO.SetUsername(oroute.user) + self.uaO.SetPassword(oroute.passw) + if oroute.credit_time > 0 { + self.uaO.SetCreditTime(oroute.credit_time) + } + self.uaO.SetConnCb(self.oConn) + if ! oroute.forward_on_fail && self.global_config.Acct_enable { + self.uaO.SetDiscCb(func(rtime *sippy_time.MonoTime, origin string, scode int, req sippy_types.SipRequest) { self.acctO.Disc(self.uaO, rtime, origin, scode) }) + self.uaO.SetFailCb(func(rtime *sippy_time.MonoTime, origin string, scode int) { self.acctO.Disc(self.uaO, rtime, origin, scode) }) + } + self.uaO.SetDeadCb(self.oDead) + if oroute.expires > 0 { + self.uaO.SetExpireTime(oroute.expires) + } + self.uaO.SetNoProgressTime(oroute.no_progress_expires) extra_headers := []sippy_header.SipHeader{ self.cGUID, self.cGUID.AsH323ConfId() } extra_headers = append(extra_headers, oroute.extra_headers...) self.uaO.SetExtraHeaders(extra_headers) @@ -341,29 +408,51 @@ func (self *callController) placeOriginate(oroute *B2BRoute) { } self.proxied = true } - self.uaO.SetKaInterval(self.global_config.keepalive_orig) - //if oroute.params.has_key('group_timeout') { - // timeout, skipto = oroute.params['group_timeout'] - // Timeout(self.group_expires, timeout, 1, skipto) - //} - //if self.global_config.getdefault('hide_call_id', false) { - // cId = SipCallId(md5(str(cId)).hexdigest() + ("-b2b_%d" % oroute.rnum)) - //} else { - cId := sippy_header.NewSipCallIdFromString(self.eTry.GetSipCallId().CallId + fmt.Sprintf("-b2b_%d", oroute.rnum)) - //} + self.uaO.SetKaInterval(self.global_config.Keepalive_orig_dur) + if gt, ok := oroute.params["gt"]; ok { + arr := strings.SplitN(gt, ",", 2) + if len(arr) == 2 { + timeout, err := strconv.Atoi(arr[0]) + if err == nil { + var skipto int + skipto, err = strconv.Atoi(arr[1]) + if err == nil { + go func() { + time.Sleep(time.Duration(timeout) * time.Second) + self.lock.Lock() + defer self.lock.Unlock() + self.group_expires(skipto) + }() + } + } + } + } + var cId *sippy_header.SipCallId + if self.global_config.Hide_call_id { + cId = sippy_header.NewSipCallIdFromString(fmt.Sprintf("%x-b2b_%d", md5.Sum([]byte(self.eTry.GetSipCallId().CallId)), oroute.rnum)) + } else { + cId = sippy_header.NewSipCallIdFromString(self.eTry.GetSipCallId().CallId + fmt.Sprintf("-b2b_%d", oroute.rnum)) + } caller_name := oroute.caller_name if caller_name == "" { caller_name = self.caller_name } event, _ := sippy.NewCCEventTry(cId, oroute.cli, cld, body, self.eTry.GetSipAuthorizationHF(), caller_name, nil, "") - //if self.eTry.max_forwards != nil { - // event.max_forwards = self.eTry.max_forwards - 1 - // if event.max_forwards <= 0 { - // self.uaA.RecvEvent(sippy.NewCCEventFail(483, "Too Many Hops", nil, "")) - // self.state = CCStateDead - // return - // } - //} + if self.eTry.GetMaxForwards() != nil { + mf_body, err := self.eTry.GetMaxForwards().GetBody() + max_forwards := mf_body.Number - 1 + if err != nil { + self.uaA.RecvEvent(sippy.NewCCEventFail(500, "Internal Server Error (8)", nil, "")) + self.state = CCStateDead + return + } + if max_forwards <= 0 { + self.uaA.RecvEvent(sippy.NewCCEventFail(483, "Too Many Hops", nil, "")) + self.state = CCStateDead + return + } + event.SetMaxForwards(sippy_header.NewSipMaxForwards(max_forwards)) + } event.SetReason(self.eTry.GetReason()) self.uaO.RecvEvent(event) } @@ -371,14 +460,16 @@ func (self *callController) placeOriginate(oroute *B2BRoute) { func (self *callController) disconnect(rtime *sippy_time.MonoTime) { self.uaA.Disconnect(rtime, "") } -/* - def oConn(self, ua, rtime, origin): - if self.acctO != nil: - self.acctO.conn(ua, rtime, origin) -*/ + +func (self *callController) oConn(rtime *sippy_time.MonoTime, origin string) { + if self.acctO != nil { + self.acctO.Conn(self.uaO, rtime, origin) + } +} + func (self *callController) aConn(rtime *sippy_time.MonoTime, origin string) { self.state = CCStateConnected - //self.acctA.conn(rtime, origin) + self.acctA.Conn(self.uaA, rtime, origin) } func (self *callController) aFail(rtime *sippy_time.MonoTime, origin string, result int) { @@ -386,18 +477,18 @@ func (self *callController) aFail(rtime *sippy_time.MonoTime, origin string, res } func (self *callController) aDisc(rtime *sippy_time.MonoTime, origin string, result int, inreq sippy_types.SipRequest) { - //if self.state == CCStateWaitRoute && self.auth_proc != nil { - // self.auth_proc.cancel() - // self.auth_proc = nil - //} + if self.state == CCStateWaitRoute && self.auth_proc != nil { + self.auth_proc.Cancel() + self.auth_proc = nil + } if self.uaO != nil && self.state != CCStateDead { self.state = CCStateDisconnecting } else { self.state = CCStateDead } - //if self.acctA != nil { - // self.acctA.disc(ua, rtime, origin, result) - //} + if self.acctA != nil { + self.acctA.Disc(self.uaA, rtime, origin, result) + } if self.rtp_proxy_session != nil { self.rtp_proxy_session.Delete() self.rtp_proxy_session = nil @@ -410,7 +501,7 @@ func (self *callController) aDead() { println("garbadge collecting", self) } self.acctA = nil - //self.acctO = nil + self.acctO = nil self.cmap.DropCC(self.id) self.cmap = nil } @@ -422,24 +513,26 @@ func (self *callController) oDead() { println("garbadge collecting", self) } self.acctA = nil - //self.acctO = nil + self.acctO = nil self.cmap.DropCC(self.id) } } -/* - def group_expires(self, skipto): - if self.state != CCStateARComplete || len(self.routes) == 0 || self.routes[0][0] > skipto || \ - (! isinstance(self.uaA.state, UasStateTrying) && ! isinstance(self.uaA.state, UasStateRinging)): - return - // When the last group in the list has timeouted don't disconnect - // the current attempt forcefully. Instead, make sure that if the - // current originate call leg fails no more routes will be - // processed. - if skipto == self.routes[-1][0] + 1: - self.routes = [] - return - while self.routes[0][0] != skipto: - self.routes.pop(0) - self.uaO.disconnect() - */ +func (self *callController) group_expires(skipto int) { + if self.state != CCStateARComplete || len(self.routes) == 0 || self.routes[0].rnum > skipto || + ((self.uaA.GetState() != sippy_types.UAS_STATE_TRYING) && (self.uaA.GetState() != sippy_types.UAS_STATE_RINGING)) { + return + } + // When the last group in the list has timeouted don't disconnect + // the current attempt forcefully. Instead, make sure that if the + // current originate call leg fails no more routes will be + // processed. + if skipto == self.routes[len(self.routes)-1].rnum + 1 { + self.routes = []*B2BRoute{} + return + } + for self.routes[0].rnum != skipto { + self.routes = self.routes[1:] + } + self.uaO.Disconnect(nil, "") +} diff --git a/cmd/b2bua_radius/call_map.go b/cmd/b2bua_radius/call_map.go index 304c86a..0a4cbb3 100644 --- a/cmd/b2bua_radius/call_map.go +++ b/cmd/b2bua_radius/call_map.go @@ -1,6 +1,6 @@ // // Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved. -// Copyright (c) 2006-2014 Sippy Software, Inc. All rights reserved. +// Copyright (c) 2006-2024 Sippy Software, Inc. All rights reserved. // // All rights reserved. // @@ -56,19 +56,12 @@ type CallMap struct { cc_id_lock sync.Mutex rtp_proxy_clients []sippy_types.RtpProxyClient static_route *B2BRoute + radius_client *RadiusClient + radius_auth *RadiusAuthorisation } -/* -class CallMap(object): - ccmap = nil - el = nil - global_config = nil - //rc1 = nil - //rc2 = nil -*/ - func NewCallMap(global_config *myConfigParser, rtp_proxy_clients []sippy_types.RtpProxyClient, - static_route *B2BRoute) *CallMap { + static_route *B2BRoute, radius_client *RadiusClient, radius_auth *RadiusAuthorisation) *CallMap { self := &CallMap{ global_config : global_config, ccmap : make(map[int64]*callController), @@ -77,6 +70,8 @@ func NewCallMap(global_config *myConfigParser, rtp_proxy_clients []sippy_types.R safe_restart : false, rtp_proxy_clients: rtp_proxy_clients, static_route : static_route, + radius_client : radius_client, + radius_auth : radius_auth, } go func() { sighup_ch := make(chan os.Signal, 1) @@ -149,27 +144,24 @@ func (self *CallMap) OnNewDialog(req sippy_types.SipRequest, sip_t sippy_types.S if ! self.global_config.checkIP(source.Host.String()) { return nil, nil, req.GenResponse(403, "Forbidden", nil, nil) } -/* var challenge *sippy_header.SipWWWAuthenticate - if self.global_config.auth_enable { + if self.global_config.Auth_enable { // Prepare challenge if no authorization header is present. // Depending on configuration, we might try remote ip auth // first and then challenge it or challenge immediately. - if self.global_config["digest_auth"] && req.countHFs("authorization") == 0 { - challenge = NewSipWWWAuthenticate() - challenge.getBody().realm = req.getRURI().host + if self.global_config.Digest_auth && req.GetFirstHF("authorization") == nil { + challenge = sippy_header.NewSipWWWAuthenticateWithRealm(req.GetRURI().Host.String(), "", req.GetRtime().Realt()) } // Send challenge immediately if digest is the // only method of authenticating - if challenge != nil && self.global_config.getdefault("digest_auth_only", false) { - resp = req.GenResponse(401, "Unauthorized") - resp.appendHeader(challenge) - return resp, nil, nil + if challenge != nil && self.global_config.Digest_auth_only { + resp := req.GenResponse(401, "Unauthorized", nil, nil) + resp.AppendHeader(challenge) + return nil, nil, resp } } -*/ pass_headers := []sippy_header.SipHeader{} - for _, header := range self.global_config.pass_headers { + for _, header := range self.global_config.Pass_headers_arr { hfs := req.GetHFs(header) pass_headers = append(pass_headers, hfs...) } @@ -185,8 +177,8 @@ func (self *CallMap) OnNewDialog(req sippy_types.SipRequest, sip_t sippy_types.S cguid = sippy_header.NewSipCiscoGUID() } cc := NewCallController(id, remote_ip, source, self.global_config, pass_headers, self.Sip_tm, cguid, self) - //cc.challenge = challenge - //rval := cc.uaA.RecvRequest(req, sip_t) + cc.challenge = challenge +// rval := cc.uaA.RecvRequest(req, sip_t) // this call is made by SipTransactionManager. It's necessary for for proper locking. self.ccmap_lock.Lock() self.ccmap[id] = cc self.ccmap_lock.Unlock() diff --git a/cmd/b2bua_radius/external_command.go b/cmd/b2bua_radius/external_command.go new file mode 100644 index 0000000..8826e61 --- /dev/null +++ b/cmd/b2bua_radius/external_command.go @@ -0,0 +1,152 @@ +// Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved. +// Copyright (c) 2006-2024 Sippy Software, Inc. All rights reserved. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +package main + +import ( + "bufio" + "io" + "os/exec" + "strings" + + "github.com/sippy/go-b2bua/sippy/log" + "github.com/sippy/go-b2bua/sippy/utils" +) + +type ExternalCommand struct { + work_ch chan *Work_item +} + +type Work_item struct { + data []string + result_callback func([]string) + cancelled bool +} + +type Worker struct { + master *ExternalCommand + command string + args []string + logger sippy_log.ErrorLogger +} + +func newWorker(master *ExternalCommand, logger sippy_log.ErrorLogger, command string, args []string) *Worker { + return &Worker{ + master : master, + command : command, + args : args, + logger : logger, + } +} + +func (self *Worker) run() { + var stdout_raw io.ReadCloser + var stdin io.WriteCloser + var err error + + cmd := exec.Command(self.command, self.args...) + if stdin, err = cmd.StdinPipe(); err != nil { + self.logger.Error("ExternalCommand Worker: cannot get stdin pipe: " + err.Error()) + return + } + if stdout_raw, err = cmd.StdoutPipe(); err != nil { + self.logger.Error("ExternalCommand Worker: cannot get stdout pipe: " + err.Error()) + return + } + err = cmd.Start() + if err != nil { + self.logger.Error("ExternalCommand Worker: " + err.Error()) + return + } + defer cmd.Wait() + stdout := bufio.NewReader(stdout_raw) + for { + wi := <-self.master.work_ch + if wi == nil { + break + } + if wi.cancelled { + wi.data = nil + wi.result_callback = nil + continue + } + batch := []byte(strings.Join(wi.data, "\n") + "\n\n") + stdin.Write(batch) + result := []string{} + for { + var buf, line []byte + var is_prefix bool + + buf, is_prefix, err = stdout.ReadLine() + if err != nil { + break + } + line = append(line, buf...) + if is_prefix { + continue + } + s := strings.TrimSpace(string(line)) + if len(s) == 0 { + break + } + result = append(result, s) + line = []byte{} + } + result_callback := wi.result_callback + if result_callback != nil { + sippy_utils.SafeCall(func() { wi.result_callback(result) }, nil, self.logger) + } + wi.data = nil + wi.result_callback = nil + } +} + +func newWork_item(data []string, result_callback func([]string)) *Work_item { + return &Work_item{ + data : data, + result_callback : result_callback, + cancelled : false, + } +} + +func (self *Work_item) Cancel() { + self.cancelled = true +} + +func newExternalCommand(max_workers int, logger sippy_log.ErrorLogger, cmd string, opts ...string) *ExternalCommand { + self := &ExternalCommand{ + work_ch : make(chan *Work_item, 1000), + } + for i := 0; i < max_workers; i++ { + w := newWorker(self, logger, cmd, opts) + go w.run() + } + return self +} +func (self *ExternalCommand) process_command(data []string, result_callback func([]string)) Cancellable { + wi := newWork_item(data, result_callback) + self.work_ch <- wi + return wi +} diff --git a/cmd/b2bua_radius/external_command_test.go b/cmd/b2bua_radius/external_command_test.go new file mode 100644 index 0000000..0c74544 --- /dev/null +++ b/cmd/b2bua_radius/external_command_test.go @@ -0,0 +1,35 @@ +package main + +import ( + "testing" + "time" + + "github.com/sippy/go-b2bua/sippy/log" +) + +func Test_ExternalCommand(t *testing.T) { + logger := sippy_log.NewErrorLogger() + ec := newExternalCommand(1, logger, "/bin/cat") + res_ch := make(chan []string, 1) + result_cb := func(res []string) { + res_ch <- res + } + for _, s := range []string{ "foo", "bar" } { + var res []string + + ec.process_command([]string{ s }, result_cb) + select { + case res = <-res_ch: + case <-time.After(500 * time.Millisecond): + t.Fatal("Timeout waiting for response") + return + } + if len(res) == 0 { + t.Fatalf("Empty response for input '%s'", s) + } else if s != res[0] { + t.Fatalf("Expected '%s', received '%s'", s, res[0]) + } else { + t.Logf("ok: sent '%s', got '%s'", s, res[0]) + } + } +} diff --git a/cmd/b2bua_radius/fake_accounting.go b/cmd/b2bua_radius/fake_accounting.go index f418f45..227f34c 100644 --- a/cmd/b2bua_radius/fake_accounting.go +++ b/cmd/b2bua_radius/fake_accounting.go @@ -1,6 +1,6 @@ // // Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved. -// Copyright (c) 2006-2014 Sippy Software, Inc. All rights reserved. +// Copyright (c) 2006-2024 Sippy Software, Inc. All rights reserved. // // All rights reserved. // @@ -26,6 +26,11 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package main +import ( + "github.com/sippy/go-b2bua/sippy/time" + "github.com/sippy/go-b2bua/sippy/types" +) + type fakeAccounting struct { } @@ -33,14 +38,9 @@ func NewFakeAccounting() *fakeAccounting { return &fakeAccounting{ } } -/* -class FakeAccounting(object): - def __init__(self, *args): - pass - def conn(self, *args): - pass +func (self *fakeAccounting) Conn(sippy_types.UA, *sippy_time.MonoTime, string) { +} - def disc(self, *args): - pass -*/ +func (self *fakeAccounting) Disc(sippy_types.UA, *sippy_time.MonoTime, string, int) { +} diff --git a/cmd/b2bua_radius/go.mod b/cmd/b2bua_radius/go.mod index 3304b47..9fd8c03 100644 --- a/cmd/b2bua_radius/go.mod +++ b/cmd/b2bua_radius/go.mod @@ -1,3 +1,17 @@ module b2bua_radius go 1.19 + +require ( + github.com/gookit/color v1.5.4 // indirect + github.com/gookit/goutil v0.6.15 // indirect + github.com/gookit/ini v1.1.1 // indirect + github.com/gookit/ini/v2 v2.2.3 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect +) diff --git a/cmd/b2bua_radius/interfaces.go b/cmd/b2bua_radius/interfaces.go new file mode 100644 index 0000000..21f5934 --- /dev/null +++ b/cmd/b2bua_radius/interfaces.go @@ -0,0 +1,41 @@ +// +// Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved. +// Copyright (c) 2024 Sippy Software, Inc. All rights reserved. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +package main + +import ( + "github.com/sippy/go-b2bua/sippy/time" + "github.com/sippy/go-b2bua/sippy/types" +) + +type Cancellable interface { + Cancel() +} + +type Accounting interface { + Conn(sippy_types.UA, *sippy_time.MonoTime, string) + Disc(sippy_types.UA, *sippy_time.MonoTime, string, int) +} diff --git a/cmd/b2bua_radius/main.go b/cmd/b2bua_radius/main.go index 84f0cc9..54ab7c5 100644 --- a/cmd/b2bua_radius/main.go +++ b/cmd/b2bua_radius/main.go @@ -1,6 +1,6 @@ // // Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved. -// Copyright (c) 2006-2014 Sippy Software, Inc. All rights reserved. +// Copyright (c) 2006-2024 Sippy Software, Inc. All rights reserved. // // All rights reserved. // @@ -28,85 +28,16 @@ package main import ( "os" + "strconv" "strings" "github.com/sippy/go-b2bua/sippy" "github.com/sippy/go-b2bua/sippy/cli" "github.com/sippy/go-b2bua/sippy/net" "github.com/sippy/go-b2bua/sippy/types" + "github.com/sippy/go-b2bua/sippy/utils" ) -/* -from sippy.Timeout import Timeout -from sippy.Signal import Signal -from sippy.SipFrom import SipFrom -from sippy.SipTo import SipTo -from sippy.SipCiscoGUID import SipCiscoGUID -from sippy.UA import UA -from sippy.CCEvents import CCEventRing, CCEventConnect, CCEventDisconnect, CCEventTry, CCEventUpdate, CCEventFail -from sippy.UasStateTrying import UasStateTrying -from sippy.UasStateRinging import UasStateRinging -from sippy.UaStateDead import UaStateDead -from sippy.SipConf import SipConf -from sippy.SipHeader import SipHeader -from sippy.RadiusAuthorisation import RadiusAuthorisation -from sippy.RadiusAccounting import RadiusAccounting -from sippy.FakeAccounting import FakeAccounting -from sippy.SipLogger import SipLogger -from sippy.Rtp_proxy_session import Rtp_proxy_session -from sippy.Rtp_proxy_client import Rtp_proxy_client -from signal import SIGHUP, SIGPROF, SIGUSR1, SIGUSR2 -from twisted.internet import reactor -from sippy.Cli_server_local import Cli_server_local -from sippy.SipTransactionManager import SipTransactionManager -from sippy.SipCallId import SipCallId -from sippy.StatefulProxy import StatefulProxy -from sippy.misc import daemonize -from sippy.B2BRoute import B2BRoute -import gc, getopt, os, sys -from re import sub -from time import time -from urllib import quote -from hashlib import md5 -from sippy.MyConfigParser import MyConfigParser -from traceback import print_exc -from datetime import datetime - -def re_replace(ptrn, s): - s = s.split('#', 1)[0] - ptrn = ptrn.split('/') - while len(ptrn) > 0: - op, p, r, mod = ptrn[:4] - mod = mod.strip() - if len(mod) > 0 && mod[0] != ';': - ptrn[3] = mod[1:] - mod = mod[0].lower() - else: - ptrn[3] = mod - if 'g' in mod: - s = sub(p, r, s) - else: - s = sub(p, r, s, 1) - if len(ptrn) == 4 && ptrn[3] == '': - break - ptrn = ptrn[3:] - return s - -def reopen(signum, logfile): - print('Signal %d received, reopening logs' % signum) - fd = os.open(logfile, os.O_WRONLY | os.O_CREAT | os.O_APPEND) - os.dup2(fd, sys.__stdout__.fileno()) - os.dup2(fd, sys.__stderr__.fileno()) - os.close(fd) - -def usage(global_config, brief = false): - print('usage: b2bua.py [--option1=value1] [--option2=value2] ... [--optionN==valueN]') - if ! brief: - print('\navailable options:\n') - global_config.options_help() - sys.exit(1) -*/ - func main() { global_config := NewMyConfigParser() err := global_config.Parse() @@ -123,27 +54,23 @@ func main() { println(err.Error()) return } - //} else if ! global_config.auth_enable { - } else if true { // radius is not implemented + } else if ! global_config.Auth_enable { println("ERROR: static route should be specified when Radius auth is disabled") return } -/* - if writeconf != nil: - global_config.write(open(writeconf, 'w')) - if ! global_config['foreground']: - daemonize(logfile = global_config['logfile']) -*/ + if ! global_config.Foreground { + sippy_utils.Daemonize(global_config.Logfile, -1, -1, global_config.ErrorLogger()) + } rtp_proxy_clients := make([]sippy_types.RtpProxyClient, len(global_config.Rtp_proxy_clients)) - for i, address := range global_config.Rtp_proxy_clients { + for i, address := range global_config.Rtp_proxy_clients_arr { opts, err := sippy.NewRtpProxyClientOpts(address, nil /*bind_address*/, global_config, global_config.ErrorLogger()) if err != nil { println("Cannot initialize rtpproxy client: " + err.Error()) return } - opts.SetHeartbeatInterval(global_config.Hrtb_ival) - opts.SetHeartbeatRetryInterval(global_config.Hrtb_retr_ival) + opts.SetHeartbeatInterval(global_config.Hrtb_ival_dur) + opts.SetHeartbeatRetryInterval(global_config.Hrtb_retr_ival_dur) rtpp := sippy.NewRtpProxyClient(opts) err = rtpp.Start() if err != nil { @@ -152,13 +79,17 @@ func main() { } rtp_proxy_clients[i] = rtpp } -/* - if global_config['auth_enable'] || global_config['acct_enable']: - global_config['_radius_client'] = RadiusAuthorisation(global_config) -*/ + + var radius_client *RadiusClient + var radius_auth *RadiusAuthorisation + + if global_config.Auth_enable || global_config.Acct_enable { + radius_client = NewRadiusClient(global_config) + radius_auth = NewRadiusAuthorisation(radius_client, global_config) + } global_config.SetMyUAName("Sippy B2BUA (RADIUS)") - cmap := NewCallMap(global_config, rtp_proxy_clients, static_route) + cmap := NewCallMap(global_config, rtp_proxy_clients, static_route, radius_client, radius_auth) /* if global_config.getdefault('xmpp_b2bua_id', nil) != nil: global_config['_xmpp_mode'] = true @@ -191,10 +122,14 @@ func main() { return } cli_server.Start() -/* - if ! global_config['foreground']: - file(global_config['pidfile'], 'w').write(str(os.getpid()) + '\n') - Signal(SIGUSR1, reopen, SIGUSR1, global_config['logfile']) -*/ + + if ! global_config.Foreground { + fd, err := os.OpenFile(global_config.Pidfile, os.O_WRONLY | os.O_CREATE, 0644) + if err != nil { + global_config.ErrorLogger().Error("Cannot open PID file: " + err.Error()) + return + } + fd.WriteString(strconv.Itoa(os.Getpid()) + "\n") + } sip_tm.Run() } diff --git a/cmd/b2bua_radius/my_config_parser.go b/cmd/b2bua_radius/my_config_parser.go index af77118..c324be8 100644 --- a/cmd/b2bua_radius/my_config_parser.go +++ b/cmd/b2bua_radius/my_config_parser.go @@ -1,6 +1,6 @@ // // Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved. -// Copyright (c) 2006-2014 Sippy Software, Inc. All rights reserved. +// Copyright (c) 2006-2024 Sippy Software, Inc. All rights reserved. // // All rights reserved. // @@ -33,463 +33,381 @@ import ( "strings" "time" + "github.com/gookit/ini/v2" + "github.com/sippy/go-b2bua/sippy/conf" "github.com/sippy/go-b2bua/sippy/log" "github.com/sippy/go-b2bua/sippy/net" ) +const ( + INI_SECTION = "general" +) + type myConfigParser struct { sippy_conf.Config - accept_ips map[string]bool + + Accept_ips_map map[string]bool + Hrtb_retr_ival_dur time.Duration + Hrtb_ival_dur time.Duration + Keepalive_ans_dur time.Duration + Keepalive_orig_dur time.Duration + Rtp_proxy_clients_arr []string + Pass_headers_arr []string + Allowed_pts_map map[string]bool + + Accept_ips string + Acct_enable bool + Alive_acct_int int + Allowed_pts string + Auth_enable bool + B2bua_socket string + Foreground bool + Hide_call_id bool + Hrtb_retr_ival int + Hrtb_ival int + Keepalive_ans int + Keepalive_orig int + Logfile string + Max_credit_time int + Max_radius_clients int + Nat_traversal bool + Precise_acct bool + Digest_auth bool + Digest_auth_only bool + Pass_headers string + Pidfile string + Radiusclient string + Radiusclient_conf string + Rtp_proxy_clients string + Rtpp_hrtb_ival int + Rtpp_hrtb_retr_ival int Static_route string + Sip_address string + Static_tr_in string + Static_tr_out string + Sip_port int Sip_proxy string - //auth_enable bool - Rtp_proxy_clients []string - pass_headers []string - keepalive_ans time.Duration - keepalive_orig time.Duration - B2bua_socket string - Hrtb_retr_ival time.Duration - Hrtb_ival time.Duration + Start_acct_enable bool + + bool_opts []_bool_opt + int_opts []_int_opt + str_opts []_str_opt +} + +type _bool_opt struct { + opt_name string + descr string + ptr *bool + def_val bool +} + +type _int_opt struct { + opt_name string + descr string + ptr *int + def_val int +} + +type _str_opt struct { + opt_name string + descr string + ptr *string + def_val string } func NewMyConfigParser() *myConfigParser { - return &myConfigParser{ - Rtp_proxy_clients : make([]string, 0), - accept_ips : make(map[string]bool), - //auth_enable : false, - pass_headers : make([]string, 0), + self := &myConfigParser{ + Rtp_proxy_clients_arr : make([]string, 0), + Accept_ips_map : make(map[string]bool), + Auth_enable : true, + Acct_enable : false, + Start_acct_enable : false, + Pass_headers_arr : make([]string, 0), + Allowed_pts_map : make(map[string]bool), + } + self.bool_opts = []_bool_opt{ + { "acct_enable", "enable or disable Radius accounting", &self.Acct_enable, true }, + { "precise_acct", "do Radius accounting with millisecond precision", &self.Precise_acct, false }, + { "auth_enable", "enable or disable Radius authentication", &self.Auth_enable, true }, + { "digest_auth", "enable or disable SIP Digest authentication of incoming INVITE requests", &self.Digest_auth, true }, + { "foreground", "run in foreground", &self.Foreground, false }, + { "hide_call_id", "do not pass Call-ID header value from ingress call leg to egress call leg", &self.Hide_call_id, false }, + { "start_acct_enable", "enable start Radius accounting", &self.Start_acct_enable, false }, + { "digest_auth_only", "only use SIP Digest method to authenticate " + + "incoming INVITE requests. If the option is not " + + "specified or set to \"off\" then B2BUA will try to " + + "do remote IP authentication first and if that fails " + + "then send a challenge and re-authenticate when " + + "challenge response comes in", &self.Digest_auth_only, false }, + { "nat_traversal", "enable NAT traversal for signalling", &self.Nat_traversal, false }, } + self.int_opts = []_int_opt{ + { "alive_acct_int", "interval for sending alive Radius accounting in " + + "second (0 to disable alive accounting)", &self.Alive_acct_int, -1 }, + { "keepalive_ans", "send periodic \"keep-alive\" re-INVITE requests on " + + "answering (ingress) call leg and disconnect a call " + + "if the re-INVITE fails (period in seconds, 0 to " + + "disable)", &self.Keepalive_ans, 0 }, + { "keepalive_orig", "send periodic \"keep-alive\" re-INVITE requests on " + + "originating (egress) call leg and disconnect a call " + + "if the re-INVITE fails (period in seconds, 0 to " + + "disable)", &self.Keepalive_orig, 0 }, + { "max_credit_time", "upper limit of session time for all calls in seconds", &self.Max_credit_time, -1 }, + { "max_radiusclients", "maximum number of Radius Client helper " + + "processes to start", &self.Max_radius_clients, 20 }, + { "sip_port", "local UDP port to listen for incoming SIP requests", &self.Sip_port, 5060 }, + { "rtpp_hrtb_ival", "rtpproxy hearbeat interval (seconds)", &self.Rtpp_hrtb_ival, 10 }, + { "rtpp_hrtb_retr_ival", "rtpproxy hearbeat retry interval (seconds)", &self.Rtpp_hrtb_retr_ival, 60 }, + } + self.str_opts = []_str_opt{ + { "b2bua_socket", "path to the B2BUA command socket or address to listen " + + "for commands in the format \"udp:host[:port]\"", &self.B2bua_socket, "/var/run/b2bua.sock" }, + { "logfile", "path to the B2BUA log file", &self.Logfile, "/var/log/b2bua.log" }, + { "pidfile", "path to the B2BUA PID file", &self.Pidfile, "/var/run/b2bua.pid" }, + { "radiusclient", "path to the radiusclient executable", &self.Radiusclient, "/usr/local/sbin/radiusclient" }, + { "radiusclient_conf", "path to the radiusclient.conf file", &self.Radiusclient_conf, "" }, + { "sip_address", "local SIP address to listen for incoming SIP requests " + + "(\"*\", \"0.0.0.0\" or \"::\" to listen on all IPv4 " + + "or IPv6 interfaces)", &self.Sip_address, "" }, + { "static_route", "static route for all SIP calls", &self.Static_route, "" }, + { "static_tr_in", "translation rule (regexp) to apply to all incoming " + + "(ingress) destination numbers", &self.Static_tr_in, "" }, + { "static_tr_out", "translation rule (regexp) to apply to all outgoing " + + "(egress) destination numbers", &self.Static_tr_out, "" }, + { "allowed_pts", "list of allowed media (RTP) IANA-assigned payload " + + "types that the B2BUA will pass from input to " + + "output, payload types not in this list will be " + + "filtered out (comma separated list)", &self.Allowed_pts, "" }, + { "accept_ips", "IP addresses that we will only be accepting incoming " + + "calls from (comma-separated list). If the parameter " + + "is not specified, we will accept from any IP and " + + "then either try to authenticate if authentication " + + "is enabled, or just let them to pass through", &self.Accept_ips, "" }, + { "sip_proxy", "address of the helper proxy to handle \"REGISTER\" " + + "and \"SUBSCRIBE\" messages. Address in the format " + + "\"host[:port]\"", &self.Sip_proxy, "" }, + { "pass_headers", "list of SIP header field names that the B2BUA will " + + "pass from ingress call leg to egress call leg " + + "unmodified (comma-separated list)", &self.Pass_headers, "" }, + { "rtp_proxy_clients", "comma-separated list of paths or addresses of the " + + "RTPproxy control socket. Address in the format " + + "\"udp:host[:port]\" (comma-separated list)", &self.Rtp_proxy_clients, "" }, + } + return self } func (self *myConfigParser) Parse() error { -/* - global_config.digest_auth = true - global_config.start_acct_enable = false - global_config.keepalive_ans = 0 - global_config.keepalive_orig = 0 - global_config.auth_enable = true - global_config.acct_enable = true - global_config._pass_headers = [] - //global_config._orig_argv = sys.argv[:] - //global_config._orig_cwd = os.getcwd() - try: - opts, args = getopt.getopt(sys.argv[1:], 'fDl:p:d:P:L:s:a:t:T:k:m:A:ur:F:R:h:c:M:HC:W:', - global_config.get_longopts()) - except getopt.GetoptError: - usage(global_config) - global_config['foreground'] = false - global_config['pidfile'] = '/var/run/b2bua.pid' - global_config['logfile'] = '/var/log/b2bua.log' - global_config['b2bua_socket'] = '/var/run/b2bua.sock' - global_config['_sip_address'] = SipConf.my_address - global_config['_sip_port'] = SipConf.my_port - rtp_proxy_clients = [] - writeconf = nil - for o, a in opts: - if o == '-f': - global_config['foreground'] = true - continue - if o == '-l': - global_config.check_and_set('sip_address', a) - continue - if o == '-P': - global_config.check_and_set('pidfile', a) - continue - */ - var logfile string - flag.StringVar(&logfile, "L", "/var/log/sip.log", "logfile") - flag.StringVar(&logfile, "logfile", "/var/log/sip.log", "path to the B2BUA log file") + auth_disable := false + acct_level := int(-1) + var disable_digest_auth bool + var ka_level int - flag.StringVar(&self.Static_route, "s", "", "static route for all SIP calls") - flag.StringVar(&self.Static_route, "static_route", "", "static route for all SIP calls") + self.setupOpts() + flag.BoolVar(&self.Foreground, "f", false, "see -foreground") + flag.StringVar(&self.Sip_address, "l", "", "see -sip_address") + flag.StringVar(&self.Pidfile, "P", "/var/run/b2bua.pid", "see -pidfile") + flag.StringVar(&self.Logfile, "L", "/var/log/sip.log", "see -logfile") + flag.StringVar(&self.Static_route, "s", "", "see -static_route") + flag.StringVar(&self.Accept_ips, "a", "", "see -accept_ips") + flag.BoolVar(&disable_digest_auth, "D", false, "disable digest authentication") + flag.StringVar(&self.Static_tr_in, "t", "", "see -static_tr_in") + flag.StringVar(&self.Static_tr_out, "T", "", "see -static_tr_out") + flag.IntVar(&ka_level, "k", 0, "keepalive level") + flag.IntVar(&self.Max_credit_time, "m", -1, "see -max_credit_time") + flag.BoolVar(&auth_disable, "u", false, "disable RADIUS authentication") + flag.IntVar(&acct_level, "A", -1, "RADIUS accounting level") + flag.StringVar(&self.Allowed_pts, "F", "", "see -allowed_pts") + flag.StringVar(&self.Radiusclient_conf, "R", "", "see -radiusclient_conf") + flag.StringVar(&self.B2bua_socket, "c", "/var/run/b2bua.sock", "see -b2bua_socket") + flag.IntVar(&self.Max_radius_clients, "M", 1, "see -max_radiusclients") + flag.BoolVar(&self.Hide_call_id, "H", false, "see -hide_call_id") + flag.IntVar(&self.Sip_port, "p", 5060, "see -sip_port") - var accept_ips string - flag.StringVar(&accept_ips, "a", "", "accept_ips") - flag.StringVar(&accept_ips, "accept_ips", "", "IP addresses that we will only be accepting incoming " + - "calls from (comma-separated list). If the parameter " + - "is not specified, we will accept from any IP and " + - "then either try to authenticate if authentication " + - "is enabled, or just let them to pass through") + var writeconf string + flag.StringVar(&writeconf, "W", "", "Config file name to write the config to") - var hrtb_ival int - flag.IntVar(&hrtb_ival, "rtpp_hrtb_ival", 10, "rtpproxy hearbeat interval (seconds)") - var hrtb_retr_ival int - flag.IntVar(&hrtb_retr_ival, "rtpp_hrtb_retr_ival", 60, "rtpproxy hearbeat retry interval (seconds)") -/* - if o == '-a': - global_config.check_and_set('accept_ips', a) - continue - if o == '-D': - global_config['digest_auth'] = false - continue - if o == '-A': - acct_level = int(a.strip()) - if acct_level == 0: - global_config['acct_enable'] = false - global_config['start_acct_enable'] = false - elif acct_level == 1: - global_config['acct_enable'] = true - global_config['start_acct_enable'] = false - elif acct_level == 2: - global_config['acct_enable'] = true - global_config['start_acct_enable'] = true - else: - sys.__stderr__.write('ERROR: -A argument not in the range 0-2\n') - usage(global_config, true) - continue - if o == '-t': - global_config.check_and_set('static_tr_in', a) - continue - if o == '-T': - global_config.check_and_set('static_tr_out', a) - continue -*/ - var ka_level, keepalive_ans, keepalive_orig int - flag.IntVar(&ka_level, "k", 0, "keepalive level") - flag.IntVar(&keepalive_ans, "keepalive_ans", 0, "send periodic \"keep-alive\" re-INVITE requests on " + - "answering (ingress) call leg and disconnect a call " + - "if the re-INVITE fails (period in seconds, 0 to disable)") - flag.IntVar(&keepalive_orig, "keepalive_orig", 0, "send periodic \"keep-alive\" re-INVITE requests on " + - "originating (egress) call leg and disconnect a call " + - "if the re-INVITE fails (period in seconds, 0 to disable)") -/* - if o == '-m': - global_config.check_and_set('max_credit_time', a) - continue -*/ - //flag.BoolVar(&self.auth_enable, "a", false, "auth_enable") - //flag.BoolVar(&self.auth_enable, "auth_enable", false, "enable or disable Radius authentication") -/* - if o == '-r': - global_config.check_and_set('rtp_proxy_client', a) - continue - if o == '-F': - global_config.check_and_set('allowed_pts', a) - continue - if o == '-R': - global_config.check_and_set('radiusclient.conf', a) - continue -*/ - var pass_header, pass_headers string - flag.StringVar(&pass_header, "h", "", "pass_header") - flag.StringVar(&pass_headers, "pass_headers", "", "list of SIP header field names that the B2BUA will " + - "pass from ingress call leg to egress call leg " + - "unmodified (comma-separated list)") - flag.StringVar(&self.B2bua_socket, "c", "/var/run/b2bua.sock", "b2bua_socket") - flag.StringVar(&self.B2bua_socket, "b2bua_socket", "/var/run/b2bua.sock", "path to the B2BUA command socket or address to listen " + - "for commands in the format \"udp:host[:port]\"") -/* - if o == '-M': - global_config.check_and_set('max_radiusclients', a) - continue - if o == '-H': - global_config['hide_call_id'] = true - continue - if o in ('-C', '--config'): - global_config.read(a.strip()) - continue - if o.startswith('--'): - global_config.check_and_set(o[2:], a) - continue - if o == '-W': - writeconf = a.strip() - continue -*/ - var rtp_proxy_clients, rtp_proxy_client string - flag.StringVar(&rtp_proxy_clients, "rtp_proxy_clients", "", "comma-separated list of paths or addresses of the " + - "RTPproxy control socket. Address in the format " + - "\"udp:host[:port]\" (comma-separated list)") - flag.StringVar(&rtp_proxy_client, "rtp_proxy_client", "", "RTPproxy control socket. Address in the format \"udp:host[:port]\"") - flag.StringVar(&self.Sip_proxy, "sip_proxy", "", "address of the helper proxy to handle \"REGISTER\" " + - "and \"SUBSCRIBE\" messages. Address in the format \"host[:port]\"") - var sip_port int - flag.IntVar(&sip_port, "p", 5060, "sip_port") - flag.IntVar(&sip_port, "sip_port", 5060, "local UDP port to listen for incoming SIP requests") + var readconf string + flag.StringVar(&readconf, "C", "", "Config file name to read the config from") + flag.StringVar(&readconf, "config", "", "Config file name to read the config from") + + // Everything's prepared. Now parse it. flag.Parse() - if sip_port <= 0 || sip_port > 65535 { + for _, pt := range strings.Split(self.Allowed_pts, ",") { + pt = strings.TrimSpace(pt) + self.Allowed_pts_map[pt] = true + } + if self.Sip_address != "" { + self.SetSipAddress(sippy_net.NewMyAddress(self.Sip_address)) + } + if disable_digest_auth { + self.Digest_auth = false + } + self.try_read(strings.TrimSpace(readconf)) + + if self.Sip_port <= 0 || self.Sip_port > 65535 { return errors.New("sip_port should be in the range 1-65535") } - rtp_proxy_clients += "," + rtp_proxy_client - arr := strings.Split(rtp_proxy_clients, ",") + arr := strings.Split(self.Rtp_proxy_clients, ",") for _, s := range arr { s = strings.TrimSpace(s) if s != "" { - self.Rtp_proxy_clients = append(self.Rtp_proxy_clients, s) + self.Rtp_proxy_clients_arr = append(self.Rtp_proxy_clients_arr, s) } } - arr = strings.Split(accept_ips, ",") + arr = strings.Split(self.Accept_ips, ",") for _, s := range arr { s = strings.TrimSpace(s) if s != "" { - self.accept_ips[s] = true + self.Accept_ips_map[s] = true } } - pass_headers += "," + pass_header - arr = strings.Split(pass_headers, ",") + arr = strings.Split(self.Pass_headers, ",") for _, s := range arr { s = strings.TrimSpace(s) if s != "" { - self.pass_headers = append(self.pass_headers, s) + self.Pass_headers_arr = append(self.Pass_headers_arr, s) } } switch ka_level { case 0: // do nothing case 1: - self.keepalive_ans = 32 * time.Second + self.Keepalive_ans_dur = 32 * time.Second case 2: - self.keepalive_orig = 32 * time.Second + self.Keepalive_orig_dur = 32 * time.Second case 3: - self.keepalive_ans = 32 * time.Second - self.keepalive_orig = 32 * time.Second + self.Keepalive_ans_dur = 32 * time.Second + self.Keepalive_orig_dur = 32 * time.Second default: return errors.New("-k argument not in the range 0-3") } - if keepalive_ans > 0 { - self.keepalive_ans = time.Duration(keepalive_ans) * time.Second + if self.Keepalive_ans > 0 { + self.Keepalive_ans_dur = time.Duration(self.Keepalive_ans) * time.Second + } else if self.Keepalive_ans < 0 { + return errors.New("keepalive_ans should be non-negative") } - if keepalive_orig > 0 { - self.keepalive_orig = time.Duration(keepalive_orig) * time.Second + if self.Keepalive_orig > 0 { + self.Keepalive_orig_dur = time.Duration(self.Keepalive_orig) * time.Second + } else if self.Keepalive_orig < 0 { + return errors.New("keepalive_orig should be non-negative") + } + if self.Max_credit_time < 0 && self.Max_credit_time != -1 { + return errors.New("max_credit_time should be more than zero") } error_logger := sippy_log.NewErrorLogger() - sip_logger, err := sippy_log.NewSipLogger("b2bua", logfile) + sip_logger, err := sippy_log.NewSipLogger("b2bua", self.Logfile) if err != nil { return err } - self.Hrtb_ival = time.Duration(hrtb_ival) * time.Second - self.Hrtb_retr_ival = time.Duration(hrtb_retr_ival) * time.Second + self.Hrtb_ival_dur = time.Duration(self.Hrtb_ival) * time.Second + self.Hrtb_retr_ival_dur = time.Duration(self.Hrtb_retr_ival) * time.Second self.Config = sippy_conf.NewConfig(error_logger, sip_logger) - self.SetMyPort(sippy_net.NewMyPort(strconv.Itoa(sip_port))) + self.SetMyPort(sippy_net.NewMyPort(strconv.Itoa(self.Sip_port))) + if auth_disable { + self.Auth_enable = false + } + switch acct_level { + case -1: + // option is not set + case 0: + self.Acct_enable = false + self.Start_acct_enable = false + case 1: + self.Acct_enable = true + self.Start_acct_enable = false + case 2: + self.Acct_enable = true + self.Start_acct_enable = true + default: + return errors.New("-A argument not in the range 0-2") + } + self.try_write(strings.TrimSpace(writeconf)) return nil } -/* -from ConfigParser import RawConfigParser -from SipConf import SipConf - -SUPPORTED_OPTIONS = { \ - 'acct_enable': ('B', 'enable or disable Radius accounting'), \ - 'precise_acct': ('B', 'do Radius accounting with millisecond precision'), \ - 'alive_acct_int': ('I', 'interval for sending alive Radius accounting in ' \ - 'second (0 to disable alive accounting)'), \ - 'config': ('S', 'load configuration from file (path to file)'), \ - 'auth_enable': ('B', 'enable or disable Radius authentication'), \ - 'b2bua_socket': ('S', 'path to the B2BUA command socket or address to listen ' \ - 'for commands in the format "udp:host[:port]"'), \ - 'digest_auth': ('B', 'enable or disable SIP Digest authentication of ' \ - 'incoming INVITE requests'), \ - 'foreground': ('B', 'run in foreground'), \ - 'hide_call_id': ('B', 'do not pass Call-ID header value from ingress call ' \ - 'leg to egress call leg'), \ - 'keepalive_ans': ('I', 'send periodic "keep-alive" re-INVITE requests on ' \ - 'answering (ingress) call leg and disconnect a call ' \ - 'if the re-INVITE fails (period in seconds, 0 to ' \ - 'disable)'), \ - 'keepalive_orig': ('I', 'send periodic "keep-alive" re-INVITE requests on ' \ - 'originating (egress) call leg and disconnect a call ' \ - 'if the re-INVITE fails (period in seconds, 0 to ' \ - 'disable)'), \ - 'logfile': ('S', 'path to the B2BUA log file'), \ - 'max_credit_time': ('I', 'upper limit of session time for all calls in ' \ - 'seconds'), \ - 'max_radiusclients': ('I', 'maximum number of Radius Client helper ' \ - 'processes to start'), \ - 'pidfile': ('S', 'path to the B2BUA PID file'), \ - 'radiusclient.conf': ('S', 'path to the radiusclient.conf file'), \ - 'sip_address': ('S', 'local SIP address to listen for incoming SIP requests ' \ - '("*", "0.0.0.0" or "::" to listen on all IPv4 ' \ - 'or IPv6 interfaces)'), - 'sip_port': ('I', 'local UDP port to listen for incoming SIP requests'), \ - 'start_acct_enable': ('B', 'enable start Radius accounting'), \ - 'static_route': ('S', 'static route for all SIP calls'), \ - 'static_tr_in': ('S', 'translation rule (regexp) to apply to all incoming ' \ - '(ingress) destination numbers'), \ - 'static_tr_out': ('S', 'translation rule (regexp) to apply to all outgoing ' \ - '(egress) destination numbers'), \ - 'allowed_pts': ('S', 'list of allowed media (RTP) IANA-assigned payload ' \ - 'types that the B2BUA will pass from input to ' \ - 'output, payload types not in this list will be ' \ - 'filtered out (comma separated list)'), \ - 'pass_headers': ('S', 'list of SIP header field names that the B2BUA will ' \ - 'pass from ingress call leg to egress call leg ' \ - 'unmodified (comma-separated list)'), \ - 'accept_ips': ('S', 'IP addresses that we will only be accepting incoming ' \ - 'calls from (comma-separated list). If the parameter ' \ - 'is not specified, we will accept from any IP and ' \ - 'then either try to authenticate if authentication ' \ - 'is enabled, or just let them to pass through'), - 'digest_auth_only': ('B', 'only use SIP Digest method to authenticate ' \ - 'incoming INVITE requests. If the option is not ' \ - 'specified or set to "off" then B2BUA will try to ' \ - 'do remote IP authentication first and if that fails ' - 'then send a challenge and re-authenticate when ' \ - 'challenge response comes in'), \ - 'rtp_proxy_clients': ('S', 'comma-separated list of paths or addresses of the ' \ - 'RTPproxy control socket. Address in the format ' \ - '"udp:host[:port]" (comma-separated list)'), \ - 'sip_proxy': ('S', 'address of the helper proxy to handle "REGISTER" ' \ - 'and "SUBSCRIBE" messages. Address in the format ' \ - '"host[:port]"'), - 'nat_traversal': false ('B', 'enable NAT traversal for signalling'), \ - 'xmpp_b2bua_id': ('I', 'ID passed to the XMPP socket server')} - -class MyConfigParser(RawConfigParser): - default_section = nil - _private_keys = nil - - def __init__(self, default_section = 'general'): - self.default_section = default_section - self._private_keys = {} - RawConfigParser.__init__(self) - self.add_section(self.default_section) - - def __getitem__(self, key): - if key.startswith('_'): - return self._private_keys[key] - value_type = SUPPORTED_OPTIONS[key][0] - if value_type == 'B': - return self.getboolean(self.default_section, key) - elif value_type == 'I': - return self.getint(self.default_section, key) - return self.get(self.default_section, key) - def __setitem__(self, key, value): - if key.startswith('_'): - self._private_keys[key] = value - else: - self.set(self.default_section, key, str(value)) - return - - def has_key(self, key): - return self.__contains__(key) - - def __contains__(self, key): - if key.startswith('_'): - return self._private_keys.has_key(key) - return self.has_option(self.default_section, key) - - def get(self, *args): - if len(args) == 1: - return self.__getitem__(args[0]) - return RawConfigParser.get(self, *args) - - def getdefault(self, key, default_value): - if self.__contains__(key): - return self.__getitem__(key) - return default_value - - def get_longopts(self): - return tuple([x + '=' for x in SUPPORTED_OPTIONS.keys()]) - - def read(self, fname): - RawConfigParser.readfp(self, open(fname)) - for key in tuple(self.options(self.default_section)): - self.check_and_set(key, RawConfigParser.get(self, \ - self.default_section, key), false) +func (self *myConfigParser) checkIP(ip string) bool { + if len(self.Accept_ips_map) == 0 { + return true + } + _, ok := self.Accept_ips_map[ip] + return ok +} - def check_and_set(self, key, value, compat = true): - value = value.strip() - if compat: - if key == 'rtp_proxy_client': - # XXX compatibility option - if self.has_key('_rtp_proxy_clients'): - self['_rtp_proxy_clients'].append(value) - else: - self['_rtp_proxy_clients'] = [value,] - if self.has_key('rtp_proxy_clients'): - self['rtp_proxy_clients'] += ',' + value - else: - self['rtp_proxy_clients'] = value - return - elif key == 'pass_header': - # XXX compatibility option - if self.has_key('_pass_headers'): - self['_pass_headers'].append(value) - else: - self['_pass_headers'] = [value,] - if self.has_key('pass_headers'): - self['pass_headers'] += ',' + value - else: - self['pass_headers'] = value - return +func (self *myConfigParser) try_write(fname string) error { + if fname == "" { + return nil + } + writer := ini.New() + for _, opt := range self.bool_opts { + writer.Set(opt.opt_name, *opt.ptr, INI_SECTION) + } + for _, opt := range self.int_opts { + writer.Set(opt.opt_name, *opt.ptr, INI_SECTION) + } + for _, opt := range self.str_opts { + writer.Set(opt.opt_name, *opt.ptr, INI_SECTION) + } + _, err := writer.WriteToFile(fname) + return err +} - value_type = SUPPORTED_OPTIONS[key][0] - if value_type == 'B': - if value.lower() ! in self._boolean_states: - raise ValueError, 'Not a boolean: %s' % value - elif value_type == 'I': - _value = int(value) - if key in ('keepalive_ans', 'keepalive_orig'): - if _value < 0: - raise ValueError, 'keepalive_ans should be non-negative' - elif key == 'max_credit_time': - if _value <= 0: - raise ValueError, 'max_credit_time should be more than zero' - elif key == 'allowed_pts': - self['_allowed_pts'] = [int(x) for x in value.split(',')] - elif key in ('accept_ips', 'rtp_proxy_clients'): - self['_' + key] = [x.strip() for x in value.split(',')] - elif key == 'pass_headers': - self['_' + key] = [x.strip().lower() for x in value.split(',')] - elif key == 'sip_address': - if 'my' in dir(value): - self['_sip_address'] = value - value = '*' - elif value in ('*', '0.0.0.0', '::'): - self['_sip_address'] = SipConf.my_address - else: - self['_sip_address'] = value - elif key == 'sip_port': - if _value <= 0 || _value > 65535: - raise ValueError, 'sip_port should be in the range 1-65535' - self['_sip_port'] = _value - self[key] = value +func (self *myConfigParser) try_read(fname string) error { + if fname == "" { + return nil + } + reader := ini.New() + err := reader.LoadFiles(fname) + if err != nil { + return err + } + for _, opt := range self.bool_opts { + *opt.ptr = reader.Bool(INI_SECTION + "." + opt.opt_name, opt.def_val) + } + for _, opt := range self.int_opts { + val, err := strconv.Atoi(reader.Get(INI_SECTION + "." + opt.opt_name, strconv.Itoa(opt.def_val))) + if err == nil { + *opt.ptr = val + } + } + for _, opt := range self.str_opts { + *opt.ptr = reader.Get(INI_SECTION + "." + opt.opt_name, opt.def_val) + } + return err +} - def options_help(self): - supported_options = SUPPORTED_OPTIONS.items() - supported_options.sort() - for option, (value_type, helptext) in supported_options: - if value_type == 'B': - value = 'on/off' - elif value_type == 'I': - value = 'number' - else: - value = '"string"' - print '--%s=%s\n\t%s\n' % (option, value, helptext) +func (self *myConfigParser) setupOpts() { + for _, opt := range self.bool_opts { + flag.BoolVar(opt.ptr, opt.opt_name, opt.def_val, opt.descr) + } + for _, opt := range self.int_opts { + flag.IntVar(opt.ptr, opt.opt_name, opt.def_val, opt.descr) + } + for _, opt := range self.str_opts { + flag.StringVar(opt.ptr, opt.opt_name, opt.def_val, opt.descr) + } + flag.Func("r", "RTPproxy control socket. See -rtp_proxy_clients for more information", self._rtp_proxy_client_cb) + flag.Func("rtp_proxy_client", "RTPproxy control socket. See -rtp_proxy_clients for more information", self._rtp_proxy_client_cb) + flag.Func("h", "SIP header field name to pass from ingress call leg to egress call leg unmodified", self._pass_header_cb) + flag.Func("pass_header", "SIP header field name to pass from ingress call leg to egress call leg unmodified", self._pass_header_cb) +} -if __name__ == '__main__': - m = MyConfigParser() - m['_foo'] = 'bar' - m['b2bua_socket'] = 'bar1' - m['acct_enable'] = true - m['auth_enable'] = 'false' - assert m.has_key('_foo') - assert m['_foo'] == 'bar' - assert m['b2bua_socket'] == 'bar1' - assert m.get('_foo') == 'bar' - assert m.get('b2bua_socket') == 'bar1' - assert m.get('general', 'b2bua_socket') == 'bar1' - assert m.get('acct_enable') - assert not m.get('auth_enable') - m.check_and_set('keepalive_ans', '15') - assert m['keepalive_ans'] == 15 - m.check_and_set('pass_header', 'a') - m.check_and_set('pass_header', 'b') - assert m['pass_headers'] == 'a,b' - assert m['_pass_headers'][0] == 'a' - assert m['_pass_headers'][1] == 'b' - m.check_and_set('accept_ips', '1.2.3.4, 5.6.7.8') - assert m['accept_ips'] == '1.2.3.4, 5.6.7.8' - assert m['_accept_ips'][0] == '1.2.3.4' - assert m['_accept_ips'][1] == '5.6.7.8' -*/ +func (self *myConfigParser) _rtp_proxy_client_cb(val string) error { + val = strings.TrimSpace(val) + if val == "" { + return nil + } + self.Rtp_proxy_clients_arr = append(self.Rtp_proxy_clients_arr, val) + return nil +} -func (self *myConfigParser) checkIP(ip string) bool { - if len(self.accept_ips) == 0 { - return true +func (self *myConfigParser) _pass_header_cb(val string) error { + val = strings.TrimSpace(val) + if val == "" { + return nil } - _, ok := self.accept_ips[ip] - return ok + self.Pass_headers_arr = append(self.Pass_headers_arr, val) + return nil } diff --git a/cmd/b2bua_radius/radius_accounting.go b/cmd/b2bua_radius/radius_accounting.go index cf3c43a..0d7e6e8 100644 --- a/cmd/b2bua_radius/radius_accounting.go +++ b/cmd/b2bua_radius/radius_accounting.go @@ -1,6 +1,6 @@ // // Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved. -// Copyright (c) 2006-2014 Sippy Software, Inc. All rights reserved. +// Copyright (c) 2006-2024 Sippy Software, Inc. All rights reserved. // // All rights reserved. // @@ -26,168 +26,269 @@ // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package main -/* -from time import time, strftime, gmtime -from Timeout import Timeout +import ( + "fmt" + "strconv" + "time" -sipErrToH323Err = {400:('7f', 'Interworking, unspecified'), 401:('39', 'Bearer capability not authorized'), \ - 402:('15', 'Call rejected'), 403:('39', 'Bearer capability not authorized'), 404:('1', 'Unallocated number'), \ - 405:('7f', 'Interworking, unspecified'), 406:('7f', 'Interworking, unspecified'), 407:('15', 'Call rejected'), \ - 408:('66', 'Recover on Expires timeout'), 409:('29', 'Temporary failure'), 410:('1', 'Unallocated number'), \ - 411:('7f', 'Interworking, unspecified'), 413:('7f', 'Interworking, unspecified'), 414:('7f', 'Interworking, unspecified'), \ - 415:('4f', 'Service or option not implemented'), 420:('7f', 'Interworking, unspecified'), 480:('12', 'No user response'), \ - 481:('7f', 'Interworking, unspecified'), 482:('7f', 'Interworking, unspecified'), 483:('7f', 'Interworking, unspecified'), \ - 484:('1c', 'Address incomplete'), 485:('1', 'Unallocated number'), 486:('11', 'User busy'), 487:('12', 'No user responding'), \ - 488:('7f', 'Interworking, unspecified'), 500:('29', 'Temporary failure'), 501:('4f', 'Service or option not implemented'), \ - 502:('26', 'Network out of order'), 503:('3f', 'Service or option unavailable'), 504:('66', 'Recover on Expires timeout'), \ - 505:('7f', 'Interworking, unspecified'), 580:('2f', 'Resource unavailable, unspecified'), 600:('11', 'User busy'), \ - 603:('15', 'Call rejected'), 604:('1', 'Unallocated number'), 606:('3a', 'Bearer capability not presently available')} + "github.com/sippy/go-b2bua/sippy/time" + "github.com/sippy/go-b2bua/sippy/types" +) -class RadiusAccounting(object): - global_config = nil - drec = nil - crec = nil - iTime = nil - cTime = nil - sip_cid = nil - origin = nil - lperiod = nil - el = nil - send_start = nil - complete = false - ms_precision = false - user_agent = nil - p1xx_ts = nil - p100_ts = nil +var sipErrToH323Err = map[int][2]string{ + 400 : { "7f", "Interworking, unspecified" }, + 401 : { "39", "Bearer capability not authorized" }, + 402 : { "15", "Call rejected" }, + 403 : { "39", "Bearer capability not authorized" }, + 404 : { "1", "Unallocated number" }, + 405 : { "7f", "Interworking, unspecified" }, + 406 : { "7f", "Interworking, unspecified" }, + 407 : { "15", "Call rejected" }, + 408 : { "66", "Recover on Expires timeout" }, + 409 : { "29", "Temporary failure" }, + 410 : { "1", "Unallocated number" }, + 411 : { "7f", "Interworking, unspecified" }, + 413 : { "7f", "Interworking, unspecified" }, + 414 : { "7f", "Interworking, unspecified" }, + 415 : { "4f", "Service or option not implemented" }, + 420 : { "7f", "Interworking, unspecified" }, + 480 : { "12", "No user response" }, + 481 : { "7f", "Interworking, unspecified" }, + 482 : { "7f", "Interworking, unspecified" }, + 483 : { "7f", "Interworking, unspecified" }, + 484 : { "1c", "Address incomplete" }, + 485 : { "1", "Unallocated number" }, + 486 : { "11", "User busy" }, + 487 : { "12", "No user responding" }, + 488 : { "7f", "Interworking, unspecified" }, + 500 : { "29", "Temporary failure" }, + 501 : { "4f", "Service or option not implemented" }, + 502 : { "26", "Network out of order" }, + 503 : { "3f", "Service or option unavailable" }, + 504 : { "66", "Recover on Expires timeout" }, + 505 : { "7f", "Interworking, unspecified" }, + 580 : { "2f", "Resource unavailable, unspecified" }, + 600 : { "11", "User busy" }, + 603 : { "15", "Call rejected" }, + 604 : { "1", "Unallocated number" }, + 606 : { "3a", "Bearer capability not presently available"}, +} - def __init__(self, global_config, origin, lperiod = nil, send_start = false): - self.global_config = global_config - self._attributes = [('h323-call-origin', origin), ('h323-call-type', 'VoIP'), \ - ('h323-session-protocol', 'sipv2')] - self.drec = false - self.crec = false - self.origin = origin - self.lperiod = lperiod - self.send_start = send_start +type RadiusAccounting struct { + crec bool + drec bool + iTime *sippy_time.MonoTime + cTime *sippy_time.MonoTime + lperiod int + p1xx_ts *sippy_time.MonoTime + p100_ts *sippy_time.MonoTime + send_start bool + user_agent string + _attributes []RadiusAttribute + el chan bool + complete bool + ms_precision bool + origin string + global_config *myConfigParser + sip_cid string + radius_client *RadiusClient +} - def setParams(self, username, caller, callee, h323_cid, sip_cid, remote_ip, \ - h323_in_cid = nil): - if caller == nil: - caller = '' - self._attributes.extend((('User-Name', username), ('Calling-Station-Id', caller), \ - ('Called-Station-Id', callee), ('h323-conf-id', h323_cid), ('call-id', sip_cid), \ - ('Acct-Session-Id', sip_cid), ('h323-remote-address', remote_ip))) - if h323_in_cid != nil and h323_in_cid != h323_cid: - self._attributes.append(('h323-incoming-conf-id', h323_in_cid)) - self.sip_cid = str(sip_cid) - self.complete = true +func NewRadiusAccounting(global_config *myConfigParser, origin string, radius_client *RadiusClient) *RadiusAccounting { + return &RadiusAccounting{ + crec : false, + drec : false, + lperiod : global_config.Alive_acct_int, + send_start : global_config.Start_acct_enable, + _attributes : []RadiusAttribute{ + { "h323-call-origin", origin }, + { "h323-call-type", "VoIP" }, + { "h323-session-protocol", "sipv2" }, + }, + el : make(chan bool, 1), + complete : false, + origin : origin, + global_config : global_config, + radius_client : radius_client, + } +} - def conn(self, ua, rtime, origin): - if self.crec: - return - self.crec = true - self.iTime = ua.setup_ts - self.cTime = ua.connect_ts - if ua.remote_ua != nil and self.user_agent == nil: - self.user_agent = ua.remote_ua - if ua.p1xx_ts != nil: - self.p1xx_ts = ua.p1xx_ts - if ua.p100_ts != nil: - self.p100_ts = ua.p100_ts - if self.send_start: - self.asend('Start', rtime, origin, ua) - self._attributes.extend((('h323-voice-quality', 0), ('Acct-Terminate-Cause', 'User-Request'))) - if self.lperiod != nil and self.lperiod > 0: - self.el = Timeout(self.asend, self.lperiod, -1, 'Alive') +func (self *RadiusAccounting) SetParams(username, caller, callee, h323_cid, sip_cid, remote_ip, h323_in_cid string) { + self._attributes = append(self._attributes, []RadiusAttribute{ + { "User-Name", username }, + { "Calling-Station-Id", caller }, + { "Called-Station-Id", callee }, + { "h323-conf-id", h323_cid }, + { "call-id", sip_cid }, + { "Acct-Session-Id", sip_cid }, + { "h323-remote-address", remote_ip }, + }...) + if h323_in_cid != "" && h323_in_cid != h323_cid { + self._attributes = append(self._attributes, RadiusAttribute{ "h323-incoming-conf-id", h323_in_cid }) + } + self.sip_cid = sip_cid + self.complete = true +} - def disc(self, ua, rtime, origin, result = 0): - if self.drec: - return - self.drec = true - if self.el != nil: - self.el.cancel() - self.el = nil - if self.iTime == nil: - self.iTime = ua.setup_ts - if self.cTime == nil: - self.cTime = rtime - if ua.remote_ua != nil and self.user_agent == nil: - self.user_agent = ua.remote_ua - if ua.p1xx_ts != nil: - self.p1xx_ts = ua.p1xx_ts - if ua.p100_ts != nil: - self.p100_ts = ua.p100_ts - self.asend('Stop', rtime, origin, result, ua) +func (self *RadiusAccounting) Conn(ua sippy_types.UA, rtime *sippy_time.MonoTime, origin string) { + if self.crec { + return + } + self.crec = true + self.iTime = ua.GetSetupTs() + self.cTime = ua.GetConnectTs() + if self.user_agent == "" { + self.user_agent = ua.GetRemoteUA() + } + if ua.GetP1xxTs() != nil { + self.p1xx_ts = ua.GetP1xxTs() + } + if ua.GetP100Ts() != nil { + self.p100_ts = ua.GetP100Ts() + } + if self.send_start { + self.asend("Start", rtime, origin, 0, ua) + } + self._attributes = append(self._attributes, []RadiusAttribute{ + { "h323-voice-quality", "0" }, + { "Acct-Terminate-Cause", "User-Request" }, + }...) + if self.lperiod > 0 { + self.el = make(chan bool) + go func() { + for { + select { + case <-self.el: + return + case <-time.After(time.Duration(self.lperiod) * time.Second): + self.asend("Alive", nil, "", 0, nil) + } + } + }() + } +} - def asend(self, type, rtime = nil, origin = nil, result = 0, ua = nil): - if not self.complete: - return - if rtime == nil: - rtime = time() - if ua != nil: - duration, delay, connected = ua.getAcct()[:3] - else: - # Alive accounting - duration = rtime - self.cTime - delay = self.cTime - self.iTime - connected = true - if not(self.ms_precision): - duration = round(duration) - delay = round(delay) - attributes = self._attributes[:] - if type != 'Start': - if result >= 400: - try: - dc = sipErrToH323Err[result][0] - except: - dc = '7f' - elif result < 200: - dc = '10' - else: - dc = '0' - attributes.extend((('h323-disconnect-time', self.ftime(self.iTime + delay + duration)), \ - ('Acct-Session-Time', '%d' % round(duration)), ('h323-disconnect-cause', dc))) - if type == 'Stop': - if origin == 'caller': - release_source = '2' - elif origin == 'callee': - release_source = '4' - else: - release_source = '8' - attributes.append(('release-source', release_source)) - attributes.extend((('h323-connect-time', self.ftime(self.iTime + delay)), ('h323-setup-time', self.ftime(self.iTime)), \ - ('Acct-Status-Type', type))) - if self.user_agent != nil: - attributes.append(('h323-ivr-out', 'sip_ua:' + self.user_agent)) - if self.p1xx_ts != nil: - attributes.append(('Acct-Delay-Time', round(self.p1xx_ts))) - if self.p100_ts != nil: - attributes.append(('provisional-timepoint', self.ftime(self.p100_ts))) - pattributes = ['%-32s = \'%s\'\n' % (x[0], str(x[1])) for x in attributes] - pattributes.insert(0, 'sending Acct %s (%s):\n' % (type, self.origin.capitalize())) - self.global_config['_sip_logger'].write(call_id = self.sip_cid, *pattributes) - self.global_config['_radius_client'].do_acct(attributes, self._process_result, self.sip_cid, time()) +func (self *RadiusAccounting) Disc(ua sippy_types.UA, rtime *sippy_time.MonoTime, origin string, result int/*= 0*/) { + if self.drec { + return + } + self.drec = true + if self.el != nil { + close(self.el) + } + if self.iTime == nil { + self.iTime = ua.GetSetupTs() + } + if self.cTime == nil { + self.cTime = rtime + } + if ua.GetRemoteUA() != "" && self.user_agent == "" { + self.user_agent = ua.GetRemoteUA() + } + if ua.GetP1xxTs() != nil { + self.p1xx_ts = ua.GetP1xxTs() + } + if ua.GetP100Ts() != nil { + self.p100_ts = ua.GetP100Ts() + } + self.asend("Stop", rtime, origin, result, ua) +} - def ftime(self, t): - gt = gmtime(t) - day = strftime('%d', gt) - if day[0] == '0': - day = day[1] - if self.ms_precision: - msec = (t % 1) * 1000 - else: - msec = 0 - return strftime('%%H:%%M:%%S.%.3d GMT %%a %%b %s %%Y' % (msec, day), gt) +func (self *RadiusAccounting) asend(typ string, rtime *sippy_time.MonoTime /*= nil*/, origin string /*= nil*/, result int /*= 0*/, ua sippy_types.UA /*= nil*/) { + var duration, delay time.Duration + //var connected bool - def _process_result(self, results, sip_cid, btime): - delay = time() - btime - rcode = results[1] - if rcode in (0, 1): - if rcode == 0: - message = 'Acct/%s request accepted (delay is %.3f)\n' % (self.origin, delay) - else: - message = 'Acct/%s request rejected (delay is %.3f)\n' % (self.origin, delay) - else: - message = 'Error sending Acct/%s request (delay is %.3f)\n' % (self.origin, delay) - self.global_config['_sip_logger'].write(message, call_id = sip_cid) -*/ + if ! self.complete { + return + } + if rtime == nil { + rtime, _ = sippy_time.NewMonoTime() + } + if ua != nil { + duration, delay, _ /*connected*/, _ = ua.GetAcct(rtime) + } else { + // Alive accounting + duration = rtime.Sub(self.cTime) + delay = self.cTime.Sub(self.iTime) + //connected = true + } + if ! self.global_config.Precise_acct { + duration = duration.Round(time.Second) + delay = delay.Round(time.Second) + } + attributes := make([]RadiusAttribute, len(self._attributes)) + copy(attributes, self._attributes) + if typ != "Start" { + var dc string + + if result >= 400 { + res, ok := sipErrToH323Err[result] + if ok { + dc = res[0] + } else { + dc = "7f" + } + } else if result < 200 { + dc = "10" + } else { + dc = "0" + } + attributes = append(attributes, []RadiusAttribute{ + { "h323-disconnect-time", self.ftime(self.iTime.Realt().Add(delay).Add(duration)) }, + { "Acct-Session-Time", strconv.Itoa(int(duration.Round(time.Second).Seconds())) }, + { "h323-disconnect-cause", dc }, + }...) + } + if typ == "Stop" { + release_source := "8" + if origin == "caller" { + release_source = "2" + } else if origin == "callee" { + release_source = "4" + } + attributes = append(attributes, RadiusAttribute{ "release-source", release_source }) + } + attributes = append(attributes, []RadiusAttribute{ + { "h323-connect-time", self.ftime(self.iTime.Realt().Add(delay)) }, + { "h323-setup-time", self.ftime(self.iTime.Realt()) }, + { "Acct-Status-Type", typ }, + }...) + if self.user_agent != "" { + attributes = append(attributes, RadiusAttribute{ "h323-ivr-out", "sip_ua:" + self.user_agent }) + } + if self.p1xx_ts != nil { + attributes = append(attributes, RadiusAttribute{ "Acct-Delay-Time", strconv.Itoa(int(self.p1xx_ts.Realt().Round(time.Second).Unix())) }) + } + if self.p100_ts != nil { + attributes = append(attributes, RadiusAttribute{ "provisional-timepoint", self.ftime(self.p100_ts.Realt()) }) + } + pattributes := fmt.Sprintf("sending Acct %s (%s):\n", typ, self.origin/*.capitalize()*/) + for _, attr := range attributes { + pattributes += fmt.Sprintf("%-32s = '%s'\n", attr.name, attr.value) + } + self.global_config.SipLogger().Write(rtime, self.sip_cid, pattributes) + self.radius_client.do_acct(attributes, func(results *RadiusResult) { self._process_result(results, self.sip_cid, time.Now()) }) +} + +func (self *RadiusAccounting) ftime(t time.Time) string { + if self.ms_precision { + return t.In(time.FixedZone("GMT", 0)).Format("15:04:05.000 MST Mon Jan 2 2006") + } + return t.In(time.FixedZone("GMT", 0)).Format("15:04:05 MST Mon Jan 2 2006") +} + +func (self *RadiusAccounting) _process_result(results *RadiusResult, sip_cid string, btime time.Time) { + var message string + + delay := time.Now().Sub(btime) + if results != nil && (results.Rcode == 0 || results.Rcode == 1) { + if results.Rcode == 0 { + message = fmt.Sprintf("Acct/%s request accepted (delay is %.3f)\n", self.origin, delay.Seconds()) + } else { + message = fmt.Sprintf("Acct/%s request rejected (delay is %.3f)\n", self.origin, delay.Seconds()) + } + } else { + message = fmt.Sprintf("Error sending Acct/%s request (delay is %.3f)\n", self.origin, delay.Seconds()) + } + self.global_config.SipLogger().Write(nil, sip_cid, message) +} diff --git a/cmd/b2bua_radius/radius_attribute.go b/cmd/b2bua_radius/radius_attribute.go new file mode 100644 index 0000000..5d4984a --- /dev/null +++ b/cmd/b2bua_radius/radius_attribute.go @@ -0,0 +1,32 @@ +// +// Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved. +// Copyright (c) 2024 Sippy Software, Inc. All rights reserved. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +package main + +type RadiusAttribute struct { + name string + value string +} diff --git a/cmd/b2bua_radius/radius_authorisation.go b/cmd/b2bua_radius/radius_authorisation.go index 6b857d3..5dc2b40 100644 --- a/cmd/b2bua_radius/radius_authorisation.go +++ b/cmd/b2bua_radius/radius_authorisation.go @@ -1,5 +1,5 @@ // Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved. -// Copyright (c) 2006-2014 Sippy Software, Inc. All rights reserved. +// Copyright (c) 2006-2024 Sippy Software, Inc. All rights reserved. // // All rights reserved. // @@ -24,45 +24,81 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package main -/* -from Radius_client import Radius_client -from time import time -class RadiusAuthorisation(Radius_client): - def do_auth(self, username, caller, callee, h323_cid, sip_cid, remote_ip, res_cb, \ - realm = nil, nonce = nil, uri = nil, response = nil, extra_attributes = nil): - sip_cid = str(sip_cid) - attributes = nil - if nil not in (realm, nonce, uri, response): - attributes = [('User-Name', username), ('Digest-Realm', realm), \ - ('Digest-Nonce', nonce), ('Digest-Method', 'INVITE'), ('Digest-URI', uri), \ - ('Digest-Algorithm', 'MD5'), ('Digest-User-Name', username), ('Digest-Response', response)] - else: - attributes = [('User-Name', remote_ip), ('Password', 'cisco')] - if caller == nil: - caller = '' - attributes.extend((('Calling-Station-Id', caller), ('Called-Station-Id', callee), ('h323-conf-id', h323_cid), \ - ('call-id', sip_cid), ('h323-remote-address', remote_ip), ('h323-session-protocol', 'sipv2'))) - if extra_attributes != nil: - for a, v in extra_attributes: - attributes.append((a, v)) - message = 'sending AAA request:\n' - message += reduce(lambda x, y: x + y, ['%-32s = \'%s\'\n' % (x[0], str(x[1])) for x in attributes]) - self.global_config['_sip_logger'].write(message, call_id = sip_cid) - Radius_client.do_auth(self, attributes, self._process_result, res_cb, sip_cid, time()) +import ( + "fmt" + "time" - def _process_result(self, results, res_cb, sip_cid, btime): - delay = time() - btime - rcode = results[1] - if rcode in (0, 1): - if rcode == 0: - message = 'AAA request accepted (delay is %.3f), processing response:\n' % delay - else: - message = 'AAA request rejected (delay is %.3f), processing response:\n' % delay - if len(results[0]) > 0: - message += reduce(lambda x, y: x + y, ['%-32s = \'%s\'\n' % x for x in results[0]]) - else: - message = 'Error sending AAA request (delay is %.3f)\n' % delay - self.global_config['_sip_logger'].write(message, call_id = sip_cid) - res_cb(results) -*/ + "github.com/sippy/go-b2bua/sippy/headers" + "github.com/sippy/go-b2bua/sippy/net" +) + +type RadiusAuthorisation struct { + radius_client *RadiusClient + global_config *myConfigParser +} + +func NewRadiusAuthorisation(radius_client *RadiusClient, global_config *myConfigParser) *RadiusAuthorisation { + return &RadiusAuthorisation{ + radius_client : radius_client, + global_config : global_config, + } +} + +func (self *RadiusAuthorisation) Do_auth(username, caller, callee string, h323_cid *sippy_header.SipCiscoGUID, + sip_cid *sippy_header.SipCallId, remote_ip *sippy_net.MyAddress, res_cb func(*RadiusResult), + realm, nonce, uri, response string, extra_attributes ...RadiusAttribute) Cancellable { + var attributes []RadiusAttribute + if realm != "" && nonce != "" && uri != "" && response != "" { + attributes = []RadiusAttribute{ + { "User-Name", username }, + { "Digest-Realm", realm }, + { "Digest-Nonce", nonce }, + { "Digest-Method", "INVITE" }, + { "Digest-URI", uri }, + { "Digest-Algorithm", "MD5" }, + { "Digest-User-Name", username }, + { "Digest-Response", response}, + } + } else { + attributes = []RadiusAttribute{ + { "User-Name", remote_ip.String() }, + { "Password", "cisco" }, + } + } + attributes = append(attributes, []RadiusAttribute{ + { "Calling-Station-Id", caller }, + { "Called-Station-Id", callee }, + { "h323-conf-id", h323_cid.StringBody() }, + { "call-id", sip_cid.StringBody() }, + { "h323-remote-address", remote_ip.String() }, + { "h323-session-protocol", "sipv2"}, + }...) + attributes = append(attributes, extra_attributes...) + message := "sending AAA request:\n" + for _, attr := range attributes { + message += fmt.Sprintf("%-32s = '%s'\n", attr.name, attr.value) + } + self.global_config.SipLogger().Write(nil, sip_cid.StringBody(), message) + return self.radius_client.do_auth(attributes, func(results *RadiusResult) { self._process_result(results, res_cb, sip_cid.StringBody(), time.Now()) }) +} + +func (self *RadiusAuthorisation) _process_result(results *RadiusResult, res_cb func(*RadiusResult), sip_cid string, btime time.Time) { + var message string + + delay := time.Now().Sub(btime) + if results != nil && results.Rcode == 0 || results.Rcode == 1 { + if results.Rcode == 0 { + message = fmt.Sprintf("AAA request accepted (delay is %.3f), processing response:\n", delay.Seconds()) + } else { + message = fmt.Sprintf("AAA request rejected (delay is %.3f), processing response:\n", delay.Seconds()) + } + for _, res := range results.Avps { + message += fmt.Sprintf("%-32s = '%s'\n", res.name, res.value) + } + } else { + message = fmt.Sprintf("Error sending AAA request (delay is %.3f)\n", delay.Seconds()) + } + self.global_config.SipLogger().Write(nil, sip_cid, message) + res_cb(results) +} diff --git a/cmd/b2bua_radius/radius_client.go b/cmd/b2bua_radius/radius_client.go index 80422c4..94eef95 100644 --- a/cmd/b2bua_radius/radius_client.go +++ b/cmd/b2bua_radius/radius_client.go @@ -1,6 +1,6 @@ // // Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved. -// Copyright (c) 2006-2014 Sippy Software, Inc. All rights reserved. +// Copyright (c) 2006-2024 Sippy Software, Inc. All rights reserved. // // All rights reserved. // @@ -25,58 +25,108 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package main -/* -from External_command import External_command -class Radius_client(External_command): - global_config = nil - _avpair_names = ('call-id', 'h323-session-protocol', 'h323-ivr-out', 'h323-incoming-conf-id', \ - 'release-source', 'alert-timepoint', 'provisional-timepoint') - _cisco_vsa_names = ('h323-remote-address', 'h323-conf-id', 'h323-setup-time', 'h323-call-origin', \ - 'h323-call-type', 'h323-connect-time', 'h323-disconnect-time', 'h323-disconnect-cause', \ - 'h323-voice-quality', 'h323-credit-time', 'h323-return-code', 'h323-redirect-number', \ - 'h323-preferred-lang', 'h323-billing-model', 'h323-currency') +import ( + "fmt" + "strings" +) - def __init__(self, global_config = {}): - self.global_config = global_config - command = global_config.getdefault('radiusclient', '/usr/local/sbin/radiusclient') - config = global_config.getdefault('radiusclient.conf', nil) - max_workers = global_config.getdefault('max_radiusclients', 20) - if config != nil: - External_command.__init__(self, (command, '-f', config, '-s'), max_workers = max_workers) - else: - External_command.__init__(self, (command, '-s'), max_workers = max_workers) +type RadiusClient struct { + external_command *ExternalCommand + _avpair_names map[string]bool + _cisco_vsa_names map[string]bool +} - def _prepare_attributes(self, type, attributes): - data = [type] - for a, v in attributes: - if a in self._avpair_names: - v = '%s=%s' % (str(a), str(v)) - a = 'Cisco-AVPair' - elif a in self._cisco_vsa_names: - v = '%s=%s' % (str(a), str(v)) - data.append('%s="%s"' % (str(a), str(v))) - return data +func NewRadiusClient(global_config *myConfigParser) *RadiusClient { + var external_command *ExternalCommand - def do_auth(self, attributes, result_callback, *callback_parameters): - return External_command.process_command(self, self._prepare_attributes('AUTH', attributes), result_callback, *callback_parameters) + if global_config.Radiusclient_conf != "" { + external_command = newExternalCommand(global_config.Max_radius_clients, global_config.ErrorLogger(), global_config.Radiusclient, "-f", global_config.Radiusclient_conf, "-s") + } else { + external_command = newExternalCommand(global_config.Max_radius_clients, global_config.ErrorLogger(), global_config.Radiusclient, "-s") + } + return &RadiusClient{ + external_command : external_command, + _avpair_names : map[string]bool { + "call-id" : true, + "h323-session-protocol" : true, + "h323-ivr-out" : true, + "h323-incoming-conf-id" : true, + "release-source" : true, + "alert-timepoint" : true, + "provisional-timepoint" : true, + }, + _cisco_vsa_names : map[string]bool { + "h323-remote-address" : true, + "h323-conf-id" : true, + "h323-setup-time" : true, + "h323-call-origin" : true, + "h323-call-type" : true, + "h323-connect-time" : true, + "h323-disconnect-time" : true, + "h323-disconnect-cause" : true, + "h323-voice-quality" : true, + "h323-credit-time" : true, + "h323-return-code" : true, + "h323-redirect-number" : true, + "h323-preferred-lang" : true, + "h323-billing-model" : true, + "h323-currency" : true, + }, + } +} - def do_acct(self, attributes, result_callback = nil, *callback_parameters): - External_command.process_command(self, self._prepare_attributes('ACCT', attributes), result_callback, *callback_parameters) +func (self *RadiusClient) _prepare_attributes(typ string, attributes []RadiusAttribute) []string { + data := []string{ typ } + var a, v string + for _, attr := range attributes { + if _, ok := self._avpair_names[attr.name]; ok { + v = fmt.Sprintf("%s=%s", attr.name, attr.value) + a = "Cisco-AVPair" + } else if _, ok := self._cisco_vsa_names[attr.name]; ok { + v = fmt.Sprintf("%s=%s", attr.name, attr.value) + a = attr.name + } else { + a = attr.name + v = attr.value + } + data = append(data, fmt.Sprintf("%s=\"%s\"", a, v)) + } + return data +} - def process_result(self, result_callback, result, *callback_parameters): - if result_callback == nil: - return - nav = [] - for av in result[:-1]: - a, v = [x.strip() for x in av.split(' = ', 1)] - v = v.strip('\'') - if (a == 'Cisco-AVPair' or a in self._cisco_vsa_names): - t = v.split('=', 1) - if len(t) > 1: - a, v = t - elif v.startswith(a + '='): - v = v[len(a) + 1:] - nav.append((a, v)) - External_command.process_result(self, result_callback, (tuple(nav), int(result[-1])), *callback_parameters) -*/ +func (self *RadiusClient) do_auth(attributes []RadiusAttribute, result_callback func(results *RadiusResult)) Cancellable { + return self.external_command.process_command(self._prepare_attributes("AUTH", attributes), func(results []string) { self.process_result(results, result_callback) }) +} + +func (self *RadiusClient) do_acct(attributes []RadiusAttribute, result_callback func(results *RadiusResult) /*= nil*/) { + self.external_command.process_command(self._prepare_attributes("ACCT", attributes), func (results []string) { self.process_result(results, result_callback) }) +} + +func (self *RadiusClient) process_result(results []string, result_callback func(*RadiusResult)) { + if result_callback == nil { + return + } + result := NewRadiusResult() + if len(results) > 0 { + for _, r := range results[:len(results)-1] { + av := strings.SplitN(r, " = ", 2) + if len(av) != 2 { + continue + } + attr := av[0] + val := strings.Trim(av[1], "'") + if _, ok := self._cisco_vsa_names[attr]; ok || attr == "Cisco-AVPair" { + av = strings.SplitN(val, "=", 2) + if len(av) > 1 { + attr = av[0] + val = av[1] + } + } else if strings.HasPrefix(val, attr + "=") { + val = val[len(attr) + 1:] + } + result.Avps = append(result.Avps, RadiusAttribute{ attr, val }) + } + } + result_callback(result) +} diff --git a/cmd/b2bua_radius/radius_result.go b/cmd/b2bua_radius/radius_result.go new file mode 100644 index 0000000..31b7a6f --- /dev/null +++ b/cmd/b2bua_radius/radius_result.go @@ -0,0 +1,38 @@ +// +// Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved. +// Copyright (c) 2024 Sippy Software, Inc. All rights reserved. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +package main + +type RadiusResult struct { + Rcode int + Avps []RadiusAttribute +} + +func NewRadiusResult() *RadiusResult { + return &RadiusResult{ + Avps : make([]RadiusAttribute, 0), + } +} diff --git a/cmd/b2bua_radius/re_replace.go b/cmd/b2bua_radius/re_replace.go new file mode 100644 index 0000000..ce397cf --- /dev/null +++ b/cmd/b2bua_radius/re_replace.go @@ -0,0 +1,92 @@ +// +// Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved. +// Copyright (c) 2006-2024 Sippy Software, Inc. All rights reserved. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +package main + +import ( + "strings" + "regexp" +) + +type re_sub_once struct { + done bool + repl string +} + +func new_re_sub_once(repl string) *re_sub_once { + return &re_sub_once{ + done : false, + repl : repl, + } +} + +func (self *re_sub_once) repl_fn(s string) string { + if self.done { + return s + } + self.done = true + return self.repl +} + +func re_replace(ptrn, s string) (string, error) { + s = strings.SplitN(s, "#", 2)[0] + ptrn_arr := strings.Split(ptrn, "/") + for len(ptrn_arr) >= 4 { + p := ptrn_arr[1] + r := ptrn_arr[2] + mod := ptrn_arr[3] + mod = strings.TrimSpace(mod) + if len(mod) > 0 && mod[0] != ';' { + ptrn_arr[3] = mod[1:] + mod = strings.ToLower(mod[:1]) + } else { + ptrn_arr[3] = mod + } + global_replace := false + for _, c := range mod { + if c == 'g' { + global_replace = true + break + } + } + re, err := regexp.CompilePOSIX(p) + if err != nil { + return s, err + } + if global_replace { + s = re.ReplaceAllString(s, r) + } else { + re_sub := new_re_sub_once(r) + s = re.ReplaceAllStringFunc(s, re_sub.repl_fn) + } + if len(ptrn_arr) == 4 && ptrn_arr[3] == "" { + break + } + ptrn_arr = ptrn_arr[3:] + } + return s, nil +} + diff --git a/cmd/b2bua_radius/re_replace_test.go b/cmd/b2bua_radius/re_replace_test.go new file mode 100644 index 0000000..dbc3af3 --- /dev/null +++ b/cmd/b2bua_radius/re_replace_test.go @@ -0,0 +1,24 @@ +package main + +import ( + "testing" +) + +func Test_re_replace(t *testing.T) { + test_cases := []struct{ inp string; ptrn string; expect string }{ + { "12345671234567", "s/1/_/", "_2345671234567" }, + { "12345671234567", "s/1/_/g", "_234567_234567" }, + { "12345671234567", "s/1/_/g;s/2/#/", "_#34567_234567" }, + { "12345671234567", "s/1/_/;s/2/#/g", "_#345671#34567" }, + } + for _, tc := range test_cases { + res, err := re_replace(tc.ptrn, tc.inp) + if err != nil { + t.Fatalf("Error applying '%s' to '%s': %s", tc.ptrn, tc.inp, err.Error()) + } else if res != tc.expect { + t.Fatalf("Expected '%s', got '%s'", tc.expect, res) + } else { + t.Logf("ok: '%s' %% '%s' -> '%s'", tc.inp, tc.ptrn, res) + } + } +} diff --git a/sippy/headers/sip_authorization.go b/sippy/headers/sip_authorization.go index 93e2cc3..d3d2ed2 100644 --- a/sippy/headers/sip_authorization.go +++ b/sippy/headers/sip_authorization.go @@ -162,10 +162,26 @@ func (self *SipAuthorizationBody) GetCopy() *SipAuthorizationBody { return &rval } +func (self *SipAuthorizationBody) GetNonce() string { + return self.nonce +} + +func (self *SipAuthorizationBody) GetRealm() string { + return self.realm +} + +func (self *SipAuthorizationBody) GetResponse() string { + return self.response +} + func (self *SipAuthorizationBody) GetUsername() string { return self.username } +func (self *SipAuthorizationBody) GetUri() string { + return self.uri +} + func (self *SipAuthorizationBody) Verify(passwd, method, entity_body string) bool { alg := sippy_security.GetAlgorithm(self.algorithm) if alg == nil {