#!/usr/bin/perl
# http://userside.ua/wiki/index.php/usm_peleng - Module Information
$moduleRevision = "114";
$moduleVersion  = "usm_peleng v.3.23." . $moduleRevision; #Version

#========================================================================
# Custom configuration
#========================================================================

$isDebugMode    = 0; #Debug Mode [0/1]

#========================================================================

use Time::Local;
use Socket; 
use FindBin;
use URI;
use URI::Escape;
use LWP::UserAgent;
use JSON;
use Data::Dumper;
use Net::Telnet();
use Net::SNMP;

$modulePath = $FindBin::RealBin . "/";

#Загрузка параметров из файла конфигурации
open ($file, $modulePath . "usm_peleng.conf") || die "ERROR: Open Config File " . $modulePath . "usm_peleng.conf\n";
close $file;
require($modulePath . "usm_peleng.conf");

#Присвоение пустым параметрам значений по-умолчанию
$logsFullPath = $logsPath . '/usm_peleng_';
if ($delay eq '') {
    $delay = 0;
}
$random = int(rand(10000));

#Выполняем задержку
if ($delay > 0) {
    sleep int(rand($delay));
}

#Стартовые действия
initializeModule(1);

#Фиксируем в список запуска свой процесс
$logPid = $logsFullPath . "listpid.txt";
open ($file, ">>" . $logPid) || die "ERROR: Open file " . $logPid . ". Verify path and rights\n";
    print $file $$ . "|" . time . "\n";
close($file);
#Выбираем все процессы и смотрим сколько им времени
$timeKill = 600;
@arrayPid = ();
$arrayPidCount = 0;
open ($file, "<" . $logPid) || die "ERROR: Open file " . $logPid . ". Verify path and rights\n";
    while ($string = readline $file) {
        my @array = split(/\n/, $string);
        my @array = split(/\|/, $array[0]);
        $pid = $array[0];
        if ($pid > 0 || $pid < 0) {
            $pidTime = time - $array[1];
            if ($pidTime > $timeKill) {
                kill(0, $pid);
            } else {
                $arrayPidCount++;
                $arrayPid[$arrayPidCount][1] = $pid;
                $arrayPid[$arrayPidCount][2] = $array[1];
            }
        }
    }
close $file;
open ($file, ">" . $logPid) || die "ERROR: Open file " . $logPid . ". Verify path and rights\n";
    for ($i = 1; $i <= $arrayPidCount; $i++) {
        print $file $arrayPid[$i][1] . "|" . $arrayPid[$i][2] . "\n";
    }
close $file;    

#Загружаем параметры из системы
my $data = {
  'key'             => $usApiKey,
  'cat'             => 'usm_peleng',
  'action'          => 'module_start',
  'module_version'  => $moduleVersion,
  'module_path'     => uri_escape($modulePath),
  'logs_path'       => uri_escape($logsPath)
};
$response = apiRequest('post', $data, 'module_start');

$isModuleEnable = $response->{'data'}->{'is_module_enable'};
$moduleMinAllowVersion = $response->{'data'}->{'module_min_allow_version'};

#Проверяем версию и включен ли модуль
verifyModulePermit();

#Фиксируем в логе параметры этого модуля
writeToLogModule("usUrl                 = " . $usUrl);
writeToLogModule("delay                 = " . $delay);
writeToLogModule("random                = " . $random);
writeToLogModule("citiesList            = " . $citiesList);
writeToLogModule("isArpPolling          = " . $isArpPolling);

#Начинаем работу

#Возможно устройство пришло в параметрах
$deviceId = 0;
$deviceType = 0;
if ($ARGV[0] ne '') {
    if ($ARGV[0] eq 's') {
        $deviceType = 3;
        $deviceId = $ARGV[1];
    } 
    if ($ARGV[0] eq 'w') {
        $deviceType = 4;
        $deviceId = $ARGV[1];
    }
    if ($ARGV[0] eq 's' || $ARGV[0] eq 'w') {
        writeToLogModule("Custom Device: " . $deviceType . " id: " . $deviceId);
    }
}

