A different approach to placing outgoing calling restrictions on certain extensions

(This post has been edited to take out some false starts and to add some new information, and to hopefully clarify the content of the original post. Some of the information below has been extracted and used in the article How to give a particular extension different or restricted trunk access for outgoing calls).

IMPORTANT: When implementing any sort of restrictions on extensions, using the method described here or any other method, please be absolutely certain that you do not inadvertently restrict access to emergency services numbers (such as 911 in the U.S./Canada)!

The other day I was thinking about the oft-stated request for a way to prevent certain extensions from making outgoing calls on certain trunks, while allowing full access (or different access) for calls from other extensions. Normally it is suggested to use the Custom Context module, but this brings with it its own set of problems, not the least of which is having to go through and check (and maybe change) all those priority dropdowns if you add, remove, or move a route. I’ve been wondering if there might be a simpler approach, especially after reading an article in Moshe’s blog entitled Restricting outbound calls in FreePBX (blacklist). The problem with Moshe’s approach is that although it allows one to blacklist calls system-wide, it does not allow calls to be passed or blocked selectively depending on the calling extension. However, he just might be onto something here that could work for that purpose.

After I originally wrote this post (and the above paragraph), Moshe came out with a second blog post, Restricting outbound calls in FreePBX (whitelist), which not only does allow calls to be passed or blocked selectively depending on the calling extension, but also allows you to “whitelist” certain numbers (in case you want the “restricted” extensions to be able to dial certain number despite the restrictions). He takes a slightly different approach than what I do below - Moshe allows you to restrict on a per-extension basis, but the restriction is for all routes with the only exceptions being the specific “whitelisted” numbers. My approach here also allows you to restrict on a per-extension basis, but the restriction is then only applied to specific routes that you wish to restrict (calls to numbers in non-restricted routes can still go through).

Consider how a route accesses trunks. Normally you give each route access to one or more trunks. When you make an outgoing call that matches the route dial pattern(s), it tries to send the call via the first trunk on the list. But, if that trunk is unavailable for any reason, it drops to the next trunk on the list.

A good example of this is the ENUM trunk. If you create an ENUM trunk and place it first on the list of trunks accessible by a route, it will try to send the call using ENUM. But if it fails, it will drop back and try the next trunk on the list.

Now, suppose that you create a CUSTOM trunk that will look to see if a call is coming from a “restricted” extension - if so, it would play a recording and hang up. Otherwise, it would (in effect) appear to be an unavailable trunk, and the outbound route would go on the the next trunk on the list. On any route you wanted to restrict, you’d go to this dummy “custom” trunk first (or maybe second, if you have put the ENUM trunk first and don’t care if the extension can make free ENUM calls), and it would check to see if the call was flagged as being from a “restricted” extension.

The trunk would have to go to a context in extensions-custom.conf using a custom dial string of something like Local/[email protected] - custom dial string syntax is something of a “black hole” to me, but I know you can do things like Local/[email protected] to route all calls to that trunk to a specific external number (great for setting up directory assistance aliases and so forth), however I don’t know what all the permissible syntaxes are.

Then, in the extension setup itself you could change the context from from-internal to something else, say custom-restrict - in custom-restrict (again in extensions-custom.conf) you’d set a flag variable of some kind, then jump to from-internal. Then, the custom trunk described above would simply check to see if the flag variable is set - if so the call would be diverted to the internal black hole (whatever that might be), otherwise it would return to the route and the route would move onto the next trunk.

Basically we’re just adding a couple of bits of code here. The nice thing is that it’s easy to specify which extensions are affected (just change from-internal to something else) and easy to specify which routes are restricted (just call the “trick” custom trunk first).

If you need different types of restrictions, you could expand on this - different extensions could call different contexts in extensions-custom.conf that would set different flag variables, and then multiple custom trunks could be created, each of which would go to a different custom context that would check for the presence/absence of the different flag variables. So, your customizations could be as simple or complicated as you like. I’m not saying it’s super elegant but in some cases might be more desirable than the alternative.

After a couple of false starts (and one really dumb mistake that I don’t want to talk about), followed by a couple days of head scratching and finally enlightenment thanks to Moshe (I had never had reason to worry about “VARIABLE INHERITANCE” before, therefore didn’t even know about that), this is what finally seems to work - use a text editor (nano, Midnight Commander’s editor, etc.) to add the following two dialplan fragments to /etc/asterisk/extensions_custom.conf:

