NOTE: There is a newer version of this article that adds TrueCNAM scoring to help weed out telemarketers, robo-callers, and other “spam” callers. See How to hack the FreePBX blacklist for better call blocking capability, take 2 – adding TrueCNAM scoring for that article, or continue here if you don’t want to use TrueCNAM scoring.
Have you ever wished that you could blacklist calls in FreePBX using criteria other than just the exact phone number? It’s quite possible, and here’s an explanation of how it can be accomplished.
First, using a text editor such as nano, go into the file /etc/asterisk/extensions_additional.conf and find the context headed [app-blacklist-check]. It will probably look like this:
[app-blacklist-check]
include => app-blacklist-check-custom
exten => s,1(check),GotoIf($[“${BLACKLIST()}”=”1”]?blacklisted)
exten => s,n,Set(CALLED_BLACKLIST=1)
exten => s,n,Return()
exten => s,n(blacklisted),Answer
exten => s,n,Wait(1)
exten => s,n,Zapateller()
exten => s,n,Playback(ss-noservice)
exten => s,n,Hangup
Note that this is a pretty simple macro context. The first line checks to see if a caller ID number appears in the Asterisk blacklist, and if so, it shuffles the call off to a “number not in service” recording.
If you copy and paste this entire context into the file /etc/asterisk/extensions_override_freepbx.conf and remove the “include” line at the start of the macro, you can then modify it to do what you want. Let’s look at that same context again, but with a few added lines, as it might appear in /etc/asterisk/extensions_override_freepbx.conf
[app-blacklist-check]
;Make sure CDR DID field is set on blacklisted calls
exten => s,1(check),Set(CDR(did)=${FROM_DID})
;Original first line of context with line number changed
exten => s,n,GotoIf($[“${BLACKLIST()}”=”1”]?blacklisted)
;Blacklist Caller ID names found in custom Asterisk database blistname if used
exten => s,n,GotoIf($[“${DB_EXISTS(blistname/${TOUPPER(${CALLERID(name)})})}”=”1”]?blacklisted)
;Blacklist Caller ID names of single letter followed by 9 or more digits
exten => s,n,Set(regx=^[A-Z][0-9]{9}[0-9]* *$)
exten => s,n,GotoIf($[${REGEX(“${regx}” ${CALLERID(name)})} = 1]?blacklisted)
;Blacklist numbers where first digit of 7 digit local number is 0 or 1
exten => s,n,Set(regx=^[+]?1?[0-9]{3}[01][0-9]{6}$)
exten => s,n,GotoIf($[${REGEX(“${regx}” ${CALLERID(num)})} = 1]?blacklisted)
;Blacklist numbers where first digit of area code is 0 or 1
exten => s,n,Set(regx=^[+]?1?[01][0-9]{9}$)
exten => s,n,GotoIf($[${REGEX(“${regx}” ${CALLERID(num)})} = 1]?blacklisted)
;Blacklist numbers where first three digits of 7 digit local number are 555
exten => s,n,Set(regx=^[+]?1?[0-9]{3}555[0-9]{4}$)
exten => s,n,GotoIf($[${REGEX(“${regx}” ${CALLERID(num)})} = 1]?blacklisted)
;Blacklist numbers that start with 1 but are longer than 11 digits total
exten => s,n,Set(regx=^[+]?1[0-9]{11}[0-9]*$)
exten => s,n,GotoIf($[${REGEX(“${regx}” ${CALLERID(num)})} = 1]?blacklisted)
;Blacklist numbers of less than 7 digits in length
exten => s,n,GotoIf($[${LEN(${CALLERID(num)})} < 7]?blacklisted)
;Blacklist numbers of 8 digits in length
exten => s,n,GotoIf($[${LEN(${CALLERID(num)})} = 8]?blacklisted)
;Next line is from the original blacklist context
exten => s,n,Set(CALLED_BLACKLIST=1)
;Special CallerID name lookup; should not really be here but works
exten => s,n,ExecIf($[“${DB_EXISTS(cidname/${CALLERID(num)})}”!=”1″]?Return())
exten => s,n,Set(CALLERID(name)=${DB(cidname/${CALLERID(num)})})
exten => s,n,Set(__SKIPCIDLOOKUP=1)
;More original blacklist context
exten => s,n,Return()
exten => s,n(blacklisted),Answer
;Prepend *BL* to Caller ID name of blacklisted numbers so they can be distinguished in CDR
exten => s,n,Set(CALLERID(name)=*BL*${CALLERID(name)})
;Set Call Detail Record Userfield to Blacklisted
exten => s,n,Set(CDR(userfield)=Blacklisted)
;Still more original blacklist context
exten => s,n,Wait(1)
exten => s,n,Zapateller()
exten => s,n,Playback(ss-noservice)
exten => s,n,Hangup
;–== end of [app-blacklist-check] ==–;
The lines in blue are from the original context that was copied from /etc/asterisk/extensions_additional.conf. We should mention here that not all the ideas in the new lines are original with us; some are adaptations of things we’ve read over many months in various forums.
You don’t need to use all of these added lines shown. On the other hand, you can add your own customizations. Here is what each added line (or set of lines) means:
The change to line 1 (moving the original line 1 down to the next line) adds Set(CDR(did)=${FROM_DID}), which causes the call detail record to show the DID the call came in on for blacklisted calls.
;Blacklist Caller ID names found in custom Asterisk database blistname if used
exten => s,n,GotoIf($[“${DB_EXISTS(blistname/${TOUPPER(${CALLERID(name)})})}”=”1”]?blacklisted)
This addition allows you to blacklist by exact Caller ID name. To add a name to the blacklist-by-name database, go into the Asterisk CLI and enter this:
database put blistname NAME 1
Where NAME is the name you want to add – enclose it in quotation marks if it contains spaces:
database put blistname “SOME NAME” 1
You can use a descriptive word if you like in place of the number 1 at the end of the line. To delete an already entered name:
database del blistname NAME
And to see what’s already in the blacklist-by-name database:
database show blistname
Next we come to:
;Blacklist Caller ID names of single letter followed by 9 or more digits
exten => s,n,Set(regx=^[A-Z][0-9]{9}[0-9]* *$)
exten => s,n,GotoIf($[${REGEX(“${regx}” ${CALLERID(name)})} = 1]?blacklisted)
These lines are an example of blacklisting a Caller ID name using a regular expression. This will catch any Caller ID name that starts with a single letter and is followed by 9 or more digits, and any number of spaces at the end. This particular example will get rid of those irritating Vnnnnnnnnn… name calls, even if they change the starting letter to something other than “V”.
;Blacklist numbers where first digit of 7 digit local number is 0 or 1
exten => s,n,Set(regx=^[+]?1?[0-9]{3}[01][0-9]{6}$)
exten => s,n,GotoIf($[${REGEX(“${regx}” ${CALLERID(num)})} = 1]?blacklisted)
;Blacklist numbers where first digit of area code is 0 or 1
exten => s,n,Set(regx=^[+]?1?[01][0-9]{9}$)
exten => s,n,GotoIf($[${REGEX(“${regx}” ${CALLERID(num)})} = 1]?blacklisted)
;Blacklist numbers where first three digits of 7 digit local number are 555
exten => s,n,Set(regx=^[+]?1?[0-9]{3}555[0-9]{4}$)
exten => s,n,GotoIf($[${REGEX(“${regx}” ${CALLERID(num)})} = 1]?blacklisted)
These are three examples of blacklisting numbers by regular expression patterns. The first example blacklists numbers that have an invalid exchange (where the 7-digit part of the number starts with 0 or 1), while the second blacklists numbers that come in with an area code starting with 0 or 1, which is invalid. The third blocks numbers that appear to come from the “directory assistance” exchange 555 in any area code. In all cases, it does not matter if the number starts with a leading + and/or 1. Note that Asterisk has a fairly restrictive syntax for regular expressions, so you can’t do many of the substitutions that would be possible in other computer languages. See the article, Did you know that Asterisk has the ability to evaluate Regular Expressions, though not in the same way as Perl or FreeSWITCH? for more information.
You can always add additional blacklist patterns by duplicating an appropriate pair of lines (for blacklisting by name or number pattern) and then modifying the regular expression. For example, to block calls from certain specific area codes, you could use a regular expression of the form ^[+]?1?(xxx|yyy|zzz)[0-9]{7}$ where xxx, yyy, and zzz are the area codes you want to block. You can include as many area codes as you need, separated by the | character.
;Blacklist numbers of 8 digits in length
exten => s,n,GotoIf($[${LEN(${CALLERID(num)})} = 8]?blacklisted)
This is an example of blocking numbers of a specific length. If you have a bad caller that sends numbers that are exactly 8 characters in length, this will catch them. You can, of course, change the “8” to catch other bad length numbers, but be careful not to inadvertently block wanted international or other valid calls with non-standard number lengths.
The next added section consists of these lines:
;Special CallerID name lookup; should not really be here but works
exten => s,n,ExecIf($[“${DB_EXISTS(cidname/${CALLERID(num)})}”!=”1″]?Return())
exten => s,n,Set(CALLERID(name)=${DB(cidname/${CALLERID(num)})})
exten => s,n,Set(__SKIPCIDLOOKUP=1)
It could be argued that this doesn’t really belong in the blacklist code, so feel free to omit it if you don’t like it – remember, these are just examples. What this does is checks the Asterisk Phonebook to see if the calling number appears there, and if it does, it sets the Caller ID name to the name associated with that number in the Phonebook. There are other ways to do this, but we show it here for one simple reason – you could use it to “whitelist” a certain number that would otherwise be taken out by a regular expression-based rule, although you’d have to modify the dialplan shown here a bit to do so. As it stands, this only does the Asterisk Phonebook lookup, and applies the name from the Asterisk Phonebook if it finds it there.
There is one issue with doing this, and that is that if you use another Caller ID lookup source it will override the Asterisk Phonebook lookup. To avoid that, you can copy the [cidlookup] context from /etc/asterisk/extensions_additional.conf to /etc/asterisk/extensions_override_freepbx.conf and change the first line to a test for the SKIPCIDLOOKUP variable. For example, if the first line is:
exten => cidlookup_1,1,Set(CURLOPT(httptimeout)=7)
You could change it to:
exten => cidlookup_1,1,ExecIf($[“${SKIPCIDLOOKUP}”=”1”]?Return())
exten => cidlookup_1,n,Set(CURLOPT(httptimeout)=7)
Don’t forget to change the line number 1 to n in the original first line, since it’s no longer line 1.
Finally, the last hack shown is this – you would probably want to use one or the other of these, but not both:
;Prepend *BL* to Caller ID name of blacklisted numbers so they can be distinguished in CDR
exten => s,n,Set(CALLERID(name)=*BL*${CALLERID(name)})
;Set Call Detail Record Userfield to Blacklisted
exten => s,n,Set(CDR(userfield)=Blacklisted)
We don’t recall where we first saw this, but it is a great idea. In the first example, if the call is blacklisted, the characters *BL* are added to the start of the Caller ID name, and appear that way in the call detail. In the second example, the “Userfield” in the call detail record is set to the word “Blacklisted”. Whichever you use, when you are looking at your call records, this tells you instantly if a problem caller is already being blacklisted, so you don’t need to wonder if you should add them to the blacklist. The first example is probably more appropriate for older FreePBX systems that didn’t display the Userfield in the call detail, while the second works much better in newer FreePBX installations. I would not suggest using both, though there is no harm if you do.
Keep in mind that any time you replace a context in /etc/asterisk/extensions_override_freepbx.conf, it is your responsibility to make sure it “tracks” any changes to the original context in /etc/asterisk/extensions_additional.conf. So, after any major FreePBX upgrade, you should take a look at the original [app-blacklist-check] context (and the [cidlookup] context, if you have modified that) and make sure that they have not changed, and if they have, that you make the same changes to your replacement contexts.
One additional point – at the top of each of the original contexts, there is an “include” statement that we suggest you remove when you copy the context to /etc/asterisk/extensions_override_freepbx.conf, for example, “include => app-blacklist-check-custom“. You might wonder if you could create a context named app-blacklist-check-custom in /etc/asterisk/extensions_custom.conf and copy the original context there. But, for whatever reason, that just doesn’t seem to work, and there’s no real advantage to putting the copied context there anyway.
EDIT: If the above seems too complicated, there is an alternate technique that may work for some FreePBX users, provided you don’t care about receiving international calls. See this thread on DSLReports for details.
Related articles:
Link: Asterisk: Blacklisting For Multiple Users
Forum thread: User-specific Caller ID/whitelist/blacklist in Asterisk/FreePBX
Nice job. If any of your readers are wanting individual user blacklists you might want to take a look at this thread on DSLReports:
http://www.dslreports.com/forum/r28606460-Asterisk-any-freepbx-asterisk-guru
I posted one way to do it in this thread, which is actually rather similar to some of what you have here, in this post:
http://www.dslreports.com/forum/r28614173-
The problem is that there is no way for individual users to add numbers to their blacklists, at least not without some extra coding. If you’d like to take what I have written and expand on it, please feel free to use it as you see fit. But, I read today that the FreePBX people might be working on a user blacklist module for the next version of FreePBX, so it may not be worth the effort.
By the way, I think I must have read the same forums you did, because I also use the *BL* tag to indicate already blacklisted numbers in the main blacklist, but change it to *BU* for numbers in the user blacklist.
I’ve been playing with this for the past day.
On FreePBX 12.0.64 / Asterisk (Ver. 11.17.1)
I’m not able to get the Caller ID Name to work. The mix of quotation marks was giving me a syntax error
with the blacklist by number section – but after fixing that, the Blacklist by number now works fine.
Could there be other syntax errors here?
Glenn, at some point in time something changed. You used to be able to specify the database name (blistname) in either uppercase or lowercase, but somewhere along the line Asterisk stopped recognizing the database name if you specified it in all caps, at least in the DB_EXISTS function. The article has been edited accordingly.
Figured it out.
It’s no longer
exten => s,1(check),Set(CDR(did)=${FROM_DID})
it’s now;
exten => s,1(check),Set(CDR(clid)=${FROM_DID})
I made a couple of other tweaks.
Here’s the final script I used.
app-blacklist-check]
exten => s,1(check),Set(CDR(clid)=${FROM_DID})
exten => s,n,GotoIf($[“${BLACKLIST()}”=”1”]?blacklistnum)
exten => s,n,GotoIf($[“${DB_EXISTS(blistname/${TOUPPER(${CALLERID(name)})})}”=”1”]?blacklistname)
exten => s,n,Set(CALLED_BLACKLIST=1)
exten => s,n,Return()
exten => s,n(blacklistnum),Answer
exten => s,n,Set(CDR(userfield)=BL_NUMBER)
exten => s,n,Wait(1)
exten => s,n,Zapateller()
exten => s,n,Playback(ss-noservice)
exten => s,n,Hangup
exten => s,n,Return()
exten => s,n(blacklistname),Answer
exten => s,n,Set(CDR(userfield)=BL_NAME)
exten => s,n,Wait(1)
exten => s,n,Zapateller()
exten => s,n,Playback(ss-noservice)
exten => s,n,Hangup
;–== end of [app-blacklist-check] ==–;
This tells me how the caller was caught (by number or name).
The purpose of the line:
exten => s,1(check),Set(CDR(did)=${FROM_DID})
Is to make sure the DID that the call came in one gets saved in the call detail, since it sometimes hasn’t been saved yet by the time the call gets to the blacklist check. We checked a system running the same software you have and in the dialplan that FreePBX generates, in extensions_additional.conf, they are still using CDR(did) and not CDR(clid) as you suggest. For example check the [ext-did-0001] context, there is a line in there that reads:
exten => s,n,Set(CDR(did)=${FROM_DID})
So we’re not sure why you needed to make the change you did, but it must be something unique to your situation. As for the quotes, that is a WordPress issue – WordPress sometimes tried to change regular single and double quotation marks to the “prettified” versions. It only does it sometimes and we haven’t figured out why, but it’s a real nuisance when it happens because the “prettified” versions won’t work when they appear in code of any kind, including Asterisk dialplan.
I actually went to the Asterisk wiki to lookup the CDR function name & options variables.
https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+Function_CDR
It doesn’t show “did” so I just assumed that the name descriptor syntax had changed.
I also checked extensions_additional.conf and I see you’re right about them still using CDR(did).
I have no explanation for this or why the script wouldn’t work for me without that change.
As for the quotation marks – It took a bit of staring at the script before I finally spotted it.
I figured it was a wordpress problem.