December 2009 Archives

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]

About this Archive

This page is an archive of entries from December 2009 listed from newest to oldest.

March 2009 is the previous archive.

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

Pages