exten => _[*0-9]!,1,Noop(Using Call Restriction 1)
exten => _[*0-9]!,n,Set(_RestrictedExt1=TRUE)
exten => _[*0-9]!,n,Goto(from-internal,${EXTEN},1)
exten => h,1,Hangup()

exten => _[*0-9]!,1,Noop(Testing for Call Restriction 1 ${RestrictedExt1})
exten => _[*0-9]!,n,GotoIf($["${RestrictedExt1}" = “TRUE”]?restrict1:norestrict1)
exten => _[*0-9]!,n(restrict1),Noop(Call blocked due to call restriction: 1)
exten => _[*0-9]!,n,NoCDR()
exten => _[*0-9]!,n,Playback(feature-not-avail-line,noanswer)
exten => _[*0-9]!,n,Goto(app-blackhole,congestion,1)
xten => _[*0-9]!,n(norestrict1),Noop(No call restriction: 1)
exten => h,1,Hangup()

After adding this additional code in extensions-custom.conf, I then created a custom trunk with the Custom Dial string Local/[email protected] , and then moved that custom trunk to the top of the trunk list in one of my routes.

Finally, I changed the context in one of my extensions to custom-restricted-ext1. After saving all the configuration changes, I placed a test call that would use the restricted route, and (finally) it worked - it answered the line, played the “That feature is not available on this line” recording (the most appropriate one I could find among the standard recordings supplied with FreePBX, although if one wanted to spend a little time with an audio editor one could probably slice and dice a couple of the supplied recordings to come up with something more informative). If I place the same call from another extension that uses the normal from-internal context, it goes out as normal.

If you wanted to have different restrictions for different extensions, you could just make additional contexts. For a second group of restricted extensions, you could do this:

exten => _[*0-9]!,1,Noop(Using Call Restriction 2)
exten => _[*0-9]!,n,Set(_RestrictedExt2=TRUE)
exten => _[*0-9]!,n,Goto(from-internal,${EXTEN},1)
exten => h,1,Hangup()

exten => _[*0-9]!,1,Noop(Testing for Call Restriction 2 ${RestrictedExt2})
exten => _[*0-9]!,n,GotoIf($["${RestrictedExt2}" = “TRUE”]?restrict2:norestrict2)
exten => _[*0-9]!,n(restrict2),Noop(Call blocked due to call restriction: 2)
exten => _[*0-9]!,n,NoCDR()
exten => _[*0-9]!,n,Playback(feature-not-avail-line,noanswer)
exten => _[*0-9]!,n,Goto(app-blackhole,congestion,1)
exten => _[*0-9]!,n(norestrict2),Noop(No call restriction: 2)
exten => h,1,Hangup()

You’d then create another custom trunk with the Custom Dial string Local/[email protected]. Then, at the top of the trunk list for any route, you could check either or both of your custom trunks for possible restrictions.

Note that as written above, this will NOT handle the case where you have two companies using the same FreePBX system, and you want one company’s calls to go out on one route while the other company’s calls use a different route. The problem is that FreePBX will only attempt to use one route for outgoing calls that match a specific dial pattern - once a dial pattern has been matched with a route, it will not try additional routes. So, you can restrict certain extensions from using a particular route, but you cannot then make FreePBX try a different route that would match the same dial pattern. You CAN do things like this using the unsupported Custom Contexts module (if it still works with the version of FreePBX you are using). You could possibly modify the above code to do it also (see the article How to give a particular extension different or restricted trunk access for outgoing calls. You might also get some ideas from this post).

This approach does not incorporate a specific “whitelist” feature, however if you wanted to whitelist certain numbers (so they could be called even by “restricted” extensions) and those numbers happen to match one of your “restricted” routes, simply create a new outbound route similar to the restricted one, but that contains only the “whitelisted” numbers listed in the Dial Patterns text box, and that duplicates the trunk list of the restricted route - except, of course, you do not include your custom trunk that enforces the restriction! Make that route higher in priority than the restricted route. Since you are only matching the “whitelisted” numbers in that route, it will allow calls to those specific numbers go through without restriction. Of course, you could use patterns rather than specific numbers if you wanted to do something like un-restrict an entire area code, or entire telephone exchange prefix.

ADDENDUM: Originally, where it shows this code:

exten => _[*0-9]!,n,NoCDR() exten => _[*0-9]!,n,Playback(feature-not-avail-line,noanswer) exten => _[*0-9]!,n,Goto(app-blackhole,congestion,1)