#Получаем устройство для работы
my $data = {
  'key'             => $usApiKey,
  'cat'             => 'usm_peleng',
  'action'          => 'get_device',
  'cities_list'     => $citiesList,
  'device_type'     => $deviceType,
  'device_id'       => $deviceId
};
$response = apiRequest('post', $data, 'get_device');
$deviceType = $response->{'data'}->{'device_type'};
$deviceId = $response->{'data'}->{'device_id'};
$pelengCycle = $response->{'data'}->{'peleng_cycle'};
$deviceHost = $response->{'device'}->{'host'};
$deviceMac = $response->{'device'}->{'mac'};
$deviceTelnetLogin = $response->{'device'}->{'telnet_login'};
$deviceTelnetPass = $response->{'device'}->{'telnet_pass'};
$deviceSnmpProtocol = $response->{'device'}->{'snmp_protocol'};
$deviceCommunityRo = $response->{'device'}->{'community_ro'};
$deviceFdbMethod = $response->{'device'}->{'fdb_method'};
$deviceBaseDeviceId = $response->{'device'}->{'base_device_id'};
$deviceUplinkPortList = $response->{'device'}->{'uplink_port_list'};
$deviceDownlinkPortList = $response->{'device'}->{'downlink_port_list'};
$deviceTypeDoing = $response->{'device'}->{'type_doing'};

if ($deviceId < 1) {
    moduleFinish("Nothing To Do - exit");
    exit(1);
} else {

    writeToLogModule("Peleng Device: " . $deviceType . " id: " . $deviceId . " host: " . $deviceHost . " doing: " . $deviceTypeDoing);

    if ($deviceTypeDoing eq 'remote') {
        #Отправляем на опрос
        my $data = {
          'key'             => $usApiKey,
          'cat'             => 'usm_peleng',
          'action'          => 'doing',
          'device_type'     => $deviceType,
          'device_id'       => $deviceId,
          'peleng_cycle'    => $pelengCycle
        };
        $response = apiRequest('post', $data, 'doing');
        $data = $response->{'data'};
        if ($isDebugMode eq 1) {
            print  Dumper($data);
        }
        @arrayData = split(/,/, $data);
        $macCount = $#arrayData;
        writeToLogModule("Receive MAC: " . $macCount);
        $list = '';
        
        #Фиксируем по устройству информацию в отдельный файл
        $logDevice = $logsFullPath . "device_" . $deviceType . "_" . $deviceId . ".txt";
        open ($file, ">" . $logDevice) || die "ERROR: Open file " . $logDevice . ". Verify path and rights\n";
            foreach my $row (@arrayData) {
                @arrayRow = split(/\|/, $row);
                writeToLogModule($arrayRow[0] . ' | ' . $arrayRow[1] . ' | ' . $arrayRow[2] . ' | ');
                print $file $arrayRow[0] . ' | ' . $arrayRow[1] . ' | ' . $arrayRow[2] . " |\n";
            }
        close $file;      
        
        #ARP
        if ($isArpPolling eq 1) {
            my $data = {
              'key'             => $usApiKey,
              'cat'             => 'usm_peleng',
              'action'          => 'get_arp',
              'device_type'     => $deviceType,
              'device_id'       => $deviceId
            };
            $response = apiRequest('post', $data, 'doing');
            $data = $response->{'data'};
            if ($isDebugMode eq 1) {
                print  Dumper($data);
            }
            @arrayData = split(/,/, $data);
            $arpCount = $#arrayData;
            writeToLogModule("Receive ARP: " . $arpCount);
            $list = '';
            
            #Фиксируем по устройству информацию в отдельный файл
            $logDevice = $logsFullPath . "device_" . $deviceType . "_" . $deviceId . ".txt";
            open ($file, ">>" . $logDevice) || die "ERROR: Open file " . $logDevice . ". Verify path and rights\n";
                foreach my $row (@arrayData) {
                    @arrayRow = split(/\|/, $row);
                    writeToLogModule($arrayRow[0] . ' | ' . $arrayRow[1] . ' | ');
                    print $file $arrayRow[0] . ' | ' . $arrayRow[1] . " |\n";
                }
            close $file;
        }     
    }
}

#Отмечаем, что мы закончили работу
my $data = {
  'key'             => $usApiKey,
  'cat'             => 'usm_peleng',
  'action'          => 'module_finish',
  'module_version'  => $moduleVersion,
  'module_path'     => uri_escape($modulePath)
};
$response = apiRequest('post', $data, 'module_finish');

