Anatomy of an Asterisk rick roll

| | Comments (0) | TrackBacks (1)
I was inspired by a friend (EFNet - steve nash aka oz) on IRC - he (or his friend(s)) had setup an incoming phone number that would just rickroll you.  Nothing else.

Well, hell, that's easy to setup with Asterisk, but I was determined to do one better.  Not only would I setup a phone number that would rickroll you, but I would also make the number call you back and rickroll you again.

With Asterisk, PHP, MySQL, and great SIP service, this is cake.  First things first -- I made a new musiconhold class (musiconhold.conf):

[rickroll]
mode=files
directory=/var/lib/asterisk/custom-moh/rickroll
And the extension logic (extensions.conf) - this goes into your incoming SIP context:

exten => 6302061300,1,Answer
exten => 6302061300,n,AGI(rickroll)
exten => 6302061300,n,Ringing
exten => 6302061300,n,Wait(4)
exten => 6302061300,n,MusicOnHold(rickroll)
exten => 6302061300,n,Hangup

Bam - if you call 630-206-1300, you will get rickrolled.  However, if you notice the AGI(rickroll) on the second line of the extension logic -  that's what makes this rickroll funnier.

I created a MySQL table:

CREATE TABLE rickroll (
  rickrollid bigint(20) NOT NULL auto_increment,
  phonenum varchar(10) NOT NULL default '',
  dialtime int(11) default NULL,
  calls tinyint(4) default NULL,
  PRIMARY KEY (rickrollid),
  UNIQUE KEY (phonenum),
  KEY (dialtime)
);

And created an AGI with command line PHP -- in /var/lib/asterisk/agi-bin -- make sure it's got execute permission (chmod 755 it when yer done):

#!/usr/bin/php -q
<?

$stdin = fopen('php://stdin', 'r');
$stdout = fopen('php://stdout', 'w');

while (!feof($stdin)) {
    $temp = fgets($stdin);
    $temp = str_replace("\n", "", $temp);
    $s = explode(":", $temp);
    $agivar[ $s[0] ] = trim($s[1]);
    if (($temp == "") || ($temp == "\n")) {
       break;
       }
    }

$mysql=mysql_connect("localhost","RICKROLLUSER","RICKROLLPASS","rickroll");
mysql_select_db("rickroll");

$callerid = $agivar[agi_callerid];
$newd = time() + 300;

$q = "INSERT INTO rickroll (phonenum, dialtime, calls) VALUES ('$callerid','$newd',0)";
$qid = mysql_query($q, $mysql);

mysql_close($mysql);

exit;

There.  We now have incoming rickroll calls getting logged to a database.  It sets the dialtime to the current time + 300 seconds (5 minutes) - so in 5 minutes, the cronjob will pick the call up and make it.

Here's where the fun comes in.  We need a cronjob that uses the manager interface of Asterisk to initiate calls after they're read out of the database.  Add this to manager.conf in asterisk:

[rickroll]
secret = MANAGER_PASSWORD_HERE
deny=0.0.0.0/0.0.0.0
permit=127.0.0.0/255.255.255.0
read = system,call,log,verbose,command,agent,user
write = system,call,log,verbose,command,agent,user

Now we're ready for the evil.  Here's the PHP cronjob (heavily commented so you can understand the flow) - I wrote a couple functions for sending manager commands so I could reuse the code in other stuff:

#!/usr/bin/php -q
<?php

// Set some variables
$callerid = "";  // Holds the caller ID we'll use to rickroll the person back
$time = time();  // Current time - in epoch (seconds since 1970)
$del = array();  // Array of entries to delete/update after we call

// Connect to the database
$mysql=mysql_connect("localhost","RICKROLLUSER","RICKROLLPASS","rickroll");
mysql_select_db("rickroll");

// Open a socket to the manager interface of Asterisk.
$ast_man = fsockopen("127.0.0.1","5038", $errno, $errstr, 30);

// Read in the version string
$in = fread($ast_man, 4096);

// Login with the manager credentials we used in manager.conf
fwrite($ast_man, "Action: login\r\n");
fwrite($ast_man, "Username: rickroll\r\n");
fwrite($ast_man, "Secret: MANAGER_PASSWORD_HERE\r\n");
fwrite($ast_man, "Events: off\r\n\r\n");

// Get the response
$resp = get_response($ast_man);

// If we get a login error - die.
if ($resp['response'] != "Success") {
    exit;
}

// Get the list of people that need a return call from Rick Astley
$q = "SELECT rickrollid, phonenum, dialtime, calls FROM rickroll WHERE dialtime < $time";
$qid = mysql_query($q, $mysql);

