NMAP Scanner by Phone

jjolly

New Member
Joined
Oct 1, 2010
Messages
7
Reaction score
0
A big thank you to all the Asterisk Guru's here for all of the great information here on the forum. I've learned more here than ever could in any Asterisk based book.

I've been pulling my hair out trying to get the following .agi script to work with PIAF but still can't seem to figure out what the problem is. The script allows one to perform an nmap network scan by way of telephone. Any help whatsoever with this will be greatly appreciated.

I am running PIAF Purple + Incredible PBX on CentOS release 5.7 (Final) :32 Bit Kernel: 2.6.18-274.3.1.el5.

I have Perl (v5.15.4) along with the following CPAN modules installed:

Asterisk::AGI
Nmap::parser
File::Basename
Digest::MD5

I also currently have Cepstral installed along with along with a single Alison 8khz license (installed with the NV script).

My dial plan (extensions_custom.conf) looks like this:

exten => 1337,1,AGI(nmap.agi)
exten => 1337,2,Hangup

I've included the source code below along with another variant that I found.

My CLI output looks like this:

Code:
[SIZE="1"]    -- Executing [1331@from-internal:1] AGI("SIP/501-00000017", "nmap.agi") in new stack
    -- Launched AGI Script /var/lib/asterisk/agi-bin/nmap.agi
    -- Playing 'tts/tts-651d10c4c41504ccd4e3f221e6afeb30' (escape_digits=) (sample_offset 0)
    -- Playing 'tts/tts-818ec742f57be488cb51121f746696b1' (escape_digits=) (sample_offset 0)
    -- <SIP/501-00000017>AGI Script nmap.agi completed, returning 0
    -- Executing [1331@from-internal:2] Hangup("SIP/501-00000017", "") in new stack
  == Spawn extension (from-internal, 1331, 2) exited non-zero on 'SIP/501-00000017'
    -- Executing [h@from-internal:1] Macro("SIP/501-00000017", "hangupcall") in new stack
    -- Executing [s@macro-hangupcall:1] GotoIf("SIP/501-00000017", "1?skiprg") in new stack
    -- Goto (macro-hangupcall,s,4)
    -- Executing [s@macro-hangupcall:4] GotoIf("SIP/501-00000017", "1?skipblkvm") in new stack
    -- Goto (macro-hangupcall,s,7)
    -- Executing [s@macro-hangupcall:7] GotoIf("SIP/501-00000017", "1?theend") in new stack
    -- Goto (macro-hangupcall,s,9)
    -- Executing [s@macro-hangupcall:9] Hangup("SIP/501-00000017", "") in new stack
  == Spawn extension (macro-hangupcall, s, 9) exited non-zero on 'SIP/501-00000017' in macro 'hangupcall'
  == Spawn extension (from-internal, h, 1) exited non-zero on 'SIP/501-00000017'[/SIZE]

Here's the source for the .agi script that is giving me the CLI log output above:

Code:
[SIZE="2"]#!/usr/bin/perl
#
# AGI Script that prompts the user for an ip address, scans the ip, and reports back to the user.
#
# Requires the Asterisk::AGI and Nmap::Parser perl modules
#

use Asterisk::AGI;
use Nmap::Parser;
use File::Basename;
use Digest::MD5 qw(md5_hex);

sub speak(){
    $text = $_[0];

    my $hash = md5_hex($text);

    my $ttsdir = "/var/lib/asterisk/sounds/tts";
    my $cepoptions = "-p audio/sampling-rate=8000,audio/channels=1";

    my $wavefile = "$ttsdir/tts-$hash.wav";

    unless (-f $wavefile) {

        open(fileOUT, ">/var/lib/asterisk/sounds/tts/say-text-$hash.txt");
        print fileOUT "$text";
        close(fileOUT);

        my $execf="/opt/swift/bin/swift -f $ttsdir/say-text-$hash.txt -o $wavefile $cepoptions";
        system($execf);

        unlink("$ttsdir/say-text-$hash.txt");
    }
    $filename = 'tts/'.basename('tts/'.basename($wavefile,".wav"));
    $AGI->stream_file($filename);
    unlink("$wavefile");

}