moduleFinish();

exit(0);

#================================================================================================================
#Custom Module Function
#================================================================================================================

### ================================================================================================================
#Standart Module Light Function v.2.8 - 29.01.2016
#================================================================================================================

sub moduleFinish
{
    my($additionalText) = @_;    
    open ($file, ">>" . $logLaunches) || die "ERROR: Open file " . $logLaunches . ". Verify path and rights\n";
        if ($additionalText ne '') {
            print $file $moduleVersion . " " . $additionalText . "\n";
            if ($isSilence ne 1) {
                print $additionalText . "\n";
            }
            #writeToLogModule($additionalText);
        }
        if ($additionalText eq '') {
            writeToLogModule($moduleVersion . " - finish module");
            print $fileLogModule "===============================================================================\n";
            close $fileLogModule;
            print $file $moduleVersion . " Module FINISH at " . getFormatDateTime() . "\n";
            print $file "==========================================================\n";
        }
    close $file;
    if ($isSilence ne 1 && $additionalText eq '') {
        print 'Finish module at ' . getFormatDateTime() . "\n";
        print "====================================\n";
    }
}

sub verifyModulePermit 
{
    if ($isModuleEnable ne 1) {
        moduleFinish("Module disabled in system - exit");
        exit(1);
    }
    if ($moduleRevision < $moduleMinAllowVersion || $moduleMinAllowVersion < 1) {
        moduleFinish('Version failure. Need version: ' . $moduleMinAllowVersion . " - exit");
        exit(1);
    }
}

sub initializeModule {
    my $isAllowPid     = $_[0];
    $logLaunches = $logsFullPath . 'launches.log';
    $logModule = $logsFullPath . 'module.log';

    if ($ARGV[0] eq 'debug' || $ARGV[1] eq 'debug' || $ARGV[2] eq 'debug') {
        $isDebugMode = 1;
    }

    #Удаляем громоздкий файл
    $size  = -s $logLaunches;
    if ($size > 100000) {
        unlink($logLaunches);
    }
    
    open ($file, ">>" . $logLaunches) || die "ERROR: Open file " . $logLaunches . ". Verify path and rights\n";
        print $file $moduleVersion . ' Module START  at ' . getFormatDateTime() . "\n";
    close $file;

    if ($isAllowPid ne 1) {
        $pidFile = $logsFullPath . 'pid.pid';
        if (-e $pidFile) {
            unless (open(PIDFILE, $pidFile)) {
                moduleFinish("Find previous PID - exit");
                close PIDFILE;
                exit(1);
            }
            $oldpid = <PIDFILE>;
            close PIDFILE;
            if ($oldpid > 1 && kill(0, $oldpid)) {
                moduleFinish("Find previous PID " . $oldpid . " - exit");
                exit(1);
            }
        }
        open(PID, ">" . $pidFile) || die "ERROR: Open file pid.pid. Verify path and rights\n";
            print PID $$;
        close(PID);
    }

    #Выведем заголовок модуля
    if ($isSilence ne 1) {
        print $moduleVersion . "\n";
        print "====================================\n";
        print ' Start module at ' . getFormatDateTime() . "\n";
    }
    
    open ($fileLogModule, ">" . $logModule) || die "ERROR: Open file " . $logModule . ". Verify path and rights\n";
    print $fileLogModule "===============================================================================\n";
    
    #Фиксируем версию модуля и путь к модулю
    writeToLogModule($moduleVersion . " - start module");
    writeToLogModule($modulePath);
    
    #Фиксируем основные параметры
    writeToLogModule("isSilence = " . $isSilence);
}

sub writeToLogModule
{
    my($string) = @_;
    print $fileLogModule getFormatDateTime() . " - " . $string . "\n";
}

sub trim 
{
    my($string) = @_;
    for ($string) {
        s/^\s+//;
        s/\s+$//;
    }
    return $string;
}

sub ip2long
{
    return unpack("N", inet_aton(shift));
}

sub long2ip 
{
    return inet_ntoa(pack("N",shift));
}