while ($r = mysql_fetch_assoc($qid)) {
    // Set the variables from the array -- this is for pure laziness:
    $rickrollid = $r['rickrollid'];
    $phonenum = $r['phonenum'];
    $calls = $r['calls'];
    $dialtime = $r['dialtime'];

    if ($calls == 0) {
        // Never been called back yet -- set the caller ID to the rickroll #
        $callerid = "6302061300";
    } else {
        // They got one callback already.  This is the second (last) one -- so
        // we'll randomize the last 4 digits of their phone # and use that as our
        // caller ID.
        $callerid = substr($phonenum, 0, 6) . rand(0,9) . rand(0,9) . rand(0,9) . rand(0,9);
    }

    // Set the array of asterisk manager parameters to initiate the call:
    $evil = array('Action' => 'Originate',
              'Channel' => "LOCAL/$phonenum@rickroll",
              'Context' => 'rickroll',
              'Exten' => '1000',
              'Priority' => '1',
              'Callerid' => $callerid,
              'Timeout' => '30000');

    // We're not going to call ourselves back.
    if ($phonenum != '6302061300') {
        send_command($ast_man, $evil);
    }

    // Add this phone number to the array - so we can update/delete the records
    $del[] = array('phonenum' => $phonenum, 'calls' => $calls, 'rickrollid' => $rickrollid);
    }

// Process the array of delete/updates
foreach($del as $r) {
    if ($r['calls'] == 0) {
        // This was their first call -- the next one will be done in a random amount of time.

        // The next call will be in some random amount of time 15 minutes from now.
        // I did 3 rand's of 0-300 to increase the odds of it not calling right back.
        $newd = time() + rand(0,300) + rand(0,300) + rand(0,300);

        // Update their entry in the database with the new call time.
        $q = "UPDATE rickroll SET dialtime = $newd, calls = 1 WHERE rickrollid = " . $r['rickrollid'];
        $qid = mysql_query($q, $mysql);
    } else {
        // Second call - just delete them from the table.
        $q = "DELETE FROM rickroll WHERE rickrollid = " . $r['rickrollid'];
        $qid = mysql_query($q, $mysql);
    }
}

fclose($ast_man); 

mysql_close($mysql);

exit;

function send_command($fp, $cmdarray) {
    $_t = "";

    foreach($cmdarray as $k => $v) {
      $_t .= "$k: $v\r\n";
    }

    $_t .= "\r\n";

    fwrite($fp, $_t);
}

function get_response($fp) {

    $ret = "";

    while(1) {
        $pkt = fread($fp, 1);
        $ret .= $pkt;

        if (substr($ret, -4) == "\r\n\r\n") break;
    }

    $a = explode("\r\n", $ret);

    foreach($a as $k => $v) {
        if (strlen($v) < 5) {
            unset($a[$k]);
        } else {
            list($param, $value) = explode(": ", $v, 2);
            $p = strtolower($param);
            $r[$p] = trim($value);
        }
    }

    return $r;
}

The comments should hopefully explain it all, if not, feel free to drop me a line w/ questions.  Basically, the cronjob grabs all the upcoming calls, and then proceeds to make them.  If its their first callback - it will set the callerID to the rickroll phone number (630-206-1300).  If its their second/last - it will set the caller ID to a number similar to the caller's # - if you call from 303-555-1122, it will use the same NPA-NXX - 303-555, and then append 4 random #'s.  I figured if someone notices rickroll is calling them back, they'll ignore the callback.  A call phone a number in their same area code + prefix might make someone answer.

To install the cronjob - stick the cronjob bin (make sure it has execute permission) somewhere.  /usr/local/bin works for me.  Add this to your crontab:

*/2 * * * *    /usr/local/bin/cron_rickroll.php  >> /tmp/rickroll.log 2>&1

The last piece is the extension context [rickroll].  In extensions.conf:

[rickroll]

;  The extension we connect the called party to -- rickroll!
exten => 1000,1,Answer
exten => 1000,n,MusicOnHold(rickroll)
exten => 1000,n,Hangup

;  The outbound calling of our rickroll victims - standard outdial
exten => _NXXNXXXXXX,1,Dial(SIP/1${EXTEN}@primary-sip-out)
exten => _NXXNXXXXXX,n,Dial(SIP/1${EXTEN}@secondary-sip-out)
exten => _NXXNXXXXXX,n,HangUp

The code isn't super pretty, and I'm sure there are a thousand improvements that can be made to the code/logic, but for a quick one hour joke - its not too bad.

Feel free to comment/email with questions :)

Andy

Links for more information:

Asterisk
Asterisk AGI
Asterisk Dial Command
Asterisk extensions.conf
Asterisk manager

Reblog this post [with Zemanta]

1 TrackBacks

Listed below are links to blogs that reference this entry: Anatomy of an Asterisk rick roll.

TrackBack URL for this entry: http://unf.net/MT/mt-tb.cgi/10

This post was mentioned on Reddit by autoatsakiklis: I've called that number and it works, so you don't have to. Also it loops the song over and over again. Read More

Leave a comment

About this Entry

This page contains a single entry by Andy Goodwin published on December 27, 2009 2:48 PM.

2009 Hydroponic Garden Week 1 was the previous entry in this blog.

Find recent content on the main index or look in the archives to find all content.

Pages