PAC Manager Code
Brought to you by:
perseo22
--- a +++ b/lib/PACUtils.pm @@ -0,0 +1,1171 @@ +package PACUtils; + +################################################################## +# This file is part of PAC( Perl Auto Connector) +# +# Copyright (C) 2010 David Torrejon Vaquerizas +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +################################################################### + +$|++; + +################################################################### +# Import Modules + +# Standard +use strict; +use warnings; + +use FindBin qw ( $RealBin $Bin $Script ); +use POSIX qw ( strftime ); +use Crypt::CBC; +use Socket; +use Sys::Hostname; +use Net::ARP; +use Net::Ping; +#use Data::Dumper; + +# GTK2 +use Gtk2 '-init'; +use Gtk2::Ex::Simple::List; + +# Module's functions/variables to export +use vars qw( $VERSION @ISA @EXPORT @EXPORT_OK ); +require Exporter; +@ISA = qw( Exporter ); +@EXPORT = qw( + _ + __ + _screenshot + _scale + _preview + _getMethods + _menuAvailableConnections + _wEnterValue + _wPopUpMenu + _wConfirm + _wMessage + _wYesNoCancel + _wPrePostEntry + _wExecEntry + _cfgSanityCheck + _cipherCFG + _decipherCFG + _wakeOnLan + _deleteOldestSessionLog + _buildPIDFile + _checkPIDFile + _deletePIDFile +); # Functions/varibles to export + +@EXPORT_OK = qw(); + +# END: Import Modules +################################################################### + +################################################################### +# Define GLOBAL CLASS variables + +our $APPNAME = 'PAC'; +our $APPVERSION = '2.5.2'; +my $RES_DIR = $RealBin . '/res'; +my $APPICON = $RES_DIR . '/pac64x64.png'; +my $APPTERMICON = $RES_DIR . '/pac_terminal64x64.png'; +my $CFG_DIR = $ENV{'HOME'} . '/.pac'; +my $CIPHER = Crypt::CBC -> new( -key => 'PAC Manager (David Torrejon Vaquerizas, david.tv@gmail.com)', -cipher => 'Blowfish', -salt => 1 ) or die "ERROR: $!"; +my $PIDFILE = '/tmp/pac.pid'; + +# END: Define GLOBAL CLASS variables +################################################################### + +###################################################### +# START: Private functions definitions + +sub _ { return shift -> {_GLADE} -> get_widget( shift ); }; + +sub __ +{ + my $str = shift; + $str =~ s/&/&/go; + $str =~ s/</</go; + $str =~ s/>/>/go; + return $str; +}; + +sub _screenshot +{ + my $widget = shift; + my $jpg = shift; + + my $gdkpixbuf = Gtk2::Gdk::Pixbuf -> new( 'rgb', 0, 8, $widget -> allocation -> width, $widget -> allocation -> height ); + + $gdkpixbuf -> get_from_drawable( $widget -> window, undef, $widget -> allocation -> x, $widget -> allocation -> y, 0, 0, $widget -> allocation -> width, $widget -> allocation -> height ); + $gdkpixbuf -> save( $jpg, 'jpeg' ); + + return 1; +} + +sub _scale +{ + my $file = shift; + my $w = shift; + my $h = shift; + my $ratio = shift // ''; + + my $gdkpixbuf = Gtk2::Gdk::Pixbuf -> new_from_file( $file ); + + if ( $ratio ) + { + if ( ( $gdkpixbuf -> get_width > $w ) || ( $gdkpixbuf -> get_height > $h ) ) + { + if ( $gdkpixbuf -> get_width > $gdkpixbuf -> get_height ) + { + $h = int( ( $w * $gdkpixbuf -> get_height ) / $gdkpixbuf -> get_width ); + } + elsif ( $gdkpixbuf -> get_height >= $gdkpixbuf -> get_width) + { + $w = int( ( $h * $gdkpixbuf -> get_width ) / $gdkpixbuf -> get_height ); + } + } + } + + return $gdkpixbuf -> scale_simple( $w, $h, 'GDK_INTERP_HYPER' ); +} + +sub _preview +{ + my $dialog = shift; + + $dialog -> set_preview_widget_active( 0 ); + + my $file = $dialog -> get_preview_filename; + ( defined $file && -f $file ) or return 1; + + my $preview = Gtk2::Image -> new; + $dialog -> set_preview_widget( $preview ); + + my $preview_pixbuf = Gtk2::Gdk::Pixbuf -> new_from_file_at_size( $file, 256, 256 ); + $preview -> set_from_pixbuf( $preview_pixbuf ); + + $dialog -> set_preview_widget_active( $preview_pixbuf ); + + return 1; +} + +sub _getMethods +{ + my $self = shift; + + my %methods = ( + 'rdesktop' => + { + 'updateGUI' => sub + { + my $port = shift // 3389; + _( $self, 'framePort' ) -> set_sensitive( 1 ); + _( $self, 'entryPort' ) -> set_text( $port ); + _( $self, 'labelIP' ) -> set_text( 'IP / Hostname:' ); + _( $self, 'entryIP' ) -> set_property( 'tooltip-markup', 'IP or Hostname of the machine to connect to' ); + _( $self, 'frameUser' ) -> set_sensitive( 1 ); + _( $self, 'frameExpect' ) -> set_sensitive( 0 ); + _( $self, 'frameRemoteMacros' ) -> set_sensitive( 0 ); + _( $self, 'frameLocalMacros' ) -> set_sensitive( 0 ); + _( $self, 'frameVariables' ) -> set_sensitive( 0 ); + _( $self, 'frameTerminalOptions' ) -> set_sensitive( 0 ); + _( $self, 'labelExpect' ) -> set_sensitive( 0 ); + _( $self, 'labelRemoteMacros' ) -> set_sensitive( 0 ); + _( $self, 'labelLocalMacros' ) -> set_sensitive( 0 ); + _( $self, 'labelVariables' ) -> set_sensitive( 0 ); + _( $self, 'labelTerminalOptions' ) -> set_sensitive( 0 ); + _( $self, 'labelCmdLineOptions' ) -> set_markup( " '<b>rdesktop</b>' command line options: " ); + }, + 'icon' => Gtk2::Gdk::Pixbuf -> new_from_file_at_scale( $RES_DIR . '/pac_method_rdesktop.jpg', 16, 16, 0 ) + }, + 'vncviewer' => + { + 'updateGUI' => sub + { + my $port = shift // 5900; + _( $self, 'framePort' ) -> set_sensitive( 1 ); + _( $self, 'entryPort' ) -> set_text( $port ); + _( $self, 'labelIP' ) -> set_text( 'IP / Hostname:' ); + _( $self, 'entryIP' ) -> set_property( 'tooltip-markup', 'IP or Hostname of the machine to connect to' ); + _( $self, 'entryUser' ) -> set_text( '' ); + _( $self, 'frameUser' ) -> set_sensitive( 0 ); + _( $self, 'frameExpect' ) -> set_sensitive( 0 ); + _( $self, 'frameRemoteMacros' ) -> set_sensitive( 0 ); + _( $self, 'frameLocalMacros' ) -> set_sensitive( 0 ); + _( $self, 'frameVariables' ) -> set_sensitive( 0 ); + _( $self, 'frameTerminalOptions' ) -> set_sensitive( 0 ); + _( $self, 'labelExpect' ) -> set_sensitive( 0 ); + _( $self, 'labelRemoteMacros' ) -> set_sensitive( 0 ); + _( $self, 'labelLocalMacros' ) -> set_sensitive( 0 ); + _( $self, 'labelVariables' ) -> set_sensitive( 0 ); + _( $self, 'labelTerminalOptions' ) -> set_sensitive( 0 ); + _( $self, 'labelCmdLineOptions' ) -> set_markup( " '<b>vncviewer</b>' command line options: " ); + }, + 'icon' => Gtk2::Gdk::Pixbuf -> new_from_file_at_scale( $RES_DIR . '/pac_method_vncviewer.jpg', 16, 16, 0 ) + }, + 'cu' => + { + 'updateGUI' => sub + { + _( $self, 'framePort' ) -> set_sensitive( 0 ); + _( $self, 'entryPort' ) -> set_text( '' ); + _( $self, 'labelIP' ) -> set_text( 'System / Phone:' ); + _( $self, 'entryIP' ) -> set_property( 'tooltip-markup', 'Enter string of kind: system | phone | "dir"' ); + _( $self, 'labelExpect' ) -> set_sensitive( 1 ); + _( $self, 'labelRemoteMacros' ) -> set_sensitive( 1 ); + _( $self, 'labelLocalMacros' ) -> set_sensitive( 1 ); + _( $self, 'labelVariables' ) -> set_sensitive( 1 ); + _( $self, 'labelTerminalOptions' ) -> set_sensitive( 1 ); + _( $self, 'frameUser' ) -> set_sensitive( 1 ); + _( $self, 'frameExpect' ) -> set_sensitive( 1 ); + _( $self, 'frameRemoteMacros' ) -> set_sensitive( 1 ); + _( $self, 'frameLocalMacros' ) -> set_sensitive( 1 ); + _( $self, 'frameVariables' ) -> set_sensitive( 1 ); + _( $self, 'frameTerminalOptions' ) -> set_sensitive( 1 ); + _( $self, 'labelCmdLineOptions' ) -> set_markup( " '<b>cu</b>' command line options: " ); + }, + 'icon' => Gtk2::Gdk::Pixbuf -> new_from_file_at_scale( $RES_DIR . '/pac_method_cu.jpg', 16, 16, 0 ), + 'escape' => [ '~.' ] + }, + 'remote-tty' => + { + 'updateGUI' => sub + { + _( $self, 'framePort' ) -> set_sensitive( 0 ); + _( $self, 'entryPort' ) -> set_text( '' ); + _( $self, 'labelIP' ) -> set_text( 'TTY Socket:' ); + _( $self, 'entryIP' ) -> set_property( 'tooltip-markup', 'Enter a TTY / Serial socket (eg: /dev/tty*)' ); + _( $self, 'labelExpect' ) -> set_sensitive( 1 ); + _( $self, 'labelRemoteMacros' ) -> set_sensitive( 1 ); + _( $self, 'labelLocalMacros' ) -> set_sensitive( 1 ); + _( $self, 'labelVariables' ) -> set_sensitive( 1 ); + _( $self, 'labelTerminalOptions' ) -> set_sensitive( 1 ); + _( $self, 'frameUser' ) -> set_sensitive( 1 ); + _( $self, 'frameExpect' ) -> set_sensitive( 1 ); + _( $self, 'frameRemoteMacros' ) -> set_sensitive( 1 ); + _( $self, 'frameLocalMacros' ) -> set_sensitive( 1 ); + _( $self, 'frameVariables' ) -> set_sensitive( 1 ); + _( $self, 'frameTerminalOptions' ) -> set_sensitive( 1 ); + _( $self, 'labelCmdLineOptions' ) -> set_markup( " '<b>remote-tty</b>' command line options: " ); + }, + 'icon' => Gtk2::Gdk::Pixbuf -> new_from_file_at_scale( $RES_DIR . '/pac_method_remote-tty.jpg', 16, 16, 0 ) + }, + 'ssh' => + { + 'updateGUI' => sub + { + my $port = shift // 22; + _( $self, 'framePort' ) -> set_sensitive( 1 ); + _( $self, 'entryPort' ) -> set_text( $port ); + _( $self, 'labelIP' ) -> set_text( 'IP / Hostname:' ); + _( $self, 'entryIP' ) -> set_property( 'tooltip-markup', 'IP or Hostname of the machine to connect to' ); + _( $self, 'labelExpect' ) -> set_sensitive( 1 ); + _( $self, 'labelRemoteMacros' ) -> set_sensitive( 1 ); + _( $self, 'labelLocalMacros' ) -> set_sensitive( 1 ); + _( $self, 'labelVariables' ) -> set_sensitive( 1 ); + _( $self, 'labelTerminalOptions' ) -> set_sensitive( 1 ); + _( $self, 'frameUser' ) -> set_sensitive( 1 ); + _( $self, 'frameExpect' ) -> set_sensitive( 1 ); + _( $self, 'frameRemoteMacros' ) -> set_sensitive( 1 ); + _( $self, 'frameLocalMacros' ) -> set_sensitive( 1 ); + _( $self, 'frameVariables' ) -> set_sensitive( 1 ); + _( $self, 'frameTerminalOptions' ) -> set_sensitive( 1 ); + _( $self, 'labelCmdLineOptions' ) -> set_markup( " '<b>ssh</b>' command line options: " ); + }, + 'icon' => Gtk2::Gdk::Pixbuf -> new_from_file_at_scale( $RES_DIR . '/pac_method_ssh.jpg', 16, 16, 0 ), + 'escape' => [ '~.' ] + }, + 'telnet' => + { + 'updateGUI' => sub + { + my $port = shift // 23; + _( $self, 'framePort' ) -> set_sensitive( 1 ); + _( $self, 'entryPort' ) -> set_text( $port ); + _( $self, 'labelIP' ) -> set_text( 'IP / Hostname:' ); + _( $self, 'entryIP' ) -> set_property( 'tooltip-markup', 'IP or Hostname of the machine to connect to' ); + _( $self, 'labelExpect' ) -> set_sensitive( 1 ); + _( $self, 'labelRemoteMacros' ) -> set_sensitive( 1 ); + _( $self, 'labelLocalMacros' ) -> set_sensitive( 1 ); + _( $self, 'labelVariables' ) -> set_sensitive( 1 ); + _( $self, 'labelTerminalOptions' ) -> set_sensitive( 1 ); + _( $self, 'frameUser' ) -> set_sensitive( 1 ); + _( $self, 'frameExpect' ) -> set_sensitive( 1 ); + _( $self, 'frameRemoteMacros' ) -> set_sensitive( 1 ); + _( $self, 'frameLocalMacros' ) -> set_sensitive( 1 ); + _( $self, 'frameVariables' ) -> set_sensitive( 1 ); + _( $self, 'frameTerminalOptions' ) -> set_sensitive( 1 ); + _( $self, 'labelCmdLineOptions' ) -> set_markup( " '<b>telnet</b>' command line options: " ); + }, + 'icon' => Gtk2::Gdk::Pixbuf -> new_from_file_at_scale( $RES_DIR . '/pac_method_telnet.jpg', 16, 16, 0 ), + 'escape' => [ "\c]", "quit\n" ] + }, + 'sftp' => + { + 'updateGUI' => sub + { + my $port = shift // 22; + _( $self, 'framePort' ) -> set_sensitive( 1 ); + _( $self, 'entryPort' ) -> set_text( $port ); + _( $self, 'labelIP' ) -> set_text( 'IP / Hostname:' ); + _( $self, 'entryIP' ) -> set_property( 'tooltip-markup', 'IP or Hostname of the machine to connect to' ); + _( $self, 'labelExpect' ) -> set_sensitive( 1 ); + _( $self, 'labelRemoteMacros' ) -> set_sensitive( 1 ); + _( $self, 'labelLocalMacros' ) -> set_sensitive( 1 ); + _( $self, 'labelVariables' ) -> set_sensitive( 1 ); + _( $self, 'labelTerminalOptions' ) -> set_sensitive( 1 ); + _( $self, 'frameUser' ) -> set_sensitive( 1 ); + _( $self, 'frameExpect' ) -> set_sensitive( 1 ); + _( $self, 'frameRemoteMacros' ) -> set_sensitive( 1 ); + _( $self, 'frameLocalMacros' ) -> set_sensitive( 1 ); + _( $self, 'frameVariables' ) -> set_sensitive( 1 ); + _( $self, 'frameTerminalOptions' ) -> set_sensitive( 1 ); + _( $self, 'labelCmdLineOptions' ) -> set_markup( " '<b>sftp</b>' command line options: " ); + }, + 'icon' => Gtk2::Gdk::Pixbuf -> new_from_file_at_scale( $RES_DIR . '/pac_method_sftp.jpg', 16, 16, 0 ) + }, + 'ftp' => + { + 'updateGUI' => sub + { + my $port = shift // 21; + _( $self, 'framePort' ) -> set_sensitive( 1 ); + _( $self, 'entryPort' ) -> set_text( $port ); + _( $self, 'labelIP' ) -> set_text( 'IP / Hostname:' ); + _( $self, 'entryIP' ) -> set_property( 'tooltip-markup', 'IP or Hostname of the machine to connect to' ); + _( $self, 'labelExpect' ) -> set_sensitive( 1 ); + _( $self, 'labelRemoteMacros' ) -> set_sensitive( 1 ); + _( $self, 'labelLocalMacros' ) -> set_sensitive( 1 ); + _( $self, 'labelVariables' ) -> set_sensitive( 1 ); + _( $self, 'labelTerminalOptions' ) -> set_sensitive( 1 ); + _( $self, 'frameUser' ) -> set_sensitive( 1 ); + _( $self, 'frameExpect' ) -> set_sensitive( 1 ); + _( $self, 'frameRemoteMacros' ) -> set_sensitive( 1 ); + _( $self, 'frameLocalMacros' ) -> set_sensitive( 1 ); + _( $self, 'frameVariables' ) -> set_sensitive( 1 ); + _( $self, 'frameTerminalOptions' ) -> set_sensitive( 1 ); + _( $self, 'labelCmdLineOptions' ) -> set_markup( " '<b>ftp</b>' command line options: " ); + }, + 'icon' => Gtk2::Gdk::Pixbuf -> new_from_file_at_scale( $RES_DIR . '/pac_method_ftp.jpg', 16, 16, 0 ) + }, + ); + + return %methods; +} + +sub _menuAvailableConnections +{ + my @tray_menu_items; + + push( @tray_menu_items, { label => 'Local Shell', stockicon => 'gtk-home', code => sub { $PACMain::FUNCS{_MAIN}{_GUI}{shellBtn} -> clicked; } } ); + push( @tray_menu_items, { separator => 1 } ); + + foreach my $environment ( sort { uc($a) cmp uc($b) } keys %{ $PACMain::FUNCS{_MAIN}{_CFG}{'environments'} } ) + { + # Create a submenu with connections for the environment + my @sub_menu; + my $is_shell = 0; + foreach my $connection ( sort { uc($a) cmp uc($b) } keys %{ $PACMain::FUNCS{_MAIN}{_CFG}{'environments'}{$environment} } ) + { + my $connected = 0; + foreach my $uuid ( keys %PACMain::RUNNING ) + { + next unless ( $PACMain::RUNNING{$uuid}{environment} // 0 && $PACMain::RUNNING{$uuid}{connection} // 0 ); + next unless ( ( $PACMain::RUNNING{$uuid}{environment} eq $environment ) && ( $PACMain::RUNNING{$uuid}{connection} eq $connection ) && $PACMain::RUNNING{$uuid}{terminal}{CONNECTED} // 0 ); + $is_shell = $PACMain::RUNNING{$uuid}{is_shell} // 0; + ++$connected; + } + push( @sub_menu, + { + label => ( $connected ? "($connected) " : '' ) . $connection, + stockicon => $connected ? 'gtk-connect' : 'gtk-disconnect', + tooltip => $PACMain::FUNCS{_MAIN}{_CFG}{'environments'}{$environment}{$connection}{'description'}, + code => sub { $PACMain::FUNCS{_MAIN} -> _executeTerminal( $environment, $connection ); } + } ); + } + # Save that submenu into this environment menu + push( @tray_menu_items, + { + label => $environment, + submenu => \@sub_menu + } ) unless $is_shell; + } + + return \@tray_menu_items; +} + +sub _wEnterValue +{ + my $self = shift; + my $lblup = shift; + my $lbldown = shift; + my $default = shift // ''; + + my $env = $self -> {_ENVIRONMENT}; + my $conn = $self -> {_CONNECTION}; + + my %w; + + # Create the dialog window, + $w{window}{data} = Gtk2::Dialog -> new_with_buttons( + "$APPNAME (v$APPVERSION) : Enter data", + undef, + 'modal', + 'gtk-ok' => 'ok', + 'gtk-cancel' => 'cancel' + ); + # and setup some dialog properties. + $w{window}{data} -> set_default_response( 'ok' ); + $w{window}{data} -> set_position( 'center' ); + $w{window}{data} -> set_icon_from_file( $APPICON ); + $w{window}{data} -> set_size_request( -1, -1 ); + $w{window}{data} -> set_resizable( 0 ); + $w{window}{data} -> set_border_width( 5 ); + + # Create an HBox to contain a picture and a label + $w{window}{gui}{hbox} = Gtk2::HBox -> new( 0, 0 ); + $w{window}{data} -> vbox -> pack_start( $w{window}{gui}{hbox}, 1, 1, 5 ); + $w{window}{gui}{hbox} -> set_border_width( 5 ); + + # Create image + $w{window}{gui}{img} = Gtk2::Image -> new_from_stock( 'gtk-edit', 'dialog' ); + $w{window}{gui}{hbox} -> pack_start( $w{window}{gui}{img}, 0, 1, 5 ); + + # Create 1st label + $w{window}{gui}{lblup} = Gtk2::Label -> new(); + $w{window}{gui}{hbox} -> pack_start( $w{window}{gui}{lblup}, 1, 1, 5 ); + $w{window}{gui}{lblup} -> set_markup( $lblup ); + + # Create 2nd label + $w{window}{gui}{lbldwn} = Gtk2::Label -> new(); + $w{window}{data} -> vbox -> pack_start( $w{window}{gui}{lbldwn}, 1, 1, 5 ); + $w{window}{gui}{lbldwn} -> set_text( $lbldown ); + + # Create the entry widget + $w{window}{gui}{entry} = Gtk2::Entry -> new(); + $w{window}{data} -> vbox -> pack_start( $w{window}{gui}{entry}, 0, 1, 5 ); + $w{window}{gui}{entry} -> set_text( $default ); + $w{window}{gui}{entry} -> set_activates_default( 1 ); + + # Show the window (in a modal fashion) + $w{window}{data} -> show_all(); + my $ok = $w{window}{data} -> run(); + + my $val = ( $ok eq 'ok' ) ? $w{window}{gui}{entry} -> get_chars( 0, -1 ) : undef; + + $w{window}{data} -> destroy(); + + return $val; +} + +# Next function borrowed (and modified) from Quentin Sculo <squentin@free.fr>, from his amazing 'gmusicbrowser' +sub _wPopUpMenu +{ + my $mref = shift; + our $event = shift; + my $below = shift // '0'; + + my $count = 0; + + my $menu = Gtk2::Menu -> new; + + for my $m ( @$mref ) + { + my $label = $m -> {label}; + my $sensitive = $m -> {sensitive} // 1; + + my $item; + + # Build menu item + if ( ! defined $label ) + { + next unless $m -> {separator}; + $item = Gtk2::SeparatorMenuItem -> new; + } + elsif ( $m -> {stockicon} ) + { + $item = Gtk2::ImageMenuItem -> new( $label ); + $item -> set_image( Gtk2::Image -> new_from_stock( $m -> {stockicon}, 'menu' ) ); + } + elsif ( $m -> {fileicon} ) + { + $item = Gtk2::ImageMenuItem -> new( $label ); + $item -> set_image( Gtk2::Image -> new_from_file( $m -> {fileicon} ) ); + } + elsif ( ( $m -> {check} || $m -> {radio} ) && ! $m -> {submenu} ) + { + $item = Gtk2::CheckMenuItem -> new( $label ); + $item -> set_active( $m -> {active} // 0 ); + $item -> set_draw_as_radio( 1 ) if $m -> {radio}; + } + else + { + $item = Gtk2::MenuItem -> new( $label ); + } + + # Set some properties + $item -> set_sensitive( $sensitive ); + $m -> {separator} or $item -> set( 'use_underline' => 0 ); + defined $m -> {tooltip} and $item -> set_property( 'tooltip-text', $m -> {tooltip} ); + + # Check if this entry is a submenu definition + if ( my $submenu = $m -> {submenu} ) + { + ref $submenu eq 'ARRAY' and $submenu = &_wPopUpMenu( $submenu, $event ); + next unless $submenu; + $item -> set_submenu( $submenu ); + } + else + { + $item -> signal_connect( activate => $m -> {code} ); + } + + $count++; + $menu -> append( $item ); + } + defined wantarray and return $menu; + return unless $count; + + $menu -> show_all; + if ( defined $event ) { $menu -> popup( undef, undef, ( $below ? \&_pos : undef ), undef, $event -> button, $event -> time ); } + else { $menu -> popup( undef, undef, undef, undef, 0, 0 ); } + + sub _pos + { + my $h = $_[0] -> size_request -> height; + my $ymax = $event -> get_screen -> get_height; + my ( $x, $y ) = $event -> window ->get_origin; + my $dy = ( $event -> window -> get_size )[1]; + + if ( $dy + $y + $h > $ymax ) + { + $y -= $h; + $y = 0 if $y < 0; + } + else + { + $y += $dy; + } + + return $x, $y; + } + } + +sub _wMessage +{ + my $window = shift; + my $msg = shift; + + my $windowConfirm = Gtk2::MessageDialog -> new_with_markup( + $window, + 'GTK_DIALOG_DESTROY_WITH_PARENT', + 'GTK_MESSAGE_INFO', + 'none', + $msg + ); + $windowConfirm -> add_buttons( 'gtk-ok' => 'ok' ); + $windowConfirm -> set_icon_from_file( $APPICON ); + $windowConfirm -> set_title( "$APPNAME (v$APPVERSION) : Message" ); + + $windowConfirm -> show_all; + my $close = $windowConfirm -> run; + $windowConfirm -> destroy; + + return 1; +} + +sub _wConfirm +{ + my $window = shift; + my $msg = shift; + + my $windowConfirm = Gtk2::MessageDialog -> new_with_markup( + $window, + 'GTK_DIALOG_DESTROY_WITH_PARENT', + 'GTK_MESSAGE_QUESTION', + 'none', + $msg + ); + $windowConfirm -> add_buttons( 'gtk-ok' => 'yes', 'gtk-cancel'=> 'no'); + $windowConfirm -> set_icon_from_file( $APPICON ); + $windowConfirm -> set_title( "Confirm action : $APPNAME (v$APPVERSION)" ); + + $windowConfirm -> show_all(); + my $close = $windowConfirm -> run; + $windowConfirm -> destroy(); + + return ( $close eq 'yes' ); +} + +sub _wYesNoCancel +{ + my $window = shift; + my $msg = shift; + + my $windowConfirm = Gtk2::MessageDialog -> new_with_markup( + $window, + 'GTK_DIALOG_DESTROY_WITH_PARENT', + 'GTK_MESSAGE_QUESTION', + 'none', + $msg + ); + $windowConfirm -> add_buttons( 'gtk-yes' => 'yes', 'gtk-no'=> 'no', 'gtk-cancel'=> 'cancel'); + $windowConfirm -> set_icon_from_file( $APPICON ); + $windowConfirm -> set_title( "Confirm action : $APPNAME (v$APPVERSION)" ); + + $windowConfirm -> show_all(); + my $close = $windowConfirm -> run; + $windowConfirm -> destroy(); + + return ( ( $close eq 'delete-event' ) || ( $close eq 'cancel' ) ) ? 'cancel' : $close; +} + +sub _cfgSanityCheck +{ + my $cfg = shift; + + $$cfg{'defaults'}{'version'} //= $APPVERSION; + $$cfg{'defaults'}{'auto accept key'} //= 1; + $$cfg{'defaults'}{'record command history'} //= 1; + $$cfg{'defaults'}{'show screenshots'} //= 1; + $$cfg{'defaults'}{'back color'} //= '#000000000000'; + $$cfg{'defaults'}{'close terminal on disconnect'} //= ''; + $$cfg{'defaults'}{'close to tray'} //= 1; + $$cfg{'defaults'}{'command prompt'} //= '[#%\$>]|\:\/\s*$'; + $$cfg{'defaults'}{'cursor shape'} //= 'block'; + $$cfg{'defaults'}{'debug'} //= 0; + $$cfg{'defaults'}{'tabs in main window'} //= 0; + $$cfg{'defaults'}{'hide on connect'} //= 0; + $$cfg{'defaults'}{'ping port before connect'} //= 0; + $$cfg{'defaults'}{'ping port timeout'} //= 1; + $$cfg{'defaults'}{'open connections in tabs'} //= 1; + $$cfg{'defaults'}{'proxy ip'} //= ''; + $$cfg{'defaults'}{'proxy pass'} //= ''; + $$cfg{'defaults'}{'proxy port'} //= ''; + $$cfg{'defaults'}{'proxy user'} //= ''; + $$cfg{'defaults'}{'shell binary'} //= '/bin/bash'; + $$cfg{'defaults'}{'shell options'} //= '-login'; + $$cfg{'defaults'}{'shell directory'} //= $ENV{'HOME'}; + $$cfg{'defaults'}{'tabs position'} //= 'top'; + $$cfg{'defaults'}{'save on exit'} //= 0; + $$cfg{'defaults'}{'start iconified'} //= 0; + $$cfg{'defaults'}{'start maximized'} //= 1; + $$cfg{'defaults'}{'start main maximized'} //= 0; + $$cfg{'defaults'}{'terminal transparency'} //= 0; + $$cfg{'defaults'}{'terminal font'} //= 'Monospace 9'; + $$cfg{'defaults'}{'terminal scrollback lines'} //= 5000; + $$cfg{'defaults'}{'text color'} //= '#cc62cc62cc62'; + $$cfg{'defaults'}{'connected color'} //= '#0CBA00'; #mid-green + $$cfg{'defaults'}{'disconnected color'} //= '#FF0000'; #red + $$cfg{'defaults'}{'new data color'} //= '#0088FF'; #blue + $$cfg{'defaults'}{'timeout command'} //= 60; + $$cfg{'defaults'}{'timeout connect'} //= 40; + $$cfg{'defaults'}{'use bw icon'} //= 0; + $$cfg{'defaults'}{'use proxy'} //= 0; + $$cfg{'defaults'}{'use system proxy'} //= 1; + $$cfg{'defaults'}{'only one instance'} //= 1; + $$cfg{'defaults'}{'save session logs'} //= 0; + $$cfg{'defaults'}{'session logs folder'} //= $CFG_DIR . '/session_logs'; + $$cfg{'defaults'}{'session logs amount'} //= 10; + + $$cfg{'defaults'}{'global variables'} //= {}; + + $$cfg{'tmp'}{'changed'} = 0; + + if ( ! defined $$cfg{'environments'} ) + { + $$cfg{'environments'} = {}; + return 1; + } + + foreach my $env ( keys %{ $$cfg{'environments'} } ) + { + foreach my $conn ( keys %{ $$cfg{'environments'}{$env} } ) + { + my $screenshot_file = ''; + if ( ! defined $$cfg{'environments'}{$env}{$conn}{'screenshot'} ) + { + $screenshot_file = $CFG_DIR . '/screenshots/pac_screenshot_' . rand( length( $env . '-' . $conn ) ). '.jpg'; + while( -f $screenshot_file ) { $screenshot_file = $CFG_DIR . '/screenshots/pac_screenshot_' . rand( length( $env . '-' . $conn ) ). '.jpg'; } + } + + $$cfg{'environments'}{$env}{$conn}{'description'} //= "Connection with '$env' -> '$conn'"; + $$cfg{'environments'}{$env}{$conn}{'screenshot'} //= $screenshot_file; + $$cfg{'environments'}{$env}{$conn}{'title'} //= "$env - $conn"; + $$cfg{'environments'}{$env}{$conn}{'ip'} //= ''; + $$cfg{'environments'}{$env}{$conn}{'port'} //= 22; + $$cfg{'environments'}{$env}{$conn}{'user'} //= ''; + $$cfg{'environments'}{$env}{$conn}{'pass'} //= ''; + $$cfg{'environments'}{$env}{$conn}{'method'} //= 'ssh'; + $$cfg{'environments'}{$env}{$conn}{'options'} //= ''; + $$cfg{'environments'}{$env}{$conn}{'manual'} //= 0; + $$cfg{'environments'}{$env}{$conn}{'use proxy'} //= 0; + + $$cfg{'environments'}{$env}{$conn}{'variables'} //= []; + + if ( ! defined $$cfg{'environments'}{$env}{$conn}{'local before'} ) + { + $$cfg{'environments'}{$env}{$conn}{'local before'} = []; + } + else + { + my $i = 0; + foreach my $hash ( @{ $$cfg{'environments'}{$env}{$conn}{'local before'} } ) + { + if ( ! ref( $hash ) ) + { + delete $$cfg{'environments'}{$env}{$conn}{'local before'}[$i]; + $$cfg{'environments'}{$env}{$conn}{'local before'}[$i]{'default'} //= 1; + $$cfg{'environments'}{$env}{$conn}{'local before'}[$i]{'command'} = $hash // ''; + } + else + { + $$hash{'default'} //= 1; + $$hash{'command'} //= ''; + } + ++$i; + } + } + + if ( ! defined $$cfg{'environments'}{$env}{$conn}{'expect'} ) + { + $$cfg{'environments'}{$env}{$conn}{'expect'} = []; + } + else + { + my $i = 0; + foreach my $hash ( @{ $$cfg{'environments'}{$env}{$conn}{'expect'} } ) + { + if ( ! ref( $hash ) ) + { + delete $$cfg{'environments'}{$env}{$conn}{'expect'}[$i]; + $$cfg{'environments'}{$env}{$conn}{'expect'}[$i]{'active'} = 1; + $$cfg{'environments'}{$env}{$conn}{'expect'}[$i]{'expect'} = ''; + $$cfg{'environments'}{$env}{$conn}{'expect'}[$i]{'send'} = ''; + $$cfg{'environments'}{$env}{$conn}{'expect'}[$i]{'hidden'} = 0; + $$cfg{'environments'}{$env}{$conn}{'expect'}[$i]{'return'} = 1; + } + else + { + $$hash{'active'} //= 1; + $$hash{'expect'} //= ''; + $$hash{'hidden'} //= 0; + $$hash{'send'} //= ''; + $$hash{'return'} //= 1; + } + ++$i; + } + } + + if ( ! defined $$cfg{'environments'}{$env}{$conn}{'local connected'} ) + { + $$cfg{'environments'}{$env}{$conn}{'local connected'} = []; + } + else + { + my $i = 0; + foreach my $hash ( @{ $$cfg{'environments'}{$env}{$conn}{'local connected'} } ) + { + if ( ! ref( $hash ) ) + { + delete $$cfg{'environments'}{$env}{$conn}{'local connected'}[$i]; + $$cfg{'environments'}{$env}{$conn}{'local connected'}[$i]{'confirm'} = 0; + $$cfg{'environments'}{$env}{$conn}{'local connected'}[$i]{'txt'} = $hash // ''; + } + else + { + $$hash{'confirm'} //= 0; + $$hash{'txt'} //= ''; + } + ++$i; + } + } + + if ( ! defined $$cfg{'environments'}{$env}{$conn}{'macros'} ) + { + $$cfg{'environments'}{$env}{$conn}{'macros'} = []; + } + else + { + my $i = 0; + foreach my $hash ( @{ $$cfg{'environments'}{$env}{$conn}{'macros'} } ) + { + if ( ! ref( $hash ) ) + { + delete $$cfg{'environments'}{$env}{$conn}{'macros'}[$i]; + $$cfg{'environments'}{$env}{$conn}{'macros'}[$i]{'confirm'} = 0; + $$cfg{'environments'}{$env}{$conn}{'macros'}[$i]{'txt'} = $hash // ''; + } + else + { + $$hash{'confirm'} //= 0; + $$hash{'txt'} //= ''; + } + ++$i; + } + } + + if ( ! defined $$cfg{'environments'}{$env}{$conn}{'local after'} ) + { + $$cfg{'environments'}{$env}{$conn}{'local after'} = []; + } + else + { + my $i = 0; + foreach my $hash ( @{ $$cfg{'environments'}{$env}{$conn}{'local after'} } ) + { + if ( ! ref( $hash ) ) + { + delete $$cfg{'environments'}{$env}{$conn}{'local after'}[$i]; + $$cfg{'environments'}{$env}{$conn}{'local after'}[$i]{'default'} = 1; + $$cfg{'environments'}{$env}{$conn}{'local after'}[$i]{'command'} = $hash // ''; + } + else + { + $$hash{'default'} //= 1; + $$hash{'command'} //= ''; + } + ++$i; + } + } + if ( ! defined $$cfg{'environments'}{$env}{$conn}{'terminal options'} ) + { + $$cfg{'environments'}{$env}{$conn}{'terminal options'}{'use personal settings'} = 0; + } + else + { + $$cfg{'environments'}{$env}{$conn}{'terminal options'}{'use personal settings'} //= 0; + } + } + } + + return 1; +} + +sub _cipherCFG +{ + my $cfg = shift; + + foreach my $var ( keys %{ $$cfg{'defaults'}{'global variables'} } ) + { + ( $$cfg{'defaults'}{'global variables'}{$var}{'hidden'} eq '1' ) and $$cfg{'defaults'}{'global variables'}{$var}{'value'} = $CIPHER -> encrypt_hex( $$cfg{'defaults'}{'global variables'}{$var}{'value'} ); + } + + foreach my $env ( keys %{ $$cfg{'environments'} } ) + { + foreach my $conn ( keys %{ $$cfg{'environments'}{$env} } ) + { + $$cfg{'environments'}{$env}{$conn}{'pass'} = $CIPHER -> encrypt_hex( $$cfg{'environments'}{$env}{$conn}{'pass'} ); + + foreach my $hash ( @{ $$cfg{'environments'}{$env}{$conn}{'expect'} } ) + { + ( $$hash{'hidden'} eq '1' ) and $$hash{'send'} = $CIPHER -> encrypt_hex( $$hash{'send'} ); + } + } + } + + return 1; +} + +sub _decipherCFG +{ + my $cfg = shift; + + foreach my $var ( keys %{ $$cfg{'defaults'}{'global variables'} } ) + { + ( $$cfg{'defaults'}{'global variables'}{$var}{'hidden'} eq '1' ) and $$cfg{'defaults'}{'global variables'}{$var}{'value'} = $CIPHER -> decrypt_hex( $$cfg{'defaults'}{'global variables'}{$var}{'value'} ); + } + + foreach my $env ( keys %{ $$cfg{'environments'} } ) + { + foreach my $conn ( keys %{ $$cfg{'environments'}{$env} } ) + { + $$cfg{'environments'}{$env}{$conn}{'pass'} = $CIPHER -> decrypt_hex( $$cfg{'environments'}{$env}{$conn}{'pass'} ); + + foreach my $hash ( @{ $$cfg{'environments'}{$env}{$conn}{'expect'} } ) + { + ( $$hash{'hidden'} eq '1' ) and $$hash{'send'} = $CIPHER -> decrypt_hex( $$hash{'send'} ); + } + } + } + + return 1; +} + +sub _wakeOnLan +{ + my $ip = shift // ''; + my $port = shift // 9; + my $mac = shift // ''; + my $ping_port = shift // 7; + + #if ( ( $ip !~ /^([1-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.([0-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.([0-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.([0-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$/go ) && ( $ip !~ /^$/go ) ) + if ( ! ( ( $ip =~ /\b(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\b/ ) && $1 < 255 && $2 < 255 && $3 < 255 && $4 < 255 ) ) + { + my $packed_ip = gethostbyname( $ip ); + defined $packed_ip and $ip = inet_ntoa( $packed_ip ); + } + + my %w; + + # Create the dialog window, + $w{window}{data} = Gtk2::Dialog -> new_with_buttons( + "$APPNAME (v$APPVERSION) : Wake On LAN", + undef, + 'modal', + 'gtk-ok' => 'ok', + 'gtk-cancel' => 'cancel' + ); + # and setup some dialog properties. + $w{window}{data} -> set_default_response( 'ok' ); + $w{window}{data} -> set_position( 'center' ); + $w{window}{data} -> set_icon_from_file( $APPICON ); + $w{window}{data} -> set_size_request( -1, -1 ); + $w{window}{data} -> set_resizable( 0 ); + $w{window}{data} -> set_border_width( 5 ); + + # Create an HBox to contain a picture and a label + $w{window}{gui}{hbox} = Gtk2::HBox -> new( 0, 0 ); + $w{window}{data} -> vbox -> pack_start( $w{window}{gui}{hbox}, 1, 1, 5 ); + $w{window}{gui}{hbox} -> set_border_width( 5 ); + + # Create image + $w{window}{gui}{img} = Gtk2::Image -> new_from_icon_name( 'computer', 'dialog' ); + $w{window}{gui}{hbox} -> pack_start( $w{window}{gui}{img}, 0, 1, 5 ); + + # Create 1st label + $w{window}{gui}{lblup} = Gtk2::Label -> new(); + $w{window}{gui}{hbox} -> pack_start( $w{window}{gui}{lblup}, 1, 1, 5 ); + $w{window}{gui}{lblup} -> set_markup( "<b>Enter the following data\nand press 'Ok' to send Magic Packet:</b>" ); + + $w{window}{gui}{table} = Gtk2::Table -> new( 3, 3, 0 ); + $w{window}{data} -> vbox -> pack_start( $w{window}{gui}{table}, 1, 1, 5 ); + + # Create MAC label + $w{window}{gui}{lblmac} = Gtk2::Label -> new(); + $w{window}{gui}{table} -> attach_defaults( $w{window}{gui}{lblmac}, 0, 1, 0, 1 ); + $w{window}{gui}{lblmac} -> set_text( 'MAC Address: ' ); + + # Create MAC entry widget + $w{window}{gui}{entrymac} = Gtk2::Entry -> new(); + $w{window}{gui}{table} -> attach_defaults( $w{window}{gui}{entrymac}, 1, 2, 0, 1 ); + $w{window}{gui}{entrymac} -> set_text( $mac ); + $w{window}{gui}{entrymac} -> set_activates_default( 1 ); + $w{window}{gui}{entrymac} -> grab_focus; + + # Create MAC icon widget + $w{window}{gui}{iconmac} = Gtk2::Image -> new_from_stock( 'gtk-no', 'menu' ); + $w{window}{gui}{table} -> attach_defaults( $w{window}{gui}{iconmac}, 2, 3, 0, 1 ); + + # Create HOST label + $w{window}{gui}{lblip} = Gtk2::Label -> new(); + $w{window}{gui}{table} -> attach_defaults( $w{window}{gui}{lblip}, 0, 1, 1, 2 ); + $w{window}{gui}{lblip} -> set_text( 'Host: ' ); + + # Create HOST entry widget + $w{window}{gui}{entryip} = Gtk2::Entry -> new(); + $w{window}{gui}{table} -> attach_defaults( $w{window}{gui}{entryip}, 1, 2, 1, 2 ); + $w{window}{gui}{entryip} -> set_text( $ip ); + $w{window}{gui}{entryip} -> set_sensitive( 0 ); + $w{window}{gui}{entryip} -> set_activates_default( 0 ); + + # Create IP icon widget + $w{window}{gui}{iconip} = Gtk2::Image -> new_from_stock( 'gtk-yes', 'menu' ); + $w{window}{gui}{table} -> attach_defaults( $w{window}{gui}{iconip}, 2, 3, 1, 2 ); + + # Create PORT label + $w{window}{gui}{lblport} = Gtk2::Label -> new(); + $w{window}{gui}{table} -> attach_defaults( $w{window}{gui}{lblport}, 0, 1, 2, 3 ); + $w{window}{gui}{lblport} -> set_text( 'Port Number: ' ); + + # Create PORT entry widget + $w{window}{gui}{entryport} = Gtk2::SpinButton -> new_with_range( 1, 65535, 1 ); + $w{window}{gui}{table} -> attach_defaults( $w{window}{gui}{entryport}, 1, 2, 2, 3 ); + $w{window}{gui}{entryport} -> set_value( $port ); + $w{window}{gui}{entryport} -> set_activates_default( 1 ); + + $w{window}{gui}{separator} = Gtk2::HSeparator -> new(); + $w{window}{data} -> vbox -> pack_start( $w{window}{gui}{separator}, 0, 1, 0 ); + + $w{window}{gui}{cbbroadcast} = Gtk2::CheckButton -> new_with_label( 'Send to broadcast' ); + $w{window}{data} -> vbox -> pack_start( $w{window}{gui}{cbbroadcast}, 0, 1, 0 ); + $w{window}{gui}{cbbroadcast} -> set_active( 1 ); + $w{window}{gui}{cbbroadcast} -> set_sensitive( $ip ); + + $w{window}{gui}{separator} = Gtk2::HSeparator -> new(); + $w{window}{data} -> vbox -> pack_start( $w{window}{gui}{separator}, 0, 1, 0 ); + + $w{window}{gui}{lblstatus} = Gtk2::Label -> new(); + $w{window}{data} -> vbox -> pack_start( $w{window}{gui}{lblstatus}, 0, 1, 5 ); + $w{window}{gui}{lblstatus} -> set_text( "Retrieving MAC for '$ip' ..." ); + + # Show the window (in a modal fashion) + $w{window}{data} -> show_all(); + + # Setup some callbacks... + $w{window}{gui}{entrymac} -> signal_connect( 'event' => sub + { + $w{window}{data} -> get_action_area -> foreach( sub + { + return 1 unless $_[0] -> get_label eq 'gtk-ok'; + $w{window}{gui}{iconmac} -> set_from_stock( $w{window}{gui}{entrymac} -> get_chars( 0, -1 ) =~ /^[\da-fA-F]{2}[:-][\da-fA-F]{2}[:-][\da-fA-F]{2}[:-][\da-fA-F]{2}[:-][\da-fA-F]{2}[:-][\da-fA-F]{2}$/go ? 'gtk-yes' : 'gtk-no', 'menu' ); + $_[0] -> set_sensitive( $w{window}{gui}{entrymac} -> get_chars( 0, -1 ) =~ /^[\da-fA-F]{2}[:-][\da-fA-F]{2}[:-][\da-fA-F]{2}[:-][\da-fA-F]{2}[:-][\da-fA-F]{2}[:-][\da-fA-F]{2}$/go ? 1 : 0 ); + } ); + return 0; + } ); + + ########################################################## + # Try some net movement to resolve remote host's MAC address + if ( $ip ) + { + $w{window}{gui}{table} -> set_sensitive( 0 ); + Gtk2 -> main_iteration while Gtk2 -> events_pending; + my $PING = Net::Ping -> new( 'tcp' ); + $PING -> tcp_service_check( 1 ); + $PING -> port_number( $ping_port ); + my $up = $PING -> ping( $ip, '1' ); + $mac = Net::ARP::arp_lookup( '', $ip ); + if ( $mac eq 'unknown' ) + { + $up = $PING -> ping( $ip, '1' ); + $mac = Net::ARP::arp_lookup( '', $ip ); + } + $w{window}{gui}{iconip} -> set_from_stock( $up ? 'gtk-connect' : 'gtk-disconnect', 'menu' ); + $w{window}{gui}{entrymac} -> set_text( $mac ); + $w{window}{gui}{entrymac} -> select_region( 0, length( $mac ) ); + $w{window}{gui}{lblstatus} -> set_text( "'$ip' TCP port $ping_port seems to be " . ( $up ? 'REACHABLE' : 'UNREACHABLE' ) ); + $w{window}{gui}{table} -> set_sensitive( 1 ); + $w{window}{gui}{entrymac} -> grab_focus; + $w{window}{data} -> get_action_area -> foreach( sub + { + return 1 unless $_[0] -> get_label eq 'gtk-ok'; + $_[0] -> set_sensitive( $w{window}{gui}{entrymac} -> get_chars( 0, -1 ) =~ /^[\da-fA-F]{2}[:-][\da-fA-F]{2}[:-][\da-fA-F]{2}[:-][\da-fA-F]{2}[:-][\da-fA-F]{2}[:-][\da-fA-F]{2}$/go ? 1 : 0 ); + } ); + Gtk2 -> main_iteration while Gtk2 -> events_pending; + } + else + { + $w{window}{gui}{entrymac} -> set_text( $mac ); + $w{window}{gui}{entrymac} -> select_region( 0, length( $mac ) ); + $w{window}{gui}{lblstatus} -> set_text( 'No IP to test reachability' ); + } + + ########################################################## + + my $ok = $w{window}{data} -> run(); + $mac = $w{window}{gui}{entrymac} -> get_chars( 0, -1 ); + $w{window}{data} -> destroy(); + + return 1 if $ok ne 'ok'; + + my $broadcast = $w{window}{gui}{cbbroadcast} -> get_active; + + # Prepare UDP socket + socket( S, PF_INET, SOCK_DGRAM, getprotobyname( 'udp' ) ) || die "ERROR: Can't create socket ($!)"; + $broadcast and setsockopt(S, SOL_SOCKET, SO_BROADCAST, 1) || die "ERROR: Can't change socket properties ($!)"; + + # Prepare destination addresss + $ip and my $ipaddr = inet_aton( $ip ) || die "ERROR: Unknown host: $ip"; + my $paddr = $broadcast ? sockaddr_in( 0x2fff, INADDR_BROADCAST ) : sockaddr_in( $port, $ipaddr )|| die "ERROR: Sockaddr_in failed ($!)"; + + # Prepare the magic packet ( 6 times the hex 'ff' and 16 times the 'clean' MAC addresss) + my $new_mac = $mac; + $new_mac =~ s/[:-]//g; + my $MAGIC = ( "\xff" x 6 ) . ( pack( 'H12', $new_mac ) x 16 ); + my $SIZE = $broadcast ? 0 : length( $MAGIC ); + + if ( ! send( S, $MAGIC, $SIZE, $paddr ) ) + { + _wMessage( undef, "ERROR: Sending magic packet to $ip (MAC: $mac) failed:\n$!" ); + return 0; + } + else + { + send( S, $MAGIC, $SIZE, $paddr ); + send( S, $MAGIC, $SIZE, $paddr ); + # Try sending a couple of packets to standard WoL ports of provided host ip... + send( S, $MAGIC, $SIZE, sockaddr_in( 7, $ipaddr ) ); + send( S, $MAGIC, $SIZE, sockaddr_in( 9, $ipaddr ) ); + + _wMessage( undef, "Wake On Lan 'Magic Packet'\nCORRECTLY sent to " . ( $broadcast ? 'BROADCAST' : "IP: $ip" ) . "\n(MAC: $mac)" ); + } + + return 1; +} + +sub _deleteOldestSessionLog +{ + my $cfg = shift; + my $env = shift; + my $conn = shift; + + my $folder = $$cfg{'defaults'}{'session logs folder'}; + my $max = $$cfg{'defaults'}{'session logs amount'}; + + opendir( my $F, $folder ) or die "ERROR: Could not open folder '$folder' for reading: $!"; + + my @total; + foreach my $file ( readdir $F ) + { + next unless $file =~ /^PAC_\[(.+)_(.+)\]_\[(\d{8})_(\d{6})\]\.txt$/g; + my ( $fenv, $fconn, $fdate, $ftime ) = ( $1, $2, $3, $4 ); + next unless ( $fenv eq $env && $fconn eq $conn ); + push( @total, "$folder/$file" ); + } + + close $F; + + return 1 unless scalar( @total ) ge $max; + + my $i = 0; + foreach my $file ( sort { $a cmp $b } @total ) + { + unlink $file or die "ERROR: Could not delete oldest log file '$file': $!"; + last if ( ( scalar( @total ) - $max ) <= $i++ ); + } + + return 1; +} + +sub _buildPIDFile +{ + if ( ! open( F, ">$PIDFILE" ) ) + { + print STDERR "ERROR: Could not open pid file '$PIDFILE' for writting: $!"; + return 0; + } + + print F $$; + + close F; + + return 1; +} + +sub _checkPIDFile +{ + open( F, "$PIDFILE" ) or return 0; + my $pid = <F>; + close F; + + return $pid; +} + +sub _deletePIDFile { return unlink $PIDFILE; } + +1;