I had instead used:

exten => _[*0-9]!,n,playback(feature-not-avail-line) exten => _[*0-9]!,n,Hangup

Even with the change, I find that my CDR shows three second completed calls because the call is considered answered while the recording is playing (note, however, that if you use Elastix and view the call detail using their “Reports” tab, the call still shows as ANSWERED, but is zero seconds in length). You might think that in my original code, all you’d have to do is add the “,noanswer” option to the Playback statement, but unfortunately, if you do that then after the recording is played it continues on to the next trunk in the sequence (skipping the “Hangup” as if it weren’t there), defeating the entire purpose of doing this.

I do not understand why the NoCDR() statement (as suggested by Leap Frog in a comment - he suggested placing it after the Playback statement, whereas I placed it before, but I tried it both ways and it makes no difference) seems to be ignored. Perhaps it’s too late in call processing to have any effect, though it’s hard to understand why that would be the case since the call isn’t “completed” until the Playback statement is executed. I’m leaving the NoCDR() in for now (if it bothers you, you can always remove it), under the theory that this may be an Asterisk or FreePBX bug (EDIT: Actually, there is mounting evidence that it IS an Asterisk bug) that will be fixed eventually, and then the NoCDR() might magically start working. I wish…

Also, if anyone can explain why “Hangup” alone won’t absolutely terminate the call at the point where that statement is encountered, I’d love to hear it. I can see why we all love FreePBX, if something this simple in an Asterisk dial plan doesn’t work as expected.

EDIT: In all cases where the extension pattern _. had been used, it’s replaced here with _[*0-9]! - it appears that using _. can have bad side effects and is not recommened.

At the time I posted this comment I still didn’t have it working - the thing I could not figure out is why the value of the flag variable RestrictedExt1 seemed to be getting lost between the two contexts. Moshe enlightened me to “variable inheritance” (hence his comment below), the upshot being that I had to change RestrictedExt1 to _RestrictedExt1 (adding the leading underscore) when it it first defined.

wiseoldowl: the answer is quite simple. Also, I saved you the effort. Its all in the comments of my blog (in the link that you posted)

Thank you, Moshe - I got it working based on tip you gave me about “VARIABLE INHERITANCE”. I have seriously edited my original post above to include the working code, plus a link back to your article.

Thanks wiseoldowl (and Moshe), this looks like is a great way of restricting specific routes.

A question, and maybe you have already done this, but could you not use the NoCDR application to prevent the restricted call from being written into the CDR log?

Maybe the addition of the indicated line would achieve this?

exten => _.,1,Noop(Testing for Call Restriction 1 ${RestrictedExt1})
exten => _.,n,GotoIf($["${RestrictedExt1}" = “TRUE”]?restrict1:norestrict1)
exten => _.,n(restrict1),Noop(Call blocked due to call restriction: 1)
exten => _.,n,playback(feature-not-avail-line)

--------> exten => _.,n,NoCDR()

exten => _.,n,Hangup
exten => _.,n(norestrict1),Noop(No call restriction: 1)
exten => h,1,Hangup()

Unfortunately I don’t have the time to test right now…


To give you an idea of the lengths I went to in order to try to get those damn CDR records to go away, I even tested this (which did NOT work):

exten => _.,1,Noop(Testing for Call Restriction 1 ${RestrictedExt1})
exten => _.,n,GotoIf($["${RestrictedExt1}" = "TRUE"]?restrict1:norestrict1)
exten => _.,n(restrict1),Noop(Call blocked due to call restriction: 1)
exten => _.,n,ResetCDR()
exten => _.,n,NoCDR()
exten => _.,n,Playback(feature-not-avail-line,noanswer)
exten => _.,n,ResetCDR()
exten => _.,n,NoCDR()
exten => _.,n,Playtones(congestion)
exten => _.,n,ResetCDR()
exten => _.,n,NoCDR()
exten => _.,n,Answer
exten => _.,n,ResetCDR()
exten => _.,n,NoCDR()
exten => _.,n,Congestion(20)
exten => _.,n,ResetCDR()
exten => _.,n,NoCDR()
exten => _.,n,Hangup
exten => _.,n(norestrict1),Noop(No call restriction: 1)
exten => h,1,Hangup()

