#!/usr/bin/perl use strict; use warnings; use Tk; use Tk::ROText; # set to `0' if you do not want the actual MACs of detected NICs to be used # if set to `1', will gracefully/silently be ignored if no NICs are detected my $use_real_macs = 1; # get NICs available to system opendir(DIR,'/sys/class/net') or die "can't opendir '/sys/class/net': $!\n"; my @nics = sort grep{!/lo/ && !/^\.\.?$/} readdir(DIR); closedir(DIR); # define some NICs (for display purposes) if none are found unless($#nics>=0){ @nics = ('eth0','eth1'); $use_real_macs = 0; } my $mw = MainWindow->new(-title => 'MAC Address Tool'); $mw->fontCreate('sans_8', -family => 'sans', -weight => 'normal', -size => 8, ); $mw->fontCreate('mono', -family => 'mono', -weight => 'bold', -size => 12, ); my $fr1 = $mw->Frame()->pack(-expand => 1, -fill => 'both'); my $lb1 = $fr1->Label(-text => 'Select NIC')->pack(-side=>'left'); # hash to store MAC addresses saved, per NIC my %macs = (); # default val of the text variable that represents the currently selected NIC my $nic = $nics[0]; # NIC drop-down menu my $nicWidget = $fr1->Optionmenu( -font => '{verdana} 10 {normal}', -bg => 'white', -foreground => 'Gray50', -activebackground => 'white', -options => [@nics], -anchor => 'w', -relief => 'sunken', -bd => 2, -padx => 1, -takefocus => 1, -textvariable => \$nic, )->pack(-side=>'left'); my $fr = $mw->Frame()->pack(-expand => 1, -fill => 'both'); my $lb = $fr->Label(-text => 'Enter MAC Address')->grid(-row=>0,-column=>0); my $okBtn = $fr->Button( -text => 'Save', -font => 'sans_8', -state => 'disabled', -bd => 1, -takefocus => 0, -pady => 5, ); my %rot; my $row = 3; for(@nics){ $fr->Label(-text=>$_.' MAC Address:')->grid(-row=>$row,-column=>0); $row++; $rot{$_} = $fr->ROText( -relief => 'solid', -height => 1, -width => 18, -bg => 'white', -font => 'mono', -foreground => 'black', -bd => 1, -takefocus => 0, -highlightthickness => 0, -state => 'disabled', ); } # widget used to display Entry field errors my $errW = $fr->Label(-foreground=>'red'); # exit button my $exitBtn = $fr->Button( -text => 'Exit', -command => sub {exit(0);}, -font => 'sans_8', -state => 'normal', -bd => 1, -takefocus => 0, -pady => 5, ); # hash to hold Entry widgets my %entries; # loop thru the number of Entry widgets desired for(1..6){ # create the Entry widget $entries{$_}{'entry'} = $fr->Entry( -font => '{verdana} 12 {normal}', -textvariable => \$entries{$_}{'addy'}, -width => 3, -bg => 'white', ); # validate using bind $entries{$_}{'entry'}->bind('',[\&validation,$_,\%entries]); # save default widget background $entries{$_}{'bg'} = $entries{$_}{'entry'}->cget('-bg'); # what type of validation will be done in this value $entries{$_}{'type'} = 'mac'; # pack/display the widget $entries{$_}{'entry'}->grid(-row=>0,-column=>$_); } # see if we should use the actual MAC addresses if($use_real_macs){ # look up real MAC addresses for(@nics){ my $file = '/sys/class/net/'.$_.'/address'; open(FH,'<',$file) or die "can't open '$file': $!\n"; my $address = readline(*FH); close(FH); die "Failed to get MAC for $_\n" unless($address); chomp($address); print "Real MAC address for $_: $address\n"; my @address = split(/:/,$address); for(my $i=0;$i<=$#address;$i++){ my $oct = $address[$i]; $macs{$_}{$i+1} = $oct; } } }else{ # create some bogus MAC values (make values invalid to test validation) for(sort keys %entries){ $macs{$nic}{$_} = ($_>1) ? $_.$_ : ''; $macs{$nic}{$_} .= 'z' if($_ == 3 or $_ == 4); } } # update MAC input fields at application start-up &update_mac_fields; # update MAC input fields whenever a new NIC is selected $nicWidget->configure(-command => [\&update_mac_fields]); # the Save button will validate *all* Entries $okBtn->configure(-command => [ \&validate_all,\%entries ]); # pack widgets based upon number of Entries $errW->grid(-row=>1,-column=>1,-columnspan=>scalar keys %entries); $okBtn->grid(-row=>2,-column=>(scalar keys %entries)-1,-columnspan=>2,-pady=>5); $row = 3; for(@nics){ $rot{$_}->grid(-row=>$row,-column=>1,-columnspan=>scalar keys %entries); $row++; } $exitBtn->grid(-row=>$row,-column=>(scalar keys %entries)-1,-columnspan=>2,-pady=>5); # auto-tab thru all Entry widgets to perform validation on pre-populated values for(1..(scalar keys %entries) + 1){ $mw->eventGenerate(''); $mw->eventGenerate(''); # $mw->idletasks; $mw->after(100); $mw->update; } MainLoop(); sub update_mac_fields { for(sort keys %entries){ $entries{$_}{'addy'} = $macs{$nic}{$_} ? $macs{$nic}{$_} : ''; $entries{$_}{'entry'}->configure(-state=>'normal'); $entries{$_}{'entry'}->configure(-bg=>$entries{$_}{'bg'}); } $errW->configure(-text=>''); $okBtn->configure(-state=>'normal'); $nicWidget->focus(); } sub validate_all { my($ref) = @_; my $failed; my $empty; my $newmac; print "\nValidating all entries: \n"; for(sort keys %$ref){ # make sure field validates unless(&validation(undef,$_,$ref)){ $failed = 1; last; } # make sure validated field is not empty my $octet = $ref->{$_}{'entry'}->get; unless($octet){ $failed = 1; $empty = $_; last; } # concatenate the new MAC address $newmac .= ($newmac) ? ':'.$octet : $octet; # save to hash $macs{$nic}{$_} = $octet; } if($failed){ if(defined($empty)){ &validation_failed($empty,$ref,"Field \`$empty' cannot be empty"); } }else{ $failed = 0; $rot{$nic}->configure(-state=>'normal'); $rot{$nic}->delete('1.0','end'); $rot{$nic}->insert('end',$newmac); $rot{$nic}->configure(-state=>'disabled'); } return $failed; } # returns `1' if valid, and `0' if invalid sub validation { my($self,$id,$ref) = @_; my $widget = defined($self) ? $self : $ref->{$id}{'entry'}; my $value = $ref->{$id}{'addy'}; my $type = $ref->{$id}{'type'}; # boolean, 1 if validation is successful, 0 if fails my $valid; # see if any value was entered in the Entry widget field if($value){ # get the index number of the last character in the value my $index = -1; if($self){$index++ while $value =~ /./g} print "Field $id ($type) index $index, val is \`",$value,"', validating..."; # MAC address octet validation if($type eq 'mac'){ if($index == 0){ if($value =~ /^[0-9a-f]$/i){ $valid = 1; }else{ $valid = 0; } }elsif(($index == 1)||($index == -1)){ if($value =~ /^[0-9a-f]{2}$/i){ $valid = 1; }else{ $valid = 0; } }else{ $valid = 0; } # place-holder for other validation data types that are not defined yet }else{ $valid = 1; } printf("%s\n",($valid) ? "ok" : "FAILED"); # field must have been cleared or is empty (this is valid), clear out errors }else{ print "Field $id ($type) has no value, resetting field...\n"; $valid = 1; } if($valid){ $widget->configure(-bg=>'white'); # clear error widget &clear_err($id,$ref); # re-enable all other widgets for(sort keys %$ref){ next if(/^$id$/); $ref->{$_}{'entry'}->configure(-state=>'normal'); } # allow focus to NIC widget $nicWidget->configure(-takefocus=>1); # enable the Save button # uncomment commented lines to implement a check for non-empty fields first # my $empty; # for(sort keys %$ref){ # unless($ref->{$_}{'entry'}->get){ # $empty = 1; # last; # } # } # unless($empty){ $okBtn->configure(-state=>'normal'); $okBtn->configure(-takefocus=>1); # } }else{ &validation_failed($id,$ref); } return $valid; } sub validation_failed { my($id,$ref,$errmsg) = @_; my $widget = $ref->{$id}{'entry'}; my $value = $ref->{$id}{'addy'}; $errmsg = "Field $id value \`$value' is invalid" unless($errmsg); # turn the background of the problem field to red $widget->configure(-bg=>'red'); $widget->update(); # update the error widget with text indicating a problem with the value $errW->configure(-text=>$errmsg); # temporarily disable focus on all other widgets for(sort keys %$ref){ next if(/^$id$/); $ref->{$_}{'entry'}->configure(-state=>'disabled'); } # disable the Save button $okBtn->configure(-state=>'disabled'); $okBtn->configure(-takefocus=>0); # unfocus the Select NIC widget $nicWidget->configure(-takefocus=>0); $widget->focus(); } sub clear_err { my($id,$ref) = @_; $errW->configure(-text=>''); $ref->{$id}{'entry'}->configure(-bg=>$ref->{$id}{'bg'}); }