$AGI = new Asterisk::AGI;

my %input = $AGI->ReadParse();

my $finished = 0;

&speak("Enter the eye-p address you wish to scan.");

my $ipaddr = '';
my $x = 0;
while (!$finished) {
        my $input = chr($AGI->wait_for_digit('5000'));
        if ($input =~ /^[0-9\*\#]$/) {
                if ($input =~ /^[\*\#]$/) {
                        $x++;
                        if ($x > 3) {
                                $finished = 1;
                        } else {
                                $ipaddr .= '.';
                        }

                } else {
                        $ipaddr .= $input;
                }
        } else {
                        #must have timed out
                        $finished = 1;
        }

        if ( length($ipaddr) > 14) {
                $finished = 1;
        }
}

if ($ipaddr !~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/) {
        &speak("Invalid Address: $ipaddr");
        exit 0;
}
&speak("Please hold, now running scan...");

&speak("Please wait");

my $np = new Nmap::Parser;

$nmap_exe = '/usr/bin/nmap';

$np->callback(\&host_handler);

$np->parsescan($nmap_exe,'-sT -p1-1023', $ipaddr);


sub host_handler {
    my $host_obj = shift; #an instance of Nmap::Parser::Host (for current)

        &speak("Host Found with " . $host_obj->tcp_port_count() . " ports open");

    my @ports = $host_obj->tcp_open_ports(); #all ports

        foreach $port (@ports){
                &speak($host_obj->tcp_service($port)->name. " on port" . $port);
        }
        &speak("Scan complete... Thank you...");

        exit;
}

$AGI->exec('Festival', '"No host found"');

exit;
[/SIZE]


Here is a variant of the same source code displayed above. I only included this for reasons of it possibly being of any help to anyone:


Code:
[SIZE="3"]#!/usr/bin/perl
#
# AGI Script that prompts the user for an ip address, scans the ip, and reports back to the user.
#
# Requires the Asterisk::AGI and Nmap::Parser perl modules
#

use Asterisk::AGI;
use Nmap::Parser;
use File::Basename;
use Digest::MD5 qw(md5_hex);

my $nmap_logfile = "/var/log/asterisk/nmap.log";
my $ttsdir = "/var/lib/asterisk/sounds/tts";

# speaks a string of text
sub speak(){
    $text = $_[0];

    my $hash = md5_hex($text);

    my $wavefile = "$ttsdir/tts-$hash.ulaw";

    unless (-f $wavefile) {

	system("echo $text | text2wave -o $wavefile -otype ulaw -");

    }
    $filename = 'tts/'.basename('tts/'.basename($wavefile,".ulaw"));
    $AGI->stream_file($filename);
    unlink("$wavefile");

}

$AGI = new Asterisk::AGI;

my %input = $AGI->ReadParse();

my ($caller) = $input{callerid} =~ /<(\d+)>/;
if (!defined $caller) {
    ($caller) = $input{callerid} =~ /(\d+)/;
}

my $finished = 0;

&speak("Enter the eye-p address you wish to scan.");

my $ipaddr = '';
my $x = 0;

# While we don't have a complete IP address, have the user enter one
# using '#' for '.'...
while (!$finished) {
	my $input = chr($AGI->wait_for_digit('5000'));
	if ($input =~ /^[0-9\*\#]$/) {
		if ($input =~ /^[\*\#]$/) {
			$x++;
			if ($x > 3) {
				$finished = 1;
			} else {
				$ipaddr .= '.';
			}
		
		} else {
			$ipaddr .= $input;
		}
	} else {
			#must have timed out
			$finished = 1;
	}

	if ( length($ipaddr) > 14) {
		$finished = 1;
	}
}

open(LOG,">>$nmap_logfile");
print LOG localtime(time) . " - $caller - $ipaddr\n";

# Double check the address is valid...
if ($ipaddr !~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/) {
	&speak("Invalid Address: $ipaddr");
	exit 0;
}

&speak("Please wait, scan is in progress.");

# Set up a new Nmap::Parser object
my $np = new Nmap::Parser;

$nmap_exe = '/usr/bin/nmap';

$np->callback(\&host_handler);

# Scan the host given.
$np->parsescan($nmap_exe,'-sT -p1-1023', $ipaddr);

# Do this after every host scanned
sub host_handler {
	my $host_obj = shift; #an instance of Nmap::Parser::Host (for current)

	&speak("Host " . $host_obj->hostname() . " found with " . $host_obj->tcp_port_count() . " ports open");

	# Make and array of all the ports.
	my @ports = $host_obj->tcp_open_ports();

	# For every port, speak the port number and service.
	foreach $port (@ports){
		&speak("Open port found, " . $host_obj->tcp_service($port)->name. " on port " . $port);
	}
	&speak("Scan completed! Good bye.");
	
	exit;
	close(LOG);
}

# If a host was found, we shouldn't get here.
&speak("No host found with eye-p address " . $ipaddr . "! Good bye.");

exit;
close(LOG);
[/SIZE]



Any help in pointing me in the right direction with this will be greatly appreciated.

Regards,
John Jolly
 

lzaf

Guru
Joined
Jan 12, 2012
Messages
13
Reaction score
2
The problem seems to come from your speak subroutine. I don't have swift and I wasn't really able to test it but the code is a bit messy there and not very safe.
Don't try to duplicate dialplan or other apps functionality in your agi script. I this case instead of writing your own text to speech function just use what asterisk already provides you. You can use the AGI exec command to run another dialplan application, like Flite, Festival, Swift or similar to get text to speech functionality. Based on this I've altered your script a bit making it use an external tts app. Here is the code:
Code:
#!/usr/bin/env perl
#
# AGI Script that prompts the user for an ip address, scans the ip, and reports back to the user.
#
# Requires the Asterisk::AGI and Nmap::Parser perl modules
#

use strict;
use warnings;

use Asterisk::AGI;
use Nmap::Parser;

my $ipaddr;
my $input    = "";
my $AGI      = new Asterisk::AGI;
my $tts_app  = "Flite";
my $nmap_exe = `/usr/bin/which nmap`;
my $range    = qr/
    ^([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.
    ([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])$
/x;

my %agi_input = $AGI->ReadParse();

if (!$nmap_exe) {
    &speak("nmap is missing. Aborting.");
    die;
}
chomp($nmap_exe);

&speak("Enter the eye-p address you wish to scan. when done press the pound key");

while ($input ne "#") {
    $input   = chr($AGI->wait_for_digit('5000'));
    $ipaddr .= $input if ($input =~ /\d/);
    $ipaddr .= "."    if ($input eq "*");
    last              if (length($ipaddr) > 14);
}

if ($ipaddr !~ /$range/) {
    &speak("Invalid Address: $ipaddr");
    die;
}

&speak("Please hold. Now running scan on $ipaddr");

my $np = new Nmap::Parser;
$np->callback(\&host_handler);
$np->parsescan($nmap_exe,'-sT -p1-1023', $ipaddr);

exit;

sub speak {
    my $text = shift;
    $AGI->exec("$tts_app", "$text");
    return;
}

sub host_handler {
    my $host_obj = shift; #an instance of Nmap::Parser::Host (for current)
    &speak("Host Found with " . $host_obj->tcp_port_count() . " ports open");
    my @ports = $host_obj->tcp_open_ports(); #all ports
    foreach my $port (@ports){
        &speak($host_obj->tcp_service($port)->name. " on port" . $port);
    }
    &speak("Scan complete... Thank you...");
    return;
}
In this case i used Flite, you can change it to whatever tts app you have in asterisk.
I also changed the regular expression you used to check the IP addres. The old one:
Code:
d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}
was very general and while not totaly wrong it would match invalid IP addresses like 056.812.087.000.
I also changed the way you were handling user input to get the IP address, Cleaned it a bit and now * is representing dot and # terminates.
Hope this helps you a bit.
 

jjolly

New Member
Joined
Oct 1, 2010
Messages
7
Reaction score
0
lzaf,

that did the trick! i can't thank you enough for taking the time to take a look at this not to mention provide an even better revision of the code!

these forums are a real goldmine for asterisk information with individuals like yourself. thank you again so very much.

regards,
j. jolly
 

wardmundy

Nerd Uno
Joined
Oct 12, 2007
Messages
19,206
Reaction score
5,228
:party::party::party::party::party:

Thanks jjolly and lzaf!! To get this working with PIAF2, you'll need to do the following:

Code:
cd /root
wget http://download.fedora.redhat.com/pub/epel/6/i386/epel-release-6-5.noarch.rpm
rpm -Uvh epel*
yum -y install perl-Nmap-Parser


Then create nmap.agi in /var/lib/asterisk/agi-bin using lzaf's code below and chown asterisk:asterisk nmap.agi and chmod +x nmap.agi

Then add the following code to extensions_custom.conf in /etc/asterisk in the [from-internal-custom] context:


Code:
exten => 6627,1,Answer
exten => 6627,2,Wait(1)
exten => 6627,3,AGI(nmap.agi)
exten => 6627,4,Hangup


Then: asterisk -rx "dialplan reload"

Now you can dial N-M-A-P. Enter an IP address with * for periods and # when you're finished.
 

lzaf

Guru
Joined
Jan 12, 2012
Messages
13
Reaction score
2
The above was mostly an example for jjolly. If people are really going to use it then better post some more sane code:

Code:
#!/usr/bin/env perl
#
# AGI Script that prompts for an ip address,
# scans the ip usng nmap and reports back to the user.
#
# This program is free software, distributed under the terms of
# the GNU General Public License Version 2.
#


use strict;
use warnings;
use Asterisk::AGI;
use Nmap::Parser;

my ($ipaddr, $np, $input, $digit);
my $nmap_args = "";
my $AGI       = new Asterisk::AGI;
my $tts_app   = "flite";
my $nmap      = `/usr/bin/which nmap`;
my $range     = qr/
    ^([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])\.
    ([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]\d|25[0-5])$
/x;

my %agi_input = $AGI->ReadParse();

if (!$nmap) {
    $AGI->exec("$tts_app", "nmap is missing. Aborting.");
    die;
}
chomp($nmap);

$AGI->answer() if ($AGI->channel_status() == 4);

$AGI->exec("$tts_app", "Enter the IP address you wish to scan. When done press the pound key");

while ($input = $AGI->wait_for_digit('5000')) {
    $digit   = chr($input);
    $ipaddr .= $digit if ($digit =~ /\d/);
    $ipaddr .= "."    if ($digit eq "*");
    last              if ($digit eq "#" || $input <= 0 || length($ipaddr) > 14);
}

if ($ipaddr !~ /$range/) {
    $AGI->exec("$tts_app", "Invalid Address: $ipaddr");
    die;
}
$AGI->exec("$tts_app", "Please hold. Scanning.");

$np = new Nmap::Parser;
$np->callback(\&host_handler);
$np->parsescan($nmap, $nmap_args, $ipaddr);
$AGI->exec("$tts_app", "Scan complete. Thank you.");
exit;

sub host_handler {
    my $host = shift;
    $AGI->exec("$tts_app", "Host " . $host->addr() . " is " . $host->status());
    return if ($host->status() ne "up");
    $AGI->exec("$tts_app", "Found " . $host->tcp_port_count() . " ports open");
    $AGI->exec("$tts_app", $host->tcp_service($_)->name . " on port $_") foreach $host->tcp_open_ports();
}
BTW since it is now mostly rewritten I took the freedom to release it under the GNU GPL license so its free for anyone to use. I hope jjolly or the original author doesn't mind. :wink5:
 

Members online

No members online now.

Forum statistics

Threads
25,825
Messages
167,841
Members
19,250
Latest member
mark-curtis
Get 3CX - Absolutely Free!

Link up your team and customers Phone System Live Chat Video Conferencing

Hosted or Self-managed. Up to 10 users free forever. No credit card. Try risk free.

3CX
A 3CX Account with that email already exists. You will be redirected to the Customer Portal to sign in or reset your password if you've forgotten it.
Top