Asterisk – Using CallerID to make decisions
CallerID information is carried along quite readily within the SIP protocol; most SIP providers pass that along to their customers for free. Other than just showing it as "This is the number the call is coming from", can we do something more useful?
Absolutely!
As an example, imagine the contexts incoming (where calls go to when they come in to Asterisk from a SIP provider) and outgoing (which allows outbound calls from internal phones). There's also an internal context to allow calls between internal phones.
[internal]
exten => s,1,WaitForExten
;Internal extensions
exten => 1000,1,Dial(SIP/alice)
exten => 1001,1,Dial(SIP/bob)
exten => 1002,1,Dial(SIP/charlie)
exten => 1003,1,Dial(SIP/jack)
[outgoing]
include => internal
;External calls
exten => _XX.,1,Dial(SIP/${EXTEN}@voipprovider)
[incoming]
; Incoming calls go to Alice, our receptionist
exten => s,1,Dial(SIP/alice)
Let's add in an override for when an employee rings in from their mobile phone - instead of having to talk to Alice, they get put back into the internal context, so they get to directly dial an extension.
Within the incoming context, add:
exten => s/0491570156,1,Playback(please-enter-the) exten => s/0491570156,n,Playback(number) exten => s/0491570156,n,Goto(internal)
We can make it a bit better yet. Let's allow another employee's mobile phone in, and let's share the config by moving them to a different context. We can also reuse parts of the dialplan in the new context by only using the CallerID information to override the first part of the sequence.
[incoming] exten => s/0491570156,1,Goto(employeemobiles) exten => s/0491570157,1,Goto(employeemobiles) exten => s/0491570158,1,Goto(employeemobiles) [employeemobiles] exten => s/0491570156,1,Set(CDR(accountcode)=alice) exten => s/0491570157,1,Set(CDR(accountcode)=bob) exten => s/0491570158,1,Set(CDR(accountcode)=charlie) exten => s,2,Background(please-enter-the) exten => s,3,Background(number) exten => s,4,WaitForExten include => internal
Note the accountcode being set - this way, you can readily choose to allow outgoing calls from your PABX for remote employees - and it all gets tracked separately (per employee) in your call data records.
You can also just as easily use include => outgoing so that calls in from the mobiles can make calls back out to any other number (eg. if you have great international rates from your PABX, or free calls to certain numbers).
SAGE-AU Presentation
I did a presentation to the SA chapter of SAGE-AU last night - the first presentation I've given in a very long time!
Hacky IP forwarding with IP aliases and SSH
We interrupt your regularly scheduled broadcast of quality sysadmin programming to bring you a brief announcement on using SSH and hacky port forwards to access something via a LAN IP over the Internet.
If I have a server at home that can _only_ be accessed via 192.168.1.12 (say) - perhaps because it is a web application that rewrites all internal URLs to always go to that IP - how do I get access?
Easy. My application listens on 192.168.1.12:8888, so I'll give it exactly that:
ip addr add 192.168.1.12/32 dev lo
ssh -L 192.168.1.12:8888:192.168.1.12:8888 user@example.net
So, I add 192.168.1.12/32 as a local IP address, bind SSH to it, forward all packets for 192.168.1.12:8888 over the SSH tunnel to the gateway server (example.net for this example), then it unbundles it from the SSH stream and passes it to the real 192.168.1.12.
It only works for that one port on that one IP address (though you can add individual IPs and ports easily enough!), but the key part is that it works.
To clean up, close your SSH links and run:
ip addr del 192.168.1.12/32 dev lo
IPv6 is BORING!
Please note that this is all personal opinion, and is not a reflection on the opinion, or policies, of any employer, past, present, future, or comma-less.
What is IPv6?
IPv6 is the next step after our current IPv4 addressing scheme - and not a lot more. Instead of the we-thought-it'd-be-ample 2^32ish addresses, we get the it'll-be-enough-this-time-for-sure 2^128ish addresses. If nothing else, it'll take us a long time to run out again!
So how do we, as sysadmins, use it?
Software developers have taken a lot of the fun out of this already. Most software - Apache, BIND, SSH, etc - have already had IPv6 support for a very long time. You can just add an IPv6 address to the "bind" or "listen" address of the vast majority of non-custom-built software and it will Just Work. Plenty of software doesn't even need that, and will bind to the IPv6 address just as soon as it exists on your system.
If you don't want to use IPv6's native autoconfiguration, statically configuring your address and gateway is a simple enough process on any vaguely modern OS. Probably Windows, too ;).
What about gotchas?
There are plenty of places this can fall down... but none that typically hurt too much.
When you add IPv6 support to a server, note that you're also adding IPv6 support for it to talk to other services. If it makes calls out to another server, it will try to do so over IPv6. So, make sure your IPv6 connectivity is good, and all of the services that your server might connect to work over IPv6 (if they have AAAA DNS records - if not, it will quickly fall back to IPv4 anyway). This is especially true if your servers chatter amongst themselves to share data (like caching resolvers and authoritative nameservers, or webservers and database servers). You'll also be at the mercy of the IPv6 support of any providers that you consume services from.
Anywhere that an IP address gets treated as "special" and not just an opaque block of text, could cause issues. For most, this won't be an issue. But, if you're storing it in a database, make sure you aren't using a short fixed-length string - IPv4 addresses max out at 15 characters but IPv6 goes to 11. I mean, 39.
Why haven't you done it yet?
There are plenty of reasons not to have IPv6 enabled your services.
The easiest answer is that everyone will need an IPv4 address to see everyone else's services, so they'll already have one to see mine. Right? Realistically, IPv4 will probably continue to be supplied behind a carrier-grade-NAT system (or "something"), and most stuff on the internet works just fine behind NAT anyway. It will be a long time before IPv4 addresses start getting dropped entirely from consumer broadband connections.
Not everyone has an IPv6-capable upstream provider. If you are with a particular hosting provider and they don't provide IPv6 connectivity, you're pretty much stuck. Find a new one (and migrate all of your hosts) or just wait it out.
Moving on, and a lot of "miscellaneous" equipment doesn't support IPv6. An up-front example of this is hardware load-balancers. Months out from the beginning of 'IPv4 exhaustion', you can buy new hardware that doesn't support hardware-accelerated IPv6. Sure, you can work around it, or buy from an alternate vendor - but really, that's pretty excessive just to support 128-bit addresses, when we all know that everyone will have IPv4 connectivity for years to come. Do your "appliance" boxes and VMs support IPv6?
The same lack of hardware applies in the consumer-space - seen very many DSL modems with native IPv6 lately? The biggest driver there will be gamers and P2P users wanting to restore end-to-end connectivity.
There are also potential security issues - how do you make sure your IPv4 and IPv6 firewalls are equivalent? Typically, they're treated as independent... changes to one but not the other can lead to breakage or security holes. (Forget to lock off SSH over IPv6 so only your corporate network can see it? Oops! Locked down IPv6 too tight, so upstream service connections over IPv6 suddenly fail? Oops!)
You're being very negative.
Yes, I am. IPv6 is crucial to the long-term health of the Internet. But its biggest driver (in most industries) is merely that it's the "new shiny", and not any real business-case. Even when there is a business case, it's typically for the network, not for the systems. IPv6 connectivity, *check*. IPv6ified webserver... come back next year. Maybe.
So, what if I do find it fun?
Seek professional help. Or, just admit you're a geek.
Community Based Alerting
There are so many ways to monitor whether a particular application is working properly or not. For the average website - Is the server pingable? Can you open a socket to port 80? Can you do a GET request and get a "200 OK" response?
Some checks are more complicated again - "Does the page '/index.php' include the text 'Blog'?". A few even go so far as to simulate multiple page-loads, "clicking" along a path to ensure functionality.
Me, I'm too lazy for that. (Well, sometimes ;). A really sneaky way of checking that your app is working, is to measure the metrics around how much it's being used! How many concurrent users do you have, compared to normal? How many page/thread/post views per second do you currently have? If your site is broken in any way, these numbers are likely to drop off - even if your checks above find nothing wrong.
Let's just say that you have an average of 1000 concurrent users. If the site is broken, people won't hang around. They'll disappear, coming back later to see if you've fixed your problem - so maybe you'll only have 300 concurrent users. Set up an alert that you must have at least [say] 500 users, and alert if the number drops below that. Bingo, you have a new alert that will catch failures with very little effort on your part.
Naturally, most sites don't have flat traffic all week long. Maybe your off-peak traffic is only 400 users, and your peak traffic is 1000. There's no sensible single threshold that can alert you that there's a problem, while also tolerating the normal fluctuations that you're likely to see. The solution, for a lot of sites, is as simple as making your threshold into a sine wave. For this example, the function 300*SIN(time())+400 sits at a pretty nice threshold for my sample data:
How you actually tie this into your alerting system, is obviously dependent on your alerting system! Any system that allows custom scripting is likely to support this pretty readily. As a random example, I've succesfully tied it into munin's alerting by adjusting the "users.critical" config fragment to be echo'd out with a dynamic number driven from a formula similar to the above; munin re-read it on each evaluation and quite happily emailed or SMS'd me when things "weren't quite right".
False alarms are moderately rare, but certainly do happen - usually I found that things like public holidays (including in other countries :) would trigger it. It's far from foolproof, but nifty - and pretty darn easy to implement.
Asterisk – Specifying outbound routes for fun and profit
...or at least a little savings.
You're in a quandry. You've found VSP1, with brilliant general rates. VSP2 has fantastic rates for mobile calls. VSP3 has the cheapest calls of all to England, where you have family. You can't use just the best bits of each... can you?
With Asterisk, yes you can!
The extension configs here assume that you've set up your VSP peers already as vsp1, vsp2, and vsp3.
Starting with your usual outbound context:
[regular_outbound]
; Handle calls out
exten => _XX.,1,Dial(SIP/${EXTEN}@vsp1,,)
;
Add in some new patterns for the different call types. (0011 is the code to dial an international number from Australia, 44 is the UK "country code", and 04 is the prefix for all mobile phones, which are always ten digits).
[regular_outbound]
; Mobiles go via VSP2
exten => _04XXXXXXXX,1,Dial(SIP/${EXTEN}@vsp2,,)
; UK numbers via VSP3
exten => _001144.,1,Dial(SIP/${EXTEN}@vsp3,,)
; Handle all other calls out
exten => _XX.,1,Dial(SIP/${EXTEN}@vsp1,,)
;
After some time, you find out that VSP3 isn't terribly reliable. Rather than just disallow the call at all, instead let's use VSP1 as a fallback.
Instead of:
; UK numbers via VSP3
exten => _001144.,1,Dial(SIP/${EXTEN}@vsp3,,)
;
Do:
; UK numbers via VSP3, fallback to VSP1
exten => _001144.,1,Dial(SIP/${EXTEN}@vsp3,,)
exten => _001144.,2,Dial(SIP/${EXTEN}@vsp1,,)
;
If the first Dial() statement succeeds, the dialplan execution will not continue. If it fails, the next statement will execute, thus running the second Dial() via VSP1. (This functionality is more commonly used to implement line hunting, or to redirect to voicemail on busy and no-answer).
Note that in some cases, the call may actually happen twice - if the remote end is genuinely busy, for instance. You can check the contents of ${DIALSTATUS} and only redirect in some cases - but then you might not fall through if VSP3 incorrectly gives you a busy tone instead of ringing!
Asterisk – Email notifications
One of the things I like about Asterisk, is its ability to send email notifications to me if certain things happen. There's no built-in ability to send emails by itself, so we'll use its integration with the underlying server to do it.
Let's start by sending me an email if someone uses my VoIP line to make an international call. In Australia, all international calls are prefixed with "0011"...
[regular_outbound]
; Email me for international calls
exten => _0011.,1,System(mail -s "International Call to ${EXTEN}" example@mibus.org < /home/mibus/Documents/international-call.msg)
exten => _0011.,n,Dial(SIP/${EXTEN}@nodephone,,)
; Handle other calls out
exten => _XX.,1,Dial(SIP/${EXTEN}@nodephone,,)
;
Note that the subject is InternationalCall, the destination email address is example@mibus.org, and the body of the email comes from a file I've already saved to /home/mibus/Documents/international-call.msg. From now on, I'll know whenever an expensive international call is made.
A rarely-used feature of extensions.conf is the built-in h extension. Let's use that (inside whatever context rings your real internal phone) to let us know by email when we've missed a call for any reason. For example, if my phone is rung on extension 99...
exten => 99,1,NoOp(Inbound)
exten => 99,n,Dial(SIP/myphone,30,)
exten => 99,n,Hangup()
exten => h,1,GotoIf($["${DIALSTATUS}" = "ANSWER"]?done)
exten => h,n,System(mail -s "Missed Call From ${CALLERID(num)}" example@mibus.org < /home/mibus/Documents/missed-call.msg)
exten => h,n(done),NoOp()
;
Note that I'm specifically using only the number component of the Caller-ID - if your VSP supports sending textual Caller-ID alongside it, it means that random foreign callers can alter your System() call!
Asterisk – Dual-dialling to save money
Asterisk saves me money, even using just the one VoIP provider. (Disclaimer: The provider I use is run by my employer, but I'm writing this only as a happy customer! :).
It just so happens that my work desk phone number is a free call, but my wife can never remember the number and is usually in too much of a hurry to stop and think about whether I'm at my desk yet etc. etc. (We have lots of small kids - I'm surprised I have time to write this post :). My VoIP provider (nodephone) also happens to allow two concurrent calls by default... let's see what fun we can have with that?
My outgoing context used to look a bit like this:
[frominside]
exten => _XX.,1,Dial(SIP/${EXTEN}@nodephone,,)
;
What I wanted, was to automatically ring my desk phone whenever I was likely to be at it. Just in case I wasn't, ring my mobile phone at the same time - let me answer whichever I choose.
Move the contents of the frominside context into a new context regular_outbound, and use an include to keep it all working the same...
[frominside]
include => regular_outbound
[regular_outbound]
exten => _XX.,1,Dial(SIP/${EXTEN}@nodephone,,)
;
Next, make a new context that will dual-dial when you call my mobile number. (No, those aren't real phone numbers, they're dummy numbers from ACMA). Include it at the top of the frominside context, and limit it to a "safe" set of working hours.
[frominside]
include => mibus_at_work|08:15-16:45|mon-fri|*|*
include => regular_outbound
[regular_outbound]
exten => _XX.,1,Dial(SIP/${EXTEN}@nodephone,,)
[mibus_at_work]
exten => 0491570156,1,Wait(1)
exten => 0491570156,n,Playback(hang-on-a-second)
exten => 0491570156,n,Set(LIMIT_WARNING_FILE=beep)
exten => 0491570156,n,Dial(SIP/${EXTEN}@nodephone&SIP/0855501234@nodephone,,)
;
Done! Now, trying to ring 0491570156 will also ring 0855501234. They're charged at vastly different rates, so if I can, I'll answer on the "cheaper" one. If not, it'll ring my mobile phone anyway - just as it always would have.
Plain-text password storage is good
...or at least, not as bad as it's sometimes made out to be.
Please note that I am not discussing any password system used by any employer, past or present. This is based on general industry knowledge only, and is meant to explain why so much software suggests or requires plaintext passwords to be stored.
When I first came across a professional application that used plaintext password storage (about six years ago now) - I thought, "Ugh, why would anyone store passwords cleartext?". There are a huge number of really valid reasons why not to - the easiest one being simply "what happens if/when that database gets stolen?". You may or may not be concerned by "Can the server admins and developers see my password?". I found out why the hard way when I tried to do something similar, maybe a year or so later.
So, why would a sysadmin or software developer choose to use cleartext password storage, with the risks that it inherently has? The same reason as with most security issues - it's overridden by convenience and usability. Password security has two main aspects; protection when stored, and protection when in transit.
In-transit seems pretty easy, but has quite a few pitfalls. A horribly common mechanism is just to pass it cleartext. Eww! Another is to pass it reversibly encrypted over SSL. Better - man-in-the-middle snooping is stopped - but still not really great. What about only sending an MD5 across the wire? Challenge-response systems are yet another solution - the server sends you a secret string, you combine it with your password in a way predictable to the server and send a munged 'response' back to the server. Great, right?
No. Your average challenge-response system uses a function along the lines of MD5(challenge+MD5(password)). So for both of the last two options, all the server needs to store is the MD5 of the password, instead of the password itself. Win. Except for the fail that you've just made your MD5 of your password valuable, because that's all the attacker needs now - and that's exactly what the server is storing!
Moving back to storage - what sort of authentication systems does your software use, and what do you need to store to support it? CRAM-MD5? Plaintext over the wire? APOP? Something custom? MD5? CHAP? What happens when CRAM-SHA256 displaces CRAM-MD5, and you're only storing an MD5? You can only use the authentication methods compatible with what you originally planned for. Maybe you can work around it by storing ten different types of hashes, but it still isn't really future-proof. You certainly aren't protected against requests you could never foresee (Can we auth IMAP users against this forum user database?).
However, if you're storing the password in cleartext, the answer becomes "So what? Can-do, boss!". You can generate whatever sort of hash you need on the fly, you can use any password authentication scheme that you want. You can integrate with any password-based authentication scheme. Any. Not just the one you're using today, or tomorrow, but the one you'll be using five years from now. No matter how it passes over the wire, you can use it.
I've simplified this quite a bit, and there are a bajillion other ways to work around it - but nothing that is as compatible or usable as just storing the passwords in cleartext.
If you do want to work around it... Be inconvenienced! Some options might be:
- Make a stand and decide what protocols you support
- Don't use just passwords, use two-factor authentication (passwords plus tokens, SMS codes, biometrics, etc)
- Accept the cost of forcing the user to visit the "change password" page to generate new hash formats when needed
Even as a user, you have a great option for keeping your own security:
- Use unique passwords for every account, so a compromised password can only affect the one account
It's universally sound advice, but often ignored.
Remember finally that we (as developers and sysadmins) can make systems that are incredibly secure - just look at banks. It's just inconvenient. Technical security, DMZs, firewalls, physical security policies, segregated backups, employee access policies. All that work! But it can be done.
Asterisk – Database-driven CallerID
One of the nice things Asterisk can do is manipulate Caller ID information on the fly. Since I'm too lazy to update the stored numbers within my individual cordless handsets, I use Asterisk to cheat. Asterisk looks up the incoming phone number ("08XXXXXXXX") in a MySQL table, finds matching text ("Bob") and passes that along to the handsets for display.
This assumes you have a MySQL database on the same box as asterisk, with a username of asterisk, password of mypassword, your database is called asterisk, and your final internal destination is SIP/myphone. (I'm inventive, I am... ;). Extension 99 in this example is where your inbound calls end up in order to ring your phone.
exten => 99,1,NoOp(Inbound)
exten => 99,n,MYSQL(Connect connid localhost asterisk mypassword asterisk)
exten => 99,n,GotoIf($["${connid}" = ""]?nodb)
exten => 99,n,MYSQL(Query resultid ${connid} SELECT\ name\ FROM\ addressbook\ WHERE\ phone_number="${CALLERID(num)}"\ LIMIT\ 1)
exten => 99,n,MYSQL(Fetch fetchid ${resultid} name)
exten => 99,n,MYSQL(Clear ${resultid})
exten => 99,n,Set(CALLERID(name)=${name})
exten => 99,n,MYSQL(Disconnect ${connid})
exten => 99,n(nodb),NoOp(DoneDB)
exten => 99,n,Dial(SIP/myphone,30,)
exten => 99,n,Congestion()
To actually make it work, you also need the database filled in.
CREATE TABLE addressbook (
phone_number VARCHAR(40),
name VARCHAR(40),
PRIMARY KEY(phone_number)
);
INSERT INTO addressbook VALUES ('08XXXXXXX1', 'Bob');
INSERT INTO addressbook VALUES ('08XXXXXXX2', 'Mary');
(and so forth).
So, what next? Well, you're logging your CDR into MySQL, right? Let's make a 'view' of the CDR that includes the names:
CREATE VIEW cdr_with_names AS
SELECT cdr.*,ab_src.name AS src_name,ab_dst.name AS dst_name
FROM cdr
LEFT JOIN addressbook ab_src
ON ((cdr.src = ab_src.phone_number)
OR (CONCAT('08',cdr.src) = ab_src.phone_number))
LEFT JOIN addressbook ab_dst
ON ((cdr.dst = ab_dst.phone_number)
OR (CONCAT('08',cdr.dst) = ab_dst.phone_number))
;
What's this CONCAT('08',cdr.src) stuff? '08' is my local area code, and I can leave it off outbound calls to the same area code if I so choose. This way, my query finds both variants ('XXXXXXXX' and '08XXXXXXXX').
It's such a messy view for direct use, though. Let's clean it up with a view that only shows us the columns we really care that much about:
CREATE VIEW cdr_easy AS SELECT calldate,src,src_name,dst,dst_name,disposition,billsec FROM cdr_with_names
In a few minutes of hacking at extensions.conf and MySQL, you now have database-driven CallerID text and a straightforward CDR view that includes human-readable names. What can't Asterisk do?
