source: trunk/klask @ 140

Last change on this file since 140 was 140, checked in by g7moreau, 10 years ago
  • Update HP3500 regex
  • Property svn:executable set to *
  • Property svn:keywords set to Date Author Id Rev
File size: 76.3 KB
Line 
1#!/usr/bin/perl -w
2#
3# Copyright (C) 2005-2013 Gabriel Moreau.
4#
5# $Id: klask 140 2014-05-20 15:22:08Z g7moreau $
6
7use strict;
8use warnings;
9use version; our $VERSION = qv('0.5.5');
10
11use Readonly;
12use FileHandle;
13use Net::SNMP;
14#use YAML;
15use YAML::Syck;
16use Net::Netmask;
17use Net::CIDR::Lite;
18use NetAddr::IP;
19use Getopt::Long qw(GetOptions);
20use Socket;
21use List::Util 'shuffle';
22
23# apt-get install snmp fping libnet-cidr-lite-perl libnet-netmask-perl libnet-snmp-perl libnetaddr-ip-perl libyaml-perl
24# libcrypt-des-perl libcrypt-hcesha-perl libdigest-hmac-perl
25# arping net-tools fping bind9-host arpwatch
26
27my $KLASK_VAR      = '/var/lib/klask';
28my $KLASK_CFG_FILE = '/etc/klask/klask.conf';
29my $KLASK_DB_FILE  = "$KLASK_VAR/klaskdb";
30my $KLASK_SW_FILE  = "$KLASK_VAR/switchdb";
31
32test_running_environnement();
33
34my $KLASK_CFG = YAML::Syck::LoadFile("$KLASK_CFG_FILE");
35
36my %DEFAULT = %{ $KLASK_CFG->{default} };
37my @SWITCH  = @{ $KLASK_CFG->{switch}  };
38
39my %switch_level = ();
40my %SWITCH_DB    = ();
41LEVEL_OF_EACH_SWITCH:
42for my $sw (@SWITCH){
43   $switch_level{$sw->{hostname}} = $sw->{level} || $DEFAULT{switch_level}  || 2;
44   $SWITCH_DB{$sw->{hostname}} = $sw;
45   }
46@SWITCH = reverse sort { $switch_level{$a->{hostname}} <=> $switch_level{$b->{hostname}} } @{$KLASK_CFG->{switch}};
47
48my %SWITCH_PORT_COUNT = ();
49
50my %CMD_DB = (
51   'help'                 => \&cmd_help,
52   'version'              => \&cmd_version,
53   'exportdb'             => \&cmd_exportdb,
54   'updatedb'             => \&cmd_updatedb,
55   'searchdb'             => \&cmd_searchdb,
56   'removedb'             => \&cmd_removedb,
57   'cleandb'              => \&cmd_cleandb,
58   'search'               => \&cmd_search,
59   'enable'               => \&cmd_enable,
60   'disable'              => \&cmd_disable,
61   'status'               => \&cmd_status,
62   'updatesw'             => \&cmd_updatesw,
63   'exportsw'             => \&cmd_exportsw,
64   'iplocation'           => \&cmd_ip_location,
65   'ip-free'              => \&cmd_ip_free,
66   'search-mac-on-switch' => \&cmd_search_mac_on_switch,
67   'bad-vlan-id'          => \&cmd_bad_vlan_id,
68   'set-vlan-port'        => \&cmd_set_vlan_port,
69   'get-vlan-port'        => \&cmd_get_vlan_port,
70   'set-vlan-name'        => \&cmd_set_vlan_name,
71   'get-vlan-name'        => \&cmd_get_vlan_name,
72   );
73
74Readonly my %INTERNAL_PORT_MAP => (
75   0 => 'A',
76   1 => 'B',
77   2 => 'C',
78   3 => 'D',
79   4 => 'E',
80   5 => 'F',
81   6 => 'G',
82   7 => 'H',
83   );
84Readonly my %INTERNAL_PORT_MAP_REV => reverse %INTERNAL_PORT_MAP;
85
86Readonly my %SWITCH_KIND => (
87   # HP
88   J3299A => { model => 'HP224M',     match => 'HP J3299A ProCurve Switch 224M'    },
89   J4120A => { model => 'HP1600M',    match => 'HP J4120A ProCurve Switch 1600M'   },
90   J9029A => { model => 'HP1800-8G',  match => 'PROCURVE J9029A'                   },
91   J9449A => { model => 'HP1810-8G',  match => 'HP ProCurve 1810G - 8 GE'          },
92   J4093A => { model => 'HP2424M',    match => 'HP J4093A ProCurve Switch 2424M'   },
93   J9279A => { model => 'HP2510G-24', match => 'ProCurve J9279A Switch 2510G-24'   },
94   J9280A => { model => 'HP2510G-48', match => 'ProCurve J9280A Switch 2510G-48'   },
95   J4813A => { model => 'HP2524',     match => 'HP J4813A ProCurve Switch 2524'    },
96   J4900A => { model => 'HP2626A',    match => 'HP J4900A ProCurve Switch 2626'    },
97   J4900B => { model => 'HP2626B',    match => 'J4900B.+?Switch 2626'              },# ProCurve J4900B Switch 2626 # HP J4900B ProCurve Switch 2626
98   J4899B => { model => 'HP2650',     match => 'ProCurve J4899B Switch 2650'       },
99   J9021A => { model => 'HP2810-24G', match => 'ProCurve J9021A Switch 2810-24G'   },
100   J9022A => { model => 'HP2810-48G', match => 'ProCurve J9022A Switch 2810-48G'   },
101   J8692A => { model => 'HP3500-24G', match => 'J8692A Switch 3500yl-24G'          },
102   J4903A => { model => 'HP2824',     match => 'J4903A.+?Switch 2824,'             },
103   J4110A => { model => 'HP8000M',    match => 'HP J4110A ProCurve Switch 8000M'   },
104   JD374A => { model => 'HP5500-24F', match => 'HP Comware.+?A5500-24G-SFP EI'     },
105   # BayStack
106   BS350T => { model => 'BS350T',     match => 'BayStack 350T HW'                  },
107   # Nexans
108   N3483G => { model => 'NA3483-6G',  match => 'GigaSwitch V3 TP SFP-I 48V ES3'    },
109   # 3COM
110   'H3C5500'        => { model => 'H3C5500',        match => 'H3C S5500-SI Series'              },
111   '3C17203'        => { model => '3C17203',        match => '3Com SuperStack 3 24-Port'        },
112   '3C17204'        => { model => '3C17204',        match => '3Com SuperStack 3 48-Port'        },
113   '3CR17562-91'    => { model => '3CR17562-91',    match => '3Com Switch 4500 50-Port'         },
114   '3CR17255-91'    => { model => '3CR17255-91',    match => '3Com Switch 5500G-EI 48-Port'     },
115   '3CR17251-91'    => { model => '3CR17251-91',    match => '3Com Switch 5500G-EI 48-Port'     },
116   '3CR17571-91'    => { model => '3CR17571-91',    match => '3Com Switch 4500 PWR 26-Port'     },
117   '3CRWX220095A'   => { model => '3CRWX220095A',   match => '3Com Wireless LAN Controller'     },
118   '3CR17254-91'    => { model => '3CR17254-91',    match => '3Com Switch 5500G-EI 24-Port'     },
119   '3CRS48G-24S-91' => { model => '3CRS48G-24S-91', match => '3Com Switch 4800G 24-Port'        },
120   '3CRS48G-48S-91' => { model => '3CRS48G-48S-91', match => '3Com Switch 4800G 48-Port'        },
121   '3C17708'        => { model => '3C17708',        match => '3Com Switch 4050'                 },
122   '3C17709'        => { model => '3C17709',        match => '3Com Switch 4060'                 },
123   '3C17707'        => { model => '3C17707',        match => '3Com Switch 4070'                 },
124   '3CR17258-91'    => { model => '3CR17258-91',    match => '3Com Switch 5500G-EI 24-Port SFP' },
125   '3CR17181-91'    => { model => '3CR17181-91',    match => '3Com Switch 5500-EI 28-Port FX'   },
126   '3CR17252-91'    => { model => '3CR17252-91',    match => '3Com Switch 5500G-EI PWR 24-Port' },
127   '3CR17253-91'    => { model => '3CR17253-91',    match => '3Com Switch 5500G-EI PWR 48-Port' },
128   '3CR17250-91'    => { model => '3CR17250-91',    match => '3Com Switch 5500G-EI 24-Port'     },
129   '3CR17561-91'    => { model => '3CR17561-91',    match => '3Com Switch 4500 26-Port'         },
130   '3CR17572-91'    => { model => '3CR17572-91',    match => '3Com Switch 4500 PWR 50-Port'     },
131   '3C17702-US'     => { model => '3C17702-US',     match => '3Com Switch 4900 SX'              },
132   '3C17700'        => { model => '3C17700',        match => '3Com Switch 4900'                 },
133   );
134
135Readonly my %OID_NUMBER => (
136   sysDescription  => '1.3.6.1.2.1.1.1.0',
137   sysName         => '1.3.6.1.2.1.1.5.0',
138   sysContact      => '1.3.6.1.2.1.1.4.0',
139   sysLocation     => '1.3.6.1.2.1.1.6.0',
140   searchPort1     => '1.3.6.1.2.1.17.4.3.1.2',       # BRIDGE-MIB (802.1D).
141   searchPort2     => '1.3.6.1.2.1.17.7.1.2.2.1.2.0', # Q-BRIDGE-MIB (802.1Q) replace last 0 with real vlan number if needed
142   vlanPortDefault => '1.3.6.1.2.1.17.7.1.4.5.1.1',   # dot1qPvid
143   vlanStatus      => '1.3.6.1.2.1.17.7.1.4.3.1.5',   # integer 4 Create, 6 Destroy
144   vlanName        => '1.3.6.1.2.1.17.7.1.4.3.1.1',   # string
145   );
146
147Readonly 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;
148Readonly my $RE_IPv4_ADDRESS => qr{ [0-9]{1,3} \. [0-9]{1,3} \. [0-9]{1,3} \. [0-9]{1,3} }xms;
149
150Readonly my $RE_FLOAT_HOSTNAME => qr{ ^float }xms;
151
152
153################
154# principal
155################
156
157my $cmd = shift @ARGV || 'help';
158if (defined $CMD_DB{$cmd}) {
159   $CMD_DB{$cmd}->(@ARGV);
160   }
161else {
162   print {*STDERR} "klask: command $cmd not found\n\n";
163   $CMD_DB{help}->();
164   exit 1;
165   }
166
167exit;
168
169sub test_running_environnement {
170   die "Configuration file $KLASK_CFG_FILE does not exists. Klask need it !\n" if not -e "$KLASK_CFG_FILE";
171   die "Var folder $KLASK_VAR does not exists. Klask need it !\n"              if not -d "$KLASK_VAR";
172   return;
173   }
174
175sub test_switchdb_environnement {
176   die "Switch database $KLASK_SW_FILE does not exists. Launch updatesw before this command !\n" if not -e "$KLASK_SW_FILE";
177   return;
178   }
179
180sub test_maindb_environnement {
181   die "Main database $KLASK_DB_FILE does not exists. Launch updatedb before this command !\n" if not -e "$KLASK_DB_FILE";
182   return;
183   }
184
185###
186# fast ping dont l'objectif est de remplir la table arp de la machine
187sub fast_ping {
188   # Launch this command without waiting...
189   system "fping -q -c 1 @_ >/dev/null 2>&1 &";
190   return;
191   }
192
193sub shell_command {
194   my $cmd = shift;
195
196   my $fh     = new FileHandle;
197   my $result = '';
198   open $fh, q{-|}, "LANG=C $cmd" or die "Can't exec $cmd\n";
199   $result .= <$fh>;
200   close $fh;
201   chomp $result;
202   return $result;
203   }
204
205###
206# donne l'@ ip, dns, arp en fonction du dns OU de l'ip
207sub resolve_ip_arp_host {
208   my $param_ip_or_host = shift;
209   my $interface = shift || q{*};
210   my $type      = shift || q{fast};
211
212   my %ret = (
213      hostname_fq  => 'unknow',
214      ipv4_address => '0.0.0.0',
215      mac_address  => 'unknow',
216      );
217
218   # perl -MSocket -E 'say inet_ntoa(scalar gethostbyname("tech7meylan.hmg.inpg.fr"))'
219   my $packed_ip = scalar gethostbyname($param_ip_or_host);
220   return %ret if not defined $packed_ip;
221   $ret{ipv4_address} = inet_ntoa($packed_ip);
222
223   # perl -MSocket -E 'say scalar gethostbyaddr(inet_aton("194.254.66.240"), AF_INET)'
224   my $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET);
225   $ret{hostname_fq} = $hostname_fq if defined $hostname_fq;
226
227   # my $cmd = q{grep  -he '\b} . $param_ip_or_host . q{\b' } . "/var/lib/arpwatch/$interface.dat | sort -rn -k 3,3 | head -1";
228   my $cmd = q{grep  -he '\b} . $ret{ipv4_address} . q{\b' } . "/var/lib/arpwatch/$interface.dat | sort -rn -k 3,3 | head -1";
229   my $cmd_arpwatch = shell_command $cmd;
230   my ($arp, $ip, $timestamp, $host) = split m/ \s+ /xms, $cmd_arpwatch;
231
232   $ret{mac_address}  = $arp       if $arp;
233   $ret{timestamp}    = $timestamp if $timestamp;
234
235   my $nowtimestamp = time;
236
237   if ( $type eq 'fast' and ( not defined $timestamp or $timestamp < ( $nowtimestamp - 45 * 60 ) ) ) { # 45 min
238      $ret{mac_address} = 'unknow';
239      return %ret;
240      }
241
242   # resultat de la commande arp
243   # tech7meylan.hmg.inpg.fr (194.254.66.240) at 00:14:22:45:28:A9 [ether] on eth0
244   # sw2-batF0-legi.hmg.priv (192.168.22.112) at 00:30:c1:76:9c:01 [ether] on eth0.37
245   my $cmd_arp  = shell_command "arp -a $param_ip_or_host";
246   if ( $cmd_arp =~ m{ (\S*) \s \( ( $RE_IPv4_ADDRESS ) \) \s at \s ( $RE_MAC_ADDRESS ) }xms ) {
247      ( $ret{hostname_fq}, $ret{ipv4_address}, $ret{mac_address} )  = ($1, $2, $3);
248      }
249
250   # Normalize MAC Address
251   if ($ret{mac_address} ne 'unknow') {
252      my @paquets = ();
253      foreach ( split m/ : /xms, $ret{mac_address} ) {
254         my @chars = split m//xms, uc "00$_";
255         push @paquets, "$chars[-2]$chars[-1]";
256         }
257      $ret{mac_address} = join q{:}, @paquets;
258      }
259
260   return %ret;
261   }
262
263# Find Surname of a switch
264sub get_switch_model {
265   my $sw_snmp_description = shift || 'unknow';
266
267   for my $sw_kind (keys %SWITCH_KIND) {
268      next if not $sw_snmp_description =~ m/$SWITCH_KIND{$sw_kind}->{match}/ms; # option xms break search, why ?
269
270      return $SWITCH_KIND{$sw_kind}->{model};
271      }
272
273   return $sw_snmp_description;
274   }
275
276###
277# va rechercher le nom des switchs pour savoir qui est qui
278sub init_switch_names {
279   my $verbose = shift;
280
281   printf "%-26s                %-25s %s\n",'Switch','Description','Type' if $verbose;
282   print "------------------------------------------------------------------------------\n" if $verbose;
283
284   INIT_EACH_SWITCH:
285   for my $sw (@SWITCH) {
286      my %session = ( -hostname   => $sw->{hostname} );
287         $session{-version} = $sw->{version}   || 1;
288         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
289         if (exists $sw->{version} and $sw->{version} eq '3') {
290            $session{-username} = $sw->{username} || 'snmpadmin';
291            }
292         else {
293            $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
294            }
295
296      $sw->{local_session} = \%session;
297
298      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
299      print "$error \n" if $error;
300
301      my $result = $session->get_request(
302         -varbindlist => [
303            $OID_NUMBER{sysDescription},
304            $OID_NUMBER{sysName},
305            $OID_NUMBER{sysContact},
306            $OID_NUMBER{sysLocation},
307            ]
308         );
309      $sw->{description} = $result->{$OID_NUMBER{sysName}} || $sw->{hostname};
310      $sw->{model} = get_switch_model( $result->{$OID_NUMBER{sysDescription}});
311      #$sw->{location} = $result->{"1.3.6.1.2.1.1.6.0"} || $sw->{hostname};
312      #$sw->{contact} = $result->{"1.3.6.1.2.1.1.4.0"} || $sw->{hostname};
313      $session->close;
314
315      # Ligne à virer car on récupère maintenant le modèle du switch
316      my ($desc, $type) = split m/ : /xms, $sw->{description}, 2;
317      printf "%-26s 0--------->>>> %-25s %s\n", $sw->{hostname}, $desc, $sw->{model} if $verbose;
318      }
319
320   print "\n" if $verbose;
321   return;
322   }
323
324###
325# convertit l'hexa (uniquement 2 chiffres) en decimal
326sub digit_hex_to_dec {
327   #00:0F:1F:43:E4:2B
328   my $car = '00' . uc shift;
329
330   return '00' if $car eq '00UNKNOW';
331   my %table = (
332      '0'=>'0',  '1'=>'1',  '2'=>'2',  '3'=>'3',  '4'=>'4',
333      '5'=>'5',  '6'=>'6',  '7'=>'7',  '8'=>'8',  '9'=>'9',
334      'A'=>'10', 'B'=>'11', 'C'=>'12', 'D'=>'13', 'E'=>'14', 'F'=>'15',
335      );
336   my @chars = split m//xms, $car;
337   return $table{$chars[-2]}*16 + $table{$chars[-1]};
338   }
339
340#--------------------------------------------------------------------------------
341
342sub normalize_mac_address {
343   my $mac_address = shift;
344
345   # D07E-28D1-7AB8 or d07e28-d17ab8
346   if ($mac_address =~ m{^ (?: [0-9A-Fa-f]{4} -){2} [0-9A-Fa-f]{4} $}xms
347      or $mac_address =~ m{^ [0-9A-Fa-f]{6} - [0-9A-Fa-f]{6} $}xms) {
348      $mac_address =~ s/-//g;
349      return join q{:}, unpack('(A2)*', uc($mac_address));
350      }
351
352   return join q{:}, map { substr( uc("00$_"), -2) } split m/ [:-] /xms, $mac_address;
353   }
354
355#--------------------------------------------------------------------------------
356# convertit l'@ mac en decimal
357sub mac_address_hex_to_dec {
358   #00:0F:1F:43:E4:2B
359   my $mac_address = shift;
360
361   my @paquets = split m/ : /xms, $mac_address;
362   my $return = q{};
363   foreach(@paquets) {
364      $return .= q{.} . digit_hex_to_dec($_);
365      }
366   return $return;
367   }
368
369###
370# va rechercher le port et le switch sur lequel est la machine
371sub find_switch_port {
372   my $mac_address     = shift;
373   my $switch_proposal = shift || q{};
374
375   my %ret;
376   $ret{switch_description} = 'unknow';
377   $ret{switch_port} = '0';
378
379   return %ret if $mac_address eq 'unknow';;
380
381   my @switch_search = @SWITCH;
382   if ($switch_proposal ne q{}) {
383      for my $sw (@SWITCH) {
384         next if $sw->{hostname} ne $switch_proposal;
385         unshift @switch_search, $sw;
386         last;
387         }
388      }
389
390   my $research1 = $OID_NUMBER{searchPort1} . mac_address_hex_to_dec($mac_address);
391   my $research2 = $OID_NUMBER{searchPort2} . mac_address_hex_to_dec($mac_address);
392
393   LOOP_ON_SWITCH:
394   for my $sw (@switch_search) {
395      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
396      print "$error \n" if $error;
397
398      my $result = $session->get_request(
399         -varbindlist => [$research1]
400         );
401      if (not defined $result) {
402         $result = $session->get_request(
403            -varbindlist => [$research2]
404            );
405         $result->{$research1} = $result->{$research2} if defined $result;
406         }
407
408      if (not (defined $result and $result->{$research1} ne 'noSuchInstance')) {
409         $session->close;
410         next LOOP_ON_SWITCH;
411         }
412
413         my $swport = $result->{$research1};
414         $session->close;
415
416         # IMPORTANT !!
417         # ceci empeche la detection sur certains port ...
418         # en effet les switch sont relies entre eux par un cable reseau et du coup
419         # tous les arp de toutes les machines sont presentes sur ces ports (ceux choisis ici sont les miens)
420         # cette partie est a ameliore, voir a configurer dans l'entete
421         # 21->24 45->48
422#         my $flag = 0;
423         SWITCH_PORT_IGNORE:
424         foreach my $p (@{$sw->{portignore}}) {
425            next SWITCH_PORT_IGNORE if $swport ne get_numerical_port($sw->{model},$p);
426#            $flag = 1;
427            next LOOP_ON_SWITCH;
428            }
429#         if ($flag == 0) {
430            $ret{switch_hostname}    = $sw->{hostname};
431            $ret{switch_description} = $sw->{description};
432            $ret{switch_port}        = get_human_readable_port($sw->{model}, $swport); # $swport;
433
434            last LOOP_ON_SWITCH;
435#            }
436#         }
437#      $session->close;
438      }
439   return %ret;
440   }
441
442###
443# va rechercher les port et les switch sur lequel est la machine
444sub find_all_switch_port {
445   my $mac_address = shift;
446
447   my $ret = {};
448
449   return $ret if $mac_address eq 'unknow';
450
451#   for my $sw (@SWITCH) {
452#      next if exists $SWITCH_PORT_COUNT{$sw->{hostname}};
453#
454#      $SWITCH_PORT_COUNT{$sw->{hostname}} = {};
455#      print "DEBUG: SWITCH_PORT_COUNT defined for $sw->{hostname}\n" if $DEBUG xor 2;
456#      }
457
458   my $research1 = $OID_NUMBER{searchPort1} . mac_address_hex_to_dec($mac_address);
459   my $research2 = $OID_NUMBER{searchPort2} . mac_address_hex_to_dec($mac_address);
460   LOOP_ON_ALL_SWITCH:
461   for my $sw (@SWITCH) {
462      my ($session, $error) = Net::SNMP->session( %{$sw->{local_session}} );
463      print "$error \n" if $error;
464
465      my $result = $session->get_request(
466         -varbindlist => [$research1]
467         );
468      if (not defined $result) {
469         $result = $session->get_request(
470            -varbindlist => [$research2]
471            );
472         $result->{$research1} = $result->{$research2} if defined $result;
473         }
474
475      if (defined $result and $result->{$research1} ne 'noSuchInstance') {
476         my $swport = $result->{$research1};
477
478         $ret->{$sw->{hostname}} = {};
479         $ret->{$sw->{hostname}}{hostname}    = $sw->{hostname};
480         $ret->{$sw->{hostname}}{description} = $sw->{description};
481         $ret->{$sw->{hostname}}{port}        = get_human_readable_port($sw->{model}, $swport);
482
483#         $SWITCH_PORT_COUNT{$sw->{hostname}}->{$swport}++;
484         }
485
486      $session->close;
487      }
488   return $ret;
489   }
490
491sub get_list_network {
492
493   return keys %{$KLASK_CFG->{network}};
494   }
495
496sub get_current_interface {
497   my $vlan_name = shift;
498
499   return $KLASK_CFG->{network}{$vlan_name}{interface};
500   }
501
502###
503# liste l'ensemble des adresses ip d'un réseau
504sub get_list_ip {
505   my @vlan_name = @_;
506
507   my $cidrlist = Net::CIDR::Lite->new;
508
509   for my $net (@vlan_name) {
510      my @line  = @{$KLASK_CFG->{network}{$net}{'ip-subnet'}};
511      for my $cmd (@line) {
512         for my $method (keys %{$cmd}){
513            $cidrlist->add_any($cmd->{$method}) if $method eq 'add';
514            }
515         }
516      }
517
518   my @res = ();
519
520   for my $cidr ($cidrlist->list()) {
521      my $net = new NetAddr::IP $cidr;
522      for my $ip (@{$net}) {
523         $ip =~ s{ /32 }{}xms;
524         push @res,  $ip;
525         }
526      }
527
528   return @res;
529   }
530
531# liste l'ensemble des routeurs du réseau
532sub get_list_main_router {
533   my @vlan_name = @_;
534
535   my @res = ();
536
537   for my $net (@vlan_name) {
538      push @res, $KLASK_CFG->{network}{$net}{'main-router'};
539      }
540
541   return @res;
542   }
543
544sub get_human_readable_port {
545   my $sw_model = shift;
546   my $sw_port  = shift;
547
548   if ($sw_model eq 'HP8000M') {
549
550      my $reste = (($sw_port - 1) % 8) + 1;
551      my $major = int (($sw_port - 1) / 8);
552      return "$INTERNAL_PORT_MAP{$major}$reste";
553      }
554
555   if ($sw_model eq 'HP2424M') {
556      if ($sw_port > 24) {
557         
558         my $reste = $sw_port - 24;
559         return "A$reste";
560         }
561      }
562
563   if ($sw_model eq 'HP1600M') {
564      if ($sw_port > 16) {
565         
566         my $reste = $sw_port - 16;
567         return "A$reste";
568         }
569      }
570
571   if ($sw_model eq 'HP2810-48G' or $sw_model eq 'HP2810-24G') {
572      if ($sw_port > 48) {
573         
574         my $reste = $sw_port - 48;
575         return "Trk$reste";
576         }
577      }
578
579   if ($sw_model eq 'HP3500-24G') {
580      if ($sw_port > 289) {
581         
582         my $reste = $sw_port - 289;
583         return "Trk$reste";
584         }
585      }
586
587   return $sw_port;
588   }
589
590sub get_numerical_port {
591   my $sw_model = shift;
592   my $sw_port  = shift;
593
594   if ($sw_model eq 'HP8000M') {
595
596      my $letter = substr $sw_port, 0, 1;
597      my $reste =  substr $sw_port, 1;
598
599      return $INTERNAL_PORT_MAP_REV{$letter} * 8 + $reste;
600      }
601
602   if ($sw_model eq 'HP2424M') {
603      if ($sw_port =~ m/^A/xms ) {
604
605         my $reste =  substr $sw_port, 1;
606
607         return 24 + $reste;
608         }
609      }
610
611   if ($sw_model eq 'HP1600M') {
612      if ($sw_port =~ m/^A/xms ) {
613
614         my $reste =  substr $sw_port, 1;
615
616         return 16 + $reste;
617         }
618      }
619
620   if ($sw_model eq 'HP2810-48G' or $sw_model eq 'HP2810-24G') {
621      if ($sw_port =~ m/^Trk/xms ) {
622
623         my $reste =  substr $sw_port, 3;
624
625         return 48 + $reste;
626         }
627      }
628
629   if ($sw_model eq 'HP3500-24G') {
630      if ($sw_port =~ m/^Trk/xms ) {
631
632         my $reste =  substr $sw_port, 3;
633
634         return 289 + $reste;
635         }
636      }
637
638   return $sw_port;
639   }
640
641################
642# Les commandes
643################
644
645sub cmd_help {
646
647print <<'END';
648klask - ports manager and finder for switch
649
650 klask version
651
652 klask updatedb
653 klask exportdb --format [txt|html]
654 klask removedb computer*
655 klask cleandb  --day number_of_day --verbose
656
657 klask updatesw
658 klask exportsw --format [txt|dot]
659
660 klask searchdb computer
661 klask search   computer
662 klask search-mac-on-switch switch mac_addr
663
664 klask ip-free --day number_of_day --format [txt|html] [vlan_name]
665
666 klask bad-vlan-id
667
668 klask enable  switch port
669 klask disable switch port
670 klask status  switch port
671END
672   return;
673   }
674
675sub cmd_version {
676
677print <<'END';
678Klask - ports manager and finder for switch
679Copyright (C) 2005-2013 Gabriel Moreau
680
681END
682   print ' $Rev: 140 $'."\n";
683   print ' $Date: 2014-05-20 15:22:08 +0000 (Tue, 20 May 2014) $'."\n";
684   print ' $Id: klask 140 2014-05-20 15:22:08Z g7moreau $'."\n";
685   return;
686   }
687
688sub cmd_search {
689   my @computer = @_;
690
691   init_switch_names();    #nomme les switchs
692   fast_ping(@computer);
693
694   LOOP_ON_COMPUTER:
695   for my $clientname (@computer) {
696      my %resol_arp = resolve_ip_arp_host($clientname);          #resolution arp
697      my %where     = find_switch_port($resol_arp{mac_address}); #retrouve l'emplacement
698
699      next LOOP_ON_COMPUTER if $where{switch_description} eq 'unknow' or $resol_arp{hostname_fq} eq 'unknow' or $resol_arp{mac_address} eq 'unknow';
700
701      printf '%-22s %2i %-30s %-15s %18s',
702         $where{switch_hostname},
703         $where{switch_port},
704         $resol_arp{hostname_fq},
705         $resol_arp{ipv4_address},
706         $resol_arp{mac_address}."\n";
707      }
708   return;
709   }
710
711sub cmd_searchdb {
712   my @ARGV  = @_;
713
714   my $kind;
715
716   GetOptions(
717      'kind=s'   => \$kind,
718      );
719
720   my %possible_search = (
721      host  => \&cmd_searchdb_host,
722      mac   => \&cmd_searchdb_mac,
723      );
724
725   $kind = 'host' if not defined $possible_search{$kind};
726
727   $possible_search{$kind}->(@ARGV);
728   return;
729   }
730
731
732sub cmd_searchdb_host {
733   my @computer = @_;
734
735   fast_ping(@computer);
736   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
737
738   LOOP_ON_COMPUTER:
739   for my $clientname (@computer) {
740      my %resol_arp = resolve_ip_arp_host($clientname);      #resolution arp
741      my $ip = $resol_arp{ipv4_address};
742
743      next LOOP_ON_COMPUTER unless exists $computerdb->{$ip};
744
745      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
746      $year += 1900;
747      $mon++;
748      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
749
750      printf "%-22s %2s %-30s %-15s %-18s %s\n",
751         $computerdb->{$ip}{switch_hostname},
752         $computerdb->{$ip}{switch_port},
753         $computerdb->{$ip}{hostname_fq},
754         $ip,
755         $computerdb->{$ip}{mac_address},
756         $date;
757      }
758   return;
759   }
760
761sub cmd_searchdb_mac {
762   my @mac = map { normalize_mac_address($_) } @_;
763
764   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
765
766   LOOP_ON_MAC:
767   for my $mac (@mac) {
768      LOOP_ON_COMPUTER:
769      for my $ip (keys %{$computerdb}) {
770         next LOOP_ON_COMPUTER if $mac ne $computerdb->{$ip}{mac_address};
771 
772         my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
773         $year += 1900;
774         $mon++;
775         my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
776
777         printf "%-22s %2s %-30s %-15s %-18s %s\n",
778            $computerdb->{$ip}{switch_hostname},
779            $computerdb->{$ip}{switch_port},
780            $computerdb->{$ip}{hostname_fq},
781            $ip,
782            $computerdb->{$ip}{mac_address},
783            $date;
784         #next LOOP_ON_MAC;
785         }
786
787      }
788   return;
789   }
790
791sub cmd_updatedb {
792   my @network = @_;
793      @network = get_list_network() if not @network;
794
795   test_switchdb_environnement();
796
797   my $computerdb = {};
798      $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE") if -e "$KLASK_DB_FILE";
799   my $timestamp = time;
800
801   my %computer_not_detected = ();
802   my $timestamp_last_week = $timestamp - (3600 * 24 * 7);
803
804   my $number_of_computer = get_list_ip(@network); # + 1;
805   my $size_of_database   = keys %{$computerdb};
806      $size_of_database   = 1 if $size_of_database == 0;
807   my $i = 0;
808   my $detected_computer = 0;
809
810   init_switch_names('yes');    #nomme les switchs
811
812   { # Remplis le champs portignore des ports d'inter-connection pour chaque switch
813   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
814   my %db_switch_output_port       = %{$switch_connection->{output_port}};
815   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
816   my %db_switch_chained_port = ();
817   for my $swport (keys %db_switch_connected_on_port) {
818      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
819      $db_switch_chained_port{$sw_connect} .= "$port_connect:";
820      }
821   for my $sw (@SWITCH){
822      push @{$sw->{portignore}}, $db_switch_output_port{$sw->{hostname}}  if exists $db_switch_output_port{$sw->{hostname}};
823      if ( exists $db_switch_chained_port{$sw->{hostname}} ) {
824         chop $db_switch_chained_port{$sw->{hostname}};
825         push @{$sw->{portignore}}, split m/ : /xms, $db_switch_chained_port{$sw->{hostname}};
826         }
827#      print "$sw->{hostname} ++ @{$sw->{portignore}}\n";
828      }
829   }
830
831   my %router_mac_ip = ();
832   DETECT_ALL_ROUTER:
833#   for my $one_router ('194.254.66.254') {
834   for my $one_router ( get_list_main_router(@network) ) {
835      my %resol_arp = resolve_ip_arp_host($one_router);
836      $router_mac_ip{ $resol_arp{mac_address} } = $resol_arp{ipv4_address};
837      }
838
839   ALL_NETWORK:
840   for my $net (@network) {
841
842      my @computer = get_list_ip($net);
843      my $current_interface = get_current_interface($net);
844
845      fast_ping(@computer);
846
847      LOOP_ON_COMPUTER:
848      for my $one_computer (@computer) {
849         $i++;
850
851         my $total_percent = int (($i*100)/$number_of_computer);
852
853         my $localtime = time - $timestamp;
854         my ($sec,$min) = localtime $localtime;
855
856         my $time_elapse = 0;
857            $time_elapse = $localtime * ( 100 - $total_percent) / $total_percent if $total_percent != 0;
858         my ($sec_elapse,$min_elapse) = localtime $time_elapse;
859
860         printf "\rComputer scanned: %4i/%i (%2i%%)",  $i,                 $number_of_computer, $total_percent;
861         printf ', detected: %4i/%i (%2i%%)', $detected_computer, $size_of_database,   int(($detected_computer*100)/$size_of_database);
862         printf ' [Time: %02i:%02i / %02i:%02i]', int($localtime/60), $localtime % 60, int($time_elapse/60), $time_elapse % 60;
863         printf ' %-8s %-14s', $current_interface, $one_computer;
864
865         my %resol_arp = resolve_ip_arp_host($one_computer, $current_interface);
866
867         # do not search on router connection (why ?)
868         if ( exists $router_mac_ip{$resol_arp{mac_address}}) {
869            $computer_not_detected{$one_computer} = $current_interface;
870            next LOOP_ON_COMPUTER;
871            }
872
873         # do not search on switch inter-connection
874         if (exists $switch_level{$resol_arp{hostname_fq}}) {
875            $computer_not_detected{$one_computer} = $current_interface;
876            next LOOP_ON_COMPUTER;
877            }
878
879         my $switch_proposal = q{};
880         if (exists $computerdb->{$resol_arp{ipv4_address}} and exists $computerdb->{$resol_arp{ipv4_address}}{switch_hostname}) {
881            $switch_proposal = $computerdb->{$resol_arp{ipv4_address}}{switch_hostname};
882            }
883
884         # do not have a mac address
885         if ($resol_arp{mac_address} eq 'unknow' or (exists $resol_arp{timestamps} and $resol_arp{timestamps} < ($timestamp - 3 * 3600))) {
886            $computer_not_detected{$one_computer} = $current_interface;
887            next LOOP_ON_COMPUTER;
888            }
889
890         my %where = find_switch_port($resol_arp{mac_address},$switch_proposal);
891
892         #192.168.24.156:
893         #  arp: 00:0B:DB:D5:F6:65
894         #  hostname: pcroyon.hmg.priv
895         #  port: 5
896         #  switch: sw-batH-legi:hp2524
897         #  timestamp: 1164355525
898
899         # do not have a mac address
900#         if ($resol_arp{mac_address} eq 'unknow') {
901#            $computer_not_detected{$one_computer} = $current_interface;
902#            next LOOP_ON_COMPUTER;
903#            }
904
905         # detected on a switch
906         if ($where{switch_description} ne 'unknow') {
907            $detected_computer++;
908            $computerdb->{$resol_arp{ipv4_address}} = {
909               hostname_fq        => $resol_arp{hostname_fq},
910               mac_address        => $resol_arp{mac_address},
911               switch_hostname    => $where{switch_hostname},
912               switch_description => $where{switch_description},
913               switch_port        => $where{switch_port},
914               timestamp          => $timestamp,
915               network            => $net,
916               };
917            next LOOP_ON_COMPUTER;
918            }
919
920         # new in the database but where it is ?
921         if (not exists $computerdb->{$resol_arp{ipv4_address}}) {
922            $detected_computer++;
923            $computerdb->{$resol_arp{ipv4_address}} = {
924               hostname_fq        => $resol_arp{hostname_fq},
925               mac_address        => $resol_arp{mac_address},
926               switch_hostname    => $where{switch_hostname},
927               switch_description => $where{switch_description},
928               switch_port        => $where{switch_port},
929               timestamp          => $resol_arp{timestamp},
930               network            => $net,
931               };
932            }
933
934         # mise a jour du nom de la machine si modification dans le dns
935         $computerdb->{$resol_arp{ipv4_address}}{hostname_fq} = $resol_arp{hostname_fq};
936
937         # mise à jour de la date de détection si détection plus récente par arpwatch
938         $computerdb->{$resol_arp{ipv4_address}}{timestamp}   = $resol_arp{timestamp} if exists $resol_arp{timestamp} and $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $resol_arp{timestamp};
939
940         # relance un arping sur la machine si celle-ci n'a pas été détectée depuis plus d'une semaine
941#         push @computer_not_detected, $resol_arp{ipv4_address} if $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $timestamp_last_week;
942         $computer_not_detected{$resol_arp{ipv4_address}} = $current_interface if $computerdb->{$resol_arp{ipv4_address}}{timestamp} < $timestamp_last_week;
943
944         }
945      }
946
947   # final end of line at the end of the loop
948   printf "\n";
949
950   my $dirdb = $KLASK_DB_FILE;
951      $dirdb =~ s{ / [^/]* $}{}xms;
952   mkdir "$dirdb", 0755 unless -d "$dirdb";
953   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
954
955   for my $one_computer (keys %computer_not_detected) {
956      my $interface = $computer_not_detected{$one_computer};
957      system "arping -c 1 -w 1 -rR -i $interface $one_computer &>/dev/null";
958#      print  "arping -c 1 -w 1 -rR -i $interface $one_computer 2>/dev/null\n";
959      }
960   return;
961   }
962
963sub cmd_removedb {
964   my @computer = @_;
965
966   test_maindb_environnement();
967
968   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
969
970   LOOP_ON_COMPUTER:
971   for my $one_computer (@computer) {
972
973      if ( $one_computer =~ m/^ $RE_IPv4_ADDRESS $/xms
974            and exists $computerdb->{$one_computer} ) {
975         delete $computerdb->{$one_computer};
976         next;
977         }
978
979      my %resol_arp = resolve_ip_arp_host($one_computer);
980
981      delete $computerdb->{$resol_arp{ipv4_address}} if exists $computerdb->{$resol_arp{ipv4_address}};
982      }
983
984   my $dirdb = $KLASK_DB_FILE;
985      $dirdb =~ s{ / [^/]* $}{}xms;
986   mkdir "$dirdb", 0755 unless -d "$dirdb";
987   YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
988   return;
989   }
990
991sub cmd_cleandb {
992   my @ARGV  = @_;
993
994   my $days_to_clean = 15;
995   my $verbose;
996   my $database_has_changed;
997
998   GetOptions(
999      'day|d=i'   => \$days_to_clean,
1000      'verbose|v' => \$verbose,
1001      );
1002
1003   my @vlan_name = get_list_network();
1004
1005   my $computerdb = {};
1006      $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE") if -e "$KLASK_DB_FILE";
1007   my $timestamp = time;
1008
1009   my $timestamp_barrier = 3600 * 24 * $days_to_clean;
1010   my $timestamp_3month  = 3600 * 24 * 90;
1011
1012   my %mactimedb = ();
1013   ALL_VLAN:
1014   for my $vlan (shuffle @vlan_name) {
1015
1016      my @ip_list   = shuffle get_list_ip($vlan);
1017     
1018      LOOP_ON_IP_ADDRESS:
1019      for my $ip (@ip_list) {
1020
1021         next LOOP_ON_IP_ADDRESS if
1022            not exists $computerdb->{$ip};
1023           
1024            #&& $computerdb->{$ip}{timestamp} > $timestamp_barrier;
1025         my $ip_timestamp   = $computerdb->{$ip}{timestamp};
1026         my $ip_mac         = $computerdb->{$ip}{mac_address};
1027         my $ip_hostname_fq = $computerdb->{$ip}{hostname_fq};
1028
1029         $mactimedb{$ip_mac} ||= {
1030            ip          => $ip,
1031            timestamp   => $ip_timestamp,
1032            vlan        => $vlan,
1033            hostname_fq => $ip_hostname_fq,
1034            };
1035         
1036         if (
1037            ( $mactimedb{$ip_mac}->{timestamp} - $ip_timestamp > $timestamp_barrier
1038               or (
1039                  $mactimedb{$ip_mac}->{timestamp} > $ip_timestamp
1040                  and $timestamp - $mactimedb{$ip_mac}->{timestamp} > $timestamp_3month
1041                  )
1042            )
1043            and (
1044               not $mactimedb{$ip_mac}->{hostname_fq} =~ m/$RE_FLOAT_HOSTNAME/
1045               or $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
1046               )) {
1047            print "remove ip $ip\n" if $verbose;
1048            delete $computerdb->{$ip};
1049            $database_has_changed++;
1050            }
1051
1052         elsif (
1053            ( $ip_timestamp - $mactimedb{$ip_mac}->{timestamp} > $timestamp_barrier
1054               or (
1055                  $ip_timestamp > $mactimedb{$ip_mac}->{timestamp}
1056                  and $timestamp - $ip_timestamp > $timestamp_3month
1057                  )
1058            )
1059            and (
1060               not $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/
1061               or $mactimedb{$ip_mac}->{hostname_fq} =~ m/$RE_FLOAT_HOSTNAME/
1062               )) {
1063            print "remove ip ".$mactimedb{$ip_mac}->{ip}."\n" if $verbose;
1064            delete $computerdb->{$mactimedb{$ip_mac}->{ip}};
1065            $database_has_changed++;
1066            }
1067
1068         if ( $ip_timestamp > $mactimedb{$ip_mac}->{timestamp}) {
1069            $mactimedb{$ip_mac} = {
1070               ip          => $ip,
1071               timestamp   => $ip_timestamp,
1072               vlan        => $vlan,
1073               hostname_fq => $ip_hostname_fq,
1074               };
1075            }
1076         }
1077      }
1078
1079   if ( $database_has_changed ) {
1080      my $dirdb = $KLASK_DB_FILE;
1081         $dirdb =~ s{ / [^/]* $}{}xms;
1082      mkdir "$dirdb", 0755 unless -d "$dirdb";
1083      YAML::Syck::DumpFile("$KLASK_DB_FILE", $computerdb);
1084      }
1085   return;
1086   }
1087
1088sub cmd_exportdb {
1089   @ARGV = @_;
1090
1091   my $format = 'txt';
1092
1093   GetOptions(
1094      'format|f=s'  => \$format,
1095      );
1096
1097   my %possible_format = (
1098      txt  => \&cmd_exportdb_txt,
1099      html => \&cmd_exportdb_html,
1100      );
1101
1102   $format = 'txt' if not defined $possible_format{$format};
1103
1104   $possible_format{$format}->(@ARGV);
1105   return;
1106   }
1107
1108sub cmd_exportdb_txt {
1109   test_maindb_environnement();
1110
1111   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
1112
1113   printf "%-27s %-4s            %-40s %-15s %-18s %-16s %s\n", qw(Switch Port Hostname-FQ IPv4-Address MAC-Address Date VLAN);
1114   print "--------------------------------------------------------------------------------------------------------------------------------------------\n";
1115
1116   LOOP_ON_IP_ADDRESS:
1117   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1118
1119#      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq 'unknow';
1120
1121      # to be improve in the future
1122      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1123
1124# dans le futur
1125#      next if $computerdb->{$ip}{hostname_fq} eq 'unknow';
1126
1127      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
1128      $year += 1900;
1129      $mon++;
1130      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1131
1132      printf "%-28s  %2s  <-------  %-40s %-15s %-18s %-16s %s\n",
1133         $computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description},
1134         $computerdb->{$ip}{switch_port},
1135         $computerdb->{$ip}{hostname_fq},
1136         $ip,
1137         $computerdb->{$ip}{mac_address},
1138         $date,
1139         $computerdb->{$ip}{network} || '';
1140      }
1141   return;
1142   }
1143
1144sub cmd_exportdb_html {
1145   test_maindb_environnement();
1146
1147   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
1148
1149#<link rel="stylesheet" type="text/css" href="style-klask.css" />
1150#<script src="sorttable-klask.js"></script>
1151
1152   print <<'END_HTML';
1153<table class="sortable" summary="Klask Host Database">
1154 <caption>Klask Host Database</caption>
1155 <thead>
1156  <tr>
1157   <th scope="col" class="klask-header-left">Switch</th>
1158   <th scope="col" class="sorttable_nosort">Port</th>
1159   <th scope="col" class="sorttable_nosort">Link</th>
1160   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
1161   <th scope="col" class="hklask-ipv4">IPv4-Address</th>
1162   <th scope="col" class="sorttable_alpha">MAC-Address</th>
1163   <th scope="col" class="sorttable_alpha">VLAN</th>
1164   <th scope="col" class="klask-header-right">Date</th>
1165  </tr>
1166 </thead>
1167 <tfoot>
1168  <tr>
1169   <th scope="col" class="klask-footer-left">Switch</th>
1170   <th scope="col" class="fklask-port">Port</th>
1171   <th scope="col" class="fklask-link">Link</th>
1172   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
1173   <th scope="col" class="fklask-ipv4">IPv4-Address</th>
1174   <th scope="col" class="fklask-mac">MAC-Address</th>
1175   <th scope="col" class="fklask-vlan">VLAN</th>
1176   <th scope="col" class="klask-footer-right">Date</th>
1177  </tr>
1178 </tfoot>
1179 <tbody>
1180END_HTML
1181
1182   my %mac_count = ();
1183   LOOP_ON_IP_ADDRESS:
1184   foreach my $ip (keys %{$computerdb}) {
1185
1186      # to be improve in the future
1187      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1188
1189      $mac_count{$computerdb->{$ip}{mac_address}}++;
1190      }
1191
1192   my $typerow = 'even';
1193
1194   LOOP_ON_IP_ADDRESS:
1195   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1196
1197      # to be improve in the future
1198      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1199
1200      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
1201      $year += 1900;
1202      $mon++;
1203      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1204
1205#      $odd_or_even++;
1206#      my $typerow = $odd_or_even % 2 ? 'odd' : 'even';
1207      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1208
1209      my $switch_hostname = $computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description} || 'unkown';
1210      chomp $switch_hostname;
1211      my $switch_hostname_sort = sprintf '%s %3s' ,$switch_hostname, $computerdb->{$ip}{switch_port};
1212
1213      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
1214
1215      my $mac_sort = sprintf '%04i-%s', 9999 - $mac_count{$computerdb->{$ip}{mac_address}}, $computerdb->{$ip}{mac_address};
1216
1217      $computerdb->{$ip}{hostname_fq} = 'unknow' if $computerdb->{$ip}{hostname_fq} =~ m/^ \d+ \. \d+ \. \d+ \. \d+ $/xms;
1218      my ( $host_short ) = split m/ \. /xms, $computerdb->{$ip}{hostname_fq};
1219
1220      my $vlan = $computerdb->{$ip}{network} || '';
1221
1222      print <<"END_HTML";
1223  <tr class="$typerow">
1224   <td sorttable_customkey="$switch_hostname_sort">$switch_hostname</td>
1225   <td class="bklask-port">$computerdb->{$ip}{switch_port}</td>
1226   <td><-------</td>
1227   <td sorttable_customkey="$host_short">$computerdb->{$ip}{hostname_fq}</td>
1228   <td sorttable_customkey="$ip_sort">$ip</td>
1229   <td sorttable_customkey="$mac_sort">$computerdb->{$ip}{mac_address}</td>
1230   <td>$vlan</td>
1231   <td>$date</td>
1232  </tr>
1233END_HTML
1234      }
1235
1236   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
1237
1238   my %db_switch_output_port       = %{$switch_connection->{output_port}};
1239   my %db_switch_parent            = %{$switch_connection->{parent}};
1240   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
1241   my %db_switch                   = %{$switch_connection->{switch_db}};
1242
1243   for my $sw (sort keys %db_switch_output_port) {
1244
1245      my $switch_hostname_sort = sprintf '%s %3s' ,$sw, $db_switch_output_port{$sw};
1246
1247      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1248
1249      if (exists $db_switch_parent{$sw}) {
1250
1251      my $mac_address = $db_switch{$db_switch_parent{$sw}->{switch}}->{mac_address};
1252      my $ipv4_address = $db_switch{$db_switch_parent{$sw}->{switch}}->{ipv4_address};
1253      my $timestamp = $db_switch{$db_switch_parent{$sw}->{switch}}->{timestamp};
1254
1255      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1256      $year += 1900;
1257      $mon++;
1258      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1259
1260      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ipv4_address;
1261
1262      my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1263
1264      my ( $host_short ) = sprintf '%s %3s' , split(m/ \. /xms, $db_switch_parent{$sw}->{switch}, 1), $db_switch_parent{$sw}->{port};
1265
1266      print <<"END_HTML";
1267  <tr class="$typerow">
1268   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1269   <td class="bklask-port">$db_switch_output_port{$sw}</>
1270   <td>+--> $db_switch_parent{$sw}->{port}</td>
1271   <td sorttable_customkey="$host_short">$db_switch_parent{$sw}->{switch}</>
1272   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1273   <td sorttable_customkey="$mac_sort">$mac_address</td>
1274   <td></td>
1275   <td>$date</td>
1276  </tr>
1277END_HTML
1278         }
1279      else {
1280         print <<"END_HTML";
1281  <tr class="$typerow">
1282   <td sorttable_customkey="$switch_hostname_sort">$sw</td>
1283   <td class="bklask-port">$db_switch_output_port{$sw}</>
1284   <td>+--></td>
1285   <td sorttable_customkey="router">router</>
1286   <td sorttable_customkey="999999999999"></td>
1287   <td sorttable_customkey="99999"></td>
1288   <td></td>
1289   <td></td>
1290  </tr>
1291END_HTML
1292         }
1293      }
1294
1295   for my $swport (sort keys %db_switch_connected_on_port) {
1296      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1297      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1298
1299         my $switch_hostname_sort = sprintf '%s %3s' ,$sw_connect, $port_connect;
1300
1301      my $mac_address = $db_switch{$sw}->{mac_address};
1302      my $ipv4_address = $db_switch{$sw}->{ipv4_address};
1303      my $timestamp = $db_switch{$sw}->{timestamp};
1304
1305      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $timestamp;
1306      $year += 1900;
1307      $mon++;
1308      my $date = sprintf '%04i-%02i-%02i %02i:%02i', $year,$mon,$mday,$hour,$min;
1309
1310      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ipv4_address;
1311
1312      my $mac_sort = sprintf '%04i-%s', 9999, $mac_address;
1313
1314      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1315
1316         if (exists $db_switch_output_port{$sw}) {
1317
1318            my ( $host_short ) = sprintf '%s %3s' , split( m/\./xms, $sw, 1), $db_switch_output_port{$sw};
1319
1320            print <<"END_HTML";
1321  <tr class="$typerow">
1322   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1323   <td class="bklask-port">$port_connect</>
1324   <td>&lt;--+ $db_switch_output_port{$sw}</td>
1325   <td sorttable_customkey="$host_short">$sw</>
1326   <td sorttable_customkey="$ip_sort">$ipv4_address</td>
1327   <td sorttable_customkey="$mac_sort">$mac_address</td>
1328   <td></td>
1329   <td>$date</td>
1330  </tr>
1331END_HTML
1332            }
1333         else {
1334            print <<"END_HTML";
1335  <tr class="$typerow">
1336   <td sorttable_customkey="$switch_hostname_sort">$sw_connect</td>
1337   <td class="bklask-port">$port_connect</>
1338   <td>&lt;--+</td>
1339   <td sorttable_customkey="$sw">$sw</>
1340   <td sorttable_customkey="">$ipv4_address</td>
1341   <td sorttable_customkey="">$mac_address</td>
1342   <td></td>
1343   <td>$date</td>
1344  </tr>
1345END_HTML
1346            }
1347         }
1348      }
1349
1350   print <<'END_HTML';
1351 </tbody>
1352</table>
1353END_HTML
1354   return;
1355   }
1356
1357sub cmd_bad_vlan_id {
1358   test_maindb_environnement();
1359
1360   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
1361
1362   # create a database with the most recent computer by switch port
1363   my %swithportdb = ();
1364   LOOP_ON_IP_ADDRESS:
1365   foreach my $ip (keys %{$computerdb}) {
1366      # to be improve in the future
1367      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1368      next LOOP_ON_IP_ADDRESS if ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}) eq 'unknow';
1369      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{switch_port} eq '0';
1370
1371      my $ip_timestamp   = $computerdb->{$ip}{timestamp};
1372      my $ip_mac         = $computerdb->{$ip}{mac_address};
1373      my $ip_hostname_fq = $computerdb->{$ip}{hostname_fq};
1374
1375      my $swpt = sprintf "%-28s  %2s",
1376         $computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description},
1377         $computerdb->{$ip}{switch_port};
1378      $swithportdb{$swpt} ||= {
1379         ip          => $ip,
1380         timestamp   => $ip_timestamp,
1381         vlan        => $computerdb->{$ip}{network},
1382         hostname_fq => $ip_hostname_fq,
1383         mac_address => $ip_mac,
1384         };
1385
1386      # if float computer, set date 15 day before warning...
1387      my $ip_timestamp_mod = $ip_timestamp;
1388      my $ip_timestamp_ref = $swithportdb{$swpt}->{timestamp};
1389      $ip_timestamp_mod -= 15 * 24 * 3600 if $ip_hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
1390      $ip_timestamp_ref -= 15 * 24 * 3600 if $swithportdb{$swpt}->{hostname_fq} =~ m/$RE_FLOAT_HOSTNAME/;
1391     
1392      if ($ip_timestamp_mod > $ip_timestamp_ref) {
1393         $swithportdb{$swpt} = {
1394            ip          => $ip,
1395            timestamp   => $ip_timestamp,
1396            vlan        => $computerdb->{$ip}{network},
1397            hostname_fq => $ip_hostname_fq,
1398            mac_address => $ip_mac,
1399            };
1400         }
1401      }
1402
1403   LOOP_ON_RECENT_COMPUTER:
1404   foreach my $swpt (keys %swithportdb) {
1405      next LOOP_ON_RECENT_COMPUTER if $swpt =~ m/^\s*0$/;
1406      next LOOP_ON_RECENT_COMPUTER if $swithportdb{$swpt}->{hostname_fq} !~ m/$RE_FLOAT_HOSTNAME/;
1407
1408      my $src_ip = $swithportdb{$swpt}->{ip};
1409      my $src_timestamp = 0;
1410      LOOP_ON_IP_ADDRESS:
1411      foreach my $ip (keys %{$computerdb}) {
1412         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{mac_address} ne  $swithportdb{$swpt}->{mac_address};
1413         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} =~ m/$RE_FLOAT_HOSTNAME/;
1414         next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{timestamp} < $src_timestamp;
1415         
1416         $src_ip = $ip;
1417         $src_timestamp = $computerdb->{$ip}{timestamp};
1418         }
1419
1420      # keep only if float computer is the most recent
1421      next LOOP_ON_RECENT_COMPUTER if $src_timestamp == 0;
1422      next LOOP_ON_RECENT_COMPUTER if $swithportdb{$swpt}->{timestamp} < $src_timestamp;
1423
1424      my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $swithportdb{$swpt}->{timestamp};
1425      $year += 1900;
1426      $mon++;
1427      my $date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
1428
1429      ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$src_ip}{timestamp};
1430      $year += 1900;
1431      $mon++;
1432      my $src_date = sprintf '%04i-%02i-%02i/%02i:%02i', $year, $mon, $mday, $hour, $min;
1433
1434      printf "%s / %-10s +-> %-10s  %s %s %s %s\n",
1435         $swpt, $swithportdb{$swpt}->{vlan}, $computerdb->{$src_ip}{network},
1436         $date,
1437         $src_date,
1438         $computerdb->{$src_ip}{mac_address},
1439         $computerdb->{$src_ip}{hostname_fq};
1440      }
1441   }
1442
1443sub cmd_set_vlan_port {
1444   my $switch_name = shift || q{};
1445   my $mac_address = shift || q{};
1446
1447   if ($switch_name eq q{} or $mac_address eq q{}) {
1448      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
1449      }
1450
1451   $switch_name = join(',', map {$_->{hostname}} @SWITCH ) if $switch_name eq q{*};
1452
1453   for my $sw_name (split /,/, $switch_name) {
1454      if (not defined $SWITCH_DB{$sw_name}) {
1455         die "Switch $sw_name must be defined in klask configuration file\n";
1456         }
1457
1458      my $sw = $SWITCH_DB{$sw_name};
1459      my %session = ( -hostname => $sw->{hostname} );
1460         $session{-version} = $sw->{version}   || 1;
1461         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
1462      if (exists $sw->{version} and $sw->{version} eq '3') {
1463         $session{-username} = $sw->{username} || 'snmpadmin';
1464         }
1465      else {
1466         $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
1467         }
1468
1469      my $research1 = $OID_NUMBER{searchPort1} . mac_address_hex_to_dec($mac_address);
1470      my $research2 = $OID_NUMBER{searchPort2} . mac_address_hex_to_dec($mac_address);
1471      print "Klask search OID $research1 on switch $sw_name\n";
1472      print "Klask search OID $research2 on switch $sw_name\n";
1473
1474      my ($session, $error) = Net::SNMP->session( %session );
1475      print "$error \n" if $error;
1476
1477      my $result = $session->get_request(
1478         -varbindlist => [$research1]
1479         );
1480      if (not defined $result) {
1481         $result = $session->get_request(
1482            -varbindlist => [$research2]
1483            );
1484         $result->{$research1} = $result->{$research2} if defined $result;
1485         }
1486
1487      if (defined $result and $result->{$research1} ne 'noSuchInstance') {
1488         my $swport = $result->{$research1};
1489         print "Klask find MAC $mac_address on switch $sw_name port $swport\n";
1490         }
1491      else {
1492         print "Klask do not find MAC $mac_address on switch $sw_name\n";
1493         }
1494
1495      $session->close;
1496      }
1497   return;
1498   }
1499
1500sub cmd_get_vlan_port {
1501   }
1502
1503sub cmd_set_vlan_name {
1504   }
1505
1506sub cmd_get_vlan_name {
1507   my $switch_name = shift || q{};
1508   my $vlan_id     = shift || q{};
1509
1510   if ($switch_name eq q{} or $vlan_id eq q{}) {
1511      die "Usage: klask get-vlan-name SWITCH_NAME VLAN_ID\n";
1512      }
1513
1514   $switch_name = join(',', map {$_->{hostname}} @SWITCH ) if $switch_name eq q{*};
1515
1516   for my $sw_name (split /,/, $switch_name) {
1517      if (not defined $SWITCH_DB{$sw_name}) {
1518         die "Switch $sw_name must be defined in klask configuration file\n";
1519         }
1520
1521      my $sw = $SWITCH_DB{$sw_name};
1522      my %session = ( -hostname => $sw->{hostname} );
1523         $session{-version} = $sw->{version}   || 1;
1524         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
1525      if (exists $sw->{version} and $sw->{version} eq '3') {
1526         $session{-username} = $sw->{username} || 'snmpadmin';
1527         }
1528      else {
1529         $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
1530         }
1531
1532      my $search_vlan_name = $OID_NUMBER{vlanName} . ".$vlan_id";
1533
1534      my ($session, $error) = Net::SNMP->session( %session );
1535      print "$error \n" if $error;
1536
1537      my $result = $session->get_request(
1538         -varbindlist => [$search_vlan_name]
1539         );
1540
1541      if (defined $result and $result->{$search_vlan_name} ne 'noSuchInstance') {
1542         my $vlan_name = $result->{$search_vlan_name} || 'empty';
1543         print "Klask find VLAN $vlan_id on switch $sw_name with name $vlan_name\n";
1544         }
1545      else {
1546         print "Klask do not find VLAN $vlan_id on switch $sw_name\n";
1547         }
1548
1549      $session->close;
1550      }
1551   return;
1552   }
1553
1554sub cmd_ip_location {
1555   my $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE");
1556
1557   LOOP_ON_IP_ADDRESS:
1558   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %{$computerdb})) {
1559
1560      next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{hostname_fq} eq ($computerdb->{$ip}{switch_hostname} || $computerdb->{$ip}{switch_description}); # switch on himself !
1561
1562      my $sw_hostname = $computerdb->{$ip}{switch_hostname} || q{};
1563      next LOOP_ON_IP_ADDRESS if $sw_hostname eq 'unknow';
1564
1565      my $sw_location = q{};
1566      LOOP_ON_ALL_SWITCH:
1567      for my $sw (@SWITCH) {
1568         next LOOP_ON_ALL_SWITCH if $sw_hostname ne $sw->{hostname};
1569         $sw_location = $sw->{location};
1570         last;
1571         }
1572
1573      printf "%s: \"%s\"\n", $ip, $sw_location if not $sw_location eq q{};
1574      }
1575   return;
1576   }
1577
1578sub cmd_ip_free {
1579   @ARGV = @_; # VLAN name with option
1580
1581   my $days_to_dead = 365 * 2;
1582   my $format = 'txt';
1583   my $verbose;
1584
1585   GetOptions(
1586      'day|d=i'      => \$days_to_dead,
1587      'format|f=s'   => \$format,
1588      'verbose|v'    => \$verbose,
1589      );
1590
1591   my %possible_format = (
1592      txt  => \&cmd_ip_free_txt,
1593      html => \&cmd_ip_free_html,
1594      none => sub {},
1595      );
1596   $format = 'txt' if not defined $possible_format{$format};
1597
1598   my @vlan_name = @ARGV;
1599   @vlan_name = get_list_network() if not @vlan_name;
1600
1601   my $computerdb = {};
1602      $computerdb = YAML::Syck::LoadFile("$KLASK_DB_FILE") if -e "$KLASK_DB_FILE";
1603   my $timestamp = time;
1604
1605   my $timestamp_barrier = $timestamp - (3600 * 24 * $days_to_dead );
1606
1607   my %result_ip = ();
1608
1609   ALL_NETWORK:
1610   for my $vlan (@vlan_name) {
1611
1612      my @ip_list = get_list_ip($vlan);
1613
1614      LOOP_ON_IP_ADDRESS:
1615      for my $ip (@ip_list) {
1616
1617         if (exists $computerdb->{$ip}) {
1618            next LOOP_ON_IP_ADDRESS if $computerdb->{$ip}{timestamp} > $timestamp_barrier;
1619           
1620            my $mac_address = $computerdb->{$ip}{mac_address};
1621            LOOP_ON_DATABASE:
1622            foreach my $ip_db (keys %{$computerdb}) {
1623               next LOOP_ON_DATABASE if $computerdb->{$ip_db}{mac_address} ne $mac_address;
1624               next LOOP_ON_IP_ADDRESS if $computerdb->{$ip_db}{timestamp} > $timestamp_barrier;
1625               }
1626            }
1627
1628         my $ip_date_last_detection = '';
1629         if (exists $computerdb->{$ip}) {
1630            my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime $computerdb->{$ip}{timestamp};
1631            $year += 1900;
1632            $mon++;
1633            $ip_date_last_detection = sprintf '%04i-%02i-%02i %02i:%02i', $year, $mon, $mday, $hour, $min;
1634            }
1635
1636         my $packed_ip = scalar gethostbyname($ip);
1637         my $hostname_fq = 'unknown';
1638            $hostname_fq = scalar gethostbyaddr($packed_ip, AF_INET) || 'unknown' if defined $packed_ip;
1639
1640         next LOOP_ON_IP_ADDRESS if $hostname_fq =~ m/$RE_FLOAT_HOSTNAME/;
1641
1642         $result_ip{$ip} ||= {};
1643         $result_ip{$ip}->{date_last_detection} = $ip_date_last_detection;
1644         $result_ip{$ip}->{hostname_fq} = $hostname_fq;
1645         $result_ip{$ip}->{vlan} = $vlan;
1646
1647         printf "VERBOSE_1: %-15s %-12s %s\n", $ip, $vlan, $hostname_fq if $verbose;
1648         }
1649      }
1650
1651   $possible_format{$format}->(%result_ip);
1652   }
1653
1654sub cmd_ip_free_txt {
1655   my %result_ip = @_;
1656   
1657   printf "%-15s %-40s %-16s %s\n", qw(IPv4-Address Hostname-FQ Date VLAN);
1658   print "-------------------------------------------------------------------------------\n";
1659   LOOP_ON_IP_ADDRESS:
1660   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
1661         printf "%-15s %-40s %-16s %s\n", $ip, $result_ip{$ip}->{hostname_fq}, $result_ip{$ip}->{date_last_detection}, $result_ip{$ip}->{vlan};
1662      }
1663   }
1664
1665sub cmd_ip_free_html {
1666   my %result_ip = @_;
1667
1668   print <<'END_HTML';
1669<table class="sortable" summary="Klask Free IP Database">
1670 <caption>Klask Free IP Database</caption>
1671 <thead>
1672  <tr>
1673   <th scope="col" class="klask-header-left">IPv4-Address</th>
1674   <th scope="col" class="sorttable_alpha">Hostname-FQ</th>
1675   <th scope="col" class="sorttable_alpha">VLAN</th>
1676   <th scope="col" class="klask-header-right">Date</th>
1677  </tr>
1678 </thead>
1679 <tfoot>
1680  <tr>
1681   <th scope="col" class="klask-footer-left">IPv4-Address</th>
1682   <th scope="col" class="fklask-hostname">Hostname-FQ</th>
1683   <th scope="col" class="fklask-vlan">VLAN</th>
1684   <th scope="col" class="klask-footer-right">Date</th>
1685  </tr>
1686 </tfoot>
1687 <tbody>
1688END_HTML
1689
1690   my $typerow = 'even';
1691
1692   LOOP_ON_IP_ADDRESS:
1693   foreach my $ip (Net::Netmask::sort_by_ip_address(keys %result_ip)) {
1694
1695      $typerow = $typerow eq 'even' ? 'odd' : 'even';
1696
1697      my $ip_sort = sprintf '%03i%03i%03i%03i', split m/ \. /xms, $ip;
1698      my ( $host_short ) = split m/ \. /xms, $result_ip{$ip}->{hostname_fq};
1699
1700      print <<"END_HTML";
1701  <tr class="$typerow">
1702   <td sorttable_customkey="$ip_sort">$ip</td>
1703   <td sorttable_customkey="$host_short">$result_ip{$ip}->{hostname_fq}</td>
1704   <td>$result_ip{$ip}->{vlan}</td>
1705   <td>$result_ip{$ip}->{date_last_detection}</td>
1706  </tr>
1707END_HTML
1708      }
1709   print <<'END_HTML';
1710 </tbody>
1711</table>
1712END_HTML
1713   }
1714
1715sub cmd_enable {
1716   my $switch = shift;
1717   my $port   = shift;
1718
1719   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 1 (up)
1720   #snmpset -v 1 -c community X.X.X.X 1.3.6.1.2.1.2.2.1.7.NoPort = 2 (down)
1721   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 1";
1722   return;
1723   }
1724
1725sub cmd_disable {
1726   my $switch = shift;
1727   my $port   = shift;
1728
1729   system "snmpset -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port = 2";
1730   return;
1731   }
1732
1733sub cmd_status {
1734   my $switch = shift;
1735   my $port   = shift;
1736
1737   system "snmpget -v 1 -c public $switch 1.3.6.1.2.1.2.2.1.7.$port";
1738   return;
1739   }
1740
1741sub cmd_search_mac_on_switch {
1742   @ARGV = @_;
1743
1744   my $verbose;
1745
1746   GetOptions(
1747      'verbose|v' => \$verbose,
1748      );
1749
1750   my $switch_name = shift @ARGV || q{};
1751   my $mac_address = shift @ARGV || q{};
1752
1753   if ($switch_name eq q{} or $mac_address eq q{}) {
1754      die "Usage: klask search-mac-on-switch SWITCH_NAME MAC_ADDRESS\n";
1755      }
1756
1757   $mac_address = normalize_mac_address($mac_address);
1758   $switch_name = join(',', map {$_->{hostname}} @SWITCH ) if $switch_name eq q{*};
1759
1760   for my $sw_name (split /,/, $switch_name) {
1761      if (not defined $SWITCH_DB{$sw_name}) {
1762         die "Switch $sw_name must be defined in klask configuration file\n";
1763         }
1764
1765      my $sw = $SWITCH_DB{$sw_name};
1766      my %session = ( -hostname => $sw->{hostname} );
1767         $session{-version} = $sw->{version}   || 1;
1768         $session{-port}    = $sw->{snmpport}  || $DEFAULT{snmpport}  || 161;
1769      if (exists $sw->{version} and $sw->{version} eq '3') {
1770         $session{-username} = $sw->{username} || 'snmpadmin';
1771         }
1772      else {
1773         $session{-community} = $sw->{community} || $DEFAULT{community} || 'public';
1774         }
1775
1776      my $research1 = $OID_NUMBER{searchPort1} . mac_address_hex_to_dec($mac_address);
1777      my $research2 = $OID_NUMBER{searchPort2} . mac_address_hex_to_dec($mac_address);
1778      print "Klask search OID $research1 on switch $sw_name\n" if $verbose;
1779      print "Klask search OID $research2 on switch $sw_name\n" if $verbose;
1780
1781      my ($session, $error) = Net::SNMP->session( %session );
1782      print "$error \n" if $error;
1783
1784      my $result = $session->get_request(
1785         -varbindlist => [$research1]
1786         );
1787      if (not defined $result) {
1788         $result = $session->get_request(
1789            -varbindlist => [$research2]
1790            );
1791         $result->{$research1} = $result->{$research2} if defined $result;
1792         }
1793
1794      if (defined $result and $result->{$research1} ne 'noSuchInstance') {
1795         my $swport = $result->{$research1};
1796         print "Klask find MAC $mac_address on switch $sw_name port $swport\n";
1797         }
1798      else {
1799         print "Klask do not find MAC $mac_address on switch $sw_name\n" if $verbose;
1800         }
1801
1802      $session->close;
1803      }
1804   return;
1805   }
1806
1807sub cmd_updatesw {
1808   @ARGV = @_;
1809
1810   my $verbose;
1811
1812   GetOptions(
1813      'verbose|v' => \$verbose,
1814      );
1815
1816   init_switch_names('yes');    #nomme les switchs
1817   print "\n";
1818
1819   my %where = ();
1820   my %db_switch_output_port = ();
1821   my %db_switch_ip_hostnamefq = ();
1822
1823   DETECT_ALL_ROUTER:
1824#   for my $one_computer ('194.254.66.254') {
1825   for my $one_router ( get_list_main_router(get_list_network()) ) {
1826      my %resol_arp = resolve_ip_arp_host($one_router, q{*}, q{low}); # resolution arp
1827
1828      next DETECT_ALL_ROUTER if $resol_arp{mac_address} eq 'unknow';
1829      print "VERBOSE_1: Router detected $resol_arp{ipv4_address} - $resol_arp{mac_address}\n" if $verbose;
1830
1831      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # retrouve les emplacements des routeurs
1832      }
1833
1834   ALL_ROUTER_IP_ADDRESS:
1835   for my $ip_router (Net::Netmask::sort_by_ip_address(keys %where)) { # '194.254.66.254')) {
1836
1837      next ALL_ROUTER_IP_ADDRESS if not exists $where{$ip_router}; # /a priori/ idiot car ne sers à rien...
1838
1839      ALL_SWITCH_CONNECTED:
1840      for my $switch_detected ( keys %{$where{$ip_router}} ) {
1841
1842         my $switch = $where{$ip_router}->{$switch_detected};
1843
1844         next ALL_SWITCH_CONNECTED if $switch->{port} eq '0';
1845
1846         $db_switch_output_port{$switch->{hostname}} = $switch->{port};
1847         print "VERBOSE_2: output port $switch->{hostname} : $switch->{port}\n" if $verbose;
1848         }
1849      }
1850
1851   my %db_switch_link_with = ();
1852
1853   my @list_all_switch = ();
1854   my @list_switch_ipv4 = ();
1855   for my $sw (@SWITCH){
1856      push @list_all_switch, $sw->{hostname};
1857      }
1858
1859   my $timestamp = time;
1860
1861   ALL_SWITCH:
1862   for my $one_computer (@list_all_switch) {
1863      my %resol_arp = resolve_ip_arp_host($one_computer, q{*}, q{low}); # arp resolution
1864      next ALL_SWITCH if $resol_arp{mac_address} eq 'unknow';
1865
1866      push @list_switch_ipv4, $resol_arp{ipv4_address};
1867
1868      $where{$resol_arp{ipv4_address}} = find_all_switch_port($resol_arp{mac_address}); # find port on all switch
1869
1870      if ($verbose) {
1871         print "VERBOSE_3: $one_computer $resol_arp{ipv4_address} $resol_arp{mac_address}\n";
1872         print "VERBOSE_3: $one_computer --- ",
1873            join(' + ', keys %{$where{$resol_arp{ipv4_address}}}),
1874            "\n";
1875         }
1876
1877      $db_switch_ip_hostnamefq{$resol_arp{ipv4_address}} = $resol_arp{hostname_fq};
1878      print "VERBOSE_4: db_switch_ip_hostnamefq $resol_arp{ipv4_address} -> $resol_arp{hostname_fq}\n" if $verbose;
1879
1880      $SWITCH_DB{$one_computer}->{ipv4_address} = $resol_arp{ipv4_address};
1881      $SWITCH_DB{$one_computer}->{mac_address}  = $resol_arp{mac_address};
1882      $SWITCH_DB{$one_computer}->{timestamp}    = $timestamp;
1883      }
1884
1885   ALL_SWITCH_IP_ADDRESS:
1886   for my $ip (Net::Netmask::sort_by_ip_address(@list_switch_ipv4)) {
1887
1888      print "VERBOSE_5: loop on $db_switch_ip_hostnamefq{$ip}\n" if $verbose;
1889
1890      next ALL_SWITCH_IP_ADDRESS if not exists $where{$ip};
1891#      next ALL_SWITCH_IP_ADDRESS if not exists $SWITCH_PORT_COUNT{ $db_switch_ip_hostnamefq{$ip} };
1892
1893      DETECTED_SWITCH:
1894      for my $switch_detected ( keys %{$where{$ip}} ) {
1895
1896         my $switch = $where{$ip}->{$switch_detected};
1897         print "VERBOSE_6: $db_switch_ip_hostnamefq{$ip} -> $switch->{hostname} : $switch->{port}\n" if $verbose;
1898
1899         next if $switch->{port}     eq '0';
1900         next if $switch->{port}     eq $db_switch_output_port{$switch->{hostname}};
1901         next if $switch->{hostname} eq $db_switch_ip_hostnamefq{$ip}; # $computerdb->{$ip}{hostname};
1902
1903         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} } ||= {};
1904         $db_switch_link_with{ $db_switch_ip_hostnamefq{$ip} }->{ $switch->{hostname} } = $switch->{port};
1905         print "VERBOSE_7: +++++\n" if $verbose;
1906         }
1907
1908      }
1909
1910   my %db_switch_connected_on_port = ();
1911   my $maybe_more_than_one_switch_connected = 'yes';
1912
1913   while ($maybe_more_than_one_switch_connected eq 'yes') {
1914      for my $sw (keys %db_switch_link_with) {
1915         for my $connect (keys %{$db_switch_link_with{$sw}}) {
1916
1917            my $port = $db_switch_link_with{$sw}->{$connect};
1918
1919            $db_switch_connected_on_port{"$connect:$port"} ||= {};
1920            $db_switch_connected_on_port{"$connect:$port"}->{$sw}++; # Just to define the key
1921            }
1922         }
1923
1924      $maybe_more_than_one_switch_connected  = 'no';
1925
1926      SWITCH_AND_PORT:
1927      for my $swport (keys %db_switch_connected_on_port) {
1928
1929         next if keys %{$db_switch_connected_on_port{$swport}} == 1;
1930
1931         $maybe_more_than_one_switch_connected = 'yes';
1932
1933         my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1934         my @sw_on_same_port = keys %{$db_switch_connected_on_port{$swport}};
1935
1936         CONNECTED:
1937         for my $sw_connected (@sw_on_same_port) {
1938
1939            next CONNECTED if not keys %{$db_switch_link_with{$sw_connected}} == 1;
1940
1941            $db_switch_connected_on_port{$swport} = {$sw_connected => 1};
1942
1943            for my $other_sw (@sw_on_same_port) {
1944               next if $other_sw eq $sw_connected;
1945
1946               delete $db_switch_link_with{$other_sw}->{$sw_connect};
1947               }
1948
1949            # We can not do better for this switch for this loop
1950            next SWITCH_AND_PORT;
1951            }
1952         }
1953      }
1954
1955   my %db_switch_parent =();
1956
1957   for my $sw (keys %db_switch_link_with) {
1958      for my $connect (keys %{$db_switch_link_with{$sw}}) {
1959
1960         my $port = $db_switch_link_with{$sw}->{$connect};
1961
1962         $db_switch_connected_on_port{"$connect:$port"} ||= {};
1963         $db_switch_connected_on_port{"$connect:$port"}->{$sw} = $port;
1964
1965         $db_switch_parent{$sw} = {switch => $connect, port => $port};
1966         }
1967      }
1968
1969   print "Switch output port and parent port connection\n";
1970   print "---------------------------------------------\n";
1971   for my $sw (sort keys %db_switch_output_port) {
1972      if (exists $db_switch_parent{$sw}) {
1973         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
1974         }
1975      else {
1976         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
1977         }
1978      }
1979   print "\n";
1980
1981   print "Switch parent and children port inter-connection\n";
1982   print "------------------------------------------------\n";
1983   for my $swport (sort keys %db_switch_connected_on_port) {
1984      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
1985      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
1986         if (exists $db_switch_output_port{$sw}) {
1987            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
1988            }
1989         else {
1990            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
1991            }
1992         }
1993      }
1994
1995   my $switch_connection = {
1996      output_port       => \%db_switch_output_port,
1997      parent            => \%db_switch_parent,
1998      connected_on_port => \%db_switch_connected_on_port,
1999      link_with         => \%db_switch_link_with,
2000      switch_db         => \%SWITCH_DB,
2001      };
2002
2003   YAML::Syck::DumpFile("$KLASK_SW_FILE", $switch_connection);
2004   return;
2005   }
2006
2007sub cmd_exportsw {
2008   @ARGV = @_;
2009
2010   test_switchdb_environnement();
2011
2012   my $format = 'txt';
2013
2014   GetOptions(
2015      'format|f=s'  => \$format,
2016      );
2017
2018   my %possible_format = (
2019      txt => \&cmd_exportsw_txt,
2020      dot => \&cmd_exportsw_dot,
2021      );
2022
2023   $format = 'txt' if not defined $possible_format{$format};
2024
2025   $possible_format{$format}->(@ARGV);
2026   return;
2027   }
2028
2029sub cmd_exportsw_txt {
2030
2031   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2032
2033   my %db_switch_output_port       = %{$switch_connection->{output_port}};
2034   my %db_switch_parent            = %{$switch_connection->{parent}};
2035   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
2036
2037   print "Switch output port and parent port connection\n";
2038   print "---------------------------------------------\n";
2039   for my $sw (sort keys %db_switch_output_port) {
2040      if (exists $db_switch_parent{$sw}) {
2041         printf "%-28s  %2s  +-->  %2s  %-25s\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{port}, $db_switch_parent{$sw}->{switch};
2042         }
2043      else {
2044         printf "%-28s  %2s  +-->  router\n", $sw, $db_switch_output_port{$sw};
2045         }
2046      }
2047   print "\n";
2048
2049   print "Switch parent and children port inter-connection\n";
2050   print "------------------------------------------------\n";
2051   for my $swport (sort keys %db_switch_connected_on_port) {
2052      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2053      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2054         if (exists $db_switch_output_port{$sw}) {
2055            printf "%-28s  %2s  <--+  %2s  %-25s\n", $sw_connect, $port_connect, $db_switch_output_port{$sw}, $sw;
2056            }
2057         else {
2058            printf "%-28s  %2s  <--+      %-25s\n", $sw_connect, $port_connect, $sw;
2059            }
2060         }
2061      }
2062   return;
2063   }
2064
2065sub cmd_exportsw_dot {
2066
2067   my $switch_connection = YAML::Syck::LoadFile("$KLASK_SW_FILE");
2068
2069   my %db_switch_output_port       = %{$switch_connection->{output_port}};
2070   my %db_switch_parent            = %{$switch_connection->{parent}};
2071   my %db_switch_connected_on_port = %{$switch_connection->{connected_on_port}};
2072   my %db_switch_link_with         = %{$switch_connection->{link_with}};
2073   my %db_switch_global            = %{$switch_connection->{switch_db}};
2074
2075   my %db_building= ();
2076   for my $sw (@SWITCH) {
2077      my ($building, $location) = split m/ \/ /xms, $sw->{location}, 2;
2078      $db_building{$building} ||= {};
2079      $db_building{$building}->{$location} ||= {};
2080      $db_building{$building}->{$location}{ $sw->{hostname} } = 'y';
2081      }
2082
2083
2084   print "digraph G {\n";
2085   print "rankdir = LR;\n";
2086
2087   print "site [label = \"site\", color = black, fillcolor = gold, shape = invhouse, style = filled];\n";
2088   print "internet [label = \"internet\", color = black, fillcolor = cyan, shape = house, style = filled];\n";
2089
2090   my $b=0;
2091   for my $building (keys %db_building) {
2092      $b++;
2093
2094      print "\"building$b\" [label = \"$building\", color = black, fillcolor = gold, style = filled];\n";
2095      print "site -> \"building$b\" [len = 2, color = firebrick];\n";
2096
2097      my $l = 0;
2098      for my $loc (keys %{$db_building{$building}}) {
2099         $l++;
2100
2101         print "\"location$b-$l\" [label = \"$building" . q{/} . join(q{\n}, split(m{ / }xms, $loc)) . "\", color = black, fillcolor = orange, style = filled];\n";
2102#         print "\"location$b-$l\" [label = \"$building / $loc\", color = black, fillcolor = orange, style = filled];\n";
2103         print "\"building$b\" -> \"location$b-$l\" [len = 2, color = firebrick]\n";
2104
2105         for my $sw (keys %{$db_building{$building}->{$loc}}) {
2106
2107            print "\"$sw:$db_switch_output_port{$sw}\" [label = $db_switch_output_port{$sw}, color = black, fillcolor = lightblue,  peripheries = 2, style = filled];\n";
2108
2109            my $swname  = $sw;
2110               $swname .= q{\n-\n} . "$db_switch_global{$sw}->{model}" if exists $db_switch_global{$sw} and exists $db_switch_global{$sw}->{model};
2111            print "\"$sw\" [label = \"$swname\", color = black, fillcolor = palegreen, shape = rect, style = filled];\n";
2112            print "\"location$b-$l\" -> \"$sw\" [len = 2, color = firebrick, arrowtail = dot]\n";
2113            print "\"$sw\" -> \"$sw:$db_switch_output_port{$sw}\" [len=2, style=bold, arrowhead = normal, arrowtail = invdot]\n";
2114
2115
2116            for my $swport (keys %db_switch_connected_on_port) {
2117               my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2118               next if not $sw_connect eq $sw;
2119               next if $port_connect eq $db_switch_output_port{$sw};
2120               print "\"$sw:$port_connect\" [label = $port_connect, color = black, fillcolor = plum,  peripheries = 1, style = filled];\n";
2121               print "\"$sw:$port_connect\" -> \"$sw\" [len=2, style=bold, arrowhead= normal, arrowtail = inv]\n";
2122              }
2123            }
2124         }
2125      }
2126
2127#   print "Switch output port and parent port connection\n";
2128#   print "---------------------------------------------\n";
2129   for my $sw (sort keys %db_switch_output_port) {
2130      if (exists $db_switch_parent{$sw}) {
2131#         printf "   \"%s:%s\" -> \"%s:%s\"\n", $sw, $db_switch_output_port{$sw}, $db_switch_parent{$sw}->{switch}, $db_switch_parent{$sw}->{port};
2132         }
2133      else {
2134         printf "   \"%s:%s\" -> internet\n", $sw, $db_switch_output_port{$sw};
2135         }
2136      }
2137   print "\n";
2138
2139#   print "Switch parent and children port inter-connection\n";
2140#   print "------------------------------------------------\n";
2141   for my $swport (sort keys %db_switch_connected_on_port) {
2142      my ($sw_connect,$port_connect) = split m/ : /xms, $swport;
2143      for my $sw (keys %{$db_switch_connected_on_port{$swport}}) {
2144         if (exists $db_switch_output_port{$sw}) {
2145            printf "   \"%s:%s\" -> \"%s:%s\" [color = navyblue]\n", $sw, $db_switch_output_port{$sw}, $sw_connect, $port_connect;
2146            }
2147         else {
2148            printf "   \"%s\"   -> \"%s%s\"\n", $sw, $sw_connect, $port_connect;
2149            }
2150         }
2151      }
2152
2153print "}\n";
2154   return;
2155   }
2156
2157
2158__END__
2159
2160=head1 NAME
2161
2162klask - ports manager and finder for switch
2163
2164
2165=head1 USAGE
2166
2167 klask updatedb
2168 klask exportdb --format [txt|html]
2169 klask removedb computer*
2170 klask cleandb  --day number_of_day --verbose
2171
2172 klask updatesw
2173 klask exportsw --format [txt|dot]
2174
2175 klask searchdb --kind [host|mac] computer [mac-address]
2176 klask search   computer
2177 klask search-mac-on-switch switch mac_addr
2178
2179 klask ip-free --day number_of_day --format [txt|html] [vlan_name]
2180
2181 klask enable  switch port
2182 klask disable swith port
2183 klask status  swith port
2184
2185
2186=head1 DESCRIPTION
2187
2188klask is a small tool to find where is a host in a big network. klask mean search in brittany.
2189
2190Klask has now a web site dedicated for it !
2191
2192 http://servforge.legi.grenoble-inp.fr/projects/klask
2193
2194
2195=head1 COMMANDS
2196
2197
2198=head2 search
2199
2200This command takes one or more computer in argument. It search a computer on the network and give the port and the switch on which the computer is connected.
2201
2202
2203=head2 enable
2204
2205This command activate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
2206
2207
2208=head2 disable
2209
2210This command deactivate a port on a switch by snmp. So you need to give the switch and the port number on the command line.
2211
2212
2213=head2 status
2214
2215This command return the status of a port number on a switch by snmp. So you need to give the switch name and the port number on the command line.
2216
2217
2218=head2 updatedb
2219
2220This command will scan networks and update a database. To know which are the cmputer scan, you have to configure the file /etc/klask/klask.conf This file is easy to read and write because klask use YAML format and not XML.
2221
2222
2223=head2 exportdb
2224
2225This command print the content of the database. There is actually only one format. It's very easy to have more format, it's just need times...
2226
2227
2228=head2 updatesw
2229
2230This command build a map of your manageable switch on your network. The list of the switch must be given in the file /etc/klask/klask.conf.
2231
2232
2233=head2 exportsw --format [txt|dot]
2234
2235This command print the content of the switch database. There is actually two format. One is just txt for terminal and the other is the dot format from the graphviz environnement.
2236
2237 klask exportsw --format dot > /tmp/map.dot
2238 dot -Tpng /tmp/map.dot > /tmp/map.png
2239
2240
2241
2242=head1 CONFIGURATION
2243
2244Because klask need many parameters, it's not possible actually to use command line parameters. The configuration is done in a /etc/klask/klask.conf YAML file. This format have many advantage over XML, it's easier to read and to write !
2245
2246Here an example, be aware with indent, it's important in YAML, do not use tabulation !
2247
2248 default:
2249   community: public
2250   snmpport: 161
2251
2252 network:
2253   labnet:
2254     ip-subnet:
2255       - add: 192.168.1.0/24
2256       - add: 192.168.2.0/24
2257     interface: eth0
2258     main-router: gw1.labnet.local
2259
2260   schoolnet:
2261     ip-subnet:
2262       - add: 192.168.6.0/24
2263       - add: 192.168.7.0/24
2264     interface: eth0.38
2265     main-router: gw2.schoolnet.local
2266
2267 switch:
2268   - hostname: sw1.klask.local
2269     portignore:
2270       - 1
2271       - 2
2272
2273   - hostname: sw2.klask.local
2274     location: BatK / 2 / K203
2275     type: HP2424
2276     portignore:
2277       - 1
2278       - 2
2279
2280I think it's pretty easy to understand. The default section can be overide in any section, if parameter mean something in theses sections. Network to be scan are define in the network section. You must put a add by network. Maybe i will make a delete line to suppress specific computers. The switch section define your switch. You have to write the port number to ignore, this is important if your switchs are cascade. Juste put the ports numbers between switch.
2281
2282
2283=head1 FILES
2284
2285 /etc/klask/klask.conf
2286 /var/lib/klask/klaskdb
2287 /var/lib/klask/switchdb
2288
2289=head1 SEE ALSO
2290
2291Net::SNMP, Net::Netmask, Net::CIDR::Lite, NetAddr::IP, YAML
2292
2293
2294=head1 VERSION
2295
2296$Id: klask 140 2014-05-20 15:22:08Z g7moreau $
2297
2298
2299=head1 AUTHOR
2300
2301Written by Gabriel Moreau, Grenoble - France
2302
2303
2304=head1 LICENSE AND COPYRIGHT
2305
2306GPL version 2 or later and Perl equivalent
2307
2308Copyright (C) 2005-2013 Gabriel Moreau.
Note: See TracBrowser for help on using the repository browser.