source: trunk/klask @ 386

Last change on this file since 386 was 386, checked in by g7moreau, 6 years ago
  • Add parameter days-before-alert
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 144.5 KB
Line 
1#!/usr/bin/perl -w
2#
3# Copyright (C) 2005-2018 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>
4# License GNU GPL version 2 or later and Perl equivalent
5#
6# $Id: klask 386 2018-01-04 19:25:42Z g7moreau $
7#
8# perltidy -i=3 -ci=3 -pt=2 -icb -cti=3 -sbt=2 -bt=2 -bbt=2 -nsfs -bar -otr -olc -nolq -l=132 -vmll -msc=1 -iscl -nbbc -nola -st klask > klask.tmp
9
10use strict;
11use warnings;
12use version; our $VERSION = qv('0.7.6');
13
14use Readonly;
15use FileHandle;
16use Net::SNMP;
17#use YAML;
18use YAML::Syck;
19use Net::Netmask;
20use Net::CIDR::Lite;
21use NetAddr::IP;
22use Getopt::Long qw(GetOptions);
23use Socket;
24use List::Util 'shuffle';
25use Digest::SHA qw(sha512_base64);
26use Text::Table; # libtext-table-perl http://blogs.perl.org/users/steven_haryanto/2014/07/benchmarking-several-ascii-table-generator-modules.html
27
28# apt-get install snmp fping libnet-cidr-lite-perl libnet-netmask-perl libnet-snmp-perl libnetaddr-ip-perl libyaml-perl
29# libcrypt-des-perl libcrypt-hcesha-perl libdigest-hmac-perl libtext-table-perl
30# arping net-tools fping bind9-host arpwatch
31
32################################################################
33# general initialization
34################################################################
35
36my $KLASK_VAR      = '/var/lib/klask';
37my $KLASK_CFG_FILE = '/etc/klask/klask.conf';
38my $KLASK_DB_FILE  = "$KLASK_VAR/klaskdb";
39my $KLASK_SW_FILE  = "$KLASK_VAR/switchdb";
40
41test_running_environnement();
42
43my $KLASK_CFG = YAML::Syck::LoadFile("$KLASK_CFG_FILE");
44
45my %DEFAULT     = %{$KLASK_CFG->{'default'}};
46my @SWITCH_LIST = @{$KLASK_CFG->{'switch'}};
47
48my %SWITCH_LEVEL = ();
49my %SWITCH_DB    = ();
50LEVEL_OF_EACH_SWITCH:
51for my $sw (@SWITCH_LIST) {
52   $SWITCH_LEVEL{$sw->{'hostname'}} = $sw->{'level'} || $DEFAULT{'switch_level'} || 2;
53   $SWITCH_DB{$sw->{'hostname'}} = $sw;
54
55   # SNMP parameter initialisation
56   my %session = (-hostname => $sw->{'hostname'});
57   $session{-version} = $sw->{'version'}  || 1;
58   $session{-port}    = $sw->{'snmpport'} || $DEFAULT{'snmpport'} || 161;
59   if (exists $sw->{'version'} and $sw->{'version'} eq '3') {
60      $session{-username} = $sw->{'username'} || 'snmpadmin';
61      }
62   else {
63      $session{-community} = $sw->{'community'} || $DEFAULT{'community'} || 'public';
64      }
65   $sw->{'snmp_param_session'} = \%session;
66   }
67@SWITCH_LIST = reverse sort {$SWITCH_LEVEL{$a->{'hostname'}} <=> $SWITCH_LEVEL{$b->{'hostname'}}} @{$KLASK_CFG->{'switch'}};
68
69#my %SWITCH_PORT_COUNT = ();
70
71my %CMD_DB = (
72   'help'                 => \&cmd_help,
73   'version'              => \&cmd_version,
74   'exportdb'             => \&cmd_exportdb,
75   'updatedb'             => \&cmd_updatedb,
76   'searchdb'             => \&cmd_searchdb,
77   'removedb'             => \&cmd_removedb,
78   'addentrydb'           => \&cmd_addentrydb,
79   'cleandb'              => \&cmd_cleandb,
80   'search'               => \&cmd_search,
81   'enable'               => \&cmd_enable,
82   'disable'              => \&cmd_disable,
83   'status'               => \&cmd_status,
84   'updatesw'             => \&cmd_updatesw,
85   'exportsw'             => \&cmd_exportsw,
86   'iplocation'           => \&cmd_ip_location,
87   'ip-free'              => \&cmd_ip_free,
88   'search-mac-on-switch' => \&cmd_search_mac_on_switch,
89   'bad-vlan-id'          => \&cmd_bad_vlan_id,
90   'poe-enable'           => \&cmd_poe_enable,
91   'poe-disable'          => \&cmd_poe_disable,
92   'poe-status'           => \&cmd_poe_status,
93   'port-setvlan'         => \&cmd_port_setvlan,
94   'port-getvlan'         => \&cmd_port_getvlan,
95   'vlan-setname'         => \&cmd_vlan_setname,
96   'vlan-getname'         => \&cmd_vlan_getname,
97   'vlan-list'            => \&cmd_vlan_list,
98   'host-setlocation'     => \&cmd_host_setlocation,
99   'rebootsw'             => \&cmd_rebootsw,
100   );
101
102#Readonly my %INTERNAL_PORT_MAP => (
103#   0 => 'A',
104#   1 => 'B',
105#   2 => 'C',
106#   3 => 'D',
107#   4 => 'E',
108#   5 => 'F',
109#   6 => 'G',
110#   7 => 'H',
111#   );
112#Readonly my %INTERNAL_PORT_MAP_REV => reverse %INTERNAL_PORT_MAP;
113
114Readonly my %SWITCH_KIND => (
115   # HP
116   J3298A           => { type => 2, model => 'HP212M',         match => 'HPJ3298A HP ProCurve Switch 212M',    revision => qr{, revision (\w[\d\.]+?), ROM},               },
117   J3299A           => { type => 1, model => 'HP224M',         match => 'HP J3299A ProCurve Switch 224M',      revision => qr{, revision (\w[\d\.]+?), ROM},               },
118   J4120A           => { type => 1, model => 'HP1600M',        match => 'HP J4120A ProCurve Switch 1600M',     revision => qr{, revision (\w[\d\.]+?), ROM},               },
119   J9029A           => { type => 1, model => 'HP1800-8G',      match => 'PROCURVE J9029A',                     revision => qr{, revision (\w[\d\.]+?), ROM},               },
120   J9449A           => { type => 1, model => 'HP1810-8G',      match => 'HP ProCurve 1810G - 8 GE',            revision => qr{, revision (\w[\d\.]+?), ROM},               },
121   J4093A           => { type => 1, model => 'HP2424M',        match => 'HP J4093A ProCurve Switch 2424M',     revision => qr{, revision (\w[\d\.]+?), ROM},               },
122   J9279A           => { type => 1, model => 'HP2510G-24',     match => 'ProCurve J9279A Switch 2510G-24',     revision => qr{, revision (\w[\d\.]+?), ROM},               },
123   J9280A           => { type => 1, model => 'HP2510G-48',     match => 'ProCurve J9280A Switch 2510G-48',     revision => qr{, revision (\w[\d\.]+?), ROM},               },
124   J4813A           => { type => 1, model => 'HP2524',         match => 'HP J4813A ProCurve Switch 2524',      revision => qr{, revision (\w[\d\.]+?), ROM},               },
125   J9625A           => { type => 2, model => 'HP2620-24P',     match => 'HP J9625A E2620-24-PoEP Switch',      revision => qr{, revision (\w[\d\.]+?), ROM},               },
126   J4900A           => { type => 1, model => 'HP2626A',        match => 'HP J4900A ProCurve Switch 2626',      revision => qr{, revision (\w[\d\.]+?), ROM},               },
127   J4900B           => { type => 1, model => 'HP2626B',        match => 'J4900B.+?Switch 2626',                revision => qr{, revision (\w[\d\.]+?), ROM},               }, # ProCurve J4900B Switch 2626 # HP J4900B ProCurve Switch 2626
128   J4899B           => { type => 1, model => 'HP2650',         match => 'ProCurve J4899B Switch 2650',         revision => qr{, revision (\w[\d\.]+?), ROM},               },
129   J9021A           => { type => 1, model => 'HP2810-24G',     match => 'ProCurve J9021A Switch 2810-24G',     revision => qr{, revision (\w[\d\.]+?), ROM},               },
130   J9022A           => { type => 1, model => 'HP2810-48G',     match => 'ProCurve J9022A Switch 2810-48G',     revision => qr{, revision (\w[\d\.]+?), ROM},               },
131   J4903A           => { type => 1, model => 'HP2824',         match => 'J4903A.+?Switch 2824,',               revision => qr{, revision (\w[\d\.]+?), ROM},               },
132   J9145A           => { type => 2, model => 'HP2910-24G',     match => 'ProCurve J9145A 2910al-24G Switch',   revision => qr{, revision (\w[\d\.]+?), ROM},               },
133   J8692A           => { type => 1, model => 'HP3500-24G',     match => 'J8692A Switch E?3500yl-24G',          revision => qr{, revision (\w[\d\.]+?), ROM},               },
134   J4121A           => { type => 2, model => 'HP4000M',        match => 'HP J4121A ProCurve Switch 4000M',     revision => qr{, revision (\w[\d\.]+?), ROM},               },
135   J4110A           => { type => 1, model => 'HP8000M',        match => 'HP J4110A ProCurve Switch 8000M',     revision => qr{, revision (\w[\d\.]+?), ROM},               },
136   JE074A           => { type => 2, model => 'HP5120-24G',     match => 'HP Comware.+?A5120-24G EI Switch',    revision => qr{Comware .*? Version (\d[\d\.]+?) Release},   },
137   JE069A           => { type => 2, model => 'HP5120-48G',     match => 'HP Comware.+?A5120-48G EI Switch',    revision => qr{Comware .*? Version (\d[\d\.]+?) Release},   },
138   JD377A           => { type => 2, model => 'HP5500-24G',     match => 'HP Comware.+?A5500-24G EI Switch',    revision => qr{Comware .*? Version (\d[\d\.]+?) Release},   },
139   JD374A           => { type => 2, model => 'HP5500-24F',     match => 'HP Comware.+?A5500-24G-SFP EI ',      revision => qr{Comware .*? Version (\d[\d\.]+?) Release},   },
140   # BayStack
141   BS350T           => { type => 1, model => 'BS350T',         match => 'BayStack 350T HW'                     },
142   # Microsens
143   'MS440209PM-48G6'=> { type => 2, model => 'MS440209PM-48G6+',  match => 'MICROSENS G6 Switch'               },
144   # Nexans
145   N3483G           => { type => 2, model => 'NA3483-6G',      match => 'GigaSwitch V3 TP SFP-I 48.+ ES3',     revision => qr{GigaSwitch .*?/SECURITY/V(\d[\d\.]+\w?)\)},  },
146   N3483P           => { type => 2, model => 'NA3483-6P',      match => 'GigaSwitch V3 TP.PSE.+ 48/54V ES3',   revision => qr{GigaSwitch .*?/SECURITY/V(\d[\d\.]+\w?)\)},  }, # GigaSwitch V3 TP(PSE+) SFP-I 48/54V ES3 (HW3/ENHANCED/SECURITY/V4.10C)
147   # DELL
148   PC7024           => { type => 2, model => 'DPC7024',        match => 'PowerConnect 7024,.+?VxWorks',        revision => qr{PowerConnect .*?, (\d[\d\.]+?), VxWorks},    },
149   N2048            => { type => 2, model => 'DN2048',         match => 'Dell Networking N2048,',              revision => qr{Dell Networking .*?, (\d[\d\.]+?), Linux},   },
150   N4032F           => { type => 2, model => 'DN4032F',        match => 'Dell Networking N4032F,',             revision => qr{Dell Networking .*?, (\d[\d\.]+?), Linux},   },
151   N4064F           => { type => 2, model => 'DN4064F',        match => 'Dell Networking N4064F,',             revision => qr{Dell Networking .*?, (\d[\d\.]+?), Linux},   },
152   # 3COM
153   'H3C5500'        => { type => 1, model => 'H3C5500',        match => 'H3C S5500-SI Series'                  },
154   '3C17203'        => { type => 1, model => '3C17203',        match => '3Com SuperStack 3 24-Port'            },
155   '3C17204'        => { type => 1, model => '3C17204',        match => '3Com SuperStack 3 48-Port'            },
156   '3CR17562-91'    => { type => 1, model => '3CR17562-91',    match => '3Com Switch 4500 50-Port'             },
157   '3CR17255-91'    => { type => 1, model => '3CR17255-91',    match => '3Com Switch 5500G-EI 48-Port'         },
158   '3CR17251-91'    => { type => 1, model => '3CR17251-91',    match => '3Com Switch 5500G-EI 48-Port'         },
159   '3CR17571-91'    => { type => 1, model => '3CR17571-91',    match => '3Com Switch 4500 PWR 26-Port'         },
160   '3CRWX220095A'   => { type => 1, model => '3CRWX220095A',   match => '3Com Wireless LAN Controller'         },
161   '3CR17254-91'    => { type => 1, model => '3CR17254-91',    match => '3Com Switch 5500G-EI 24-Port'         },
162   '3CRS48G-24S-91' => { type => 1, model => '3CRS48G-24S-91', match => '3Com Switch 4800G 24-Port'            },
163   '3CRS48G-48S-91' => { type => 1, model => '3CRS48G-48S-91', match => '3Com Switch 4800G 48-Port'            },
164   '3C17708'        => { type => 1, model => '3C17708',        match => '3Com Switch 4050'                     },
165   '3C17709'        => { type => 1, model => '3C17709',        match => '3Com Switch 4060'                     },
166   '3C17707'        => { type => 1, model => '3C17707',        match => '3Com Switch 4070'                     },
167   '3CR17258-91'    => { type => 1, model => '3CR17258-91',    match => '3Com Switch 5500G-EI 24-Port SFP'     },
168   '3CR17181-91'    => { type => 1, model => '3CR17181-91',    match => '3Com Switch 5500-EI 28-Port FX'       },
169   '3CR17252-91'    => { type => 1, model => '3CR17252-91',    match => '3Com Switch 5500G-EI PWR 24-Port'     },
170   '3CR17253-91'    => { type => 1, model => '3CR17253-91',    match => '3Com Switch 5500G-EI PWR 48-Port'     },
171   '3CR17250-91'    => { type => 1, model => '3CR17250-91',    match => '3Com Switch 5500G-EI 24-Port'         },
172   '3CR17561-91'    => { type => 1, model => '3CR17561-91',    match => '3Com Switch 4500 26-Port'             },
173   '3CR17572-91'    => { type => 1, model => '3CR17572-91',    match => '3Com Switch 4500 PWR 50-Port'         },
174   '3C17702-US'     => { type => 1, model => '3C17702-US',     match => '3Com Switch 4900 SX'                  },
175   '3C17700'        => { type => 1, model => '3C17700',        match => '3Com Switch 4900'                     },
176   );
177
178Readonly my %OID_NUMBER => (
179   sysDescription  => '1.3.6.1.2.1.1.1.0',
180   sysName         => '1.3.6.1.2.1.1.5.0',
181   sysContact      => '1.3.6.1.2.1.1.4.0',
182   sysLocation     => '1.3.6.1.2.1.1.6.0',
183   searchPort1     => '1.3.6.1.2.1.17.4.3.1.2',       # BRIDGE-MIB (802.1D).
184   searchPort2     => '1.3.6.1.2.1.17.7.1.2.2.1.2',   # Q-BRIDGE-MIB (802.1Q) add 0 if unknown vlan id
185   vlanPortDefault => '1.3.6.1.2.1.17.7.1.4.5.1.1',   # dot1qPvid
186   vlanStatus      => '1.3.6.1.2.1.17.7.1.4.3.1.5',   # integer 4 Create, 6 Destroy
187   vlanName        => '1.3.6.1.2.1.17.7.1.4.3.1.1',   # string
188   HPicfReset      => '1.3.6.1.4.1.11.2.14.11.1.4.1', # HP reboot switch
189   ifIndex         => '1.3.6.1.2.1.17.1.4.1.2',       # dot1dBasePortIfIndex - Interface index redirection
190   ifName          => '1.3.6.1.2.1.31.1.1.1.1',       # Interface name (give port number)
191   portUpDown      => '1.3.6.1.2.1.2.2.1.7',          # 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)  = 2 (down)
192   poeState        => '1.3.6.1.2.1.105.1.1.1.3.1',    # 1.3.6.1.2.1.105.1.1.1.3.1.NoPort = 1 (poe up)  = 2 (poe down) - Cisco and Zyxel
193   NApoeState      => '1.3.6.1.4.1.266.20.3.1.1.21',  # .NoPort = 2 (poe off)  = 8 (poe atHighPower) - Nexans
194   ifAggregator => '1.2.840.10006.300.43.1.2.1.1.12', # dot3adAggPortSelectedAggID - 0 not part of an  Aggregator - Ciso Dell HP Comware -  See https://stackoverflow.com/questions/14960157/how-to-map-portchannel-to-interfaces-via-snmp https://gist.github.com/bldewolf/6314435
195   );
196
197Readonly my %PORT_UPDOWN => (
198   1 => 'enable',
199   2 => 'disable',
200   );
201
202Readonly my $RE_MAC_ADDRESS  => qr{ [0-9,A-Z]{2} : [0-9,A-Z]{2} : [0-9,A-Z]{2} : [0-9,A-Z]{2} : [0-9,A-Z]{2} : [0-9,A-Z]{2} }xms;
203Readonly my $RE_IPv4_ADDRESS => qr{ [0-9]{1,3} \. [0-9]{1,3} \. [0-9]{1,3} \. [0-9]{1,3} }xms;
204
205Readonly my $RE_FLOAT_HOSTNAME => $DEFAULT{'float-regex'} || qr{ ^float }xms;
206
207Readonly my $SEP_AGGREGATOR_PORT => ','; # : is already use to join switch and port
208Readonly my $SEP_SWITCH_PORT     => ':';
209
210
211################################################################
212# main program
213################################################################
214
215my $cmd = shift @ARGV || 'help';
216if (defined $CMD_DB{$cmd}) {
217   $CMD_DB{$cmd}->(@ARGV);
218   }
219else {
220   print {*STDERR} "klask: command $cmd not found\n\n";
221   $CMD_DB{'help'}->();
222   exit 1;
223   }
224
225exit;
226
227################################################################
228# subroutine
229################################################################
230
231#---------------------------------------------------------------
232sub test_running_environnement {
233   die "Configuration file $KLASK_CFG_FILE does not exists. Klask need it !\n" if not -e "$KLASK_CFG_FILE";
234   die "Var folder $KLASK_VAR does not exists. Klask need it !\n"              if not -d "$KLASK_VAR";
235   return;
236   }
237
238#---------------------------------------------------------------
239sub test_switchdb_environnement {
240   die "Switch database $KLASK_SW_FILE does not exists. Launch updatesw before this command !\n" if not -e "$KLASK_SW_FILE";
241   return;
242   }
243
244#---------------------------------------------------------------
245sub test_maindb_environnement {
246   die "Main database $KLASK_DB_FILE does not exists. Launch updatedb before this command !\n" if not -e "$KLASK_DB_FILE";
247   return;
248   }
249
250#---------------------------------------------------------------
251# fast ping dont l'objectif est de remplir la table arp de la machine
252sub fast_ping {
253   # Launch this command without waiting...
254   system "fping -q -c 1 @_ > /dev/null 2>&1 &";
255   return;
256   }
257
258#---------------------------------------------------------------
259sub shell_command {
260   my $cmd = shift;
261
262   my $fh     = new FileHandle;
263   my $result = '';
264   open $fh, q{-|}, "LANG=C $cmd" or die "Can't exec $cmd\n";
265   $result .= <$fh>;
266   close $fh;
267   chomp $result;
268   return $result;
269   }
270
271#---------------------------------------------------------------
272# donne l'@ ip, dns, arp en fonction du dns OU de l'ip
273sub resolve_ip_arp_host {
274   my $param_ip_or_host = shift;
275   my $interface        = shift || q{*};
276   my $type             = shift || q{fast};
277   my $already          = shift || q{yes};
278
279   my %ret = (
280      hostname_fq  => 'unknow',
281      ipv4_address => '0.0.0.0',
282      mac_address  => 'unknow',
283      );
284
285   # perl -MSocket -E 'say inet_ntoa(scalar gethostbyname("tech7meylan.hmg.inpg.fr"))'
286   my $packed_ip = scalar gethostbyname($param_ip_or_host);
287   return %ret if not defined $packed_ip;
288   $ret{'ipv4_address'} = inet_ntoa($packed_ip);
289   #if ($ret{'ipv4_address'} !~ m/$RE_IPv4_ADDRESS/) {
290   #   print "Error: for computer $param_ip_or_host on interface $interface, IP $ret{'ipv4_address'} is not valide\n";
291   #   return %ret;
292   #   }
293
294   # perl -MSocket -E 'say scalar gethostbyaddr(inet_aton("194.254.66.240"), AF_INET)'
295   my $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) if $already eq 'yes';
296   $ret{'hostname_fq'} = $hostname_fq if defined $hostname_fq;
297
298   # my $cmd = q{grep  -he '\b} . $param_ip_or_host . q{\b' } . "/var/lib/arpwatch/$interface.dat | sort -rn -k 3,3 | head -1";
299   #my $cmd = q{grep  -he '\b} . $ret{'ipv4_address'} . q{\b' } . "/var/lib/arpwatch/$interface.dat | sort -rn -k 3,3 | head -1";
300   my $cmd = q{grep  -He '\b} . $ret{'ipv4_address'} . q{\b' } . "/var/lib/arpwatch/$interface.dat" . '| sed -e \'s|^/var/lib/arpwatch/\(.*\)\.dat:|\1 |;\' | sort -rn -k 4,4 | head -1';
301   #grep -He 194.254.66.252 /var/lib/arpwatch/*.dat | sed -e 's|^/var/lib/arpwatch/\(.*\)\.dat:|\1\t|;' | sort -rn -k 4,4 | head -1
302
303   my $cmd_arpwatch = shell_command $cmd;
304   #my ($arp, $ip, $timestamp, $host) = split m/ \s+ /xms, $cmd_arpwatch;
305   my ($interface2, $arp, $ip, $timestamp, $host) = split m/ \s+ /xms, $cmd_arpwatch;
306
307   $ret{'interface'}    = $interface2 || $interface;
308   $ret{'mac_address'}  = $arp       if $arp;
309   $ret{'timestamp'}    = $timestamp if $timestamp;
310
311   my $nowtimestamp = time;
312
313   if ($type eq 'fast' and (not defined $timestamp or $timestamp < ($nowtimestamp - 45 * 60))) { # 45 min
314      $ret{'mac_address'} = 'unknow';
315      return %ret;
316      }
317
318   # ARP result
319   #
320   # LANG=C arp -a 194.254.66.62 -i eth331
321   # gw66-62.legi.grenoble-inp.fr (194.254.66.62) at 00:08:7c:bb:0f:c0 [ether] on eth331
322   #
323   # LANG=C ip neigh show to 194.254.66.62 dev eth331
324   # 194.254.66.62 lladdr 00:08:7c:bb:0f:c0 REACHABLE
325   # LANG=C ip neigh show to 194.254.66.62
326   # 194.254.66.62 dev eth331 lladdr 00:08:7c:bb:0f:c0 REACHABLE
327   #   my $cmd_arp  = shell_command "arp -a $param_ip_or_host -i $ret{'interface'}";
328   #   if ( $cmd_arp =~ m{ (\S*) \s \( ( $RE_IPv4_ADDRESS ) \) \s at \s ( $RE_MAC_ADDRESS ) }xms ) {
329   #      ( $ret{'hostname_fq'}, $ret{'ipv4_address'}, $ret{'mac_address'} )  = ($1, $2, $3);
330   #      }
331   if ($ret{'mac_address'} eq 'unknow') {
332      # Last chance to have the mac_address
333      if ($ret{'interface'} eq '*') {
334         my $cmd_arp = shell_command "ip neigh show to $ret{'ipv4_address'}";
335         if ($cmd_arp =~ m{ ^$RE_IPv4_ADDRESS \s dev \s ([\w\d\.\:]+) \s lladdr \s ( $RE_MAC_ADDRESS ) \s }xms) {
336            ($ret{'interface'}, $ret{'mac_address'}) = ($1, $2);
337            }
338         }
339      else {
340         my $cmd_arp = shell_command "ip neigh show to $ret{'ipv4_address'} dev $ret{'interface'}";
341         if ($cmd_arp =~ m{ ^$RE_IPv4_ADDRESS \s lladdr \s ( $RE_MAC_ADDRESS ) \s }xms) {
342            $ret{'mac_address'} = $1;
343            }
344         }
345      }
346
347   # Normalize MAC Address
348   if ($ret{'mac_address'} ne 'unknow') {
349      my @paquets = ();
350      for (split m/ : /xms, $ret{'mac_address'}) {
351         my @chars = split m//xms, uc "00$_";
352         push @paquets, "$chars[-2]$chars[-1]";
353         }
354      $ret{'mac_address'} = join q{:}, @paquets;
355      }
356
357   return %ret;
358   }
359
360#---------------------------------------------------------------
361# Find Surname of a switch
362sub get_switch_model {
363   my $sw_snmp_description = shift || 'unknow';
364   $sw_snmp_description =~ s/[\n\r]/ /g;
365
366   for my $sw_kind (keys %SWITCH_KIND) {
367      next if not $sw_snmp_description =~ m/$SWITCH_KIND{$sw_kind}->{'match'}/ms; # option xms break search, why ?
368
369      return $SWITCH_KIND{$sw_kind}->{'model'};
370      }
371
372   return $sw_snmp_description;
373   }
374
375#---------------------------------------------------------------
376# Find Revision Firmware of a switch
377sub get_switch_revision {
378   my $sw_snmp_description = shift || 'unknow';
379   $sw_snmp_description =~ s/[\n\r]/ /g;
380
381   for my $sw_kind (keys %SWITCH_KIND) {
382      next if not $sw_snmp_description =~ m/$SWITCH_KIND{$sw_kind}->{'match'}/ms; # option xms break search, why ?
383      last if not exists $SWITCH_KIND{$sw_kind}->{'revision'};
384
385      my ($revision) = $sw_snmp_description =~ m/$SWITCH_KIND{$sw_kind}->{'revision'}/xms;
386      return $revision || 'unknow';
387      }
388
389   return 'unknow';
390   }
391
392#---------------------------------------------------------------
393# Get switch name and switch model
394sub init_switch_names {
395   my ($verbose, $verb_description, $check_hostname, $check_location) = @_;
396
397   printf "%-26s                %-25s %s\n", 'Switch', 'Description', 'Type(Revision)' if $verbose;
398   print "----------------------------------------------------------------------------------\n" if $verbose;
399
400   INIT_EACH_SWITCH:
401   for my $sw (my @CLONE = @SWITCH_LIST) { # Make a local clone because some element can be deleted
402      my ($session, $error) = Net::SNMP->session(%{$sw->{'snmp_param_session'}});
403      print "$error \n" if $error;
404
405      my $result = $session->get_request(
406         -varbindlist => [
407            $OID_NUMBER{'sysDescription'},
408            $OID_NUMBER{'sysName'},
409            $OID_NUMBER{'sysContact'},
410            $OID_NUMBER{'sysLocation'},
411            ]
412         );
413      if (!defined $result) {
414         printf {*STDERR} "ERROR: %s.\n", $session->error();
415         $session->close();
416         # Remove bad switch
417         @SWITCH_LIST = grep {$_->{'hostname'} ne $sw->{'hostname'}} @SWITCH_LIST;
418         delete $SWITCH_LEVEL{$sw->{'hostname'}} if exists $SWITCH_LEVEL{$sw->{'hostname'}};
419         delete $SWITCH_DB{$sw->{'hostname'}}    if exists $SWITCH_DB{$sw->{'hostname'}};
420         next INIT_EACH_SWITCH;
421         }
422
423      $sw->{'description'} = $result->{$OID_NUMBER{'sysName'}} || $sw->{'hostname'};
424      $sw->{'model'}       = get_switch_model($result->{$OID_NUMBER{'sysDescription'}});
425      $sw->{'revision'}    = get_switch_revision($result->{$OID_NUMBER{'sysDescription'}});
426      printf "%-26s 0--------->>>> %-25s %s\n", $sw->{'hostname'}, $sw->{'description'}, $sw->{'model'}.'('.$sw->{'revision'}.')' if $verbose;
427
428      if ($verb_description) {
429         my $desc = $result->{$OID_NUMBER{'sysDescription'}};
430         $desc =~ s/[\n\r]/ /g;
431         print " +> $sw->{'hostname'} - description: $desc\n";
432         }
433      if ($check_hostname) {
434         my ($hostname) = split /\./, $sw->{'hostname'}, 2;
435         print " +> $hostname - error internal hostname: $sw->{'hostname'}\n" if $result->{$OID_NUMBER{'sysName'}} ne $hostname;
436         }
437      if ($check_location) {
438         my $location = $result->{$OID_NUMBER{'sysLocation'}};
439         $location =~ s/^"(.+)"$/$1/;
440         print " +> $sw->{'hostname'} - error location: '$location' -> '$sw->{'location'}'\n" if $location ne $sw->{'location'};
441         }
442
443      $session->close;
444      }
445
446   print "\n" if $verbose;
447   return;
448   }
449
450#---------------------------------------------------------------
451# convert hexa (only 2 digits) to decimal
452sub digit_hex2dec {
453   #00:0F:1F:43:E4:2B
454   my $car = '00' . uc shift;
455
456   return '00' if $car eq '00UNKNOW';
457   my %table = (
458      '0'=>'0',  '1'=>'1',  '2'=>'2',  '3'=>'3',  '4'=>'4',
459      '5'=>'5',  '6'=>'6',  '7'=>'7',  '8'=>'8',  '9'=>'9',
460      'A'=>'10', 'B'=>'11', 'C'=>'12', 'D'=>'13', 'E'=>'14', 'F'=>'15',
461      );
462   my @chars = split m//xms, $car;
463   return $table{$chars[-2]} * 16 + $table{$chars[-1]};
464   }
465
466#---------------------------------------------------------------
467
468sub normalize_mac_address {
469   my $mac_address = shift;
470
471   # D07E-28D1-7AB8 or D07E.28D1.7AB8 or d07e28-d17ab8
472   if ($mac_address =~ m{^ (?: [0-9A-Fa-f]{4} [-\.]){2} [0-9A-Fa-f]{4} $}xms
473      or $mac_address =~ m{^ [0-9A-Fa-f]{6} - [0-9A-Fa-f]{6} $}xms
474      ) {
475      $mac_address =~ s/[-\.]//g;
476      return join q{:}, unpack('(A2)*', uc($mac_address));
477      }
478
479   return join q{:}, map {substr(uc("00$_"), -2)} split m/ [:-] /xms, $mac_address;
480   }
481
482#---------------------------------------------------------------
483# convert MAC hex address to decimal
484sub mac_address_hex2dec {
485   #00:0F:1F:43:E4:2B
486   my $mac_address = shift;
487
488   my @paquets = split m/ : /xms, $mac_address;
489   my $return = q{};
490   for (@paquets) {
491      $return .= q{.} . digit_hex2dec($_);
492      }
493   return $return;
494   }
495
496#---------------------------------------------------------------
497sub format_aggregator4html {
498   my $port_hr = shift;
499   $port_hr =~ s/($SEP_AGGREGATOR_PORT)/: /; # First occurence
500   $port_hr =~ s/($SEP_AGGREGATOR_PORT)/ /g; # Other occurence
501   return $port_hr;
502   }
503
504#---------------------------------------------------------------
505sub format_aggregator4dot {
506   my $port_hr = shift;
507   $port_hr =~ s/($SEP_AGGREGATOR_PORT)/ - /; # First occurence
508   $port_hr =~ s/($SEP_AGGREGATOR_PORT)/ /g;  # Other occurence
509   return $port_hr;
510   }
511
512#---------------------------------------------------------------
513# port can be switch index or human readable
514sub format_switchport4sort {
515   my ($switch_name, $port) = @_;
516
517   # numeric port
518   return sprintf '%s %09i', $switch_name, $port if $port =~ m{^\d+$};
519
520   # Gi3/0/14 type port
521   if ($port =~ m{^(\w+?)(\d+)/(\d+)/(\d+)$}) {
522      return sprintf '%s %03i%03i%03i', $switch_name, $2, $3, $4;
523      }
524
525   # Gi3/14 type port
526   if ($port =~ m{^(\w+?)(\d+)/(\d+)$}) {
527      return sprintf '%s %06i%03i', $switch_name, $2, $3;
528      }
529
530   # Po14 or A3 type port
531   if ($port =~ m{^(\w+?)(\d+)(.*)$}) {
532      return sprintf '%s %s%04i%s', $switch_name, $1, $2, $3;
533      }
534
535   # otherwise
536   return sprintf '%s %s', $switch_name, $port;
537   }
538
539#---------------------------------------------------------------
540# fqdn_html_breakable change it's parameter, use it like chomp perl function
541
542sub fqdn_html_breakable {
543   my $ref_fqdn = \($_[0]);
544
545   # Add HTML <wbr> before every . to permit line break
546   $$ref_fqdn =~ s{\.}{.<wbr />}g;
547   }
548
549#---------------------------------------------------------------
550# return the port and the switch where the computer is connected
551sub find_switch_port {
552   my $mac_address     = shift;
553   my $switch_proposal = shift || q{};
554   my $vlan_id         = shift || 0;
555
556   my %ret;
557   $ret{'switch_description'} = 'unknow';
558   $ret{'switch_port_id'}     = '0';
559
560   return %ret if $mac_address eq 'unknow';
561
562   my @switch_search = @SWITCH_LIST;
563   if ($switch_proposal ne q{}) {
564      for my $sw (@SWITCH_LIST) {
565         next if $sw->{'hostname'} ne $switch_proposal;
566         unshift @switch_search, $sw;
567         last;
568         }
569      }
570
571   my $oid_search_port1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
572   my $oid_search_port2 = $OID_NUMBER{'searchPort2'} . '.' . $vlan_id . mac_address_hex2dec($mac_address);
573
574   LOOP_ON_SWITCH:
575   for my $sw (@switch_search) {
576      my ($session, $error) = Net::SNMP->session(%{$sw->{'snmp_param_session'}});
577      print "$error \n" if $error;
578
579      my $result = $session->get_request(
580         -varbindlist => [$oid_search_port1]
581         );
582      if (not defined $result) {
583         $result = $session->get_request(
584            -varbindlist => [$oid_search_port2]
585            );
586         $result->{$oid_search_port1} = $result->{$oid_search_port2} if defined $result;
587         }
588
589      if (not(defined $result and $result->{$oid_search_port1} ne 'noSuchInstance')) {
590         $session->close;
591         next LOOP_ON_SWITCH;
592         }
593
594      my $swport_id = $result->{$oid_search_port1};
595      my $swport_hr = snmp_get_switchport_id2hr($session, $swport_id);
596
597      $session->close;
598
599      # IMPORTANT !!
600      # ceci empeche la detection sur certains port ...
601      # en effet les switch sont relies entre eux par un cable reseau et du coup
602      # tous les arp de toutes les machines sont presentes sur ces ports (ceux choisis ici sont les miens)
603      # cette partie est a ameliore, voir a configurer dans l'entete
604      # 21->24 45->48
605      SWITCH_PORT_IGNORE:
606      for my $portignore (@{$sw->{'portignore'}}) {
607         next LOOP_ON_SWITCH if $swport_hr eq $portignore;
608         my ($swport_hr_limited) = split /$SEP_AGGREGATOR_PORT/, $swport_hr; # Beginning of the swith port (Aggregator)
609         next LOOP_ON_SWITCH if $swport_hr_limited eq $portignore;
610         }
611
612      $ret{'switch_hostname'}    = $sw->{'hostname'};
613      $ret{'switch_description'} = $sw->{'description'};
614      $ret{'switch_port_id'}     = $swport_id;
615      $ret{'switch_port_hr'}     = $swport_hr; # human readable
616
617      last LOOP_ON_SWITCH;
618      }
619   return %ret;
620   }
621
622#---------------------------------------------------------------
623# search all the port on all the switches where the computer is detected
624sub find_all_switch_port {
625   my $mac_address = shift;
626   my $vlan_id     = shift || 0;
627
628   my $ret = {};
629
630   return $ret if $mac_address eq 'unknow';
631
632   my $oid_search_port1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
633   my $oid_search_port2 = $OID_NUMBER{'searchPort2'} . '.' . $vlan_id . mac_address_hex2dec($mac_address);
634   LOOP_ON_ALL_SWITCH:
635   for my $sw (@SWITCH_LIST) {
636      my ($session, $error) = Net::SNMP->session( %{$sw->{'snmp_param_session'}} );
637      print "$error \n" if $error;
638
639      my $result = $session->get_request(
640         -varbindlist => [$oid_search_port1]
641         );
642      if (not defined $result) {
643         $result = $session->get_request(
644            -varbindlist => [$oid_search_port2]
645            );
646         $result->{$oid_search_port1} = $result->{$oid_search_port2} if defined $result;
647         }
648
649      if (defined $result and $result->{$oid_search_port1} ne 'noSuchInstance') {
650         my $swport_id = $result->{$oid_search_port1};
651         my $swport_hr = snmp_get_switchport_id2hr($session, $swport_id);
652
653         SWITCH_PORT_IGNORE:
654         for my $portignore (@{$sw->{'portignore'}}) {
655            if ($swport_hr eq $portignore) {
656               $session->close;
657               next LOOP_ON_ALL_SWITCH;
658               }
659            }
660
661         $ret->{$sw->{'hostname'}} = {};
662         $ret->{$sw->{'hostname'}}{'hostname'}    = $sw->{'hostname'};
663         $ret->{$sw->{'hostname'}}{'description'} = $sw->{'description'};
664         $ret->{$sw->{'hostname'}}{'port_id'}     = $swport_id;
665         $ret->{$sw->{'hostname'}}{'port_hr'}     = $swport_hr;
666         }
667
668      $session->close;
669      }
670   return $ret;
671   }
672
673#---------------------------------------------------------------
674sub get_list_network {
675
676   return keys %{$KLASK_CFG->{'network'}};
677   }
678
679#---------------------------------------------------------------
680sub get_current_interface {
681   my $vlan_name = shift;
682
683   return $KLASK_CFG->{'network'}{$vlan_name}{'interface'};
684   }
685
686#---------------------------------------------------------------
687sub get_current_vlan_id {
688   my $vlan_name = shift;
689
690   return 0 if not exists $KLASK_CFG->{'network'}{$vlan_name};
691   return $KLASK_CFG->{'network'}{$vlan_name}{'vlan-id'};
692   }
693
694#---------------------------------------------------------------
695sub get_current_scan_mode {
696   my $vlan_name = shift;
697
698   return $KLASK_CFG->{'network'}{$vlan_name}{'scan-mode'} || $DEFAULT{'scan-mode'} || 'active';
699   }
700
701#---------------------------------------------------------------
702sub get_current_vlan_name_for_interface {
703   my $interface = shift;
704
705   for my $vlan_name (keys %{$KLASK_CFG->{'network'}}) {
706      next if $KLASK_CFG->{'network'}{$vlan_name}{'interface'} ne $interface;
707      return $vlan_name;
708      }
709   }
710
711#---------------------------------------------------------------
712# liste l'ensemble des adresses ip d'un réseau
713sub get_list_ip {
714   my @vlan_name = @_;
715
716   my $cidrlist = Net::CIDR::Lite->new;
717
718   for my $net (@vlan_name) {
719      my @line = @{$KLASK_CFG->{'network'}{$net}{'ip-subnet'}};
720      for my $cmd (@line) {
721         for my $method (keys %{$cmd}) {
722            $cidrlist->add_any($cmd->{$method}) if $method eq 'add';
723            }
724         }
725      }
726
727   my @res = ();
728
729   for my $cidr ($cidrlist->list()) {
730      my $net = new NetAddr::IP $cidr;
731      for my $ip (@{$net}) {
732         $ip =~ s{ /32 }{}xms;
733         push @res, $ip;
734         }
735      }
736
737   return @res;
738   }
739
740#---------------------------------------------------------------
741# liste l'ensemble des routeurs du réseau
742sub get_list_main_router {
743   my @vlan_name = @_;
744
745   my @res = ();
746
747   for my $net (@vlan_name) {
748      push @res, $KLASK_CFG->{'network'}{$net}{'main-router'};
749      }
750
751   return @res;
752   }
753
754#---------------------------------------------------------------
755sub normalize_port_human_readable {
756   my $sw_port_hr = shift;
757
758   # Manufacturer abbreviation
759   $sw_port_hr =~ s/^Bridge-Aggregation/Br/i;
760   $sw_port_hr =~ s/^Port-Channel/Po/i;
761   $sw_port_hr =~ s/^Forty-?GigabitEthernet/Fo/i;
762   $sw_port_hr =~ s/^Ten-?GigabitEthernet/Te/i;
763   $sw_port_hr =~ s/^GigabitEthernet/Gi/i;
764   $sw_port_hr =~ s/^FastEthernet/Fa/i;
765
766   # Customer abbreviation
767   $sw_port_hr =~ s/^Ten/Te/i;
768   $sw_port_hr =~ s/^Giga/Gi/i;
769
770   return ucfirst $sw_port_hr;
771   }
772
773#---------------------------------------------------------------
774sub snmp_get_rwsession {
775   my ($sw) = @_;
776
777   my %session = %{$sw->{'snmp_param_session'}};
778   $session{-community} = $sw->{'community-rw'} || $DEFAULT{'community-rw'} || 'private';
779   return %session;
780   }
781
782#---------------------------------------------------------------
783sub snmp_get_switchport_id2hr {
784   my ($snmp_session, $swport_id) = @_;
785
786   # On H3C, port id (port_id) and port index (port_ix) are not the same
787   # Double SNMP request to get the name
788   # First get the index, second get the name (port_hr)
789
790   my $oid_search_ix = $OID_NUMBER{'ifIndex'} . '.' . $swport_id;
791   my $result_ix = $snmp_session->get_request(
792      -varbindlist => [$oid_search_ix]
793      );
794
795   my $swport_ix = $swport_id;
796   $swport_ix = $result_ix->{$oid_search_ix} if defined $result_ix;
797
798   return snmp_get_switchport_ix2hr($snmp_session, $swport_ix);
799   }
800
801#---------------------------------------------------------------
802sub snmp_get_switchport_ix2hr {
803   my ($snmp_session, $swport_ix) = @_;
804
805   my $oid_search_hr = $OID_NUMBER{'ifName'} .'.'. $swport_ix;
806   my $result_hr = $snmp_session->get_request(
807      -varbindlist => [$oid_search_hr]
808      );
809   my $swport_hr = $swport_ix;
810   $swport_hr = normalize_port_human_readable($result_hr->{$oid_search_hr}) if defined $result_hr;
811
812   # Aggregator port
813   if ($swport_hr =~ m/^(Trk|Br|Po)/) {
814      my $oid_search_index = $OID_NUMBER{'ifAggregator'}; # base OID
815      my @args = (-varbindlist => [$oid_search_index]);
816      LOOP_ON_OID_PORT:
817      while (defined $snmp_session->get_next_request(@args)) {
818         my ($oid_current) = $snmp_session->var_bind_names;
819         last LOOP_ON_OID_PORT if not Net::SNMP::oid_base_match($oid_search_index, $oid_current);
820
821         # IEEE8023-LAG-MIB::dot3adAggPortSelectedAggID.28 = INTEGER: 337
822         # IEEE8023-LAG-MIB::dot3adAggPortAttachedAggID.28 = INTEGER: 337
823         my $port_aggregator_index = $snmp_session->var_bind_list->{$oid_current};
824         my ($current_port_ix) = reverse split /\./, $oid_current; # last number
825
826         # prepare next loop item
827         @args = (-varbindlist => [$oid_current]);
828
829         next LOOP_ON_OID_PORT if $port_aggregator_index == 0;
830         next LOOP_ON_OID_PORT if not $port_aggregator_index == $swport_ix;
831
832         my $current_port_name = snmp_get_switchport_ix2hr($snmp_session, $current_port_ix);
833         $swport_hr .= "$SEP_AGGREGATOR_PORT$current_port_name";
834         }
835      }
836   return $swport_hr;
837   }
838
839#---------------------------------------------------------------
840# Reverse search port number
841sub snmp_get_switchport_hr2id {
842   my ($snmp_session, $swport_hr, $verbose) = @_;
843
844   # Split for Aggregator port
845   # Keep only the Aggregator part
846   ($swport_hr) = split /$SEP_AGGREGATOR_PORT/, $swport_hr;
847
848   my $swport_id = $swport_hr;
849   # direct return if already numeric (next loop is expensive) / old or simple switch
850   return $swport_id if $swport_id =~ m/^\d+$/;
851
852   my $oid_search_ix = $OID_NUMBER{'ifIndex'}; # base OID
853   my @args = (-varbindlist => [$oid_search_ix]);
854   LOOP_ON_OID_PORT:
855   while (defined $snmp_session->get_next_request(@args)) {
856      my ($oid_current) = $snmp_session->var_bind_names;
857      last LOOP_ON_OID_PORT if not Net::SNMP::oid_base_match($oid_search_ix, $oid_current);
858
859      my $port_ifIndex = $snmp_session->var_bind_list->{$oid_current};
860      my ($port_ix) = reverse split /\./, $oid_current; # last number
861      printf "PORT1: %s => %s\n", $oid_current, $port_ifIndex if $verbose;
862
863      # prepare next loop item
864      @args = (-varbindlist => [$oid_current]);
865
866      my $oid_search_ifName = $OID_NUMBER{'ifName'} . '.' . $port_ifIndex;
867      my $result = $snmp_session->get_request(-varbindlist => [$oid_search_ifName]);
868      next LOOP_ON_OID_PORT if not defined $result;
869
870      my $current_port_hr = normalize_port_human_readable($result->{$oid_search_ifName});
871      printf "PORT2: $oid_search_ifName => $current_port_hr\n" if $verbose;
872      if ($current_port_hr eq $swport_hr) {
873         print "PORT3: $current_port_hr <-> $port_ix\n" if $verbose;
874
875         # return port number ifIndex need by OID portUpDown
876         $swport_id = $port_ifIndex; # other possible value could be $port_ix
877         last LOOP_ON_OID_PORT;
878         }
879      }
880   return $swport_id;
881   }
882
883#---------------------------------------------------------------
884# Get the list of all the VLAN define on a switch
885sub snmp_get_vlan_list {
886   my ($snmp_session, $verbose) = @_;
887
888   my %vlandb = (); # Hash vlan number => vlan name
889
890   my $oid_search_index = $OID_NUMBER{'vlanName'}; # base OID
891   my @args = (-varbindlist => [$oid_search_index]);
892   LOOP_ON_VLAN:
893   while (defined $snmp_session->get_next_request(@args)) {
894      my ($oid_current) = $snmp_session->var_bind_names;
895      last LOOP_ON_VLAN if not Net::SNMP::oid_base_match($oid_search_index, $oid_current);
896
897      my $vlan_name = $snmp_session->var_bind_list->{$oid_current};
898      my ($vlan_id) = reverse split /\./, $oid_current; # last number
899      printf "VLAN: %s => %s\n", $oid_current, $vlan_name if $verbose;
900
901      $vlandb{$vlan_id} = $vlan_name;
902
903      # prepare next loop item
904      @args = (-varbindlist => [$oid_current]);
905      }
906   return %vlandb;
907   }
908
909#---------------------------------------------------------------
910# Load computer database
911sub computerdb_load {
912   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
913
914   LOOP_ON_IP_ADDRESS:
915   for my $ip (keys %{$computerdb}) {
916
917      # Rename switch_port -> switch_port_id (2017/09/15)
918      if (not exists $computerdb->{$ip}{'switch_port_id' and exists $computerdb->{$ip}{'switch_port'}}) {
919         $computerdb->{$ip}{'switch_port_id'} = $computerdb->{$ip}{'switch_port'} if defined $computerdb->{$ip}{'switch_port'};
920         $computerdb->{$ip}{'switch_port_id'} = 0 if $computerdb->{$ip}{'switch_port_id'} !~ m/^\d+$/; # force numeric
921         }
922      delete $computerdb->{$ip}{'switch_port'} if exists $computerdb->{$ip}{'switch_port'};
923
924      next LOOP_ON_IP_ADDRESS if exists $computerdb->{$ip}{'switch_port_hr'} and defined $computerdb->{$ip}{'switch_port_hr'};
925
926      $computerdb->{$ip}{'switch_port_hr'} = $computerdb->{$ip}{'switch_port_id'};
927      }
928
929   return $computerdb;
930   }
931
932#---------------------------------------------------------------
933sub get_switchdb_checksum {
934   my %switch_db = @_; # same as global %SWITCH_DB
935
936   my $checksum_data = '';
937   for my $sw_name (sort keys %switch_db) { # sort to always have the same order
938      $checksum_data .= join ':',
939         $switch_db{$sw_name}->{'description'},
940         $switch_db{$sw_name}->{'model'},
941         $switch_db{$sw_name}->{'hostname'},
942         "\n";
943      }
944
945   return sha512_base64($checksum_data);
946   }
947
948#---------------------------------------------------------------
949sub update_switchdb {
950   my %args = (
951      verbose => 0,
952      @_);
953
954   init_switch_names('yes'); # nomme les switchs
955   print "\n";
956
957   my %where = ();
958   my %db_switch_output_port = ();
959   my %db_switch_ip_hostnamefq = ();
960
961   DETECT_ALL_ROUTER:
962   for my $one_router (get_list_main_router(get_list_network())) {
963      print "Info: router loop $one_router\n" if $args{'verbose'};
964      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
965
966      next DETECT_ALL_ROUTER if $resol_arp{'mac_address'} eq 'unknow';
967      print "VERBOSE_1: Router detected $resol_arp{'ipv4_address'} - $resol_arp{'mac_address'}\n" if $args{'verbose'};
968
969      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
970      my $vlan_id   = get_current_vlan_id($vlan_name);
971      $where{$resol_arp{'ipv4_address'}} = find_all_switch_port($resol_arp{'mac_address'}, $vlan_id); # retrouve les emplacements des routeurs
972      }
973
974   ALL_ROUTER_IP_ADDRESS:
975   for my $ip_router (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
976
977      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip_router}; # /a priori/ idiot car ne sers à rien...
978
979      ALL_SWITCH_CONNECTED:
980      for my $switch_detected (keys %{$where{$ip_router}}) {
981
982         my $switch = $where{$ip_router}->{$switch_detected};
983
984         next ALL_SWITCH_CONNECTED if $switch->{'port_id'} eq '0';
985
986         $db_switch_output_port{$switch->{'hostname'}} = $switch->{'port_hr'};
987         print "VERBOSE_2: output port $switch->{'hostname'} : $switch->{'port_hr'}\n" if $args{'verbose'};
988         }
989      }
990
991   my %db_switch_link_with = ();
992
993   my @list_all_switch  = ();
994   my @list_switch_ipv4 = ();
995   for my $sw (@SWITCH_LIST) {
996      push @list_all_switch, $sw->{'hostname'};
997      }
998
999   my $timestamp = time;
1000
1001   ALL_SWITCH:
1002   for my $one_switch (@list_all_switch) {
1003      my %resol_arp = resolve_ip_arp_host($one_switch, q{*}, q{low}); # arp resolution
1004      if (exists $SWITCH_DB{$one_switch}{'fake-ip'}) {
1005         my $fake_ip = $SWITCH_DB{$one_switch}{'fake-ip'};
1006         fast_ping($fake_ip);
1007         print "WARNING: fake ip on switch $one_switch -> $fake_ip / $resol_arp{'ipv4_address'}\n" if $args{'verbose'};
1008         my %resol_arp_alt = resolve_ip_arp_host($fake_ip, q{*}, q{low}); # arp resolution
1009         if ($resol_arp_alt{'mac_address'} ne 'unknow') {
1010            $resol_arp{'mac_address'} = $resol_arp_alt{'mac_address'};
1011            $resol_arp{'interface'}   = $resol_arp_alt{'interface'};
1012            $resol_arp{'ipv4_address'} .= '*';
1013            # Force a MAC trace on switch
1014            system "arping -c 1 -w 1 -rR -i $resol_arp_alt{'interface'} $fake_ip > /dev/null 2>&1";
1015            }
1016         }
1017      print "Info: switch loop $one_switch\n" if $args{'verbose'};
1018      next ALL_SWITCH if $resol_arp{'mac_address'} eq 'unknow';
1019
1020      push @list_switch_ipv4, $resol_arp{'ipv4_address'};
1021
1022      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
1023      my $vlan_id   = get_current_vlan_id($vlan_name);
1024      $where{$resol_arp{'ipv4_address'}} = find_all_switch_port($resol_arp{'mac_address'}, $vlan_id); # find port on all switch
1025
1026      if ($args{'verbose'}) {
1027         print "VERBOSE_3: $one_switch $resol_arp{'ipv4_address'} $resol_arp{'mac_address'}\n";
1028         print "VERBOSE_3: $one_switch --- ",
1029            join(' + ', keys %{$where{$resol_arp{'ipv4_address'}}}),
1030            "\n";
1031         }
1032
1033      $db_switch_ip_hostnamefq{$resol_arp{'ipv4_address'}} = $resol_arp{'hostname_fq'};
1034      print "VERBOSE_4: db_switch_ip_hostnamefq $resol_arp{'ipv4_address'} -> $resol_arp{'hostname_fq'}\n" if $args{'verbose'};
1035
1036      $SWITCH_DB{$one_switch}->{'ipv4_address'} = $resol_arp{'ipv4_address'};
1037      $SWITCH_DB{$one_switch}->{'mac_address'}  = $resol_arp{'mac_address'};
1038      $SWITCH_DB{$one_switch}->{'timestamp'}    = $timestamp;
1039      $SWITCH_DB{$one_switch}->{'network'}      = $vlan_name;
1040      }
1041
1042   ALL_SWITCH_IP_ADDRESS:
1043   for my $ip (@list_switch_ipv4) {
1044      #   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
1045
1046      print "VERBOSE_5: loop on $db_switch_ip_hostnamefq{$ip}\n" if $args{'verbose'};
1047
1048      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
1049      #      next ALL_SWITCH_IP_ADDRESS if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostnamefq{$ip} };
1050
1051      DETECTED_SWITCH:
1052      for my $switch_detected (keys %{$where{$ip}}) {
1053
1054         my $switch = $where{$ip}->{$switch_detected};
1055         print "VERBOSE_6: $db_switch_ip_hostnamefq{$ip} -> $switch->{'hostname'} : $switch->{'port_hr'}\n" if $args{'verbose'};
1056
1057         next if $switch->{'port_id'}  eq '0';
1058         next if $switch->{'port_hr'}  eq $db_switch_output_port{$switch->{'hostname'}};
1059         next if $switch->{'hostname'} eq $db_switch_ip_hostnamefq{$ip}; # $computerdb->{$ip}{'hostname'};
1060
1061         $db_switch_link_with{$db_switch_ip_hostnamefq{$ip}} ||= {};
1062         $db_switch_link_with{$db_switch_ip_hostnamefq{$ip}}->{$switch->{'hostname'}} = $switch->{'port_hr'};
1063         print "VERBOSE_7: +++++\n" if $args{'verbose'};
1064         }
1065
1066      }
1067
1068   my %db_switch_connected_on_port          = ();
1069   my $maybe_more_than_one_switch_connected = 'yes';
1070   my $cloop                                = 0;
1071
1072   while ($maybe_more_than_one_switch_connected eq 'yes' and $cloop < 100) {
1073      $cloop++;
1074      print "VERBOSE_9: cloop reduction step: $cloop\n" if $args{'verbose'};
1075      for my $sw (keys %db_switch_link_with) {
1076         for my $connect (keys %{$db_switch_link_with{$sw}}) {
1077
1078            my $port_hr = $db_switch_link_with{$sw}->{$connect};
1079
1080            $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"} ||= {};
1081            $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"}->{$sw}++; # Just to define the key
1082            }
1083         }
1084
1085      $maybe_more_than_one_switch_connected = 'no';
1086
1087      SWITCH_AND_PORT:
1088      for my $swport (keys %db_switch_connected_on_port) {
1089
1090         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
1091
1092         $maybe_more_than_one_switch_connected = 'yes';
1093
1094         my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
1095         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
1096         print "VERBOSE_10: $swport -- " . $#sw_on_same_port . " -- @sw_on_same_port\n" if $args{'verbose'};
1097
1098         CONNECTED:
1099         for my $sw_connected (@sw_on_same_port) {
1100
1101            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
1102
1103            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
1104
1105            for my $other_sw (@sw_on_same_port) {
1106               next if $other_sw eq $sw_connected;
1107
1108               delete $db_switch_link_with{$other_sw}->{$sw_connect};
1109               }
1110
1111            # We can not do better for this switch for this loop
1112            next SWITCH_AND_PORT;
1113            }
1114         }
1115      }
1116
1117   my %db_switch_parent = ();
1118
1119   for my $sw (keys %db_switch_link_with) {
1120      for my $connect (keys %{$db_switch_link_with{$sw}}) {
1121
1122         my $port_hr = $db_switch_link_with{$sw}->{$connect};
1123
1124         $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"} ||= {};
1125         $db_switch_connected_on_port{"$connect$SEP_SWITCH_PORT$port_hr"}->{$sw} = $port_hr;
1126
1127         $db_switch_parent{$sw} = {switch => $connect, port_hr => $port_hr};
1128         }
1129      }
1130
1131   print "Switch output port and parent port connection\n";
1132   print "---------------------------------------------\n";
1133   for my $sw (sort keys %db_switch_output_port) {
1134      if (exists $db_switch_parent{$sw}) {
1135         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'port_hr'}, $db_switch_parent{$sw}->{'switch'};
1136         }
1137      else {
1138         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
1139         }
1140      }
1141   print "\n";
1142
1143   print "Switch parent and children port inter-connection\n";
1144   print "------------------------------------------------\n";
1145   for my $swport (sort keys %db_switch_connected_on_port) {
1146      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
1147      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1148         if (exists $db_switch_output_port{$sw}) {
1149            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
1150            }
1151         else {
1152            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
1153            }
1154         }
1155      }
1156
1157   my $switch_connection = {
1158      output_port       => \%db_switch_output_port,
1159      parent            => \%db_switch_parent,
1160      connected_on_port => \%db_switch_connected_on_port,
1161      link_with         => \%db_switch_link_with,
1162      switch_db         => \%SWITCH_DB,
1163      timestamp         => $timestamp,
1164      checksum          => get_switchdb_checksum(%SWITCH_DB),
1165      };
1166
1167   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
1168   return;
1169   }
1170
1171################################################################
1172# command
1173################################################################
1174
1175#---------------------------------------------------------------
1176sub cmd_help {
1177
1178   print <<'END';
1179klask - port and search manager for switches, map management
1180
1181 klask version
1182 klask help
1183
1184 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l] [--no-rebuildsw|-R]
1185 klask exportdb [--format|-f txt|html]
1186 klask removedb IP* computer*
1187 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
1188
1189 klask updatesw [--verbose|-v]
1190 klask exportsw [--format|-f txt|dot] [--modulo|-m XX] [--shift|-s YY] [--way all|desc|child|parent] [--no-header|-H]
1191
1192 klask searchdb [--kind|-k host|mac] computer [mac-address]
1193 klask search   computer
1194 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
1195
1196 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
1197
1198 klask bad-vlan-id [--day|-d days_before_alert] [--format|-f txt|html]
1199
1200 klask enable  [--verbose|-v] switch port
1201 klask disable [--verbose|-v] switch port
1202 klask status  [--verbose|-v] switch port
1203
1204 klask poe-enable  [--verbose|-v] switch port
1205 klask poe-disable [--verbose|-v] switch port
1206 klask poe-status  [--verbose|-v] switch port
1207
1208 klask vlan-getname switch vlan-id
1209 klask vlan-list switch
1210END
1211   return;
1212   }
1213
1214#---------------------------------------------------------------
1215sub cmd_version {
1216
1217   print <<'END';
1218klask - port and search manager for switches, map management
1219Copyright (C) 2005-2018 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>
1220License GNU GPL version 2 or later and Perl equivalent
1221END
1222
1223   print "Version $VERSION\n\n";
1224   print ' $Id: klask 386 2018-01-04 19:25:42Z g7moreau $'."\n";
1225   return;
1226   }
1227
1228#---------------------------------------------------------------
1229sub cmd_search {
1230   my @computer = @_;
1231
1232   init_switch_names(); # nomme les switchs
1233   fast_ping(@computer);
1234
1235   LOOP_ON_COMPUTER:
1236   for my $clientname (@computer) {
1237      my %resol_arp = resolve_ip_arp_host($clientname); # resolution arp
1238      my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
1239      my $vlan_id   = get_current_vlan_id($vlan_name);
1240      my %where     = find_switch_port($resol_arp{'mac_address'}, '', $vlan_id); # retrouve l'emplacement
1241
1242      next LOOP_ON_COMPUTER if $where{'switch_description'} eq 'unknow' or $resol_arp{'hostname_fq'} eq 'unknow' or $resol_arp{'mac_address'} eq 'unknow';
1243
1244      printf '%-22s %2s %-30s %-15s %18s',
1245         $where{'switch_hostname'},
1246         $where{'switch_port_hr'},
1247         $resol_arp{'hostname_fq'},
1248         $resol_arp{'ipv4_address'},
1249         $resol_arp{'mac_address'} . "\n";
1250      }
1251   return;
1252   }
1253
1254#---------------------------------------------------------------
1255sub cmd_searchdb {
1256   my @ARGV = @_;
1257
1258   my $kind;
1259
1260   GetOptions(
1261      'kind=s'   => \$kind,
1262      );
1263
1264   my %possible_search = (
1265      host => \&cmd_searchdb_host,
1266      mac  => \&cmd_searchdb_mac,
1267      );
1268
1269   $kind = 'host' if not defined $possible_search{$kind};
1270
1271   $possible_search{$kind}->(@ARGV);
1272   return;
1273   }
1274
1275#---------------------------------------------------------------
1276sub cmd_searchdb_host {
1277   my @computer = @_;
1278
1279   fast_ping(@computer);
1280   my $computerdb = computerdb_load();
1281
1282   LOOP_ON_COMPUTER:
1283   for my $clientname (@computer) {
1284      my %resol_arp = resolve_ip_arp_host($clientname); # resolution arp
1285      my $ip        = $resol_arp{'ipv4_address'};
1286
1287      next LOOP_ON_COMPUTER unless exists $computerdb->{$ip};
1288
1289      my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $computerdb->{$ip}{'timestamp'};
1290      $year += 1900;
1291      $mon++;
1292      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1293
1294      printf "%-22s %2s %-30s %-15s %-18s %s\n",
1295         $computerdb->{$ip}{'switch_hostname'},
1296         $computerdb->{$ip}{'switch_port_hr'},
1297         $computerdb->{$ip}{'hostname_fq'},
1298         $ip,
1299         $computerdb->{$ip}{'mac_address'},
1300         $date;
1301      }
1302   return;
1303   }
1304
1305#---------------------------------------------------------------
1306sub cmd_searchdb_mac {
1307   my @mac = map {normalize_mac_address($_)} @_;
1308
1309   my $computerdb = computerdb_load();
1310
1311   LOOP_ON_MAC:
1312   for my $mac (@mac) {
1313      LOOP_ON_COMPUTER:
1314      for my $ip (keys %{$computerdb}) {
1315         next LOOP_ON_COMPUTER if $mac ne $computerdb->{$ip}{'mac_address'};
1316
1317         my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $computerdb->{$ip}{'timestamp'};
1318         $year += 1900;
1319         $mon++;
1320         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1321
1322         printf "%-22s %2s %-30s %-15s %-18s %s\n",
1323            $computerdb->{$ip}{'switch_hostname'},
1324            $computerdb->{$ip}{'switch_port_hr'},
1325            $computerdb->{$ip}{'hostname_fq'},
1326            $ip,
1327            $computerdb->{$ip}{'mac_address'},
1328            $date;
1329         #next LOOP_ON_MAC;
1330         }
1331
1332      }
1333   return;
1334   }
1335
1336#---------------------------------------------------------------
1337sub cmd_updatedb {
1338   @ARGV = @_;
1339
1340   my ($verbose, $verb_description, $check_hostname, $check_location, $no_rebuildsw);
1341
1342   GetOptions(
1343      'verbose|v'          => \$verbose,
1344      'verb-description|d' => \$verb_description,
1345      'chk-hostname|h'     => \$check_hostname,
1346      'chk-location|l'     => \$check_location,
1347      'no-rebuildsw|R'     => \$no_rebuildsw,
1348      );
1349
1350   my @network = @ARGV;
1351   @network = get_list_network() if not @network;
1352
1353   test_switchdb_environnement();
1354
1355   my $computerdb = {};
1356   $computerdb = computerdb_load() if -e "$KLASK_DB_FILE";
1357   my $timestamp = time;
1358
1359   my %computer_not_detected = ();
1360   my $timestamp_last_week = $timestamp - (3600 * 24 * 7);
1361
1362   my $number_of_computer = get_list_ip(@network); # + 1;
1363   my $size_of_database   = keys %{$computerdb};
1364   $size_of_database = 1 if $size_of_database == 0;
1365   my $i                 = 0;
1366   my $detected_computer = 0;
1367
1368   init_switch_names('yes', $verb_description, $check_hostname, $check_location); # nomme les switchs
1369
1370   {
1371   my $switch_checksum = get_switchdb_checksum(%SWITCH_DB);
1372   # Remplis le champs portignore des ports d'inter-connection pour chaque switch
1373   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1374   if ($switch_checksum ne $switch_connection->{'checksum'}) { # Verify checksum
1375      if ($no_rebuildsw) {
1376         print "WARNING: switch database is outdate, please rebuild if with updatesw command\n";
1377         }
1378      else {
1379         print "WARNING: switch database is going to be rebuilt\n";
1380         update_switchdb(verbose => $verbose)
1381         }
1382      }
1383
1384   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
1385   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
1386   my %db_switch_chained_port = ();
1387   for my $swport (keys %db_switch_connected_on_port) {
1388      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
1389      $db_switch_chained_port{$sw_connect} .= "$port_connect:";
1390      }
1391   for my $sw (@SWITCH_LIST) {
1392      push @{$sw->{'portignore'}}, $db_switch_output_port{$sw->{'hostname'}}  if exists $db_switch_output_port{$sw->{'hostname'}};
1393      if ( exists $db_switch_chained_port{$sw->{'hostname'}} ) {
1394         chop $db_switch_chained_port{$sw->{'hostname'}};
1395         push @{$sw->{'portignore'}}, split m/ : /xms, $db_switch_chained_port{$sw->{'hostname'}};
1396         }
1397#      print "$sw->{'hostname'} ++ @{$sw->{'portignore'}}\n";
1398      }
1399   }
1400
1401   my %router_mac_ip = ();
1402   DETECT_ALL_ROUTER:
1403   #   for my $one_router ('194.254.66.254') {
1404   for my $one_router (get_list_main_router(@network)) {
1405      my %resol_arp = resolve_ip_arp_host($one_router);
1406      $router_mac_ip{$resol_arp{'mac_address'}} = $resol_arp{'ipv4_address'};
1407      }
1408
1409   ALL_NETWORK:
1410   for my $current_net (@network) {
1411
1412      my @computer          = get_list_ip($current_net);
1413      my $current_interface = get_current_interface($current_net);
1414
1415      fast_ping(@computer) if get_current_scan_mode($current_net) eq 'active';
1416
1417      LOOP_ON_COMPUTER:
1418      for my $one_computer (@computer) {
1419         $i++;
1420
1421         my $total_percent = int(($i * 100) / $number_of_computer);
1422
1423         my $localtime = time - $timestamp;
1424         my ($sec, $min) = localtime $localtime;
1425
1426         my $time_elapse = 0;
1427         $time_elapse = $localtime * (100 - $total_percent) / $total_percent if $total_percent != 0;
1428         my ($sec_elapse, $min_elapse) = localtime $time_elapse;
1429
1430         printf "\rComputer scanned: %4i/%i (%2i%%)", $i, $number_of_computer, $total_percent;
1431         printf ', detected: %4i/%i (%2i%%)', $detected_computer, $size_of_database, int(($detected_computer * 100) / $size_of_database);
1432         printf ' [Time: %02i:%02i / %02i:%02i]', int($localtime / 60), $localtime % 60, int($time_elapse / 60), $time_elapse % 60;
1433         printf ' %-8s %-14s', $current_interface, $one_computer;
1434
1435         my $already_exist = exists $computerdb->{$one_computer} ? 'yes' : 'no';
1436         my %resol_arp = resolve_ip_arp_host($one_computer, $current_interface, 'fast', $already_exist);
1437
1438         # do not search on router connection (why ?)
1439         if (exists $router_mac_ip{$resol_arp{'mac_address'}}) {
1440            $computer_not_detected{$one_computer} = $current_net;
1441            next LOOP_ON_COMPUTER;
1442            }
1443
1444         # do not search on switch inter-connection
1445         if (exists $SWITCH_LEVEL{$resol_arp{'hostname_fq'}}) {
1446            $computer_not_detected{$one_computer} = $current_net;
1447            next LOOP_ON_COMPUTER;
1448            }
1449
1450         my $switch_proposal = q{};
1451         if (exists $computerdb->{$resol_arp{'ipv4_address'}} and exists $computerdb->{$resol_arp{'ipv4_address'}}{'switch_hostname'}) {
1452            $switch_proposal = $computerdb->{$resol_arp{'ipv4_address'}}{'switch_hostname'};
1453            }
1454
1455         # do not have a mac address
1456         if ($resol_arp{'mac_address'} eq 'unknow' or (exists $resol_arp{'timestamps'} and $resol_arp{'timestamps'} < ($timestamp - 3 * 3600))) {
1457            $computer_not_detected{$one_computer} = $current_net;
1458            next LOOP_ON_COMPUTER;
1459            }
1460
1461         my $vlan_name = get_current_vlan_name_for_interface($resol_arp{'interface'});
1462         my $vlan_id   = get_current_vlan_id($vlan_name);
1463         my %where     = find_switch_port($resol_arp{'mac_address'}, $switch_proposal, $vlan_id);
1464
1465         #192.168.24.156:
1466         #  arp: 00:0B:DB:D5:F6:65
1467         #  hostname: pcroyon.hmg.priv
1468         #  port: 5
1469         #  switch: sw-batH-legi:hp2524
1470         #  timestamp: 1164355525
1471
1472         # do not have a mac address
1473         #         if ($resol_arp{'mac_address'} eq 'unknow') {
1474         #            $computer_not_detected{$one_computer} = $current_interface;
1475         #            next LOOP_ON_COMPUTER;
1476         #            }
1477
1478         # detected on a switch
1479         if ($where{'switch_description'} ne 'unknow') {
1480            $detected_computer++;
1481            $computerdb->{$resol_arp{'ipv4_address'}} = {
1482               hostname_fq        => $resol_arp{'hostname_fq'},
1483               mac_address        => $resol_arp{'mac_address'},
1484               switch_hostname    => $where{'switch_hostname'},
1485               switch_description => $where{'switch_description'},
1486               switch_port_id     => $where{'switch_port_id'},
1487               switch_port_hr     => $where{'switch_port_hr'},
1488               timestamp          => $timestamp,
1489               network            => $current_net,
1490               };
1491            next LOOP_ON_COMPUTER;
1492            }
1493
1494         # new in the database but where it is ?
1495         if (not exists $computerdb->{$resol_arp{'ipv4_address'}}) {
1496            $detected_computer++;
1497            $computerdb->{$resol_arp{'ipv4_address'}} = {
1498               hostname_fq        => $resol_arp{'hostname_fq'},
1499               mac_address        => $resol_arp{'mac_address'},
1500               switch_hostname    => $where{'switch_hostname'},
1501               switch_description => $where{'switch_description'},
1502               switch_port_id     => $where{'switch_port_id'},
1503               switch_port_hr     => $where{'switch_port_hr'},
1504               timestamp          => $resol_arp{'timestamp'},
1505               network            => $current_net,
1506               };
1507            }
1508
1509         # mise a jour du nom de la machine si modification dans le dns
1510         $computerdb->{$resol_arp{'ipv4_address'}}{'hostname_fq'} = $resol_arp{'hostname_fq'};
1511
1512         # mise à jour de la date de détection si détection plus récente par arpwatch
1513         $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'}   = $resol_arp{'timestamp'} if exists $resol_arp{'timestamp'} and $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $resol_arp{'timestamp'};
1514
1515         # relance un arping sur la machine si celle-ci n'a pas été détectée depuis plus d'une semaine
1516         # push @computer_not_detected, $resol_arp{'ipv4_address'} if $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $timestamp_last_week;
1517         $computer_not_detected{$resol_arp{'ipv4_address'}} = $current_net if $computerdb->{$resol_arp{'ipv4_address'}}{'timestamp'} < $timestamp_last_week;
1518
1519         }
1520      }
1521
1522   # final end of line at the end of the loop
1523   printf "\n";
1524
1525   my $dirdb = $KLASK_DB_FILE;
1526      $dirdb =~ s{ / [^/]* $}{}xms;
1527   mkdir "$dirdb", 0755 unless -d "$dirdb";
1528   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1529
1530   for my $one_computer (keys %computer_not_detected) {
1531      my $current_net       = $computer_not_detected{$one_computer};
1532      my $current_interface = get_current_interface($current_net);
1533      system "arping -c 1 -w 1 -rR -i $current_interface $one_computer > /dev/null 2>&1" if get_current_scan_mode($current_net) eq 'active';
1534      }
1535   return;
1536   }
1537
1538#---------------------------------------------------------------
1539sub cmd_removedb {
1540   my @computer = @_;
1541
1542   test_maindb_environnement();
1543
1544   my $computerdb = computerdb_load();
1545
1546   LOOP_ON_COMPUTER:
1547   for my $one_computer (@computer) {
1548
1549      if ($one_computer =~ m/^ $RE_IPv4_ADDRESS $/xms
1550            and exists $computerdb->{$one_computer}) {
1551         delete $computerdb->{$one_computer};
1552         next;
1553         }
1554
1555      my %resol_arp = resolve_ip_arp_host($one_computer);
1556
1557      delete $computerdb->{$resol_arp{'ipv4_address'}} if exists $computerdb->{$resol_arp{'ipv4_address'}};
1558      }
1559
1560   my $dirdb = $KLASK_DB_FILE;
1561      $dirdb =~ s{ / [^/]* $}{}xms;
1562   mkdir "$dirdb", 0755 unless -d "$dirdb";
1563   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1564   return;
1565   }
1566
1567#---------------------------------------------------------------
1568sub cmd_addentrydb {
1569   my @ARGV = @_;
1570
1571   my ($ip, $mac, $net);
1572
1573   GetOptions(
1574      'ip|i=s'      => \$ip,
1575      'mac|m=s'     => \$mac,
1576      'network|n=s' => \$net,
1577      );
1578
1579   if (not $ip or not $mac or not $net) {
1580      print {*STDERR} "Error: mandatory parameter --ip\n"      if not $ip;
1581      print {*STDERR} "Error: mandatory parameter --mac\n"     if not $mac;
1582      print {*STDERR} "Error: mandatory parameter --network\n" if not $net;
1583      print {*STDERR} "\n";
1584      $CMD_DB{'help'}->();
1585      exit 1;
1586      }
1587
1588   test_maindb_environnement();
1589
1590   $mac = normalize_mac_address($mac);
1591
1592   if ($ip !~ m/^ $RE_IPv4_ADDRESS $/xms or $mac !~ m/^ $RE_MAC_ADDRESS $/xms or not exists $KLASK_CFG->{'network'}{$net}) {
1593      print {*STDERR} "Error: parameter not valid --ip $ip\n"       if $ip !~ m/^ $RE_IPv4_ADDRESS $/xms;
1594      print {*STDERR} "Error: parameter not valid --mac $mac\n"     if $mac !~ m/^ $RE_MAC_ADDRESS $/xms;
1595      print {*STDERR} "Error: parameter not valid --network $net\n" if not exists $KLASK_CFG->{'network'}{$net};
1596      print {*STDERR} "\n";
1597      $CMD_DB{'help'}->();
1598      exit 1;
1599      }
1600
1601   my %resol_arp = resolve_ip_arp_host($ip); # Get hostname_fq ipv4_address
1602
1603   if ($resol_arp{'hostname_fq'} eq 'unknow') {
1604      print {*STDERR} "Error: DNS name not defined for IP $ip (mandatory)\n";
1605      exit 1;
1606      }
1607
1608   if ($resol_arp{'ipv4_address'} ne $ip) {
1609      print {*STDERR} "Error: IP DNS resolution strange between $ip and $resol_arp{'ipv4_address'}\n";
1610      exit 1;
1611      }
1612
1613   my $computerdb = computerdb_load();
1614   my $timestamp  = time;
1615
1616   LOOP_ON_COMPUTER:
1617   for my $current_ip (keys %{$computerdb}) {
1618      if ($current_ip eq $ip) {
1619         print {*STDERR} "Error: IP $ip already exists in the computer database\n";
1620         exit 1;
1621         }
1622
1623      if ($computerdb->{$current_ip}{'mac_address'} eq $mac) {
1624         $timestamp = $computerdb->{$current_ip}{'timestamp'} if $computerdb->{$current_ip}{'timestamp'} < $timestamp;
1625         }
1626      }
1627
1628   my $days_before_alert = $DEFAULT{'days-before-alert'} || 15;
1629   $timestamp -= $days_before_alert * 24 * 3600 + 1; # Just decrement one second to be oldier
1630
1631   $computerdb->{$ip} = {
1632      hostname_fq        => $resol_arp{'hostname_fq'},
1633      mac_address        => $mac,
1634      switch_hostname    => 'Unknow',
1635      switch_description => 'Unknow',
1636      switch_port_id     => 0,
1637      switch_port_hr     => 0,
1638      timestamp          => $timestamp,
1639      network            => $net,
1640      };
1641
1642   my $dirdb = $KLASK_DB_FILE;
1643      $dirdb =~ s{ / [^/]* $}{}xms;
1644   mkdir "$dirdb", 0755 unless -d "$dirdb";
1645   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1646   return;
1647   }
1648
1649#---------------------------------------------------------------
1650sub cmd_cleandb {
1651   my @ARGV = @_;
1652
1653   my $days_to_clean = 15;
1654   my $repairdns;
1655   my $verbose;
1656   my $database_has_changed;
1657
1658   GetOptions(
1659      'day|d=i'      => \$days_to_clean,
1660      'verbose|v'    => \$verbose,
1661      'repair-dns|r' => \$repairdns,
1662      );
1663
1664   my @vlan_name = get_list_network();
1665
1666   my $computerdb = computerdb_load();
1667   my $timestamp  = time;
1668
1669   my $timestamp_barrier = 3600 * 24 * $days_to_clean;
1670   my $timestamp_3month  = 3600 * 24 * 90;
1671
1672   my %mactimedb = ();
1673   ALL_VLAN:
1674   for my $vlan (shuffle @vlan_name) {
1675
1676      my @ip_list = shuffle get_list_ip($vlan);
1677
1678      LOOP_ON_IP_ADDRESS:
1679      for my $ip (@ip_list) {
1680
1681         next LOOP_ON_IP_ADDRESS
1682            if not exists $computerdb->{$ip};
1683
1684         #&& $computerdb->{$ip}{'timestamp'} > $timestamp_barrier;
1685         my $ip_timestamp   = $computerdb->{$ip}{'timestamp'};
1686         my $ip_mac         = $computerdb->{$ip}{'mac_address'};
1687         my $ip_hostname_fq = $computerdb->{$ip}{'hostname_fq'};
1688
1689         $mactimedb{$ip_mac} ||= {
1690            ip          => $ip,
1691            timestamp   => $ip_timestamp,
1692            vlan        => $vlan,
1693            hostname_fq => $ip_hostname_fq,
1694            };
1695
1696         if (
1697            ( $mactimedb{$ip_mac}->{'timestamp'} - $ip_timestamp > $timestamp_barrier
1698               or (
1699                  $mactimedb{$ip_mac}->{'timestamp'} > $ip_timestamp
1700                  and $timestamp - $mactimedb{$ip_mac}->{'timestamp'} > $timestamp_3month
1701                  )
1702            )
1703            and (
1704               not $mactimedb{$ip_mac}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/
1705               or $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
1706               )) {
1707            print "remove ip $ip\n" if $verbose;
1708            delete $computerdb->{$ip};
1709            $database_has_changed++;
1710            }
1711
1712         elsif (
1713            ( $ip_timestamp - $mactimedb{$ip_mac}->{'timestamp'} > $timestamp_barrier
1714               or (
1715                  $ip_timestamp > $mactimedb{$ip_mac}->{'timestamp'}
1716                  and $timestamp - $ip_timestamp > $timestamp_3month
1717                  )
1718            )
1719            and (
1720               not $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
1721               or $mactimedb{$ip_mac}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/
1722               )) {
1723            print "remove ip ".$mactimedb{$ip_mac}->{'ip'}."\n" if $verbose;
1724            delete $computerdb->{$mactimedb{$ip_mac}->{'ip'}};
1725            $database_has_changed++;
1726            }
1727
1728         if ($ip_timestamp > $mactimedb{$ip_mac}->{'timestamp'}) {
1729            $mactimedb{$ip_mac} = {
1730               ip          => $ip,
1731               timestamp   => $ip_timestamp,
1732               vlan        => $vlan,
1733               hostname_fq => $ip_hostname_fq,
1734               };
1735            }
1736         }
1737      }
1738
1739   if ($repairdns) { # Search and update unkown computer in reverse DNS
1740      LOOP_ON_IP_ADDRESS:
1741      for my $ip (keys %{$computerdb}) {
1742         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} ne 'unknow';
1743
1744         my $packed_ip = scalar gethostbyname($ip);
1745         next LOOP_ON_IP_ADDRESS if not defined $packed_ip;
1746
1747         my $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET);
1748         next LOOP_ON_IP_ADDRESS if not defined $hostname_fq;
1749
1750         $computerdb->{$ip}{'hostname_fq'} = $hostname_fq;
1751         $database_has_changed++;
1752         }
1753      }
1754
1755   if ($database_has_changed) {
1756      my $dirdb = $KLASK_DB_FILE;
1757         $dirdb =~ s{ / [^/]* $}{}xms;
1758      mkdir "$dirdb", 0755 unless -d "$dirdb";
1759      YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1760      }
1761   return;
1762   }
1763
1764#---------------------------------------------------------------
1765sub cmd_exportdb {
1766   @ARGV = @_;
1767
1768   my $format = 'txt';
1769
1770   GetOptions(
1771      'format|f=s'  => \$format,
1772      );
1773
1774   my %possible_format = (
1775      txt  => \&cmd_exportdb_txt,
1776      html => \&cmd_exportdb_html,
1777      );
1778
1779   $format = 'txt' if not defined $possible_format{$format};
1780
1781   $possible_format{$format}->(@ARGV);
1782   return;
1783   }
1784
1785#---------------------------------------------------------------
1786sub cmd_exportdb_txt {
1787   test_maindb_environnement();
1788
1789   my $computerdb = computerdb_load();
1790
1791   my $tb_computer = Text::Table->new(
1792      {align => 'left',   align_title => 'left',   title => 'Switch'},
1793      {align => 'right',  align_title => 'right',  title => 'Port'},
1794      {align => 'center', align_title => 'center', title => 'Link'},
1795      {align => 'left',   align_title => 'left',   title => 'Hostname-FQ'},
1796      {align => 'left',   align_title => 'left',   title => 'IPv4-Address'},
1797      {align => 'left',   align_title => 'left',   title => 'MAC-Address'},
1798      {align => 'left',   align_title => 'left',   title => 'Date'},
1799      {align => 'left',   align_title => 'left',   title => 'VLAN'},
1800      );
1801
1802   LOOP_ON_IP_ADDRESS:
1803   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1804
1805      # to be improve in the future
1806      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1807
1808      my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $computerdb->{$ip}{'timestamp'};
1809      $year += 1900;
1810      $mon++;
1811      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1812
1813      my $vlan = '';
1814      $vlan = $computerdb->{$ip}{'network'} . '(' . get_current_vlan_id($computerdb->{$ip}{'network'}).')' if $computerdb->{$ip}{'network'};
1815
1816      my $arrow = '<---';
1817      $arrow = '<===' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
1818
1819      $tb_computer->add(
1820         $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'},
1821         $computerdb->{$ip}{'switch_port_hr'},
1822         $arrow,
1823         $computerdb->{$ip}{'hostname_fq'},
1824         $ip,
1825         $computerdb->{$ip}{'mac_address'},
1826         $date,
1827         $vlan,
1828         );
1829      }
1830
1831   print $tb_computer->title();
1832   print $tb_computer->rule('-');
1833   print $tb_computer->body();
1834
1835   return;
1836   }
1837
1838#---------------------------------------------------------------
1839sub cmd_exportdb_html {
1840   test_maindb_environnement();
1841
1842   my $computerdb = computerdb_load();
1843
1844   #<link rel="stylesheet" type="text/css" href="style-klask.css" />
1845   #<script src="sorttable-klask.js"></script>
1846
1847   print <<'END_HTML';
1848<table class="sortable">
1849 <caption>Klask Host Database</caption>
1850 <thead>
1851  <tr>
1852   <th scope="col" class="klask-header-left">Switch</th>
1853   <th scope="col" class="sorttable_nosort">Port</th>
1854   <th scope="col" class="sorttable_nosort">Link</th>
1855   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
1856   <th scope="col" class="hklask-ipv4">IPv4-Address</th>
1857   <th scope="col" class="sorttable_alpha">MAC-Address</th>
1858   <th scope="col" class="sorttable_alpha">VLAN</th>
1859   <th scope="col" class="klask-header-right">Date</th>
1860  </tr>
1861 </thead>
1862 <tbody>
1863END_HTML
1864
1865   my %mac_count = ();
1866   LOOP_ON_IP_ADDRESS:
1867   for my $ip (keys %{$computerdb}) {
1868
1869      # to be improve in the future
1870      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1871
1872      $mac_count{$computerdb->{$ip}{'mac_address'}}++;
1873      }
1874
1875   my $typerow = 'even';
1876
1877   LOOP_ON_IP_ADDRESS:
1878   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1879
1880      # to be improve in the future
1881      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
1882
1883      my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $computerdb->{$ip}{'timestamp'};
1884      $year += 1900;
1885      $mon++;
1886      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1887
1888      #      $odd_or_even++;
1889      #      my $typerow = $odd_or_even % 2 ? 'odd' : 'even';
1890      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1891
1892      #my $arrow ='&#8592;';
1893      #   $arrow ='&#8656;' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
1894      my $arrow = '&#10229;';
1895      $arrow = '&#10232;' if $computerdb->{$ip}{'switch_port_hr'} =~ m/^(Trk|Br|Po)/;
1896
1897      my $switch_hostname = $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'} || 'unkown';
1898      chomp $switch_hostname;
1899      my $switch_hostname_sort = format_switchport4sort($switch_hostname, $computerdb->{$ip}{'switch_port_hr'});
1900
1901      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
1902
1903      my $mac_sort = sprintf '%04i-%s', 9999 - $mac_count{$computerdb->{$ip}{'mac_address'}}, $computerdb->{$ip}{'mac_address'};
1904
1905      $computerdb->{$ip}{'hostname_fq'} = 'unknow' if $computerdb->{$ip}{'hostname_fq'} =~ m/^ \d+ \. \d+ \. \d+ \. \d+ $/xms;
1906      my ($host_short) = split m/ \. /xms, $computerdb->{$ip}{'hostname_fq'};
1907
1908      my $vlan = '';
1909      $vlan = $computerdb->{$ip}{'network'}.' ('.get_current_vlan_id($computerdb->{$ip}{'network'}).')' if $computerdb->{$ip}{'network'};
1910
1911      my $parent_port_hr = format_aggregator4html($computerdb->{$ip}{'switch_port_hr'});
1912
1913      fqdn_html_breakable($switch_hostname);
1914      fqdn_html_breakable(my $hostname_fq_html = $computerdb->{$ip}{'hostname_fq'});
1915
1916      print <<"END_HTML";
1917  <tr class="$typerow">
1918   <td sorttable_customkey="$switch_hostname_sort">$switch_hostname</td>
1919   <td class="bklask-port">$parent_port_hr</td>
1920   <td class="bklask-arrow">$arrow</td>
1921   <td sorttable_customkey="$host_short">$hostname_fq_html</td>
1922   <td sorttable_customkey="$ip_sort">$ip</td>
1923   <td sorttable_customkey="$mac_sort">$computerdb->{$ip}{'mac_address'}</td>
1924   <td>$vlan</td>
1925   <td>$date</td>
1926  </tr>
1927END_HTML
1928      }
1929
1930   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1931
1932   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
1933   my %db_switch_parent            = %{$switch_connection->{'parent'}};
1934   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
1935   my %db_switch                   = %{$switch_connection->{'switch_db'}};
1936
1937   # Output switch connection
1938   LOOP_ON_OUTPUT_SWITCH:
1939   for my $sw (sort keys %db_switch_output_port) {
1940
1941      my $switch_hostname_sort = format_switchport4sort($sw, $db_switch_output_port{$sw});
1942
1943      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1944
1945      #my $arrow ='&#8702;';
1946      #   $arrow ='&#8680;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1947      my $arrow = '&#10236;';
1948      $arrow = '&#10238;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
1949
1950      if (exists $db_switch_parent{$sw}) {
1951         # Link to uplink switch
1952         next LOOP_ON_OUTPUT_SWITCH;
1953
1954         # Do not print anymore
1955         my $mac_address  = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'mac_address'};
1956         my $ipv4_address = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'ipv4_address'};
1957         my $timestamp    = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'timestamp'};
1958
1959         my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $timestamp;
1960         $year += 1900;
1961         $mon++;
1962         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1963
1964         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
1965
1966         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1967
1968         my $host_short = format_switchport4sort(split(m/ \. /xms, $db_switch_parent{$sw}->{'switch'}, 1), $db_switch_parent{$sw}->{'port_hr'});
1969
1970         my $vlan = $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'};
1971         $vlan .= ' (' . get_current_vlan_id($db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'}) . ')' if $db_switch{$db_switch_parent{$sw}->{'switch'}}->{'network'};
1972
1973         my $parent_port_hr = format_aggregator4html($db_switch_output_port{$sw});
1974         my $child_port_hr  = format_aggregator4html($db_switch_parent{$sw}->{'port_hr'});
1975
1976         fqdn_html_breakable($sw);
1977         fqdn_html_breakable(my $sw_child_html = $db_switch_parent{$sw}->{'switch'});
1978
1979         print <<"END_HTML";
1980  <tr class="$typerow">
1981   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1982   <td class="bklask-port">$parent_port_hr</td>
1983   <td class="bklask-arrow">$arrow $child_port_hr</td>
1984   <td sorttable_customkey="$host_short">$sw_child_html</td>
1985   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1986   <td sorttable_customkey="$mac_sort">$mac_address</td>
1987   <td>$vlan</td>
1988   <td>$date</td>
1989  </tr>
1990END_HTML
1991         }
1992      else {
1993         # Router
1994         my $parent_port_hr = format_aggregator4html($db_switch_output_port{$sw});
1995
1996         my $host_short = format_switchport4sort($sw, $db_switch_output_port{$sw});
1997
1998         my $mac_address  = $db_switch{$sw}->{'mac_address'};
1999         my $ipv4_address = $db_switch{$sw}->{'ipv4_address'};
2000         my $timestamp    = $db_switch{$sw}->{'timestamp'};
2001
2002         my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $timestamp;
2003         $year += 1900;
2004         $mon++;
2005         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2006
2007         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
2008
2009         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
2010
2011         my $vlan = $db_switch{$sw}->{'network'};
2012         $vlan .= ' (' . get_current_vlan_id($db_switch{$sw}->{'network'}) . ')' if $db_switch{$sw}->{'network'};
2013
2014         my $arrow = '&#10235;';
2015         $arrow = '&#10237;' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
2016
2017         fqdn_html_breakable($sw);
2018
2019         print <<"END_HTML";
2020  <tr class="$typerow">
2021   <td sorttable_customkey="router">router</td>
2022   <td class="bklask-port"></td>
2023   <td class="bklask-arrow">$arrow $parent_port_hr</td>
2024   <td sorttable_customkey="$host_short">$sw</td>
2025   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
2026   <td sorttable_customkey="$mac_sort">$mac_address</td>
2027   <td>$vlan</td>
2028   <td>$date</td>
2029  </tr>
2030END_HTML
2031         next LOOP_ON_OUTPUT_SWITCH;
2032         }
2033      }
2034
2035   # Child switch connection : parent <- child
2036   for my $swport (sort keys %db_switch_connected_on_port) {
2037      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
2038      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2039
2040         my $switch_hostname_sort = format_switchport4sort($sw_connect, $port_connect);
2041
2042         my $mac_address  = $db_switch{$sw}->{'mac_address'};
2043         my $ipv4_address = $db_switch{$sw}->{'ipv4_address'};
2044         my $timestamp    = $db_switch{$sw}->{'timestamp'};
2045
2046         my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $timestamp;
2047         $year += 1900;
2048         $mon++;
2049         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2050
2051         my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ [\.\*] /xms, $ipv4_address; # \* for fake-ip
2052
2053         my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
2054
2055         $typerow = $typerow eq 'even' ? 'odd' : 'even';
2056
2057         #my $arrow ='&#8701;';
2058         #   $arrow ='&#8678;' if $port_connect =~ m/^(Trk|Br|Po)/;
2059         my $arrow = '&#10235;';
2060         $arrow = '&#10237;' if $port_connect =~ m/^(Trk|Br|Po)/;
2061
2062         my $vlan = $db_switch{$sw}->{'network'};
2063         $vlan .= ' (' . get_current_vlan_id($db_switch{$sw}->{'network'}) . ')' if $db_switch{$sw}->{'network'};
2064
2065         if (exists $db_switch_output_port{$sw}) {
2066
2067            my $host_short = format_switchport4sort(split(m/\./xms, $sw, 1), $db_switch_output_port{$sw});
2068
2069            my $parent_port_hr = format_aggregator4html($port_connect);
2070            my $child_port_hr  = format_aggregator4html($db_switch_output_port{$sw});
2071
2072            fqdn_html_breakable($sw);
2073            fqdn_html_breakable($sw_connect);
2074
2075            print <<"END_HTML";
2076  <tr class="$typerow">
2077   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
2078   <td class="bklask-port">$parent_port_hr</td>
2079   <td class="bklask-arrow">$arrow $child_port_hr</td>
2080   <td sorttable_customkey="$host_short">$sw</td>
2081   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
2082   <td sorttable_customkey="$mac_sort">$mac_address</td>
2083   <td>$vlan</td>
2084   <td>$date</td>
2085  </tr>
2086END_HTML
2087            }
2088         else {
2089            my $parent_port_hr = format_aggregator4html($port_connect);
2090
2091            fqdn_html_breakable($sw);
2092            fqdn_html_breakable($sw_connect);
2093
2094            print <<"END_HTML";
2095  <tr class="$typerow">
2096   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
2097   <td class="bklask-port">$parent_port_hr</td>
2098   <td class="bklask-arrow">$arrow</td>
2099   <td sorttable_customkey="$sw">$sw</td>
2100   <td sorttable_customkey="">$ipv4_address</td>
2101   <td sorttable_customkey="">$mac_address</td>
2102   <td>$vlan</td>
2103   <td>$date</td>
2104  </tr>
2105END_HTML
2106            }
2107         }
2108      }
2109
2110   print <<'END_HTML';
2111 </tbody>
2112 <tfoot>
2113  <tr>
2114   <th scope="col" class="klask-footer-left">Switch</th>
2115   <th scope="col" class="fklask-port">Port</th>
2116   <th scope="col" class="fklask-link">Link</th>
2117   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
2118   <th scope="col" class="fklask-ipv4">IPv4-Address</th>
2119   <th scope="col" class="fklask-mac">MAC-Address</th>
2120   <th scope="col" class="fklask-vlan">VLAN</th>
2121   <th scope="col" class="klask-footer-right">Date</th>
2122  </tr>
2123 </tfoot>
2124</table>
2125END_HTML
2126   return;
2127   }
2128
2129#---------------------------------------------------------------
2130sub cmd_bad_vlan_id {
2131   @ARGV = @_;
2132
2133   my $days_before_alert = $DEFAULT{'days-before-alert'} || 15;
2134   my $format = 'txt';
2135   my $verbose;
2136
2137   GetOptions(
2138      'day|d=i'    => \$days_before_alert,
2139      'format|f=s' => \$format,
2140      );
2141
2142   my %possible_format = (
2143      txt  => \&cmd_bad_vlan_id_txt,
2144      html => \&cmd_bad_vlan_id_html,
2145      none => sub { },
2146      );
2147   $format = 'txt' if not defined $possible_format{$format};
2148
2149   test_maindb_environnement();
2150
2151   my $computerdb = computerdb_load();
2152
2153   # create a database with the most recent computer by switch port
2154   my %switchportdb = ();
2155   LOOP_ON_IP_ADDRESS:
2156   for my $ip (keys %{$computerdb}) {
2157      # to be improve in the future
2158      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
2159      next LOOP_ON_IP_ADDRESS if ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}) eq 'unknow';
2160      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'switch_port_id'} eq '0';
2161
2162      my $ip_timestamp   = $computerdb->{$ip}{'timestamp'};
2163      my $ip_mac         = $computerdb->{$ip}{'mac_address'};
2164      my $ip_hostname_fq = $computerdb->{$ip}{'hostname_fq'};
2165
2166      my $swpt = sprintf "%-28s  %2s",
2167         $computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'},
2168         $computerdb->{$ip}{'switch_port_hr'};
2169      $switchportdb{$swpt} ||= {
2170         ip          => $ip,
2171         timestamp   => $ip_timestamp,
2172         vlan        => $computerdb->{$ip}{'network'},
2173         hostname_fq => $ip_hostname_fq,
2174         mac_address => $ip_mac,
2175         };
2176
2177      # if float computer, set date 15 day before warning...
2178      my $ip_timestamp_mod = $ip_timestamp;
2179      my $ip_timestamp_ref = $switchportdb{$swpt}->{'timestamp'};
2180      $ip_timestamp_mod -= $days_before_alert * 24 * 3600 if $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
2181      $ip_timestamp_ref -= $days_before_alert * 24 * 3600 if $switchportdb{$swpt}->{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/;
2182
2183      if ($ip_timestamp_mod > $ip_timestamp_ref) {
2184         $switchportdb{$swpt} = {
2185            ip          => $ip,
2186            timestamp   => $ip_timestamp,
2187            vlan        => $computerdb->{$ip}{'network'},
2188            hostname_fq => $ip_hostname_fq,
2189            mac_address => $ip_mac,
2190            };
2191         }
2192      }
2193
2194   my @result = ();
2195
2196   LOOP_ON_RECENT_COMPUTER:
2197   for my $swpt (keys %switchportdb) {
2198      next LOOP_ON_RECENT_COMPUTER if $swpt =~ m/^\s*0$/;
2199      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{'hostname_fq'} !~ m/$RE_FLOAT_HOSTNAME/;
2200
2201      my $src_ip        = $switchportdb{$swpt}->{'ip'};
2202      my $src_timestamp = 0;
2203      LOOP_ON_IP_ADDRESS:
2204      for my $ip (keys %{$computerdb}) {
2205         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'mac_address'} ne $switchportdb{$swpt}->{'mac_address'};
2206         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} =~ m/$RE_FLOAT_HOSTNAME/;
2207         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'timestamp'} < $src_timestamp;
2208
2209         $src_ip        = $ip;
2210         $src_timestamp = $computerdb->{$ip}{'timestamp'};
2211         }
2212
2213      # keep only if float computer is the most recent
2214      next LOOP_ON_RECENT_COMPUTER if $src_timestamp == 0;
2215      next LOOP_ON_RECENT_COMPUTER if $switchportdb{$swpt}->{'timestamp'} < $src_timestamp;
2216
2217      my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $switchportdb{$swpt}->{'timestamp'};
2218      $year += 1900;
2219      $mon++;
2220      my $date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
2221
2222      ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $computerdb->{$src_ip}{'timestamp'};
2223      $year += 1900;
2224      $mon++;
2225      my $src_date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
2226
2227      my $vlan_id = get_current_vlan_id($computerdb->{$src_ip}{'network'});
2228      my ($switch_hostname, $port_hr) = split /\s+/, $swpt, 2;
2229
2230      push @result, {
2231         switch      => $switch_hostname,
2232         port_hr     => $port_hr,
2233         vlan_bad    => $switchportdb{$swpt}->{'vlan'},
2234         vlan_good   => $computerdb->{$src_ip}{'network'},
2235         vlan_id     => $vlan_id,
2236         date_last   => $date,
2237         date_good   => $src_date,
2238         mac_address => $computerdb->{$src_ip}{'mac_address'},
2239         hostname_fq => $computerdb->{$src_ip}{'hostname_fq'},
2240         };
2241      }
2242
2243   $possible_format{$format}->(@result);
2244   }
2245
2246#---------------------------------------------------------------
2247sub cmd_bad_vlan_id_txt {
2248   my @result = @_;
2249
2250   my $tb_bad = Text::Table->new(
2251      {align  => 'left',   align_title => 'left',   title => 'Switch'},
2252      {is_sep => 1,              title => ' ',       body => ' '},
2253      {align  => 'right',  align_title => 'right',  title => 'Port'},
2254      {is_sep => 1,              title => ' ',       body => ' !'},
2255      {align  => 'left',   align_title => 'left',   title => 'VLAN-Bad'},
2256      {is_sep => 1,              title => ' ',       body => ' +-> '},
2257      {align  => 'left',   align_title => 'left',   title => 'VLAN-Good'},
2258      {is_sep => 1,              title => ' ',       body => ' '},
2259      {align  => 'left',   align_title => 'left',   title => 'VLAN-ID'},
2260      {is_sep => 1,              title => ' ',       body => ' '},
2261      {align  => 'left',   align_title => 'left',   title => 'Date-Last'},
2262      {is_sep => 1,              title => ' ',       body => '  '},
2263      {align  => 'left',   align_title => 'left',   title => 'Date-Good'},
2264      {is_sep => 1,              title => ' ',       body => '  '},
2265      {align  => 'left',   align_title => 'left',   title => 'MAC-Address'},
2266      {is_sep => 1,              title => ' ',       body => ' '},
2267      {align  => 'left',   align_title => 'left',   title => 'Hostname-FQ'},
2268      );
2269
2270   for my $item (@result) {
2271      $tb_bad->add(
2272         $item->{'switch'},
2273         $item->{'port_hr'},
2274         $item->{'vlan_bad'},
2275         $item->{'vlan_good'},
2276         '(' . $item->{'vlan_id'} . ')',
2277         $item->{'date_last'},
2278         $item->{'date_good'},
2279         $item->{'mac_address'},
2280         $item->{'hostname_fq'},
2281         );
2282      }
2283
2284   print $tb_bad->title();
2285   print $tb_bad->rule('-');
2286   print $tb_bad->body();
2287   }
2288
2289#---------------------------------------------------------------
2290sub cmd_bad_vlan_id_html {
2291   my @result = @_;
2292
2293   print <<'END_HTML';
2294<table class="sortable">
2295 <caption>Klask VLAN ID Mismatch Database</caption>
2296 <thead>
2297  <tr>
2298   <th scope="col" class="klask-header-left">Switch</th>
2299   <th scope="col" class="sorttable_nosort">Port</th>
2300   <th scope="col" class="sorttable_alpha">VLAN-Bad</th>
2301   <th scope="col" class="sorttable_alpha">VLAN-Good</th>
2302   <th scope="col" class="sorttable_alpha">Date-Last</th>
2303   <th scope="col" class="sorttable_alpha">Date-Good</th>
2304   <th scope="col" class="sorttable_alpha">MAC-Address</th>
2305   <th scope="col" class="klask-header-right">Hostname-FQ</th>
2306  </tr>
2307 </thead>
2308 <tbody>
2309END_HTML
2310
2311   my $typerow = 'even';
2312
2313   for my $item (@result) {
2314
2315      $typerow = $typerow eq 'even' ? 'odd' : 'even';
2316
2317      my $switch_hostname_sort = format_switchport4sort($item->{'switch'}, $item->{'port_hr'});
2318      my ($host_short) = split m/ \. /xms, $item->{'hostname_fq'};
2319
2320      my $vlan_nameid = $item->{'vlan_good'} . ' (' . $item->{'vlan_id'} . ')';
2321
2322      fqdn_html_breakable(my $hostname_fq_html = $item->{'hostname_fq'});
2323
2324      print <<"END_HTML";
2325  <tr class="$typerow">
2326   <td sorttable_customkey="$switch_hostname_sort">$item->{'switch'}</td>
2327   <td class="bklask-port">$item->{'port_hr'}</td>
2328   <td>!$item->{'vlan_bad'}</td>
2329   <td>$vlan_nameid</td>
2330   <td>$item->{'date_last'}</td>
2331   <td>$item->{'date_good'}</td>
2332   <td>$item->{'mac_address'}</td>
2333   <td sorttable_customkey="$host_short">$hostname_fq_html</td>
2334  </tr>
2335END_HTML
2336      }
2337   print <<'END_HTML';
2338 </tbody>
2339 <tfoot>
2340  <tr>
2341   <th scope="col" class="klask-footer-left">Switch</th>
2342   <th scope="col" class="fklask-nosort">Port</th>
2343   <th scope="col" class="fklask-alpha">VLAN-Bad</th>
2344   <th scope="col" class="fklask-alpha">VLAN-Good</th>
2345   <th scope="col" class="fklask-alpha">Date-Last</th>
2346   <th scope="col" class="fklask-alpha">Date-Good</th>
2347   <th scope="col" class="fklask-alpha">MAC-Address</th>
2348   <th scope="col" class="klask-footer-right">Hostname-FQ</th>
2349  </tr>
2350 </tfoot>
2351</table>
2352END_HTML
2353   }
2354
2355#---------------------------------------------------------------
2356sub cmd_poe_enable {
2357   @ARGV = @_;
2358
2359   my $verbose;
2360   GetOptions(
2361      'verbose|v' => \$verbose,
2362      );
2363
2364   my $switch_name = shift @ARGV || q{};
2365   my $switch_port = shift @ARGV || q{};
2366
2367   if ($switch_name eq q{} or $switch_port eq q{}) {
2368      die "Usage: klask poe-enable SWITCH_NAME PORT\n";
2369      }
2370
2371   for my $sw_name (split /,/, $switch_name) {
2372      if (not defined $SWITCH_DB{$sw_name}) {
2373         die "Switch $sw_name must be defined in klask configuration file\n";
2374         }
2375
2376      my $oid_search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS switch and low port number
2377
2378      my $sw = $SWITCH_DB{$sw_name};
2379      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2380      print "$error \n" if $error;
2381
2382      my $result = $session->set_request(
2383         -varbindlist => [$oid_search, INTEGER, 8], # Only NEXANS
2384         );
2385      print $session->error() . "\n" if $session->error_status();
2386
2387      $session->close;
2388      }
2389   cmd_poe_status($switch_name, $switch_port);
2390   return;
2391   }
2392
2393#---------------------------------------------------------------
2394sub cmd_poe_disable {
2395   @ARGV = @_;
2396
2397   my $verbose;
2398   GetOptions(
2399      'verbose|v' => \$verbose,
2400      );
2401
2402   my $switch_name = shift @ARGV || q{};
2403   my $switch_port = shift @ARGV || q{};
2404
2405   if ($switch_name eq q{} or $switch_port eq q{}) {
2406      die "Usage: klask poe-disable SWITCH_NAME PORT\n";
2407      }
2408
2409   for my $sw_name (split /,/, $switch_name) {
2410      if (not defined $SWITCH_DB{$sw_name}) {
2411         die "Switch $sw_name must be defined in klask configuration file\n";
2412         }
2413
2414      my $oid_search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS switch and low port number
2415
2416      my $sw = $SWITCH_DB{$sw_name};
2417      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2418      print "$error \n" if $error;
2419
2420      my $result = $session->set_request(
2421         -varbindlist => [$oid_search, INTEGER, 2], # Only NEXANS
2422         );
2423      print $session->error() . "\n" if $session->error_status();
2424
2425      $session->close;
2426      }
2427   cmd_poe_status($switch_name, $switch_port);
2428   return;
2429   }
2430
2431#---------------------------------------------------------------
2432sub cmd_poe_status {
2433   @ARGV = @_;
2434
2435   my $verbose;
2436   GetOptions(
2437      'verbose|v' => \$verbose,
2438      );
2439
2440   my $switch_name = shift @ARGV || q{};
2441   my $switch_port = shift @ARGV || q{};
2442
2443   if ($switch_name eq q{} or $switch_port eq q{}) {
2444      die "Usage: klask poe-status SWITCH_NAME PORT\n";
2445      }
2446
2447   for my $sw_name (split /,/, $switch_name) {
2448      if (not defined $SWITCH_DB{$sw_name}) {
2449         die "Switch $sw_name must be defined in klask configuration file\n";
2450         }
2451
2452      my $oid_search = $OID_NUMBER{'NApoeState'} . ".$switch_port"; # Only NEXANS switch and low port number
2453
2454      my $sw = $SWITCH_DB{$sw_name};
2455      my ($session, $error) = Net::SNMP->session(%{$sw->{'snmp_param_session'}});
2456      print "$error \n" if $error;
2457
2458      my $result = $session->get_request(
2459         -varbindlist => [$oid_search],
2460         );
2461
2462      if (defined $result and $result->{$oid_search} ne 'noSuchInstance') {
2463         my $poe_status = $result->{$oid_search} || 'empty';
2464         $poe_status =~ s/8/enable/;
2465         $poe_status =~ s/2/disable/;
2466         printf "%s  %s poe %s\n", $sw_name, $switch_port, $poe_status;
2467         }
2468      else {
2469         print "Klask do not find PoE status on switch $sw_name on port $switch_port\n";
2470         }
2471
2472      $session->close;
2473      }
2474   return;
2475   }
2476
2477#---------------------------------------------------------------
2478sub cmd_host_setlocation {
2479   @ARGV = @_;
2480
2481   my ($verbose, $force);
2482   GetOptions(
2483      'verbose|v' => \$verbose,
2484      'force|f'   => \$force,
2485      );
2486
2487   my $switch_name     = shift @ARGV || q{};
2488   my $switch_location = shift @ARGV || q{};
2489
2490   if ($switch_name eq q{} or $switch_location eq q{}) {
2491      die "Usage: klask host-setlocation SWITCH_NAME LOCATION\n";
2492      }
2493
2494   for my $sw_name (split /,/, $switch_name) {
2495      if (not defined $SWITCH_DB{$sw_name}) {
2496         die "Switch $sw_name must be defined in klask configuration file\n";
2497         }
2498
2499      my $oid_search = $OID_NUMBER{'sysLocation'};
2500
2501      my $sw = $SWITCH_DB{$sw_name};
2502      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2503      print "$error \n" if $error;
2504
2505      my $result = $session->set_request(
2506         -varbindlist => [$oid_search, OCTET_STRING, $switch_location],
2507         );
2508      print $session->error()."\n" if $session->error_status();
2509
2510      $session->close;
2511      }
2512   return;
2513   }
2514
2515#---------------------------------------------------------------
2516# not finish - do not use
2517sub cmd_port_setvlan {
2518   my $switch_name = shift || q{};
2519   my $mac_address = shift || q{};
2520
2521   if ($switch_name eq q{} or $mac_address eq q{}) {
2522      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
2523      }
2524
2525   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST) if $switch_name eq q{*};
2526
2527   for my $sw_name (split /,/, $switch_name) {
2528      if (not defined $SWITCH_DB{$sw_name}) {
2529         die "Switch $sw_name must be defined in klask configuration file\n";
2530         }
2531
2532      my $oid_search_port1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
2533      my $oid_search_port2 = $OID_NUMBER{'searchPort2'} . '.' . 0 . mac_address_hex2dec($mac_address);
2534      print "Klask search OID $oid_search_port1 on switch $sw_name\n";
2535      print "Klask search OID $oid_search_port2 on switch $sw_name\n";
2536
2537      my $sw = $SWITCH_DB{$sw_name};
2538      my ($session, $error) = Net::SNMP->session(%{$sw->{'snmp_param_session'}});
2539      print "$error \n" if $error;
2540
2541      my $result = $session->get_request(
2542         -varbindlist => [$oid_search_port1]
2543         );
2544      if (not defined $result) {
2545         $result = $session->get_request(
2546            -varbindlist => [$oid_search_port2]
2547            );
2548         $result->{$oid_search_port1} = $result->{$oid_search_port2} if defined $result;
2549         }
2550
2551      if (defined $result and $result->{$oid_search_port1} ne 'noSuchInstance') {
2552         my $swport = $result->{$oid_search_port1};
2553         print "Klask find MAC $mac_address on switch $sw_name port $swport\n";
2554         }
2555      else {
2556         print "Klask do not find MAC $mac_address on switch $sw_name\n";
2557         }
2558
2559      $session->close;
2560      }
2561   return;
2562   }
2563
2564#---------------------------------------------------------------
2565sub cmd_port_getvlan {
2566   @ARGV = @_;
2567
2568   my $verbose;
2569   GetOptions(
2570      'verbose|v' => \$verbose,
2571      );
2572
2573   my $switch_name = shift @ARGV || q{};
2574   my $switch_port = shift @ARGV || q{};
2575
2576   if ($switch_name eq q{} or $switch_port eq q{}) {
2577      die "Usage: klask port-getvlan SWITCH_NAME PORT\n";
2578      }
2579
2580   for my $sw_name (split /,/, $switch_name) {
2581      if (not defined $SWITCH_DB{$sw_name}) {
2582         die "Switch $sw_name must be defined in klask configuration file\n";
2583         }
2584
2585      my $oid_search = $OID_NUMBER{'vlanPortDefault'} . ".$switch_port";
2586
2587      my $sw = $SWITCH_DB{$sw_name};
2588      my ($session, $error) = Net::SNMP->session(%{$sw->{'snmp_param_session'}});
2589      print "$error \n" if $error;
2590
2591      my $result = $session->get_request(
2592         -varbindlist => [$oid_search],
2593         );
2594
2595      if (defined $result and $result->{$oid_search} ne 'noSuchInstance') {
2596         my $vlan_id = $result->{$oid_search} || 'empty';
2597         print "Klask VLAN Id $vlan_id on switch $sw_name on port $switch_port\n";
2598         }
2599      else {
2600         print "Klask do not find VLAN Id on switch $sw_name on port $switch_port\n";
2601         }
2602
2603      $session->close;
2604      }
2605   return;
2606   }
2607
2608#---------------------------------------------------------------
2609sub cmd_vlan_setname {
2610   }
2611
2612#---------------------------------------------------------------
2613# snmpset -v 1 -c public sw1-batG0-legi.hmg.priv "$OID_NUMBER{'HPicfReset'}.0" i 2;
2614sub cmd_rebootsw {
2615   @ARGV = @_;
2616
2617   my $verbose;
2618   GetOptions(
2619      'verbose|v' => \$verbose,
2620      );
2621
2622   my $switch_name = shift @ARGV || q{};
2623
2624   if ($switch_name eq q{}) {
2625      die "Usage: klask rebootsw SWITCH_NAME\n";
2626      }
2627
2628   for my $sw_name (split /,/, $switch_name) {
2629      if (not defined $SWITCH_DB{$sw_name}) {
2630         die "Switch $sw_name must be defined in klask configuration file\n";
2631         }
2632
2633      my $sw = $SWITCH_DB{$sw_name};
2634      my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2635      print "$error \n" if $error;
2636
2637      my $result = $session->set_request(
2638         -varbindlist => ["$OID_NUMBER{'HPicfReset'}.0", INTEGER, 2],
2639         );
2640
2641      $session->close;
2642      }
2643   return;
2644   }
2645
2646#---------------------------------------------------------------
2647sub cmd_vlan_getname {
2648   my $switch_name = shift || q{};
2649   my $vlan_id     = shift || q{};
2650
2651   if ($switch_name eq q{} or $vlan_id eq q{}) {
2652      die "Usage: klask vlan-getname SWITCH_NAME VLAN_ID\n";
2653      }
2654
2655   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST) if $switch_name eq q{*};
2656
2657   for my $sw_name (split /,/, $switch_name) {
2658      if (not defined $SWITCH_DB{$sw_name}) {
2659         die "Switch $sw_name must be defined in klask configuration file\n";
2660         }
2661
2662      my $oid_search_vlan_name = $OID_NUMBER{'vlanName'} . ".$vlan_id";
2663
2664      my $sw = $SWITCH_DB{$sw_name};
2665      my ($session, $error) = Net::SNMP->session(%{$sw->{'snmp_param_session'}});
2666      print "$error \n" if $error;
2667
2668      my $result = $session->get_request(
2669         -varbindlist => [$oid_search_vlan_name]
2670         );
2671
2672      if (defined $result and $result->{$oid_search_vlan_name} ne 'noSuchInstance') {
2673         my $vlan_name = $result->{$oid_search_vlan_name} || 'empty';
2674         print "Klask find VLAN $vlan_id on switch $sw_name with name $vlan_name\n";
2675         }
2676      else {
2677         print "Klask do not find VLAN $vlan_id on switch $sw_name\n";
2678         }
2679
2680      $session->close;
2681      }
2682   return;
2683   }
2684
2685#---------------------------------------------------------------
2686sub cmd_vlan_list {
2687   my $switch_name = shift || q{};
2688
2689   if ($switch_name eq q{}) {
2690      die "Usage: klask vlan-list SWITCH_NAME\n";
2691      }
2692
2693   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST) if $switch_name eq q{*};
2694
2695   for my $sw_name (split /,/, $switch_name) {
2696      if (not defined $SWITCH_DB{$sw_name}) {
2697         die "Switch $sw_name must be defined in klask configuration file\n";
2698         }
2699
2700      my $sw = $SWITCH_DB{$sw_name};
2701      my ($session, $error) = Net::SNMP->session(%{$sw->{'snmp_param_session'}});
2702      print "$error \n" if $error;
2703
2704      my %vlandb = snmp_get_vlan_list($session);
2705      $session->close;
2706
2707      print "VLAN_ID - VLAN_NAME # $sw_name\n";
2708      for my $vlan_id (keys %vlandb) {
2709         printf "%7i - %s\n", $vlan_id, $vlandb{$vlan_id};
2710         }
2711      }
2712   return;
2713   }
2714
2715#---------------------------------------------------------------
2716sub cmd_ip_location {
2717   my $computerdb = computerdb_load();
2718
2719   LOOP_ON_IP_ADDRESS:
2720   for my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
2721
2722      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'hostname_fq'} eq ($computerdb->{$ip}{'switch_hostname'} || $computerdb->{$ip}{'switch_description'}); # switch on himself !
2723
2724      my $sw_hostname = $computerdb->{$ip}{'switch_hostname'} || q{};
2725      next LOOP_ON_IP_ADDRESS if $sw_hostname eq 'unknow';
2726
2727      my $sw_location = q{};
2728      LOOP_ON_ALL_SWITCH:
2729      for my $sw (@SWITCH_LIST) {
2730         next LOOP_ON_ALL_SWITCH if $sw_hostname ne $sw->{'hostname'};
2731         $sw_location = $sw->{'location'};
2732         last;
2733         }
2734
2735      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq q{};
2736      }
2737   return;
2738   }
2739
2740#---------------------------------------------------------------
2741sub cmd_ip_free {
2742   @ARGV = @_;
2743
2744   my $days_to_death = $DEFAULT{'days-to-death'} || 365 * 2;
2745   my $format = 'txt';
2746   my $verbose;
2747
2748   GetOptions(
2749      'day|d=i'    => \$days_to_death,
2750      'format|f=s' => \$format,
2751      'verbose|v'  => \$verbose,
2752      );
2753
2754   my %possible_format = (
2755      txt  => \&cmd_ip_free_txt,
2756      html => \&cmd_ip_free_html,
2757      none => sub { },
2758      );
2759   $format = 'txt' if not defined $possible_format{$format};
2760
2761   my @vlan_name = @ARGV;
2762   @vlan_name = get_list_network() if not @vlan_name;
2763
2764   my $computerdb = {};
2765   $computerdb = computerdb_load() if -e "$KLASK_DB_FILE";
2766   my $timestamp = time;
2767
2768   my $timestamp_barrier = $timestamp - (3600 * 24 * $days_to_death);
2769
2770   my %result_ip = ();
2771
2772   ALL_NETWORK:
2773   for my $vlan (@vlan_name) {
2774
2775      my @ip_list = get_list_ip($vlan);
2776
2777      LOOP_ON_IP_ADDRESS:
2778      for my $ip (@ip_list) {
2779
2780         if (exists $computerdb->{$ip}) {
2781            next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{'timestamp'} > $timestamp_barrier;
2782
2783            my $mac_address = $computerdb->{$ip}{'mac_address'};
2784            LOOP_ON_DATABASE:
2785            for my $ip_db (keys %{$computerdb}) {
2786               next LOOP_ON_DATABASE if $computerdb->{$ip_db}{'mac_address'} ne $mac_address;
2787               next LOOP_ON_IP_ADDRESS if $computerdb->{$ip_db}{'timestamp'} > $timestamp_barrier;
2788               }
2789            }
2790
2791         my $ip_date_last_detection = '';
2792         if (exists $computerdb->{$ip}) {
2793            my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $computerdb->{$ip}{'timestamp'};
2794            $year += 1900;
2795            $mon++;
2796            $ip_date_last_detection = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
2797            }
2798
2799         my $packed_ip   = scalar gethostbyname($ip);
2800         my $hostname_fq = 'unknown';
2801            $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) || 'unknown' if defined $packed_ip and get_current_scan_mode($vlan) eq 'active';
2802
2803         next LOOP_ON_IP_ADDRESS if $hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
2804
2805         $result_ip{$ip} ||= {};
2806         $result_ip{$ip}->{'date_last_detection'} = $ip_date_last_detection;
2807         $result_ip{$ip}->{'hostname_fq'}         = $hostname_fq;
2808         $result_ip{$ip}->{'vlan'}                = $vlan;
2809
2810         printf "VERBOSE_1: %-15s %-12s %s\n", $ip, $vlan, $hostname_fq if $verbose;
2811         }
2812      }
2813
2814   $possible_format{$format}->(%result_ip);
2815   }
2816
2817#---------------------------------------------------------------
2818sub cmd_ip_free_txt {
2819   my %result_ip = @_;
2820
2821   my $tb_computer = Text::Table->new(
2822      {align => 'left', align_title => 'left', title => 'IPv4-Address'},
2823      {align => 'left', align_title => 'left', title => 'Hostname-FQ'},
2824      {align => 'left', align_title => 'left', title => 'VLAN'},
2825      {align => 'left', align_title => 'left', title => 'Date'},
2826      );
2827
2828   #printf "%-15s %-40s %-16s %s\n", qw(IPv4-Address Hostname-FQ Date VLAN);
2829   #print "-------------------------------------------------------------------------------\n";
2830   LOOP_ON_IP_ADDRESS:
2831   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
2832      my $vlan_nameid = $result_ip{$ip}->{'vlan'} . '(' . get_current_vlan_id($result_ip{$ip}->{'vlan'}) . ')';
2833      #printf "%-15s %-40s %-16s %s\n", $ip, $result_ip{$ip}->{'hostname_fq'}, $result_ip{$ip}->{'date_last_detection'}, $vlan_nameid;
2834      $tb_computer->add(
2835         $ip,
2836         $result_ip{$ip}->{'hostname_fq'},
2837         $vlan_nameid,
2838         $result_ip{$ip}->{'date_last_detection'},
2839         );
2840      }
2841   print $tb_computer->title();
2842   print $tb_computer->rule('-');
2843   print $tb_computer->body();
2844   }
2845
2846#---------------------------------------------------------------
2847sub cmd_ip_free_html {
2848   my %result_ip = @_;
2849
2850   print <<'END_HTML';
2851<table class="sortable">
2852 <caption>Klask Free IP Database</caption>
2853 <thead>
2854  <tr>
2855   <th scope="col" class="klask-header-left">IPv4-Address</th>
2856   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
2857   <th scope="col" class="sorttable_alpha">VLAN</th>
2858   <th scope="col" class="klask-header-right">Date</th>
2859  </tr>
2860 </thead>
2861 <tbody>
2862END_HTML
2863
2864   my $typerow = 'even';
2865
2866   LOOP_ON_IP_ADDRESS:
2867   for my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
2868
2869      $typerow = $typerow eq 'even' ? 'odd' : 'even';
2870
2871      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
2872      my ($host_short) = split m/ \. /xms, $result_ip{$ip}->{'hostname_fq'};
2873
2874      my $vlan_nameid = $result_ip{$ip}->{'vlan'} . ' (' . get_current_vlan_id($result_ip{$ip}->{'vlan'}) . ')';
2875
2876      fqdn_html_breakable(my $hostname_fq_html = $result_ip{$ip}->{'hostname_fq'});
2877
2878      print <<"END_HTML";
2879  <tr class="$typerow">
2880   <td sorttable_customkey="$ip_sort">$ip</td>
2881   <td sorttable_customkey="$host_short">$hostname_fq_html</td>
2882   <td>$vlan_nameid</td>
2883   <td>$result_ip{$ip}->{'date_last_detection'}</td>
2884  </tr>
2885END_HTML
2886      }
2887   print <<'END_HTML';
2888 </tbody>
2889 <tfoot>
2890  <tr>
2891   <th scope="col" class="klask-footer-left">IPv4-Address</th>
2892   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
2893   <th scope="col" class="fklask-vlan">VLAN</th>
2894   <th scope="col" class="klask-footer-right">Date</th>
2895  </tr>
2896 </tfoot>
2897</table>
2898END_HTML
2899   }
2900
2901#---------------------------------------------------------------
2902sub cmd_enable {
2903   @ARGV = @_;
2904
2905   my $verbose;
2906
2907   GetOptions(
2908      'verbose|v' => \$verbose,
2909      );
2910
2911   my $switch_name = shift @ARGV || q{};
2912   my $port_hr     = shift @ARGV || q{};
2913
2914   if ($switch_name eq q{} or $port_hr eq q{}) {
2915      die "Usage: klask disable SWITCH_NAME PORT\n";
2916      }
2917
2918   if (not defined $SWITCH_DB{$switch_name}) {
2919      die "Switch $switch_name must be defined in klask configuration file\n";
2920      }
2921
2922   my $sw = $SWITCH_DB{$switch_name};
2923   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2924   print "$error \n" if $error;
2925
2926   # Retrieve numeric port value
2927   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port_hr), $verbose ? 'yes' : '');
2928   die "Error : Port $port_hr does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
2929
2930   my $oid_search_portstatus = $OID_NUMBER{'portUpDown'} . '.' . $port_id;
2931   print "Info: switch $switch_name port $port_hr SNMP OID $oid_search_portstatus\n" if $verbose;
2932
2933   my $result = $session->set_request(
2934      -varbindlist => [$oid_search_portstatus, INTEGER, 1],
2935      );
2936   print $session->error()."\n" if $session->error_status();
2937
2938   $session->close;
2939
2940   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
2941   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
2942   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
2943   return;
2944   }
2945
2946#---------------------------------------------------------------
2947sub cmd_disable {
2948   @ARGV = @_;
2949
2950   my $verbose;
2951
2952   GetOptions(
2953      'verbose|v' => \$verbose,
2954      );
2955
2956   my $switch_name = shift @ARGV || q{};
2957   my $port_hr     = shift @ARGV || q{};
2958
2959   if ($switch_name eq q{} or $port_hr eq q{}) {
2960      die "Usage: klask disable SWITCH_NAME PORT\n";
2961      }
2962
2963   if (not defined $SWITCH_DB{$switch_name}) {
2964      die "Switch $switch_name must be defined in klask configuration file\n";
2965      }
2966
2967   my $sw = $SWITCH_DB{$switch_name};
2968   my ($session, $error) = Net::SNMP->session(snmp_get_rwsession($sw));
2969   print "$error \n" if $error;
2970
2971   # Retrieve numeric port value
2972   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port_hr), $verbose ? 'yes' : '');
2973   die "Error : Port $port_hr does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
2974
2975   my $oid_search_portstatus = $OID_NUMBER{'portUpDown'} . '.' . $port_id;
2976   print "Info: switch $switch_name port $port_hr SNMP OID $oid_search_portstatus\n" if $verbose;
2977
2978   my $result = $session->set_request(
2979      -varbindlist => [$oid_search_portstatus, INTEGER, 2],
2980      );
2981   print $session->error()."\n" if $session->error_status();
2982
2983   $session->close;
2984
2985   #system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
2986   return;
2987   }
2988
2989#---------------------------------------------------------------
2990sub cmd_status {
2991   @ARGV = @_;
2992
2993   my $verbose;
2994
2995   GetOptions(
2996      'verbose|v' => \$verbose,
2997      );
2998
2999   my $switch_name = shift @ARGV || q{};
3000   my $port_hr     = shift @ARGV || q{};
3001
3002   if ($switch_name eq q{} or $port_hr eq q{}) {
3003      die "Usage: klask status SWITCH_NAME PORT\n";
3004      }
3005
3006   if (not defined $SWITCH_DB{$switch_name}) {
3007      die "Switch $switch_name must be defined in klask configuration file\n";
3008      }
3009
3010   my $sw = $SWITCH_DB{$switch_name};
3011   my ($session, $error) = Net::SNMP->session(%{$sw->{'snmp_param_session'}});
3012   print "$error \n" if $error;
3013
3014   # Retrieve numeric port value
3015   my $port_id = snmp_get_switchport_hr2id($session, normalize_port_human_readable($port_hr), $verbose ? 'yes' : '');
3016   die "Error : Port $port_hr does not exist on switch $switch_name\n" if not $port_id =~ m/^\d+$/;
3017
3018   my $oid_search_portstatus = $OID_NUMBER{'portUpDown'} . '.' . $port_id;
3019   print "Info: switch $switch_name port $port_hr ($port_id) SNMP OID $oid_search_portstatus\n" if $verbose;
3020
3021   my $result = $session->get_request(
3022      -varbindlist => [$oid_search_portstatus]
3023      );
3024   print $session->error()."\n" if $session->error_status();
3025   if (defined $result) {
3026      print "$PORT_UPDOWN{$result->{$oid_search_portstatus}}\n";
3027      }
3028
3029   $session->close;
3030
3031   #system "snmpget -v 1 -c public $switch_name 1.3.6.1.2.1.2.2.1.7.$port";
3032   return;
3033   }
3034
3035#---------------------------------------------------------------
3036sub cmd_search_mac_on_switch {
3037   @ARGV = @_;
3038
3039   my $verbose;
3040   my $vlan_id = 0;
3041
3042   GetOptions(
3043      'verbose|v' => \$verbose,
3044      'vlan|l=i'  => \$vlan_id,
3045      );
3046
3047   my $switch_name = shift @ARGV || q{};
3048   my $mac_address = shift @ARGV || q{};
3049
3050   if ($switch_name eq q{} or $mac_address eq q{}) {
3051      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
3052      }
3053
3054   $mac_address = normalize_mac_address($mac_address);
3055   $switch_name = join(',', map {$_->{'hostname'}} @SWITCH_LIST) if $switch_name eq q{*} or $switch_name eq q{all};
3056
3057   for my $sw_name (split /,/, $switch_name) {
3058      if (not defined $SWITCH_DB{$sw_name}) {
3059         die "Switch $sw_name must be defined in klask configuration file\n";
3060         }
3061
3062      my $oid_search_port1 = $OID_NUMBER{'searchPort1'} . mac_address_hex2dec($mac_address);
3063      my $oid_search_port2 = $OID_NUMBER{'searchPort2'} . '.' . $vlan_id . mac_address_hex2dec($mac_address);
3064      print "Klask search OID $oid_search_port1 on switch $sw_name\n" if $verbose;
3065      print "Klask search OID $oid_search_port2 on switch $sw_name\n" if $verbose;
3066
3067      my $sw = $SWITCH_DB{$sw_name};
3068      my ($session, $error) = Net::SNMP->session(%{$sw->{'snmp_param_session'}});
3069      print "$error \n" if $error;
3070
3071      my $result = $session->get_request(
3072         -varbindlist => [$oid_search_port1]
3073         );
3074      if (not defined $result) {
3075         $result = $session->get_request(
3076            -varbindlist => [$oid_search_port2]
3077            );
3078         $result->{$oid_search_port1} = $result->{$oid_search_port2} if defined $result;
3079         }
3080
3081      if (defined $result and $result->{$oid_search_port1} ne 'noSuchInstance') {
3082         my $swport_id = $result->{$oid_search_port1};
3083         my $swport_hr = snmp_get_switchport_id2hr($session, $swport_id);
3084         print "Klask find MAC $mac_address on switch $sw_name port $swport_hr\n";
3085         }
3086      else {
3087         print "Klask do not find MAC $mac_address on switch $sw_name\n" if $verbose;
3088         }
3089
3090      $session->close;
3091      }
3092   return;
3093   }
3094
3095#---------------------------------------------------------------
3096sub cmd_updatesw {
3097   @ARGV = @_;
3098
3099   my $verbose;
3100
3101   GetOptions(
3102      'verbose|v' => \$verbose,
3103      );
3104   
3105   update_switchdb(verbose => $verbose);
3106   }
3107
3108#---------------------------------------------------------------
3109sub cmd_exportsw {
3110   @ARGV = @_;
3111
3112   test_switchdb_environnement();
3113
3114   my $format       = 'txt';
3115   my $graph_modulo = 0;
3116   my $graph_shift  = 1;
3117   my $way          = 'all';
3118   my $no_header;
3119
3120   GetOptions(
3121      'format|f=s'  => \$format,
3122      'modulo|m=i'  => \$graph_modulo,
3123      'shift|s=i'   => \$graph_shift,
3124      'way|w=s'     => \$way,
3125      'no-header|H' => \$no_header,
3126      );
3127
3128   my %possible_format = (
3129      txt => \&cmd_exportsw_txt,
3130      dot => \&cmd_exportsw_dot,
3131      );
3132
3133   $format = 'txt' if not defined $possible_format{$format};
3134
3135   $possible_format{$format}->(modulo => $graph_modulo, shift => $graph_shift, way => $way, 'no-header' => $no_header, @ARGV);
3136   return;
3137   }
3138
3139#---------------------------------------------------------------
3140sub cmd_exportsw_txt {
3141   my %args = (
3142      'way'       => 'all',
3143      'no-header' => undef,
3144      @_);
3145
3146   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
3147   my $interway = 0;
3148
3149   if ($args{'way'} =~ m/all|desc/) {
3150      print "\n" if $interway++;
3151      # Switch description
3152      my %db_switch_global = %{$switch_connection->{'switch_db'}};
3153      my $arrow            = '0--------->>>>';
3154
3155      my $tb_desc = Text::Table->new(
3156         {align => 'left',   align_title => 'left',   title => 'Switch'},
3157         {align => 'center', align_title => 'center', title => 'Is'},
3158         {align => 'left',   align_title => 'left',   title => 'Description'},
3159         {align => 'left',   align_title => 'left',   title => 'Model'},
3160         {align => 'left',   align_title => 'left',   title => 'Revision'},
3161         );
3162
3163      for my $sw (values %db_switch_global) {
3164         $tb_desc->add($sw->{'hostname'}, $arrow, $sw->{'description'}, $sw->{'model'}, $sw->{'revision'});
3165         }
3166
3167      print $tb_desc->title()   unless $args{'no-header'};
3168      print $tb_desc->rule('-') unless $args{'no-header'};
3169      print $tb_desc->body();
3170      $tb_desc->clear;
3171      }
3172
3173   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
3174   my %db_switch_parent            = %{$switch_connection->{'parent'}};
3175   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
3176
3177   if ($args{'way'} =~ m/all|child/) {
3178      print "\n" if $interway++;
3179      # Switch output port and parent port connection
3180      my $tb_child = Text::Table->new( # http://www.perlmonks.org/?node_id=988320
3181         {align => 'left',   align_title => 'left',   title => 'Child-Switch'},
3182         {align => 'right',  align_title => 'right',  title => 'Output-Port'},
3183         {align => 'center', align_title => 'center', title => 'Link'},
3184         {align => 'left',   align_title => 'left',   title => 'Input-Port'},
3185         {align => 'left',   align_title => 'left',   title => 'Parent-Switch'},
3186         );
3187      for my $sw (sort keys %db_switch_output_port) {
3188         my $arrow = '--->';
3189         $arrow = '===>' if $db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/;
3190         if (exists $db_switch_parent{$sw}) {
3191            $tb_child->add($sw, $db_switch_output_port{$sw}, $arrow, $db_switch_parent{$sw}->{'port_hr'}, $db_switch_parent{$sw}->{'switch'});
3192
3193            }
3194         else {
3195            $tb_child->add($sw, $db_switch_output_port{$sw}, $arrow, '', 'router');
3196            }
3197         }
3198      my @colrange = map {scalar $tb_child->colrange($_)} (0 .. 4); # force scaler context
3199      $tb_child->add(map {' ' x $_} reverse @colrange);             # add empty line to force symetric table output
3200      print $tb_child->title()   unless $args{'no-header'};
3201      print $tb_child->rule('-') unless $args{'no-header'};
3202      print $tb_child->body(0, $tb_child->body_height() - 1);       # remove last fake line
3203      $tb_child->clear;
3204      }
3205
3206   if ($args{'way'} =~ m/all|parent/) {
3207      print "\n" if $interway++;
3208      # Switch parent and children port inter-connection
3209      my $tb_parent = Text::Table->new(                             # http://www.perlmonks.org/?node_id=988320
3210         {align => 'left',   align_title => 'left',   title => 'Parent-Switch'},
3211         {align => 'right',  align_title => 'right',  title => 'Input-Port'},
3212         {align => 'center', align_title => 'center', title => 'Link'},
3213         {align => 'left',   align_title => 'left',   title => 'Output-Port'},
3214         {align => 'left',   align_title => 'left',   title => 'Child-Switch'},
3215         );
3216      for my $swport (sort keys %db_switch_connected_on_port) {
3217         my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
3218         for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
3219            my $arrow = '<---';
3220            $arrow = '<===' if $port_connect =~ m/^(Trk|Br|Po)/;
3221            if (exists $db_switch_output_port{$sw}) {
3222               $tb_parent->add($sw_connect, $port_connect, $arrow, $db_switch_output_port{$sw}, $sw);
3223               }
3224            else {
3225               $tb_parent->add($sw_connect, $port_connect, $arrow, '', $sw);
3226               }
3227            }
3228         }
3229      my @colrange = map {scalar $tb_parent->colrange($_)} (0 .. 4); # force scaler context
3230      $tb_parent->add(map {' ' x $_} reverse @colrange);             # add empty line to force symetric table output
3231      print $tb_parent->title()   unless $args{'no-header'};
3232      print $tb_parent->rule('-') unless $args{'no-header'};
3233      print $tb_parent->body(0, $tb_parent->body_height() - 1);      # remove last fake line
3234      $tb_parent->clear;
3235      }
3236   return;
3237   }
3238
3239#---------------------------------------------------------------
3240sub cmd_exportsw_dot {
3241   my %args = (
3242      modulo   => 0,
3243      shift    => 1,
3244      @_);
3245
3246   my $graph_modulo = $args{'modulo'};
3247   my $graph_shift  = $args{'shift'};
3248
3249   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
3250
3251   my %db_switch_output_port       = %{$switch_connection->{'output_port'}};
3252   my %db_switch_parent            = %{$switch_connection->{'parent'}};
3253   my %db_switch_connected_on_port = %{$switch_connection->{'connected_on_port'}};
3254   my %db_switch_link_with         = %{$switch_connection->{'link_with'}};
3255   my %db_switch_global            = %{$switch_connection->{'switch_db'}};
3256   my $timestamp                   =   $switch_connection->{'timestamp'};
3257
3258   my $invisible_node = 0; # Count number of invisible node
3259
3260   my %db_building    = ();
3261   my %db_switch_line = (); # Number of line drawed on a switch
3262   for my $sw (values %db_switch_global) {
3263      my ($building, $location) = split m/ \/ /xms, $sw->{'location'}, 2;
3264      $db_building{$building} ||= {};
3265      $db_building{$building}->{$location} ||= {};
3266      $db_building{$building}->{$location}{$sw->{'hostname'}} = 'y';
3267
3268      $db_switch_line{$sw} = 0;
3269      }
3270
3271
3272   print "digraph G {\n";
3273   print "rankdir=LR;\n";
3274   #print "splines=polyline;\n";
3275
3276   print "site [label=\"site\", color=black, fillcolor=gold, shape=invhouse, style=filled];\n";
3277   print "internet [label=\"internet\", color=black, fillcolor=cyan, shape=house, style=filled];\n";
3278
3279   my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime $timestamp;
3280   $year += 1900;
3281   $mon++;
3282   my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
3283   print "\"$date\" [label=\"MAP DATE\\n\\n$date\", color=white, fillcolor=black, shape=polygon, sides=14, style=filled, fontcolor=white];\n";
3284   print "site -> \"$date\" [style=invis];\n";
3285
3286   my $b = 0;
3287   for my $building (keys %db_building) {
3288      $b++;
3289
3290      print "\"building$b\" [label=\"$building\", color=black, fillcolor=gold, style=filled];\n";
3291      print "site -> \"building$b\" [len=2, color=firebrick];\n";
3292
3293      my $l = 0;
3294      for my $loc (keys %{$db_building{$building}}) {
3295         $l++;
3296
3297         print "\"location$b-$l\" [label=\"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color=black, fillcolor=orange, style=filled];\n";
3298#         print "\"location$b-$l\" [label=\"$building / $loc\", color=black, fillcolor=orange, style=filled];\n";
3299         print "\"building$b\" -> \"location$b-$l\" [len=2, color=firebrick];\n";
3300
3301         for my $sw (keys %{$db_building{$building}->{$loc}}) {
3302
3303            my $peripheries = 1;
3304            my $color       = 'lightblue';
3305            if ($db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/) {
3306               $peripheries = 2;
3307               $color       = "\"$color:$color\"";
3308               }
3309            print "\"$sw:$db_switch_output_port{$sw}\" [label=\"".format_aggregator4dot($db_switch_output_port{$sw})."\", color=black, fillcolor=lightblue, peripheries=$peripheries, style=filled];\n";
3310
3311            my $swname  = $sw;
3312               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{'model'}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{'model'};
3313            print "\"$sw\" [label=\"$swname\", color=black, fillcolor=palegreen, shape=rect, style=filled];\n";
3314            print "\"location$b-$l\" -> \"$sw\" [len=2, color=firebrick, arrowtail=dot];\n";
3315            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, color=$color, arrowhead=normal, arrowtail=invdot];\n";
3316
3317
3318            for my $swport (keys %db_switch_connected_on_port) {
3319               my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
3320               next if not $sw_connect eq $sw;
3321               next if $port_connect eq $db_switch_output_port{$sw};
3322               my $peripheries = 1;
3323               my $color       = 'plum';
3324               if ($port_connect =~ m/^(Trk|Br|Po)/) {
3325                  $peripheries = 2;
3326                  $color       = "\"$color:$color\"";
3327                  }
3328               print "\"$sw:$port_connect\" [label=\"".format_aggregator4dot($port_connect)."\", color=black, fillcolor=plum, peripheries=$peripheries, style=filled];\n";
3329               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, color=$color, arrowhead=normal, arrowtail=inv];\n";
3330
3331               #$db_switch_line{$sw}++;
3332               #if ($db_switch_line{$sw} % 9 == 0) {
3333               #   # Create invisible node
3334               #   $invisible_node++;
3335               #   my $invisible = '__Invisible_' . $invisible_node;
3336               #   print "$invisible [shape=none, label=\"\"]\n";
3337               #   print "\"$sw:$port_connect\" -> $invisible [style=invis]\n";
3338               #   print "$invisible            -> \"$sw\"    [style=invis]\n";
3339               #   }
3340               }
3341            }
3342         }
3343      }
3344
3345   #   print "Switch output port and parent port connection\n";
3346   #   print "---------------------------------------------\n";
3347   for my $sw (sort keys %db_switch_output_port) {
3348      if (exists $db_switch_parent{$sw}) {
3349#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{'switch'}, $db_switch_parent{$sw}->{'port_id'};
3350         }
3351      else {
3352         my $style = 'solid';
3353         my $color = 'black'; # navyblue
3354         if ($db_switch_output_port{$sw} =~ m/^(Trk|Br|Po)/) {
3355            $style = 'bold';
3356            $color = "\"$color:invis:$color\"";
3357            }
3358         printf "   \"%s:%s\" -> internet [style=$style, color=$color];\n", $sw, $db_switch_output_port{$sw};
3359         }
3360      }
3361   print "\n";
3362
3363   # shift graph between 1 or 2 when $graph_shift = 3
3364   my $graph_breaker = 1;
3365
3366   #   print "Switch parent and children port inter-connection\n";
3367   #   print "------------------------------------------------\n";
3368   for my $swport (sort keys %db_switch_connected_on_port) {
3369      my ($sw_connect, $port_connect) = split m/ $SEP_SWITCH_PORT /xms, $swport, 2;
3370      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
3371         my $style = 'solid';
3372         my $color = 'black'; # navyblue
3373         if ($port_connect =~ m/^(Trk|Br|Po)/) {
3374            $style = 'bold';
3375            $color = "\"$color:invis:$color\"";
3376            }
3377         if (exists $db_switch_output_port{$sw}) {
3378            printf "   \"%s:%s\" -> \"%s:%s\" [style=$style, color=$color];\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
3379
3380            next if $graph_modulo == 0; # No shift (invisible nodes) in graph
3381            $db_switch_line{$sw_connect}++;
3382            if ($db_switch_line{$sw_connect} % $graph_modulo == 0) {
3383               # Create invisible node
3384               $invisible_node++;
3385               my $invisible = '__Invisible_' . $invisible_node;
3386               print  "   \"$invisible.a\" [shape=none, label=\"\"];\n";
3387               printf "   \"%s:%s\"  -> \"$invisible.a\" [style=invis];\n", $sw, $db_switch_output_port{$sw};
3388               $graph_breaker++;
3389               if ($graph_shift == 2 or ($graph_shift == 3 and ($graph_breaker % 2) == 0)) {
3390                  # Two invisible node
3391                  print  "   \"$invisible.b\" [shape=none, label=\"\"];\n";
3392                  print  "   \"$invisible.a\" -> \"$invisible.b\" [style=invis];\n";
3393                  printf "   \"$invisible.b\" -> \"%s:%s\"  [style=invis];\n", $sw_connect, $port_connect;
3394                  }
3395               else {
3396                  # One invisible node
3397                  printf "   \"$invisible.a\" -> \"%s:%s\"  [style=invis];\n", $sw_connect, $port_connect;
3398                  }
3399               }
3400            }
3401         else {
3402            printf "   \"%s\"   -> \"%s:%s\" [style=$style];\n", $sw, $sw_connect, $port_connect;
3403            }
3404         }
3405      }
3406
3407   print "}\n";
3408   return;
3409   }
3410
3411################################################################
3412# documentation
3413################################################################
3414
3415__END__
3416
3417=head1 NAME
3418
3419klask - port and search manager for switches, map management
3420
3421
3422=head1 USAGE
3423
3424 klask version
3425 klask help
3426
3427 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l] [--no-rebuildsw|-R]
3428 klask exportdb [--format|-f txt|html]
3429 klask removedb IP* computer*
3430 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
3431
3432 klask updatesw [--verbose|-v]
3433 klask exportsw [--format|-f txt|dot] [--modulo|-m XX] [--shift|-s YY] [--way all|desc|child|parent] [--no-header|-H]
3434
3435 klask searchdb [--kind|-k host|mac] computer [mac-address]
3436 klask search   computer
3437 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
3438
3439 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
3440
3441 klask bad-vlan-id [--day|-d days_before_alert] [--format|-f txt|html]
3442
3443 klask enable  [--verbose|-v] switch port
3444 klask disable [--verbose|-v] switch port
3445 klask status  [--verbose|-v] switch port
3446
3447 klask poe-enable  [--verbose|-v] switch port
3448 klask poe-disable [--verbose|-v] switch port
3449 klask poe-status  [--verbose|-v] switch port
3450
3451 klask vlan-getname switch vlan-id
3452 klask vlan-list switch
3453
3454
3455=head1 DESCRIPTION
3456
3457Klask is a small tool to find where is connected a host in a big network
3458and on which VLAN.
3459Klask mean search in brittany.
3460No hight level protocol like CDL, LLDP are use.
3461Everything is just done with SNMP request on MAC address.
3462
3463Limitation : loop cannot be detected and could be problematic when the map is created (C<updatesw> method).
3464If you use PVST or MSTP and create loop between VLAN,
3465you have to use C<portignore> functionality on switch port to cut manually loop
3466(see config file below).
3467
3468When you use a management port to administrate a switch,
3469it's not possible to create the map with this switch because it does not have a MAC address,
3470so other switch cannot find the real downlink port...
3471One way to work around this problem is, if you have a computer directly connected on the switch,
3472to put this IP as the fake ip for the switch.
3473The MAC address associated will be use just for the map detection.
3474The C<fake-ip> parameter is defined in the config file.
3475
3476Klask has now a web site dedicated for it: L<http://servforge.legi.grenoble-inp.fr/projects/klask>!
3477
3478
3479=head1 COMMANDS
3480
3481Some command are defined in the source code but are not documented here.
3482Theses could be not well defined, not finished, not well tested...
3483You can read the source code and use them at your own risk
3484(like for all the Klask code).
3485
3486=head2 search
3487
3488 klask search   computer
3489
3490This command takes one or more computer in argument.
3491It search a computer on the network and give the port and the switch on which the computer is connected.
3492
3493=head2 search-mac-on-switch
3494
3495 klask search-mac-on-switch [--verbose|-v] [--vlan|-i vlan-id] switch mac_addr
3496
3497This command search a MAC address on a switch.
3498To search on all switch, you could put C<'*'> or C<all>.
3499The VLAN parameter could help.
3500
3501
3502=head2 enable
3503
3504 klask enable  [--verbose|-v] switch port
3505
3506This command activate a port (or an agrregate bridge port) on a switch by SNMP.
3507So you need to give the switch name and a port on the command line.
3508See L</ABBREVIATION FOR PORT>.
3509
3510Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
3511
3512
3513=head2 disable
3514
3515 klask disable [--verbose|-v] switch port
3516
3517This command deactivate a port (or an agrregate bridge port) on a switch by SNMP.
3518So you need to give the switch name and a port on the command line.
3519See L</ABBREVIATION FOR PORT>.
3520
3521Warning: You need to have the SNMP write access on the switch in order to modify it's configuration.
3522
3523
3524=head2 status
3525
3526 klask status  [--verbose|-v] switch port
3527
3528This command return the status of a port number on a switch by SNMP.
3529The return value could be C<enable> or C<disable> word.
3530So you need to give the switch name and a port on the command line.
3531See L</ABBREVIATION FOR PORT>.
3532
3533If it's not possible to change port status with command L</enable> and L</disable>
3534(SNMP community read write access),
3535it's always possible to have the port status even for bridge agrregate port.
3536
3537
3538=head2 updatedb
3539
3540 klask updatedb [--verbose|-v] [--verb-description|-d] [--chk-hostname|-h] [--chk-location|-l] [--no-rebuildsw|-R]
3541
3542This command will scan networks and update the computer database.
3543To know which are the cmputer scanned, you have to configure the file F</etc/klask/klask.conf>.
3544This file is easy to read and write because Klask use YAML format and not XML
3545(see L</CONFIGURATION>).
3546
3547Option are not stable and could be use manually when you have a new kind of switch.
3548Maybe some option will be transfered in a future C<checksw> command!
3549
3550The network parameter C<scan-mode> can have two values: C<active> or C<passive>.
3551By default, a network is C<active>.
3552This means that an C<fping> command is done at the beginning on all the IP of the network
3553and the computers that was not detected in this pass, but where their Klask entry is less than one week,
3554will have an C<arping>
3555(some OS do not respond to C<ping> but a computer have to respond to C<arping> if it want to interact with other).
3556In the scan mode C<passive>, no C<fping> and no C<arping> are done.
3557It's good for big subnet with few computer (telephone...).
3558The idea of the C<active> scan mode is to force computer to regulary send packet over the network.
3559
3560At the beginning, the command verify that the switch map checksum is always valid.
3561Otherwise, a rebuild procedure will ne done automatically.
3562
3563=head2 exportdb
3564
3565 klask exportdb [--format|-f txt|html]
3566
3567This command print the content of the computer database.
3568There is actually only two format : TXT and HTML.
3569By default, format is TXT.
3570It's very easy to have more format, it's just need times...
3571
3572=head2 removedb
3573
3574 klask removedb IP* computer*
3575
3576This command remove an entry in the database.
3577There is only one kind of parameter, the IP of the computers to be removed.
3578You can put as many IP as you want...
3579
3580Computer DNS names are also a valid entry because a DNS resolver is executed at the beginning.
3581
3582=head2 cleandb
3583
3584 klask cleandb  [--verbose|-v] --day number_of_day --repair-dns
3585
3586Remove double entry (same MAC-Address) in the computer database when the older one is older than X day (C<--day>) the new one.
3587Computer name beginning by 'float' (regex C<^float>) are not really taken into account but could be remove.
3588This could be configure with the global regex parameter C<float-regex> in the configuration file F</etc/klask/klask.conf>.
3589This functionality could be use when computer define in VLAN 1
3590could have a float IP when they are connected on VLAN 2.
3591In the Klask database, the float DNS entries are less important.
3592
3593When reverse DNS has not been done by the past, option C<--repair-dns> force a reverse DNS check on all unkown host.
3594
3595=head2 updatesw
3596
3597 klask updatesw [--verbose|-v]
3598
3599This command build a map of your manageable switch on your network.
3600The list of the switches must be given in the file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3601
3602The database has a checksum which depend of all the active switches.
3603It's use when rebuilding the database in case of change in switch configuration (one more for example).
3604
3605=head2 exportsw
3606
3607 klask exportsw [--format|-f txt|dot] [--modulo|-m XX] [--shift|-s YY] [--way all|desc|child|parent] [--no-header|-H]
3608
3609This command print the content of the switch database. There is actually two format.
3610One is just TXT for terminal and the other is the DOT format from the graphviz environnement.
3611By default, format is TXT.
3612
3613B<Options for TXT format:>
3614
3615Tree tables are print :
3616C<desc> gives the switches description, model and revision,
3617C<child> return the switch to parent switch table and
3618C<parent> return the switch parent to parent child table.
3619With option C<--way>, you can choose which on to print.
3620C<all> will print all and C<child,parent> will print only C<child> and C<parent> table.
3621With option C<--no-header>, you can remove the header for each table.
3622
3623B<Options for DOT format:>
3624
3625 klask exportsw --format dot > /tmp/map.dot
3626 dot -Tpng /tmp/map.dot > /tmp/map.png
3627
3628In case you have too many switch connected on one switch,
3629the graphviz result graph could be too much vertical.
3630With C<--modulo> > 0, you can specify how many switches (connected on one switch) are on the same columns
3631before shifting them to one column to the left and back again.
3632The C<--shift> parameter must be 1, 2 or 3.
3633With C<--shift> egual to 2, the shift will be to two column to the left.
3634With 3, it will be 1 to the left and 2 to the left one time over two !
3635In practise, we just add virtuals nodes in the dot file,
3636that means the result graph is generated with theses virtuals but invisibles nodes...
3637
3638=head2 ip-free
3639
3640 klask ip-free [--verbose|-v] [--day|-d days-to-death] [--format|-f txt|html] [vlan_name]
3641
3642This command return IP address that was not use (detected by Klask) at this time.
3643The list returned could be limited to just one VLAN.
3644IP returned could have been never used or no computer have been detected since the number of days specified
3645(2 years by default).
3646This parameter could also be define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3647
3648 default:
3649   days-to-death: 730
3650
3651Computer that does not have the good IP but takes a float one (see L</cleandb>) are taken into account.
3652
3653
3654=head2 bad-vlan-id
3655
3656 klask bad-vlan-id [--day|-d days_before_alert] [--format|-f txt|html]
3657
3658This command return a list of switch port that are not configure with the good VLAN.
3659Computer which are in bad VLAN are detected with the float regex parameter (see L</cleandb>)
3660and another prior trace where they had the good IP (good DNS name).
3661The computer must stay connected on a bad VLAN more than XX days (15 days by default) before alert.
3662This parameter could also define in the configuration file F</etc/klask/klask.conf> (see L</CONFIGURATION>).
3663
3664 default:
3665   days-before-alert: 15
3666
3667This functionality is not need if your switch use RADIUS 802.1X configuration...
3668
3669
3670=head2 poe-enable
3671
3672 klask poe-enable  [--verbose|-v] switch port
3673
3674This command activate the PoE (Power over Ethernet) on a switch port by SNMP.
3675So you need to give the switch name and a port on the command line.
3676See L</ABBREVIATION FOR PORT>.
3677
3678Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3679You need to have the SNMP write access on the switch in order to modify it's configuration.
3680
3681
3682=head2 poe-disable
3683
3684 klask poe-disable [--verbose|-v] switch port
3685
3686This command deactivate the PoE (Power over Ethernet) on a switch port by SNMP.
3687So you need to give the switch name and a port on the command line.
3688See L</ABBREVIATION FOR PORT>.
3689
3690Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3691You need to have the SNMP write access on the switch in order to modify it's configuration.
3692
3693
3694=head2 poe-status
3695
3696 klask poe-status  [--verbose|-v] switch port
3697
3698This command return the status of the PoE (Power over Ethernet) on a switch port by SNMP.
3699The return value could be C<enable> or C<disable> word.
3700So you need to give the switch name and a port on the command line.
3701See L</ABBREVIATION FOR PORT>.
3702
3703If it's not possible to change the PoE status with command L</poe-enable> and L</poe-disable>
3704(SNMP community read write access),
3705it's always possible to have the PoE port status.
3706
3707Warning: Only NEXANS switches are supported (we do not have other switch for testing).
3708
3709
3710=head1 CONFIGURATION
3711
3712Because Klask need many parameters, it's not possible actually to use command line parameters for everything.
3713The configuration is done in a F</etc/klask/klask.conf> YAML file.
3714This format have many advantage over XML, it's easier to read and to write !
3715
3716Here an example, be aware with indent, it's important in YAML, do not use tabulation !
3717
3718 default:
3719   community: public
3720   community-rw: private
3721   snmpport: 161
3722   float-regex: '(?^msx: ^float )'
3723   scan-mode: active
3724
3725 network:
3726   labnet:
3727     ip-subnet:
3728       - add: 192.168.1.0/24
3729       - add: 192.168.2.0/24
3730     interface: eth0
3731     vlan-id: 12
3732     main-router: gw1.labnet.local
3733
3734   schoolnet:
3735     ip-subnet:
3736       - add: 192.168.3.0/24
3737       - add: 192.168.4.0/24
3738     interface: eth0.38
3739     vlan-id: 13
3740     main-router: gw2.schoolnet.local
3741     scan-mode: passive
3742
3743   etunet:
3744     ip-subnet:
3745       - add: 192.168.5.0/24
3746     interface: eth2
3747     vlan-id: 14
3748     main-router: gw3.etunet.local
3749     scan-mode: passive
3750
3751 switch:
3752   - hostname: sw1.klask.local
3753     location: BatY / 1 floor / K004
3754     portignore:
3755       - 1
3756       - 2
3757
3758   - hostname: sw2.klask.local
3759     location: BatY / 2 floor / K203
3760     type: HP2424
3761     portignore:
3762       - 1
3763       - 2
3764     fake-ip: 192.168.9.14
3765
3766   - hostname: sw3.klask.local
3767     location: BatY / 2 floor / K203
3768
3769I think it's pretty easy to understand.
3770The default section can be overide in any section, if parameter mean something in theses sections.
3771Network to be scan are define in the network section. You must put an add by network.
3772Maybe I will make a delete line to suppress specific computers.
3773The switch section define your switch.
3774You have to write the port number to ignore, this was important if your switchs are cascades
3775(right now, method C<updatesw> find them automatically)
3776and is still important if you have loop (with PVST or MSTP).
3777Just put the ports numbers between switch.
3778
3779The C<community> parameter is use to get SNMP data on switch.
3780It could be overload for each switch.
3781By default, it's value is C<public> and you have to configure a readonly word for safety reason.
3782Some few command change the switch state as the commands L</enable> and L</disable>.
3783In theses rares cases, you need a readwrite SNMP community word define in your configuration file.
3784Klask then use since version C<0.6.2> the C<community-rw> parameter which by default is egal to C<private>.
3785
3786
3787=head1 ABBREVIATION FOR PORT
3788
3789HP Procurve and Nexans switches have a simplistic numbering scheme.
3790It's just number: 1, 2, 3... 24.
3791On HP8000 chassis, ports names begin with an uppercase letter: A1, A2...
3792Nothing is done on theses ports names.
3793
3794On HP Comware and DELL, port digitization schema use a port speed word (generally a very verbose word)
3795followed by tree number.
3796In order to have short name,
3797we made the following rules:
3798
3799 Bridge-Aggregation     -> Br
3800 FastEthernet           -> Fa
3801 Forty-GigabitEthernet  -> Fo
3802 FortyGigabitEthernet   -> Fo
3803 GigabitEthernet        -> Gi
3804 Giga                   -> Gi
3805 Port-Channel           -> Po
3806 Ten-GigabitEthernet    -> Te
3807 TenGigabitEthernet     -> Te
3808 Ten                    -> Te
3809
3810All Klask command automatically normalize the port name on standart output
3811and also on input command line.
3812
3813In the case of use an aggregator port (Po, Tk, Br ...),
3814the real ports used are also return.
3815
3816
3817=head1 SWITCH SUPPORTED
3818
3819Here is a list of switches where Klask gives or gave (for old switches) good results.
3820We have only a few manageable switches to actually test Klask.
3821It is quite possible that switches from other brands will work just as well.
3822You just have to do a test on it and add the line of description that goes well in the source code.
3823Contact us for any additional information.
3824
3825In the following list, the names of the switch types written in parentheses are the code names returned by Klask.
3826This makes it possible to adjust the code names of the different manufacturers!
3827
3828HP: J3299A(HP224M), J4120A(HP1600M), J9029A(HP1800-8G), J9449A(HP1810-8G), J4093A(HP2424M), J9279A(HP2510G-24),
3829J9280A(HP2510G-48), J4813A(HP2524), J4900A(HP2626A), J4900B(HP2626B), J4899B(HP2650), J9021A(HP2810-24G), J9022A(HP2810-48G),
3830J8692A(HP3500-24G), J4903A(HP2824), J4110A(HP8000M), JE074A(HP5120-24G), JE069A(HP5120-48G), JD377A(HP5500-24G), JD374A(HP5500-24F),
3831J4121A(HP4000M), J9145A(HP2910-24G), J3298A(HP212M), J9625A(HP2620-24P).
3832
3833BayStack: BayStack 350T HW(BS350T)
3834
3835Nexans: GigaSwitch V3 TP SFP-I 48V ES3(NA3483-6G), GigaSwitch V3 TP.PSE.+ 48/54V ES3(NA3483-6P)
3836
3837DELL: PC7024(DPC7024), N2048(DN2048), N4032F(DN4032F), N4064F(DN4064F)
3838
3839H3C and 3COM switches have never not been tested but the new HP Comware switches are exactly the same...
3840
3841H3C: H3C5500
3842
38433COM: 3C17203, 3C17204, 3CR17562-91, 3CR17255-91, 3CR17251-91, 3CR17571-91, 3CRWX220095A, 3CR17254-91, 3CRS48G-24S-91,
38443CRS48G-48S-91, 3C17708, 3C17709, 3C17707, 3CR17258-91, 3CR17181-91, 3CR17252-91, 3CR17253-91, 3CR17250-91, 3CR17561-91,
38453CR17572-91, 3C17702-US, 3C17700.
3846
3847
3848=head1 FILES
3849
3850 /etc/klask/klask.conf
3851 /var/lib/klask/klaskdb
3852 /var/lib/klask/switchdb
3853
3854
3855=head1 SEE ALSO
3856
3857Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
3858
3859=over
3860
3861=item * L<Web site|http://servforge.legi.grenoble-inp.fr/projects/klask>
3862
3863=item * L<Online Manual|http://servforge.legi.grenoble-inp.fr/pub/klask/klask.html>
3864
3865=back
3866
3867
3868=head1 VERSION
3869
3870$Id: klask 386 2018-01-04 19:25:42Z g7moreau $
3871
3872
3873=head1 AUTHOR
3874
3875Written by Gabriel Moreau, Grenoble - France
3876
3877=head1 SPECIAL THANKS
3878
3879The list of people below did not directly contribute to Klask's source code
3880but provided me with some data, returned bugs
3881or helped me in another small task like having new ideas ...
3882Maybe I forgot your contribution in recent years,
3883please forgive me in advance and send me an e-mail to correct this.
3884
3885Kevin Reverchon, Olivier De Marchi, Patrick Begou, Herve Colasuonno.
3886
3887
3888=head1 LICENSE AND COPYRIGHT
3889
3890Licence GNU GPL version 2 or later and Perl equivalent
3891
3892Copyright (C) 2005-2018 Gabriel Moreau <Gabriel.Moreau(A)univ-grenoble-alpes.fr>.
Note: See TracBrowser for help on using the repository browser.