; verification.conf - Verification subroutines ; v 3.0.10 (2022/07/26) ; *** As of 2021/12/16, verification.conf (verification subroutines) has been mostly ; supplanted by app_verify (see https://docs.phreaknet.org/#verification). The ; verification subroutines are not deprecated, however, and remain fully supported. ; The verification subroutines will automatically use the newer modules ; if available. ;;;;;;;;;;;;;;;;;;;;;; PhreakNet Verification Subroutines ;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;; NOTICE: Do not make modifications to this file! ;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;; QUESTIONS: Direct to PhreakNet Business Office ;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;; USAGE: Merely including this in your dialplan will NOT help you! ;;;;; ;;;;;;;;;; You must LEVERAGE the tools below if they are to be useful! ;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Required Variables: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;[globals] ; add to your [globals] dialplan context, if they're not already there ;clli= ; Your 11-char Bell System format CLLI code. ;interlinkedkey= ; PhreakNet: InterLinked API key ;mainphreaknetdisa=5551212 ; PhreakNet only: this should be a PhreakNet # on your node ; ** Other Variables ** ;maincnetdisa=15551212 ; C*NET only: this should be a C*NET # on your node ;npstnkey= ; NPSTN only: your 32-character NPSTN auth key ;mainnpstndisa=5551212 ; NPSTN only: this should be an NPSTN # on your node ;;;;;;;;;;;;;;;;;;;;;;;;;;; *** Verification SRs *** ;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; CNAM Subroutines [disa-rewrite-cnam] ; 20201203 NA, updated 20210915 ; rewrites CNAM if not already tagged exten => s,1,Set(LOCAL(lowered)=${TOLOWER("${CALLERID(name)}")}) same => n,GotoIf($[${REGEX(" pstn" ${lowered})}]?pstn) same => n,GotoIf($[${REGEX(" gateway" ${lowered})}|${REGEX(" disa" ${lowered})}|${REGEX(" via " ${lowered})}]?disa) same => n,ExecIf($[$["${clidverif:-2:1}"="1"]&${EXISTS(${npstnclli})}]?Set(CALLERID(name)=${CALLERID(name)} via ${npstnclli})) same => n,ExecIf($["${clidverif:-2:1}"="3"]?Set(CALLERID(name)=${CALLERID(name)} via ${clli} US PSTN)) same => n,ExecIf($["${clidverif:-2:1}"="4"]?Set(CALLERID(name)=${CALLERID(name)} via ${clli} UK PSTN)) same => n,ExecIf($["${clidverif:-2:1}"="5"]?Set(CALLERID(name)=${CALLERID(name)} via ${clli} AU PSTN)) same => n,ExecIf($["${clidverif:-2:1}"="6"]?Set(CALLERID(name)=${CALLERID(name)} via ${clli} ${countrycode} PSTN)) same => n,ExecIf($[$["${clidverif:-2:1}"="7"]&${EXISTS(${phreaknetclli})}]?Set(CALLERID(name)=${CALLERID(name)} via ${phreaknetclli})) same => n,GotoIf(${REGEX(" PSTN" "${CALLERID(name)}")}?pstn) ; Did we just rewrite CNAM for PSTN? same => n(rewrite),Set(CALLERID(name)=${CALLERID(name)} via ${clli}) ; via ${clli} DISA has been deprecated in favor of via ${clli} (DISA is redundant) same => n,Return(0) ;;;;;;;;;;;;;;;;;;;; Return NONE code same => n(disa),Return(1) ;;;;; Return via CLLI (formerly DISA) code same => n(pstn),Return(2) ;;;;; Return PSTN code ;;; Outgoing verification [phreaknet-verify] ; 20210629 NA, updated 20211212 ; to be called immediately on all [from-phreaknet] calls. ARG1 = # called, ARG2 = # to verify against. exten => s,1,GotoIf($[${EXISTS(${ARG2})}&${EXISTS(${IAXVAR(clidverif)})}]?reject,1) ; if we're verifying against a specific #, this MUST be a direct call. same => n,GotoIf($[${IFMODULE(app_verify.so)}&${EXISTS(${AST_CONFIG(verify.conf,phreaknet,verifymethod,0)})}&${IFMODULE(app_keyprefetch)}]?:old) ; try to use modules if possible. same => n,Verify(phreaknet,${ARG2}) same => n,GotoIf($[${EXISTS(${ARG2})}&$["${clidverif}"!="70"]]?reject,1) ; if we're verifying against a specific number, it must be a network number. same => n,KeyPrefetch(phreaknet,phreaknet-rsa-${FILTER(A-Za-z0-9\x2D\x2E,${fqdn})},https://api.phreaknet.org/v1/rsa?key=${interlinkedkey}&fqdn=${fqdn}&nodevia=${IAXVAR(nodevia)}) same => n,Return() same => n(old),Set(CURLOPT(conntimeout)=10) ; wait up to 10 seconds. In the unlikely event no response received, we don't hang. If your Internet connection is REALLY slow, you may need to increase this. same => n,Set(LOCAL(lookup)=${CURL(https://api.phreaknet.org/v1/?key=${interlinkedkey}&asterisk=${VERSION()}&asteriskv=${VERSION(ASTERISK_VERSION_NUM)}&called=${ARG1}&number=${IF(${EXISTS(${ARG2})}?${ARG2}:${IAXVAR(nodevia)})}&ip=${CHANNEL(peerip)}&clid=${CALLERID(num)}&ani2=${CALLERID(ani2)}&cnam=${STRREPLACE(FILTER(0-9A-Za-z \x20\x2C\x2D\x2E,${CALLERID(name)}), ,+)}&cvs=${IAXVAR(clidverif)}&threshold=2.4&token=${IAXVAR(vertoken)}&verify)}) same => n,Set(LOCAL(cvs)=${CUT(lookup,~,1)}) same => n,Set(LOCAL(fqdn)=${CUT(lookup,~,2)}) same => n,Set(__clidverif=${IF(${EXISTS(${cvs})}?${cvs}:77)}) ; if API request fails for some reason, assign it code 77 same => n,ExecIf($["${clidverif:-1}"!="0"]?Set(GROUP(spam)=phreaknet)) same => n,Gosub(phreaknet-rsa-prefetch,s,1(${fqdn},${IAXVAR(nodevia)})) same => n,ExecIf($[${LEN(${FILTER(A-D0,${IAXVAR(mlpp)})})}=1]?Set(__mlppdigit=${FILTER(A-D0,${IAXVAR(mlpp)})})) ; MLPP/AUTOVON same => n,ExecIf($[${LEN(${FILTER(A-F,${IAXVAR(ssverstat)})})}=1]?Set(__ssverstat=${FILTER(A-F,${IAXVAR(ssverstat)})})) ; MLPP/AUTOVON same => n,GotoIf($["${clidverif:-1}"="8"|${GROUP_COUNT(phreaknet@spam)}>1]?reject,1) ; return 1 to end the call immediately, 0 if we are okay same => n,GotoIf($[${EXISTS(${ARG2})}&$["${clidverif}"!="70"]]?reject,1) ; if we're verifying against a specific number, it must be a network number. same => n,Return() exten => reject,1,StackPop() ; we're not returning, so end nicely. same => n,Hangup(21) [phreaknet-rsa-prefetch] ; 20210629 NA ; ARG1 = FQDN, ARG2 = 7D# of the node that called us, so we can fetch its RSA public key. exten => s,1,ExecIf(${ISNULL(${ARG1})}?Return) same => n,Set(LOCAL(fqdn)=${FILTER(A-Za-z0-9\x2D\x2E\x3A,${ARG1})}) ; sanitize FQDN: allow alphanumeric, hyphens, periods, and colons only (FQDNs/IPv4/IPv6) same => n,Set(LOCAL(inkey)=phreaknet-rsa-${FILTER(A-Za-z0-9\x2D\x2E,${ARG1})}) ; filename cannot contain the colon, because that is the delimeter same => n,Set(LOCAL(file)=/etc/asterisk/iax-phreaknet-rsa-in.conf) same => n,GotoIf($[${STAT(e,/var/lib/asterisk/keys/${inkey}.pub)}]?:prefetch) ; check each time if key doesn't exist, because it could have just been added same => n,GotoIf($[$[$[${EPOCH}-${STAT(C,/var/lib/asterisk/keys/${inkey}.pub)}]>86400]|$[$[${EPOCH}-${STAT(M,/var/lib/asterisk/keys/${inkey}.pub)}]>86400]]?prefetch) ; if file changed/modified > 24 hrs. ago, refetch. This is frequent enough that stale keys will be refreshed soon enough without causing significant issues, but infrequent enough that we are not constantly pulling keys for no reason. If you have a slow connection, you may want to up 86400 to a higher value. same => n,Return() ; key exists and we're not going to fetch it again right now same => n(prefetch),System(wget "https://api.phreaknet.org/v1/rsa?key=${interlinkedkey}&fqdn=${fqdn}&nodevia=${ARG2}" -O /var/lib/asterisk/keys/${inkey}.pub) ; this will save a file, regardless of whether response was 200 or 404. If 404, we'll delete it immediately. ; key exists now (though it may or may not be legitimate. same => n,GotoIf($[${STAT(s,/var/lib/asterisk/keys/${inkey}.pub)}=0]?missing) ; if we downloaded a dummy key, get rid of it, and no reason to modify configuration same => n,Set(LOCAL(wordcount)=${FILTER(0-9,${SHELL(grep "inkeys" "${file}" | grep -e "=${inkey}" -e ":${inkey}:" -e ":${inkey} " | wc -l)})}) same => n,GotoIf($[${ISNULL(${wordcount})}|"${wordcount}"="0"]?:done) ; key name already listed in inkeys? same => n,Set(LOCAL(inkeys)=${FILTER(A-Za-z0-9\x2D\x2E\x3A,${SHELL(grep "inkeys" "${file}" | cut -d'\;' -f 1 | cut -d'=' -f 2)})}) ; new key, so get list of current keys same => n,GotoIf(${ISNULL(${inkeys})}?init) same => n,System(sed -i 's/${inkeys}/${inkeys}:${inkey}/' ${file}) ; append key to list of inkeys same => n,Goto(done) same => n(init),Set(FILE(${file},,,l,u)=inkeys=phreaknetrsa) ; initialize the file same => n(done),ExecIf(${IFMODULE(app_reload.so)}?Reload(iax2):System(asterisk -rx "iax2 reload")) ; reload IAX2 and crypto keys. same => n,ExecIf(${IFMODULE(app_reload.so)}?Reload(res_crypto):System(asterisk -rx "module reload res_crypto")) same => n,Return() ; note that this subroutine does not flush out stale or obsolete keys over time, but periodically cleaning this file and the keys is okay. same => n(missing),System(rm -f /var/lib/asterisk/keys/${inkey}.pub) ; find /var/lib/asterisk/keys/ -size 0 -delete for cleanup same => n,Return() ; failed to download a key [phreaknet-out-verify] ; ARG1 = lookup exten => s,1,GotoIf($[${IFMODULE(app_verify.so)}&${EXISTS(${AST_CONFIG(verify.conf,phreaknet,verifymethod,0)})}]?:old) ; try to use modules if possible. same => n,OutVerify(phreaknet,${ARG1}) ; you must have the app_verify module and a configured verify.conf same => n,ExecIf($["${OUTVERIFYSTATUS}"!="PROCEED"]?Return(2)) ; protect against channel attacks/bad lookups/local IP attacks. Bail out on bad lookup. same => n,Return() same => n(old),Set(IAXVAR(clidverif)=${clidverif}) same => n,Set(IAXVAR(nodevia)=${mainphreaknetdisa}) ; Lets other nodes know where this call came from so they can verify this node if needed same => n,ExecIf($[${LEN(${FILTER(A-D0,${mlppdigit})})}=1]?Set(IAXVAR(mlpp)=${FILTER(A-D0,${mlppdigit})})) ; MLPP/AUTOVON same => n,ExecIf($[${LEN(${FILTER(A-F,${ssverstat})})}=1]?Set(IAXVAR(ssverstat)=${FILTER(A-F,${ssverstat})})) ; STIR/SHAKEN same => n,GosubIf($[${LEN(${clidverif})}>0]?disa-rewrite-cnam,s,1) same => n,Gosub(lookupchan,s,1(${ARG1})) ; verifies lookup for extra security same => n,ExecIf($["${GOSUB_RETVAL}"="0"]?Return(2)) ; protect against channel attacks. Bail out on bad lookup same => n,Gosub(lookupipcheck,s,1(${ARG1})) ; protect against local IP attacks same => n,ExecIf($["${GOSUB_RETVAL}"!="0"]?Return(2)) ; Bail out on bad lookup same => n,Return() [phreaknet-lookup] ; last updated 20211124, ARG1 = # to call, ARG2 = (optional) trunking flag, ARG3 = (optional) request IP token (1 if your outbound IP does not match inbound IP, empty otherwise) exten => s,1,Set(LOCAL(lookup)=${CURL(https://api.phreaknet.org/v1/?key=${interlinkedkey}&asterisk=${VERSION()}&asteriskv=${VERSION(ASTERISK_VERSION_NUM)}&number=${ARG1}&clid=${CALLERID(num)}&ani2=${CALLERID(ani2)}&cnam=${STRREPLACE(FILTER(0-9A-Za-z \x20\x2C\x2D\x2E,${CALLERID(name)}), ,+)}&cvs=${clidverif}&nodevia=${mainphreaknetdisa}&flags=${ARG2}&threshold=2.1${IF($["${ARG3}"="1"]?&requestkey)})}) same => n,ExecIf($[$[${FIELDQTY(lookup,~)}>1]&${EXISTS(${CUT(lookup,~,2)})}]?Set(IAXVAR(vertoken)=${CUT(lookup,~,1)})) same => n,ExecIf($[$[${FIELDQTY(lookup,~)}>1]&${EXISTS(${CUT(lookup,~,2)})}]?Set(LOCAL(lookup)=${CUT(lookup,~,2)})) ; app_if is more elegant, but can't be relied on being present everywhere... same => n,Return(${CUT(lookup,^,1)},${CUT(lookup,^,2)}) [npstn-out-verify] ; 20190212 NA ; set IAX flags and modifies CNAM if necessary exten => _X!,1,Goto(s,1) ; needed for backwards-compatibility exten => s,1,GotoIf($[${IFMODULE(app_verify.so)}&${EXISTS(${AST_CONFIG(verify.conf,npstn,verifymethod,0)})}]?:old) ; try to use modules if possible. same => n,OutVerify(npstn,${ARG1}) ; you must have the app_verify module and a configured verify.conf same => n,ExecIf($["${OUTVERIFYSTATUS}"!="PROCEED"]?Return(2)) ; protect against channel attacks/bad lookups/local IP attacks. Bail out on bad lookup. same => n,Return() same => n(old),ExecIf($[${LEN(${FILTER(A-D0,${autovonprioritydigit})})}=1]?Set(IAXVAR(npstnmlpp)=${FILTER(A-D0,${autovonprioritydigit})})) ; MLPP/AUTOVON same => n,GosubIf($[${LEN(${clidverif})}>0|"${requestverifytokenonipfail}"="1"]?npstn-set-flags,s,1) same => n,Set(IAXVAR(DISAVIA)=${mainphreaknetdisa}) ; Lets other nodes know where this call came from so they can verify this node if needed (duplicate needed in case npstn-set-flags not called) same => n,GosubIf($[${LEN(${clidverif})}>0]?disa-rewrite-cnam,s,1) same => n,Gosub(npstn-out-allow,s,1(${GOSUB_RETVAL})) same => n,Return(${GOSUB_RETVAL}) ; Return 1 to complete outgoing call, 0 to divert to intercept [cnet-out-verify] ; 20210117 NA ; set IAX flags and modifies CNAM if necessary exten => s,1,GotoIf($[${IFMODULE(app_verify.so)}&${EXISTS(${AST_CONFIG(verify.conf,cnet,verifymethod,0)})}]?:old) ; try to use modules if possible. same => n,OutVerify(cnet,${ARG1}) ; you must have the app_verify module and a configured verify.conf same => n,ExecIf($["${OUTVERIFYSTATUS}"!="PROCEED"]?Return(2)) ; protect against channel attacks/bad lookups/local IP attacks. Bail out on bad lookup. same => n,Return() same => n(old),Set(IAXVAR(nodevia)=${maincnetdisa}) ; Lets other nodes know where this call came from so they can verify this C*NET node if necessary same => n,GosubIf($[${LEN(${clidverif})}>0]?cnet-set-flags,s,1) same => n,GosubIf($[${LEN(${clidverif})}>0]?disa-rewrite-cnam,s,1) same => n,Gosub(npstn-out-allow,s,1(${GOSUB_RETVAL})) ; optional, and not NPSTN-specific same => n,Return(${GOSUB_RETVAL}) ; Return 1 to complete outgoing call, 0 to divert to intercept [npstn-set-flags] exten => s,1,Set(IAXVAR(clidverif)=${clidverif}) same => n,Set(IAXVAR(DISAVIA)=${mainnpstndisa}) ; Lets other nodes know where this call came from so they can verify this NPSTN node if necessary same => n,GotoIf($["${requestverifytokenonipfail}"="1"]?:done) same => n,Set(IAXVAR(npstnverifytoken)=${SHELL(curl "https://crtc.npstn.us/api/v1/verification/temptoken.php?auth=${npstnkey}&disavia=${mainnpstndisa}")}) same => n(done),Return() [cnet-set-flags] exten => s,1,Set(IAXVAR(clidverif)=${clidverif}) same => n,Set(IAXVAR(nodevia)=${maincnetdisa}) ; Lets other nodes know where this call came from so they can verify this C*NET node if necessary same => n,Return() [npstn-blacklist-check] exten => s,1,Return(${FILTER(0-9\x2E,${SHELL(curl "https://npstn.us/api/v1/blacklist/?get&auth=${npstnkey}&cvs=${clidverif}&number=${CALLERID(num)}")})}) [npstn-out-allow] ; 20190215 NA ; determine whether outgoing call should be allowed to complete exten => s,1,Gosub(npstn-blacklist-check,s,1) same => n,GotoIf(${ISNULL(${GOSUB_RETVAL})}?term) same => n,GotoIf($[${GOSUB_RETVAL}>=2]?term) same => n(reg),Set(LOCAL(cnamcode)=${ARG1}) ; 0=CNAM just modified,1=CNAM received tagged as via DISA,2=CNAM received tagged as via PSTN same => n,GotoIf($["${cnamcode}"="1"]?disa) same => n,GotoIf($["${cnamcode}"="2"]?disa) same => n,Return(1) ; if cnamcode=0 (CNAM just modified), allow outgoing call to complete regardless same => n(disa),GotoIf($["${allowdisathru}"="NO"]?term) ; allowdisathru global var. must be set to YES/NO per NPSTN Docs same => n,Return(1) ; if cnamcode=1 (CNAM received as via DISA), allow outgoing call if global var. allowdisathru=YES same => n(pstn),GotoIf($["${allowpstnthru}"="NO"]?term) ; allowpstnthru global var. must be set to YES/NO per NPSTN Docs same => n(term),Return(0) ; call should not be allowed to leave this switch [npstn-out-blocked] ; 20190215 NA ; destination for calls that are not allowed to leave this node ; updated 20201214 to check for existence exten => s,1,PlayTones(congestion) same => n,Wait(5) same => n,Hangup(21) [lookupchan] ; 20190316 NA, updated 20210928 ; analyzes lookup request and returns 0 if it is not a supported channel ; ARG1 = lookup exten => s,1,ExecIf($["${TOUPPER(${ARG1:0:4})}"="IAX2"]?Return(${REGEX("^[iI][aA][xX]2\/[a-zA-Z0-9_\-]+(:[a-zA-Z0-9]+)?+(:\[[a-zA-Z0-9]+\])?+@([a-zA-Z0-9_\-]+\.)+[a-zA-Z0-9_\-]+\/([A-Za-z0-9*#^:\(\)])+(@[a-zA-Z0-9_\-]+)?$" ${ARG1})})) ; J. Covert: ensures there is at least 1 "." in the host (that it isn't a reference to a local peer with username/password) ; updated 20210125 NA to include : to allow for IAX2 passwords. updated 20210702 NA to allow context to be specified in dial string + more rigorous user:secret validaton same => n,ExecIf($["${TOUPPER(${ARG1:0:3})}"="SIP"]?Return(2)) ; don't further check the destination if it's SIP same => n,ExecIf($["${TOUPPER(${ARG1:0:4})}"="MGCP"]?Return(3)) same => n,ExecIf($["${TOUPPER(${ARG1:0:4})}"="H323"]?Return(4)) same => n(bad),Return(0) ; allowed channel technologies are IAX, SIP, MGCP, and H323 [lookupipcheck] ; 20201006 NA ; returns 0 if an IP address is public ; ARG1 = lookup exten => s,1,Gosub(isitip,s,1(${ARG1})) same => n,Set(ARRAY(iaxisip,ip)=${GOSUB_RETVAL}) same => n,GotoIf(${iaxisip}?checkip) same => n,Gosub(fqdn2ip,s,1(${ARG1})) same => n,Set(ARRAY(ds1,ip)=${GOSUB_RETVAL}) same => n(checkip),Return(${REGEX("^(127\.0\.0\.1|10(\.(25[0-5]|2[0-4][0-9]|1[0-9]{1,2}|[0-9]{1,2})){3}|((172\.(1[6-9]|2[0-9]|3[01]))|192\.168)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{1,2}|[0-9]{1,2})){2})$" ${ARG1})}) ; updated 20210709 to use REGEX: https://stackoverflow.com/a/44333761 ;;; Incoming verification [pstn-us-verify] ; 20190212 NA ; to be called immediately on any incoming call from a US PSTN DID exten => s,1,GotoIf($[${IFMODULE(app_verify.so)}&${EXISTS(${AST_CONFIG(verify.conf,pstn-us,verifymethod,0)})}]?:old) ; try to use modules if possible. same => n,Verify(pstn-us) same => n,Return() same => n(old),Set(CALLERID(num)=${FILTER(0-9A-Za-z,${CALLERID(num)})}) same => n,Gosub(anonymous-check,s,1) same => n,GotoIf($[${GOSUB_RETVAL}=0]?anonymous:checklen) same => n(checklen),GotoIf($[${LEN(${FILTER(0-9,${CALLERID(num)})})}<10]?invalid) same => n,Gosub(pstn-us-format-lookup,${FILTER(0-9,${CALLERID(num)}):-10},1) same => n,GotoIf($["${GOSUB_RETVAL}"="0"]?invalid) same => n(valid),Set(__clidverif=30) same => n,Gosub(stir-shaken-result-verify,s,1) same => n,ExecIf(${EXISTS(${GOSUB_RETVAL})}?Set(__ssverstat=${GOSUB_RETVAL})) same => n,Goto(done) same => n(invalid),Set(__clidverif=31) same => n,Goto(done) same => n(anonymous),Set(__clidverif=32) same => n,Goto(done) same => n(done),Return() [stir-shaken-result-analyze] ; 20220718 NA exten => s,1,ExecIf(${ISNULL(${ARG1})}?Return(E)) same => n,ExecIf($["${ARG1:0:20}"="TN-Validation-Passed"]?Return(A)) ; TN-Validation-Passed = A same => n,ExecIf($["${ARG1:0:22}"="TN-Validation-Passed-A"]?Return(A)) same => n,ExecIf($["${ARG1:0:22}"="TN-Validation-Passed-B"]?Return(B)) same => n,ExecIf($["${ARG1:0:22}"="TN-Validation-Passed-C"]?Return(C)) same => n,ExecIf($["${ARG1:0:16}"="No-TN-Validation"]?Return(D)) same => n,ExecIf($["${ARG1:0:20}"="TN-Validation-Failed"]?Return(F)) same => n,Log(WARNING,Unexpected verstat parameter value: ${ARG1}) same => n,Return(E) ; treat as empty [stir-shaken-result-verify] ; 20220718 NA exten => s,1,ExecIf($["${CHANNEL:0:6}"!="PJSIP/"]?Return) ; PJSIP only. And don't use ReturnIf in case a system doesn't have it. ; Standard location for STIR/SHAKEN result is the verstat parameter in the From or PAI headers. same => n,Set(UNSHIFT(LOCAL(from),\;)=${PJSIP_HEADER(read,From,1)}) ; https://www.carrierx.com/documentation/how-it-works/stir-shaken same => n,While(${EXISTS(${SET(LOCAL(param)=${SHIFT(from,\;)})})}) ; iterate through parameters same => n,ExecIf($[!${REGEX("verstat" ${param})}]?ContinueWhile) ; sanity check to prevent expression warning with quotes same => n,ExecIf($["${param:0:7}"!="verstat"]?ContinueWhile) ; not our guy same => n,Gosub(stir-shaken-result-analyze,s,1(${param:8})) same => n,Return(${GOSUB_RETVAL}) same => n,EndWhile() same => n,Set(UNSHIFT(LOCAL(pai),\;)=${PJSIP_HEADER(read,P-Asserted-Identity,1)}) same => n,While(${EXISTS(${SET(LOCAL(param)=${SHIFT(pai,\;)})})}) ; iterate through parameters same => n,ExecIf($[!${REGEX("verstat" ${param})}]?ContinueWhile) ; sanity check to prevent expression warning with quotes same => n,ExecIf($["${param:0:7}"!="verstat"]?ContinueWhile) ; not our guy same => n,Gosub(stir-shaken-result-analyze,s,1(${param:8})) same => n,Return(${GOSUB_RETVAL}) same => n,EndWhile() same => n,Return(E) [pstn-us-format-lookup] ; 20210109 NA exten => _NXXNXXXXXX,1,Return(1) exten => _X!,1,Return(0) exten => _[01]XXXXXXXXX,1,Return(0) exten => _XXX[01]XXXXXX,1,Return(0) exten => _[2-79]00NXXXXXX,1,Return(0) exten => _[2-79]22NXXXXXX,1,Return(0) exten => _[2-79]33NXXXXXX,1,Return(0) exten => _[2-79]44NXXXXXX,1,Return(0) exten => _[2-79]55NXXXXXX,1,Return(0) exten => _[2-79]66NXXXXXX,1,Return(0) exten => _[2-79]77NXXXXXX,1,Return(0) exten => _[2-79]88NXXXXXX,1,Return(0) exten => _[2-79]99NXXXXXX,1,Return(0) exten => _X11XXXXXXX,1,Return(0) [pstn-us-verify-patterns] ; Used by app_verify exten => _[A-Za-z]!,1,Return(32) exten => _X!,1,Return(31) exten => _NXXNXXXXXX,1,Return(30) exten => _1NXXNXXXXXX,1,Return(30) exten => _N00NXXXXXX,1,Return(31) exten => _1N00NXXXXXX,1,Return(31) exten => _800NXXXXXX,1,Return(30) exten => _1800NXXXXXX,1,Return(30) exten => _[01]XXXXXXXXX,1,Return(31) exten => _XXX[01]XXXXXX,1,Return(31) exten => _[2-79]00NXXXXXX,1,Return(31) exten => _[2-79]22NXXXXXX,1,Return(31) exten => _[2-79]33NXXXXXX,1,Return(31) exten => _[2-79]44NXXXXXX,1,Return(31) exten => _[2-79]55NXXXXXX,1,Return(31) exten => _[2-79]66NXXXXXX,1,Return(31) exten => _[2-79]77NXXXXXX,1,Return(31) exten => _[2-79]88NXXXXXX,1,Return(31) exten => _[2-79]99NXXXXXX,1,Return(31) exten => _X11XXXXXXX,1,Return(31) [pstn-uk-verify] ; 20190212 NA ; to be called immediately on any incoming call from a UK PSTN DID exten => s,1,GotoIf($[${IFMODULE(app_verify.so)}&${EXISTS(${AST_CONFIG(verify.conf,pstn-uk,verifymethod,0)})}]?:old) ; try to use modules if possible. same => n,Verify(pstn-uk) same => n,Return() same => n(old),Set(CALLERID(num)=${FILTER(0-9A-Za-z,${CALLERID(num)})}) same => n,Gosub(anonymous-check,s,1) same => n,GotoIf($[${GOSUB_RETVAL}=0]?anonymous:checklen) same => n(checklen),GotoIf($[${LEN(${CALLERID(num)})}<8]?invalid) ; minimum acceptable UK CLID length assumed to be 8 same => n(valid),Set(__clidverif=40) same => n,Goto(done) same => n(invalid),Set(__clidverif=41) same => n,Goto(done) same => n(anonymous),Set(__clidverif=42) same => n,Goto(done) same => n(done),Return() [pstn-au-verify] ; 20200122 NA ; to be called immediately on any incoming call from a AU PSTN DID exten => s,1,GotoIf($[${IFMODULE(app_verify.so)}&${EXISTS(${AST_CONFIG(verify.conf,pstn-au,verifymethod,0)})}]?:old) ; try to use modules if possible. same => n,Verify(pstn-au) same => n,Return() same => n(old),Set(CALLERID(num)=${FILTER(0-9A-Za-z,${CALLERID(num)})}) same => n,Gosub(anonymous-check,s,1) same => n,GotoIf($[${GOSUB_RETVAL}=0]?anonymous:checklen) same => n(checklen),GotoIf($[${LEN(${CALLERID(num)})}<10]?invalid) ; minimum acceptable AU CLID is 10 same => n,GotoIf($["${CALLERID(num):-9:1}"="6"]?invalid) ; AU numbers don't start with 06 or 09 or 01 same => n,GotoIf($["${CALLERID(num):-9:1}"="9"]?invalid) same => n,GotoIf($["${CALLERID(num):-9:1}"="1"]?invalid) same => n,GotoIf($["${CALLERID(num):-10:1}"="0"]?valid:invalid) ; AU numbers start with a 0 (10th last digit) same => n(valid),Set(__clidverif=50) same => n,Goto(done) same => n(invalid),Set(__clidverif=51) same => n,Goto(done) same => n(anonymous),Set(__clidverif=52) same => n,Goto(done) same => n(done),Return() ; NOTICE: IF USING THIS SUBROUTINE ANYWHERE!: you MUST define a global variable named countrycode which contains your 2-character country code! [pstn-other-verify] ; 20200122 NA ; to be called immediately on any incoming call from a PSTN DID outside the US, UK, or AU exten => s,1,GotoIf($[${IFMODULE(app_verify.so)}&${EXISTS(${AST_CONFIG(verify.conf,pstn-${countrycode},verifymethod,0)})}]?:old) ; try to use modules if possible. same => n,Verify(pstn-${countrycode}) same => n,Return() same => n(old),Set(CALLERID(num)=${FILTER(0-9A-Za-z,${CALLERID(num)})}) same => n,Gosub(anonymous-check,s,1) same => n,GotoIf($[${GOSUB_RETVAL}=0]?anonymous:checklen) same => n(checklen),GotoIf($[${LEN(${CALLERID(num)})}<8]?invalid) ; minimum acceptable CLID length, in general, assumed to be 7 same => n(valid),Set(__clidverif=60) same => n,Goto(done) same => n(invalid),Set(__clidverif=61) same => n,Goto(done) same => n(anonymous),Set(__clidverif=62) same => n,Goto(done) same => n(done),Return() [anonymous-check] ; 20190212 NA, updated 20210906 ; returns 0 if the CLID is likely private/anonymous/withheld/blocked/restricted/etc. ; otherwise returns 1 exten => s,1,ExecIf(${ISNULL(${FILTER(0-9,${CALLERID(num)})})}?Return(0)) same => n,Set(LOCAL(cnum)=${CALLERID(num)}) same => n,Return($[!$["${TOLOWER(${cnum}):0:9}"="anonymous"|"${TOLOWER(${cnum}):0:7}"="private"|"${TOLOWER(${cnum}):0:10}"="restricted"|"${TOLOWER(${cnum}):0:8}"="withheld"|"${TOLOWER(${cnum}):0:8}"="unknown"|${ISNULL(${FILTER(0-9,${cnum})})}]]) [cnet-verify] ; 20190212 NA ; to be called immediately on all [from-cnet] calls exten => _X!,1,Goto(s,1) ; backwards-compatibility exten => s,1,GotoIf($[${IFMODULE(app_verify.so)}&${EXISTS(${AST_CONFIG(verify.conf,cnet,verifymethod,0)})}]?:old) ; try to use modules if possible. same => n,Verify(cnet) same => n,Return() same => n(old),Set(CALLERID(num)=${FILTER(0-9A-Za-z,${CALLERID(num)})}) same => n,Set(LOCAL(fromnum)=${IAXVAR(nodevia)}) ; this should be a C*NET network # on the upstream node, with full country code + number same => n,Set(LOCAL(clidverified)=${IAXVAR(clidverif)}) same => n,GotoIf($[${LEN(${clidverified})}=2]?verifynode) ; Should the node reporting the caller's verification status be trusted? (Confirm it's a valid C*NET node telling us, not a rogue server) same => n,Gosub(cnet-verifyclid,${EXTEN},1(${CALLERID(num)})) same => n,Set(LOCAL(valid)=${GOSUB_RETVAL}) same => n,GotoIf(${valid}?valid:invalid) same => n(verifynode),Gosub(cnet-verifyclid,s,1(${fromnum})) ; Verify the C*NET node through which the C*NET call is passing same => n,Set(LOCAL(valid)=${GOSUB_RETVAL}) same => n,GotoIf(${valid}?judgedvalid:judgedinvalid) same => n(valid),Set(__clidverif=20) same => n,Return() same => n(invalid),Set(__clidverif=21) same => n,Set(GROUP(spam)=cnet) same => n,Return() same => n(judgedvalid),Set(__clidverif=${clidverified}) same => n,Return() same => n(judgedinvalid),Set(__clidverif=29) same => n,Return() [cnet-verifyclid] ; 20190218 NA exten => s,1,NoOp() same => n(ewr),Gosub(cnet-reverse-iax,s,1(${ARG1})) same => n,Set(ARRAY(LOCAL(calleriax),LOCAL(cnumvalid))=${GOSUB_RETVAL}) same => n,GotoIf(${cnumvalid}?:invalid) same => n,GotoIf(${ISNULL(${calleriax})}?invalid) same => n,Gosub(isitip,s,1(${calleriax})) same => n,Set(ARRAY(iaxisip,calleriplookup)=${GOSUB_RETVAL}) same => n,GotoIf(${iaxisip}?getpeer) same => n,Gosub(fqdn2ip,s,1(${calleriax})) same => n,Set(ARRAY(calleriaxip,calleriplookup)=${GOSUB_RETVAL}) same => n(getpeer),Gosub(getpeerip,s,1) same => n,Set(LOCAL(ipactual)=${GOSUB_RETVAL}) ;same => n,GotoIf($[${ipactual}=1]?:invalid) ; commented out to allow for cnet-exceptions same => n,GotoIf(${ISNULL(${FILTER(0-9,${CALLERID(num)})})}]?ipcomp) ; no Caller ID? Then can't verify using exception. same => n,Gosub(cnet-exceptions,${FILTER(0-9,${CALLERID(num)})},1(${ipactual})) same => n,Set(LOCAL(match)=${GOSUB_RETVAL}) same => n,GotoIf($["${match}"="1"}]?done) same => n(ipcomp),Gosub(ipcomp,s,1(${calleriplookup},${ipactual})) same => n,Set(LOCAL(match)=${GOSUB_RETVAL}) same => n(done),Return(${match}) same => n(invalid),Return(0) [cnet-exceptions] ; C*NET does not support verification tokens as PhreakNet does, so this is a hardcoded list of IPs to trust for certain number blocks. Last updated 20211209. exten => _[A-Za-z0-9]!,1,Return(0) exten => _1488XXXX,1,Gosub(ipcomp,s,1(199.199.196.82,${ARG1})) same => n,Return(${GOSUB_RETVAL}) exten => _1596XXXX,1,Gosub(ipcomp,s,1(45.32.212.173,${ARG1})) same => n,Return(${GOSUB_RETVAL}) [dialnpstn-rsa] ; NPSTNNA 20210524 ; ARG1 = lookup, ARG2 = num, ARG3 = 1 to return IAX2 URI only but not dial / RETURN = 0 if RSA call failed, 1 if RSA call succeeded or IAX2 URI if ARG3 = 1 exten => s,1,ExecIf($["${ARG1:0:5}"!="IAX2/"]?Return(0)) ; primary protocol is not IAX2, and RSA encryption requires IAX2 same => n,Set(LOCAL(fqdn)=${CUT(CUT(ARG1,@,2),/,1)}) same => n,Set(LOCAL(secret)=${CUT(CUT(ARG1,:,2),@,1)}) same => n,ExecIf(${ISNULL(${secret})}?Return(0)) ; "no secret? No RSA service" same => n,Set(LOCAL(rsauser)=${FILTER(A-Za-z0-9\x2D\x2E,${SHELL(grep -B 2 "host=${fqdn}" /etc/asterisk/iax-rsa-npstn-out.conf | head -1 | cut -d "[" -f2 | cut -d "]" -f1)})}) ; rely on config. arrangement same => n,GotoIf(${EXISTS(${rsauser})}?attempt:write) same => n(flush),Set(LOCAL(flush)=1) ; flush the peers so we can start fresh same => n(write),Set(LOCAL(writes)=1) same => n,Set(LOCAL(rsauser)=npstn-rsa-${FILTER(A-Za-z0-9\x2D\x2E,${fqdn})}) same => n,Set(FILE(/etc/asterisk/iax-rsa-npstn-out.conf,,,${IF($["${flush}"!="1"]?a)}l,u)=[${rsauser}]) same => n,Set(FILE(/etc/asterisk/iax-rsa-npstn-out.conf,,,al,u)=type=peer) ; peer is for outgoing, user is for incoming (friend is both) same => n,Set(FILE(/etc/asterisk/iax-rsa-npstn-out.conf,,,al,u)=host=${fqdn}) same => n,Set(FILE(/etc/asterisk/iax-rsa-npstn-out.conf,,,al,u)=username=npstn-rsa) ; standard NPSTN RSA user name same => n,Set(FILE(/etc/asterisk/iax-rsa-npstn-out.conf,,,al,u)=auth=rsa) same => n,Set(FILE(/etc/asterisk/iax-rsa-npstn-out.conf,,,al,u)=forceencryption=yes) same => n,Set(FILE(/etc/asterisk/iax-rsa-npstn-out.conf,,,al,u)=secret=${secret}) ; the RSA secret is the same as the peer's MD5 secret. If the peer doesn't have an MD5 user, then the call will fail. same => n,Set(FILE(/etc/asterisk/iax-rsa-npstn-out.conf,,,al,u)=outkey=npstnrsa) ; name of private out key, without directory or extension same => n,Set(FILE(/etc/asterisk/iax-rsa-npstn-out.conf,,,al,u)=) ; new line same => n,ExecIf(${IFMODULE(app_reload.so)}?Reload(chan_iax2):System(asterisk -rx "iax2 reload")) ; reload IAX2 and res_crypto so changes take effect immediately. Eventually, replace this with Reload application calls. same => n,ExecIf(${IFMODULE(app_reload.so)}?Reload(res_crypto):System(asterisk -rx "module reload res_crypto")) same => n(attempt),ExecIf($["${ARG3}"="1"]?Return(IAX2/${rsauser}/${ARG2})) same => n,Dial(IAX2/${rsauser}/${ARG2},,gF(autovonpreempted-callee,${autovonchan},1)) ; same Dial format as a non-RSA call same => n,ExecIf($["${DIALSTATUS}"="CONGESTION"|"${HANGUPCAUSE}"="0"|"${HANGUPCAUSE}"="20"|"${HANGUPCAUSE}"="27"|"${HANGUPCAUSE}"="50"]?NoOp(${DIALSTATUS} / ${HANGUPCAUSE}):Return(1)) ; 0, 20, and 50 are common failure codes with encryption. same => n,GotoIf($["${writes}"!="1"]?flush) ; perhaps the secret has changed and we need to try again. But don't try again if we just wrote the peer config out. That's unnecessary. same => n,Return(0) ; RSA call failed [npstn-verify] ; 20190212 NA ; to be called immediately on all [from-npstn] calls, updated 20210518 to save FQDN from npstn-verifyclid exten => _X!,1,Goto(s,1) ; backwards-compatibility exten => s,1,GotoIf($[${IFMODULE(app_verify.so)}&${EXISTS(${AST_CONFIG(verify.conf,npstn,verifymethod,0)})}]?:old) ; try to use modules if possible. same => n,Verify(npstn) same => n,Return() same => n(old),Set(CALLERID(num)=${FILTER(0-9A-Za-z,${CALLERID(num)})}) same => n,Set(LOCAL(fromnum)=${IAXVAR(DISAVIA)}) ; this should be a number on the upstream node same => n,Set(LOCAL(clidverified)=${IAXVAR(clidverif)}) same => n,GotoIf($[${LEN(${clidverified})}=2]?verifynode) ; Should the node reporting the caller's verification status be trusted? (Confirm it's a valid NPSTN node telling us, not a rogue server) same => n,Gosub(npstn-verifyclid,s,1(${CALLERID(num)})) ; Allows callers dialing through DISAs to still maintain verification status same => n,Set(ARRAY(LOCAL(valid),LOCAL(fqdn))=${GOSUB_RETVAL}) ; Now, not just any server can set IAXVAR(clidverif) to spoof verification status (only a valid NPSTN node could do this) same => n,GotoIf(${valid}?valid) same => n(tokenverify),ExecIf(${ISNULL(${IAXVAR(npstnverifytoken)})}?GotoIf($[${LEN(${clidverified})}=2]?judgedinvalid:invalid)) ; failed reverse IP check, fallback to token verification if available same => n,Set(LOCAL(tokenresult)=${SHELL(curl "https://crtc.npstn.us/api/v1/verification/temptoken.php?auth=${npstnkey}&token=${IAXVAR(npstnverifytoken)}")}) same => n,Set(IAXVAR(npstnverifytoken)=) ; clear variable same => n,ExecIf($[$[${LEN(${disavia})}=7]&$["${fromnum}"="${tokenresult}"]]?GotoIf($[${LEN(${clidverified})}=2]?judgedvalid:valid):GotoIf($[${LEN(${clidverified})}=2]?judgedinvalid:invalid)) same => n(verifynode),Gosub(npstn-verifyclid,s,1(${fromnum})) ; Verify the NPSTN node through which the NPSTN call is passing same => n,Set(ARRAY(LOCAL(valid),LOCAL(fqdn))=${GOSUB_RETVAL}) same => n,GotoIf(${valid}?judgedvalid) same => n,Goto(tokenverify) same => n(valid),Set(__clidverif=10) same => n,Gosub(npstn-rsa-in-prefetch,s,1(${fqdn})) same => n,Goto(writeflags) same => n(invalid),Set(__clidverif=11) same => n,Set(GROUP(spam)=npstn) same => n,Goto(writeflags) same => n(judgedvalid),Set(__clidverif=${FILTER(0-9,${clidverified})}) same => n,Gosub(npstn-rsa-in-prefetch,s,1(${fqdn})) same => n,Goto(writeflags) same => n(judgedinvalid),Set(__clidverif=19) same => n,Goto(writeflags) same => n(writeflags),NoOp() same => n,ExecIf($[${LEN(${FILTER(A-D0,${IAXVAR(npstnmlpp)})})}=1]?Set(__autovonprioritydigit=${FILTER(A-D0,${IAXVAR(npstnmlpp)})})) ; MLPP/AUTOVON same => n,ExecIf($[${LEN(${FILTER(0-1,${IAXVAR(forcesecurechannel)})})}=1]?Set(__forcesecurechannel=${FILTER(0-1,${IAXVAR(forcesecurechannel)})})) ; Secure Calling same => n,Return() [npstn-rsa-in-prefetch] ; 20210518 ; RSA public key prefetcher: check if a public key for this FQDN exists, redownloading it at most often every 12 hours exten => s,1,Set(LOCAL(fqdn)=${FILTER(A-Za-z0-9\x2D\x2E\x3A,${ARG1})}) ; sanitize FQDN: allow alphanumeric, hyphens, periods, and colons only (FQDNs/IPv4/IPv6) same => n,Set(LOCAL(inkey)=npstn-rsa-${FILTER(A-Za-z0-9\x2D\x2E,${ARG1})}) ; filename cannot contain the colon, because that is the delimeter same => n,Set(LOCAL(files)=${FILTER(0-9,${SHELL(find /var/lib/asterisk/keys/ -name '*${fqdn}*' | wc -l)})}) same => n,GotoIf($["${files}"="0"]?prefetch) ; no public key exists for this server, try to fetch same => n,GotoIf($[${STAT(e,/var/lib/asterisk/keys/${inkey}.pub)}]?:prefetch) ; check each time if key doesn't exist, because it could have just been added same => n,GotoIf($[$[$[${EPOCH}-${STAT(C,/var/lib/asterisk/keys/${inkey}.pub)}]>43200]|$[$[${EPOCH}-${STAT(M,/var/lib/asterisk/keys/${inkey}.pub)}]>43200]]?prefetch) ; if file changed/modified > 12 hrs. ago, refetch same => n,Return() ; key exists and we're not going to fetch it again right now same => n(prefetch),System(wget "https://crtc.npstn.us/api/v1/rsa/?auth=${npstnkey}&fqdn=${fqdn}" -O /var/lib/asterisk/keys/${inkey}.pub) ; this will save a file, regardless of whether response was 200 or 404 ; key exists now (though it may or may not be legitimate. Dummy public keys are a good thing, actually. It prevents us from trying to fetch each time and lets us know no key exists right now.) same => n,GotoIf($[${STAT(s,/var/lib/asterisk/keys/${inkey}.pub)}=0]?missing) ; if we downloaded a dummy key, delete and done same => n,Set(LOCAL(wordcount)=${FILTER(0-9,${SHELL(grep "inkeys" "/etc/asterisk/iax-rsa-npstn-in.conf" | grep -e "=${inkey}" -e ":${inkey}:" -e ":${inkey} " | wc -l)})}) same => n,GotoIf($[${ISNULL(${wordcount})}|"${wordcount}"="0"]?:done) ; key name already listed in inkeys? same => n,Set(LOCAL(inkeys)=${FILTER(A-Za-z0-9\x2D\x2E\x3A,${SHELL(grep "inkeys" "/etc/asterisk/iax-rsa-npstn-in.conf" | cut -d'\;' -f 1 | cut -d'=' -f 2)})}) ; new key, so get list of current keys same => n,ExecIf(${EXISTS(${inkeys})}?System(sed -i 's/${inkeys}/${inkeys}:${inkey}/' /etc/asterisk/iax-rsa-npstn-in.conf)) ; append key to list of inkeys same => n(done),ExecIf(${IFMODULE(app_reload.so)}?Reload(iax2):System(asterisk -rx "iax2 reload")) ; reload IAX2 and crypto keys. same => n,ExecIf(${IFMODULE(app_reload.so)}?Reload(res_crypto):System(asterisk -rx "module reload res_crypto")) same => n,Return() ; note that this subroutine does not flush out stale or obsolete keys over time, but periodically cleaning this file and the keys is okay. same => n(missing),System(rm -f /var/lib/asterisk/keys/${inkey}.pub) same => n,Return() [npstn-verifyclid] ; 20190212 NA, updated 20210518 to return FQDN as well exten => _X!,1,Goto(s,1) ; backwards-compatibility exten => s,1,Set(LOCAL(cnum)=${ARG1}) same => n,Gosub(npstn-reverse-iax,s,1(${cnum})) same => n,Set(ARRAY(iaxlookup,cnumvalid)=${GOSUB_RETVAL}) same => n,GotoIf($["${cnumvalid}"="1"]?:invalid) same => n,GotoIf(${ISNULL(${iaxlookup})}?invalid) same => n,Gosub(isitip,s,1(${iaxlookup})) same => n,Set(ARRAY(LOCAL(iaxisip),LOCAL(iplookup))=${GOSUB_RETVAL}) same => n,GotoIf(${iaxisip}?getpeer) same => n(convert),Gosub(fqdn2ip,s,1(${iaxlookup})) same => n,Set(ARRAY(LOCAL(iaxip),LOCAL(iplookup))=${GOSUB_RETVAL}) same => n(getpeer),Gosub(getpeerip,s,1) same => n,GotoIf($[${GOSUB_RETVAL}=0]?invalid) same => n,Gosub(ipcomp,s,1(${iplookup},${GOSUB_RETVAL})) same => n,GotoIf(${GOSUB_RETVAL}?valid:invalid) same => n(invalid),Return(0,${fqdn}) same => n(valid),Return(1,${CUT(CUT(iaxlookup,@,2),/,1)}) [npstn-get-number-ip] ; 20201213 NA ; Returns IP address associated with a VALID NPSTN # (ARG1) exten => s,1,Set(LOCAL(iaxuri)=${SHELL(curl "https://crtc.npstn.us/api/v1/?auth=${npstnkey}&lookup=${ARG1}")}) same => n,ExecIf(${ISNULL(${iaxuri})}?Return()) ; invalid lookup same => n,Gosub(isitip,s,1(${iaxuri})) same => n,Set(ARRAY(iaxisip,iaxiplookup)=${GOSUB_RETVAL}) same => n,GotoIf(${iaxisip}?return) same => n,Gosub(fqdn2ip,s,1(${iaxuri})) same => n,Set(ARRAY(iaxip,iaxiplookup)=${GOSUB_RETVAL}) same => n(return),Return(${iaxiplookup}) [npstn-reverse-iax] ; 20190212 NA ; based on npstn-ewr-fetch exten => s,1,Set(LOCAL(nocid)=${SHELL(curl "https://crtc.npstn.us/api/v1/?auth=${npstnkey}&lookup=")}) same => n,GotoIf($["${nocid:0:5}"!="IAX2/"]?failover) ; temporary CRTC issue same => n,Set(LOCAL(badcid)=${SHELL(curl "https://crtc.npstn.us/api/v1/?auth=${npstnkey}&lookup=442072221234")}) same => n,Set(LOCAL(rtcr)=${SHELL(curl "https://crtc.npstn.us/api/v1/?auth=${npstnkey}&lookup=${ARG1}")}) same => n,Set(LOCAL(cv)=${IF($[$["${rtcr}"="${nocid}"]|$["${rtcr}"="${badcid}"]]?0:1)}) same => n,ExecIf($[${cv}&$["${rtcr:0:5}"="IAX2/"]&$[${LEN(${ARG1})}=7]]?Set(DB(npstnlookups/${ARG1})=${rtcr})) ; cache the incoming NPSTN # same => n,GotoIf($[$[${LEN(${FILTER(0-9,${IAXVAR(DISAVIA)})})}=7]&$[${DB_EXISTS(npstnlookups/${FILTER(0-9,${IAXVAR(DISAVIA)})})}!=1]]?:ret) same => n,Set(LOCAL(node)=${SHELL(curl "https://crtc.npstn.us/api/v1/?auth=${npstnkey}&lookup=${FILTER(0-9,${IAXVAR(DISAVIA)})}")}) same => n,ExecIf($[${cv}&$["${badcid:0:5}"="IAX2/"]]?Set(DB(npstnlookups/${FILTER(0-9,${IAXVAR(DISAVIA)})})=${node})) ; cache the incoming node same => n(ret),Return(${rtcr},${cv}) same => n(failover),ExecIf(${DB_EXISTS(npstnlookups/${ARG1})}?Return(${DB(npstnlookups/${ARG1})},1)) ; exact # in cache? same => n,ExecIf($[$[${LEN(${FILTER(0-9,${IAXVAR(DISAVIA)})})}=7]&${DB_EXISTS(npstnlookups/${FILTER(0-9,${IAXVAR(DISAVIA)})})}]?Return(${DB(npstnlookups/${FILTER(0-9,${IAXVAR(DISAVIA)})})},1)) ; node in cache? same => n,Return(,0) ; give up [cktslookup] ; 20190218 NA ; ARG1 = full international number to lookup on C*NET exten => s,1,Set(LOCAL(enum)=${ENUMLOOKUP(+${ARG1},ALL,,1,std.ckts.info)}) same => n,ExecIf(${ISNULL(${enum})}?Return) same => n,ExecIf($[${enum:0:3}=iax]?Return(IAX2/${enum:5})) same => n,ExecIf($[${enum:0:3}=sip]?Return(SIP/${enum:4})) same => n,ExecIf($[${enum:0:4}=h323]?Return(H323/${enum:5})) same => n,Return(${enum}) [cnet-reverse-iax] ; 20190212 NA ; based on npstn-ewr-fetch exten => s,1,Set(LOCAL(nocid)=) same => n,Set(LOCAL(badcid)=) same => n,Gosub(cktslookup,s,1(${ARG1})) same => n,Set(LOCAL(rtcr)=${GOSUB_RETVAL}) same => n,Set(LOCAL(cv)=${IF($[$["${rtcr}"="${nocid}"]|$["${rtcr}"="${badcid}"]]?0:1)}) same => n,Return(${rtcr},${cv}) [isitip] ;20181116 BJC - ARG1 is a iaxuri - if ARG1 contains an IP address, returns dnf=1 otherwise dnf=0 exten => s,1,Set(LOCAL(oc1)=${ISNULL(${FILTER(0-9,${CUT(CUT(CUT(ARG1,/,2),@,2),.,1)})})}) ; slice iaxuri and evaluate octet1 same => n,Set(LOCAL(oc2)=${ISNULL(${FILTER(0-9,${CUT(CUT(CUT(ARG1,/,2),@,2),.,2)})})}) ;slice iaxuri and evaluate octet2 same => n,Set(LOCAL(oc3)=${ISNULL(${FILTER(0-9,${CUT(CUT(CUT(ARG1,/,2),@,2),.,3)})})}) ;slice iaxuri and evaluate octet3 same => n,Set(LOCAL(oc4)=${ISNULL(${FILTER(0-9,${CUT(CUT(CUT(ARG1,/,2),@,2),.,4)})})}) ;slice iaxuri and evaluate octet4 same => n,Set(LOCAL(dnf)=${IF($[${oc1}|${oc2}|${oc3}|${oc4}]=0?0:1)}) ;OR sum octets same => n,Set(LOCAL(ip)=${IF($["${dnf}"="1"]?${CUT(CUT(ARG1,@,2),/,1)}:0)}) ;IP address same => n,Return(${dnf},${ip}) ;return IP flag & IP [fqdn2ip] ;20181114 BJC - ARG1 is iaxuri containing an fqdn, returns iaxuri containing ip address and ip address if host exists else returns Unknown Host and fqdn exten => s,1,Set(ARRAY(LOCAL(ds1),LOCAL(ds2),LOCAL(ds3))=${CUT(ARG1,@,1)},${CUT(CUT(ARG1,@,2),/,1)},${CUT(CUT(ARG1,@,2),/,2)}) ;extract fqdn to {DS2} same => n,Set(LOCAL(ds2)=${FILTER(0-9.,${SHELL(dig ${CUT(ds2,:,1)} +noall +short | sed -n '$'p)})}) ;convert fqdn to IP - modified 20210125 NA to support : for non-4569 ports same => n,GotoIf(${ISNULL(${ds2})}?nn) ;unknown host? same => n,Return(${ds1}@${ds2}/${ds3},${ds2}) ;return converted iaxuri & IP address same => n(nn),Return(Unknown Host,${ds2}) ;Unknown Host - no IP address [getpeerip] ;20181114 BJC / 20190223 NA - returns peerip for IAX2/SIP/H323/MGCP channel or 0 for all other channel technologies exten => s,1,Set(LOCAL(ct)=${CHANNEL(channeltype)}) ;get technology same => n,GotoIf($["${ct}"="IAX2"]?setpip) ;incoming NPSTN calls are IAX2 or one of the other 3 technologies, any other technology must be local/test channels same => n,GotoIf($["${ct}"="SIP"]?setpip) same => n,GotoIf($["${ct}"="H323"]?setpip) same => n,GotoIf($["${ct}"="MGCP"]?setpip) same => n,GotoIf($["${ct}"="PJSIP"]?setpjsipip) ; added 20210629 same => n(z),Return(0) ;return 0 for local/other channels same => n(setpip),Return(${CHANNEL(peerip)}) ;return peer IP same => n(setpjsipip),Return(${CHANNEL(pjsip,remote_addr)}) [ipcomp] ;20181114 BJC, updated 20211209 NA - ARG1 and ARG2 are IP addresses - returns cpf=1 if ARG1 & ARG2 coincidence otherwise cpf=0 exten => s,1,ExecIf($[${LEN(${ARG1})}=0|${LEN(${ARG2})}=0]?Return(0)) ; added 20210125 NA to prevent dialplan warnings if empty argument(s) provided same => n,Return(${IF($["${FILTER(0-9,${ARG1})}"="${FILTER(0-9,${ARG2})}"]?1:0)}) ;compare ip addresses, return compare flag