source: trunk/klask @ 126

Last change on this file since 126 was 126, checked in by g7moreau, 11 years ago

/bin/bash: q : commande introuvable

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