1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.
  2. If you had a PIAF Forum account in the vBulletin days, log in with your old credentials. Otherwise, sign up again and we'll get you back in business as soon as we can.
  3. A serious FreePBX vulnerability has been reported. Update your Framework Module immediately. Click here for details.

NMAP Scanner by Phone

Discussion in 'Add-On Install Instructions' started by jjolly, Feb 3, 2012.

  1. jjolly New Member

    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
  2. lzaf Guru

    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.
  3. jjolly New Member

    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
  4. wardmundy Nerd Uno

    :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.
  5. lzaf Guru

    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:

Share This Page