The real problem is this: If you Answer the call, it generates a CDR (actually two). If you DON’T answer the call, then the hangup is ignored. If you go to one of the app-blackhole macros, it contains an Answer statement, e.g.:

include => app-blackhole-custom
exten => hangup,1,Noop(Blackhole Dest: Hangup)
exten => hangup,n,Hangup
exten => zapateller,1,Noop(Blackhole Dest: Play SIT Tone)
exten => zapateller,n,Answer
exten => zapateller,n,Zapateller()
exten => musiconhold,1,Noop(Blackhole Dest: Put caller on hold forever)
exten => musiconhold,n,Answer
exten => musiconhold,n,MusicOnHold()
exten => congestion,1,Noop(Blackhole Dest: Congestion)
exten => congestion,n,Answer
exten => congestion,n,Playtones(congestion)
exten => congestion,n,Congestion(20)
exten => congestion,n,Hangup
exten => busy,1,Noop(Blackhole Dest: Busy)
exten => busy,n,Answer
exten => busy,n,Playtones(busy)
exten => busy,n,Busy(20)
exten => busy,n,Hangup
exten => ring,1,Noop(Blackhole Dest: Ring)
exten => ring,n,Answer
exten => ring,n,Playtones(ring)
exten => ring,n,Wait(300)
exten => ring,n,Hangup

; end of [app-blackhole]

The point is, NoCDR() and ResetCDR() don’t work as advertised, and Hangup doesn’t unconditionally terminate the call (someone explained it once as saying that if the call hasn’t been answered, there’s nothing to hangup - I’m not sure I totally buy that, but I guess it’s as good an explanation as any). Maybe what we need is a Nuke() command, that would simply stop the call in its tracks and destroy all associated CDR records, etc. but as far as I know, there’s nothing like that in Asterisk.

Looks like someone reported this as a bug with NoCDR already,


Not sure which version of Asterisk it is fixed in though.

So probably it is better to leave it in there, assuming that the NoCDR fix will eventually make it’s way in.

As best I can tell, those changes were rolled into Asterisk 1.4 on July 3, 2008. But I’m running Asterisk 1.4.22, which (as best I can tell) was released on October 2. So those changes should be in there already (and I spot-checked one file, cdr.h, which appeared to have the changes already in it). So I have a feeling that either they didn’t fix it in the first place, or they broke it again, or something like that. But who knows, all I know is that everything I tried that should have worked, didn’t work.

You might also find the comments in this thread interesting…


I need create 1 comtext in wich the extension can`t dial international prefix only local calls, 1 comtex without restrictions and 1 comtex only dial emergency numbers.

Anybody help me?

teleinformatica, do this three times - one for each level of restriction

Rob Thomas (one of the original developers of FreePBX) has developed an Outbound Route Permissions module for FreePBX, that allows you to block access to certain routes from specified extensions. You can do bulk changes on the Route Extensions configuration page, and you can individually change access to routes on the extension’s page. At this point this is still an unsupported module in the first stages of development, and you have to install Subversion on your system to grab the module (it’s easy on a Centos system, the documentation explains how).

Hopefully, sometime in the next month or two, the current development team will “bless” this module and it will either become part of the main FreePBX distribution, or at least be added to the third-party module repository. But it does work now, and it lets you both restrict access to routes from various extensions, or to restrict but prepend a prefix and send the call back through the from-internal context, so you can select a different Outbound Route (with different trunk selections). It’s even possible to use a Misc. destination to send a “blocked” call to some internal destination - the documentation explains how.

This module offer the most flexible method of blocking or redirecting outgoing calls with the least pain of setup (NO priority dropdowns, as in the Custom Contexts module, and no messing with custom dialplan code in extensions_custom.conf!). If you’re having any trouble grasping the principle behind how this module can redirect calls to other routes, try reading the document Basic usage of route prefixes to reroute calls from specific extensions - it might help your understanding.

I just want to point out that there’s yet another approach to this, using something that’s been around practically forever but that hardly anyone knew about. Here are a couple of articles on my blog that explain:

Asterisk hiding a useful feature in plain sight by giving it a “cute” name

How to block a single extension’s ability to make outgoing toll calls in FreePBX

What’s kind of sad is that the answer was probably right under our noses all along, if only we’d known to look up what the Asterisk folks decided to call “ex-girlfriend logic”, for some inexplicable reason (well, probably not inexplicable, but certainly not the best choice if they had wanted anyone to be able to find it when using a search engine)!