So here is what I did.
First, they had an existing MySQL database with a list of 5 digit codes and CID numbers to use with them.
So I added func_obdc.conf
to connect to that database and run a query based on the autcode (authorization code) that the user dialed.
cat /etc/asterisk/func_odbc.conf
; Return the CID to use based on the AUTCODE dialed.
[AUTCODECID]
prefix=GET
dsn=jared-custom
readsql=SELECT cid_num FROM autcodes WHERE code = '${ARG1}'
; Return the ID of the autcode if it exists.
; This is used to validate the call can be placed.
[VALIDATEAUTCODE]
prefix=GET
dsn=jared-custom
readsql=SELECT id FROM autcodes WHERE code = '${ARG1}'
Then I setup outbound routes with pattern matches set appropriately.
They dial 7 or 8 depending on the office, then 11 digits then 5 digit code.
That outbound route has a custom trunk that simply terminates the call, as the next bit will move the call to a different route.
[macro-dialout-trunk-predial-hook]
exten => s,1,NoOp()
; Skip if this is an emergency call
exten => s,n,GotoIf($["${EMERGENCYROUTE}"=="YES"]?emergencyrouteused)
; Check if the call has already been validated to go out, and skip the rest of the processing
exten => s,n,GotoIf($["${OUTBOUND_ROUTE_NAME}"=="Call_Validated_to_go_Out"]?skipvalidation)
; Depending on the outbound route send the call to be validated
; International calling needs no autcode
exten => s,n,GotoIf($["${OUTBOUND_ROUTE_NAME}"=="International_Allowed"]?skipvalidation)
; International calling needs no autcode
exten => s,n,GotoIf($["${OUTBOUND_ROUTE_NAME}"=="Intl_Out"]?skipvalidation)
; NANPA Allowed to dial with no autcode
; Executive Conference room, etc.
exten => s,n,GotoIf($["${OUTBOUND_ROUTE_NAME}"=="NANPA_Allowed_No_Code"]?skipvalidation)
; NANPA dialed with a code
exten => s,n,GotoIf($["${OUTBOUND_ROUTE_NAME}"=="NANPA_Dialed_with_Code"]?validatenanpawithautcode,s,1)
; NANPA dialed without a code
exten => s,n,GotoIf($["${OUTBOUND_ROUTE_NAME}"=="NANPA_Dialed_no_Code"]?validatenanpanoautcode,s,1)
;More logic coming. For now, dump channel and terminate call.
exten => s,n,DumpChan
exten => s,n,Hangup()
; The call was an emegerncy dial, do no processing, but note the log.
exten => s,n(emergencyrouteused),NoOp()
exten => s,n,Log(NOTICE, 911 was dialed by extension ${FROMEXTEN})
; The call was validated, nothing else to do.
exten => s,n(skipvalidation),NoOp()
The above will send a call dialed with a code to this routine, that get the CID to use based on the code used.
[validatenanpawithautcode]
exten => s,1,NoOp()
; NANPA format means digit zero is 7 or 8 and digit one is a 1
; digits two through ten should be a 10 digit number
; the last five digits are the AUTCODE
exten => s,n,Set(USERDIALED=${OUTNUM:2:10})
exten => s,n,Set(AUTCODE=${OUTNUM:-5})
; Go check for a valid code
exten => s,n,Set(RETURNTOCONTEXT=validatenanpawithautcode)
exten => s,n,GoTo(checkvalidautcode,s,1)
; Return location if AUTCODE is valid
exten => s,n(autcodevalid),NoOp()
; Check if the AUTCODE has a CID associated with it.
exten => s,n,Set(AUTCODECIDNUM=${GET_AUTCODECID(${AUTCODE})})
; If there is no CID jump to autcidno, otherwise autcidyes
exten => s,n,GotoIf($["${AUTCODECIDNUM}" == ""]?autcidno:autcidyes)
exten => s,n,Hangup()
exten => s,n(autcidyes),NoOp()
exten => s,n,Log(NOTICE, The AUTCODE of ${AUTCODE} has an associated CID of ${AUTCODECIDNUM}. Forcing it to apply.)
exten => s,n,Set(TRUNKCIDOVERRIDE=${AUTCODECIDNUM})
exten => s,n(autcidno),NoOp()
exten => s,n,DumpChan
; Go back to outbound route logic with the "secret" prefix of 8675309 (Jenny) that will match the pattern to get to an outboud trunk
exten => s,n,Goto(outbound-allroutes,8675309${USERDIALED},1)
exten => s,n,Hangup()
That code will send it to this bit of logic to validate the code prior to getting the CID.
; This context checks that the AUTCODE value (last five digits) exists in the `autcodes` table
; See func_odbc.conf for the SQL statement.
; This context requires that the variable RETURNTOCONTEXT be set in by the context that calls it
; This context also requres that the calling context have a line labeled autcodevalid to return to
[checkvalidautcode]
exten => s,1,NoOp()
; Start a rety counter.
exten => s,n,Set(RETRYCODE=0)
; If code is invalid, user is prompted to try again and the call is sent back to here
exten => s,n(tryagain),NoOp()
; Check the database for the autcode. SQL query returns the `id` of the row if found
exten => s,n,Set(AUTCODEID=${GET_VALIDATEAUTCODE(${AUTCODE})})
; if no rows are returned jump to badaut, if something is returned jump to goodaut
exten => s,n,GotoIf($[${ODBCROWS} = 0]?badaut,1:goodaut,1)
exten => s,n,Hangup()
exten => badaut,1,NoOp()
exten => badaut,n(notfound),Log(NOTICE, The AUTCODE of ${AUTCODE} was not found, user needs to try again)
; Increment the fail counter
exten => badaut,n,Set(RETRYCODE=$[${RETRYCODE} + 1])
; If the user has failed 3 times (or more somehow) jump to toomanytries
exten => badaut,n,GotoIf($[${RETRYCODE} >= 3]?toomanytries,1)
; Tell them the code was bad and to try again
exten => badaut,n,Playback(you-entered)
exten => badaut,n,Playback(access-code)
exten => badaut,n,SayDigits(${AUTCODE})
exten => badaut,n,Playback(vm-pls-try-again)
exten => badaut,n,Set(TIMEOUT(response)=10)
exten => badaut,n,Read(AUTCODE,access-code,5,,2,5)
; If the entered code was 5 digits jump back up to try again, otherwise jump up to not found
exten => badaut,n,GotoIf($[${LEN(${AUTCODE})}==5]?s,tryagain:notfound)
exten => badaut,n,Hangup()
exten => goodaut,1,NoOp()
exten => goodaut,n,Log(NOTICE, The AUTCODE of ${AUTCODE} was found, let call proceed)
; The code was found, so go back to the context that we came from at the spot marked autcodevalid
exten => goodaut,n,GoTo(${RETURNTOCONTEXT},s,autcodevalid)
exten => goodaut,n,Hangup()
exten => toomanytries,1,NoOp()
exten => toomanytries,n,Log(NOTICE, The user failed to enter a valid AUTCODE too many times)
; The user the dialed the code wrong too many times, hangup the call
exten => toomanytries,n,Playback(sorry)
exten => toomanytries,n,Playback(goodbye)
exten => toomanytries,n,Hangup()
Assuming it validated and returned a CID, the dialed number, minus the code is prefixed with Jenny’s number 8675309 and sent back to the main outbound route functionality. Which now matches this outbound route that has a normal trunk referenced.
That does hit the above macro again, since it is an outbound hook. That is why it has a match for this route name to skip any more validation.
; Check if the call has already been validated to go out, and skip the rest of the processing
exten => s,n,GotoIf($["${OUTBOUND_ROUTE_NAME}"=="Call_Validated_to_go_Out"]?skipvalidation)
Feel free to rip this apart. What you want to do is not this complicated, but this should give you an idea of how flexible you can get with dial plan.