sub apiRequest
{
    my $requestType     = $_[0];
    my $data     = $_[1];
    my $comment     = $_[2];
    writeToLogModule("API Request: " . $comment);
    if ($requestType eq 'get') {
        $response = curlGet($data);
    }
    if ($requestType eq 'post') {
        $response = curlPost($data);
    }
    $result = $response->{'result'};
    if ($result eq '') {
        $result = $response->{'Result'};
    }
    writeToLogModule("Result: " . $result);
    if ($result eq 'ERROR') {
        $errorText = $response->{'error_text'};
        if ($errorText eq '') {
            $errorText = $response->{'ErrorText'};
        }
        writeToLogModule("Error: " . $errorText);
        moduleFinish("API Error - " . $errorText . " - exit");
        exit(1);
    }
    return $response;
}

sub curlPost
{
    my $data     = $_[0];
    my $ua       = LWP::UserAgent->new(ssl_opts=>{verify_hostname => 0});
    my $response = $ua->post($usUrl . "/api.php", $data);
    if ($isDebugMode eq 1) {
        print  Dumper($response);
    }
    $content = trim($response->decoded_content);
    if (substr(trim($content), 0, 1) ne '{') {
        writeToLogModule("Content: " . $content);
        $content = '{}';
    }
    return decode_json($content);
}

sub curlGet
{
    my $data     = $_[0];
    my $ua       = LWP::UserAgent->new(ssl_opts=>{verify_hostname => 0});
    my $url = URI->new($usUrl . "/api.php");
    $url->query_form($data);
    my $req = HTTP::Request->new(GET => $url);
    $req->header('content-type' => 'application/json', 'accept' => 'application/json');
    my $response = $ua->request($req);
    if ($isDebugMode eq 1) {
        print  Dumper($response);
    }
    return decode_json($response->decoded_content);
}

sub getFormatDateTime 
{
    my $format = shift @_ || "YYYY-MM-DD hh:mm:ss";
    my $datetime = shift @_ || time;
    my %strings = (
        '500'=>"AM", '501'=>"PM",
        '400'=>"January", '401'=>"February", '402'=>"March", '403'=>"April", '404'=>"May", '405'=>"June", '406'=>"July", '407'=>"August", '408'=>"September", '409'=>"October", '410'=>"November", '411'=>"December",
        '300'=>"Jan", '301'=>"Feb", '302'=>"Mar", '303'=>"Apr", '304'=>"May", '305'=>"Jun", '306'=>"Jul", '307'=>"Aug", '308'=>"Sep", '309'=>"Oct", '310'=>"Nov", '311'=>"Dec",
        '200'=>"Sunday", '201'=>"Monday", '202'=>"Tuesday", '203'=>"Wednesday", '204'=>"Thursday", '205'=>"Friday", '206'=>"Saturday",
        '100'=>"Sun", '101'=>"Mon", '102'=>"Tue", '103'=>"Wed", '104'=>"Thu", '105'=>"Fri", '106'=>"Sat"
    );
    ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime($datetime);
    $year += 1900;
    if ($format=~m/AM/) {
        my $ampmhour=($hour % 12) || 12;
        $format=~s/hh/sprintf("%02u",$ampmhour)/eg;
        $format=~s/h/$ampmhour/eg;
        $format=~s/AM/($hour>11?"^501":"^500")/eg;
    } else {
        $format=~s/hh/sprintf("%02u",$hour)/eg;
        $format=~s/h/$hour/eg;
    };
    $format=~s/mm/sprintf("%02u",$min)/eg;
    $format=~s/m/$min/eg;
    $format=~s/ss/sprintf("%02u",$sec)/eg;
    $format=~s/s/$sec/eg;

    $format=~s/YYYY/$year/eg;
    $format=~s/YY/sprintf("%02u",$year % 100)/eg;
    $format=~s/MMMM/sprintf("^4%02u",$mon)/eg;
    $format=~s/MMM/sprintf("^3%02u",$mon)/eg;
    $format=~s/MM/sprintf("%02u",$mon+1)/eg;
    $format=~s/M/($mon+1)/eg;
    $format=~s/DDDD/sprintf("^2%02u",$wday)/eg;
    $format=~s/DDD/sprintf("^1%02u",$wday)/eg;
    $format=~s/DD/sprintf("%02u",$mday)/eg;
    $format=~s/D/$mday/eg;
    $format=~s/\^(\d\d\d)/$strings{$1}/g;
    return $format;
}
