#!/usr/bin/env perl # gscriptor: GTK interface to send APDU commands to a smart card # Copyright (C) 2001 Lionel Victor, Ludovic Rousseau # 2002-2010 Ludovic Rousseau # # 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 2 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, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # gscriptor uses libgtk-perl, please make sure it is correctly installed # on your system use strict; use warnings; use Glib qw(TRUE FALSE); use Gtk2 '-init'; use Gtk2::SimpleMenu; use File::Basename; use Carp; use Chipcard::PCSC; use Chipcard::PCSC::Card; #set_locale Gtk; my $strAppName = "gscriptor"; my $strConfigFileName = "$ENV{HOME}/.$strAppName"; my @ResultStruct; my %hConfig; my ($vscScript, $vscResult); # PCSC related variables my $hContext = Chipcard::PCSC->new; die ("Can't create the Chipcard::PCSC object: $Chipcard::PCSC::errno\n") unless (defined $hContext); my $hCard = Chipcard::PCSC::Card->new ($hContext); die ("Can't create the Chipcard::PCSC::Card object: $Chipcard::PCSC::errno\n") unless (defined $hContext); ############################### create widgets ############################### my $wndMain = Gtk2::Window->new("toplevel"); my $txtScript = Gtk2::TextView->new; my $txtResult = Gtk2::TextView->new; my $rdbASCII = Gtk2::RadioButton->new_with_label(undef, "ASCII"); my $rdbHex = Gtk2::RadioButton->new_with_label($rdbASCII->get_group(), "Hex"); my $chkWrap = Gtk2::CheckButton->new("Wrap lines"); my $btnRun = Gtk2::Button->new("Run"); my $txtStatus = Gtk2::Entry->new; ############################## arrange widgets ############################### # arrange_widgets () is used to arrange widgets in the windows. # It allows to conveniently allocate temporary variables for the various # containers and the scrollbars, as well as the menu. # This function also sets some basic properties of the different # widgets used... configuration is therefore grouped in a single place. sub arrange_widgets () { $wndMain->set_title ("$strAppName"); $txtScript->set_editable(TRUE); $txtResult->set_editable(FALSE); $txtStatus->set_sensitive(FALSE); $chkWrap->set_active(TRUE); $rdbHex->set_active(TRUE); # create and bind toolbars $vscScript = Gtk2::ScrolledWindow->new; $vscScript->set_size_request(200, 200); $vscScript->set_policy ('automatic', 'automatic'); $vscScript->add($txtScript); $vscResult = Gtk2::ScrolledWindow->new; $vscResult->set_size_request(200, 200); $vscResult->set_policy ('automatic', 'automatic'); $vscResult->add($txtResult); # create text tags my $buffer = $txtResult->get_buffer; $buffer->create_tag("red", foreground => "red"); $buffer->create_tag("blue", foreground => "blue"); $buffer->create_tag("monospace", family => "monospace"); # create and set tooltips my $tipScript = Gtk2::Tooltips->new; my $tipResult = Gtk2::Tooltips->new; my $tipASCII = Gtk2::Tooltips->new; my $tipHex = Gtk2::Tooltips->new; my $tipRun = Gtk2::Tooltips->new; my $tipWrap = Gtk2::Tooltips->new; $tipScript->set_tip ($txtScript, "list of APDUs to exchange"); $tipResult->set_tip ($txtResult, "result window"); $tipASCII->set_tip ($rdbASCII, "show ASCII data"); $tipHex->set_tip ($rdbHex, "show hexadecimal data"); $tipRun->set_tip ($btnRun, "run the current script"); $tipWrap->set_tip ($chkWrap, "wrap long lines"); # create and set the menu with the accel_table my $menu_model = [ _File => { item_type => '', children => [ New => { callback => \&NewScriptFile, accelerator => 'N', item_type => '', extra_data => 'gtk-new' }, Open => { callback => \&LoadScriptFile, accelerator => 'O', item_type => '', extra_data => 'gtk-open' }, Save => { callback => \&SaveScriptFile, accelerator => 'S', item_type => '', extra_data => 'gtk-save' }, 'Save As' => { callback => \&SaveAsScriptFile, accelerator => 'S', item_type => '', extra_data => 'gtk-save-as' }, Separator => { item_type => '' }, Quit => { callback => \&CloseAppWindow, accelerator => 'Q', item_type => '', extra_data => 'gtk-quit' } ] }, R_eader => { item_type => '', children => [ _Connect => { callback => \&ConnectDefaultReader, accelerator => 'C' }, Reco_nnect => { callback => \&ReconnectDefaultReader, item_type => '', extra_data => 'gtk-redo' }, _Disconnect => { callback => \&DisconnectDefaultReader, accelerator => 'D' }, Separator => { item_type => '' }, '_Status...' => { callback => \&DefaultReaderStatus, item_type => '', extra_data => 'gtk-properties' } ] }, Ru_n => { item_type => '', children => [ '_Run Script' => { callback => \&RunScript, accelerator => 'R', item_type => '', extra_data => 'gtk-execute' }, 'C_lear Result Area' => { callback => \&ClearResult, accelerator => 'L', item_type => '', extra_data => 'gtk-clear' } ] }, _Settings => { item_type => '', children => [ Reader => { callback => \&ReaderConfig, item_type => '', extra_data => 'gtk-preferences' } ] }, _Help => { item_type => '', children => [ About => { callback => \&Help, item_type => '', extra_data => 'gtk-dialog-info' } ] } ]; my $menu = Gtk2::SimpleMenu->new(menu_tree => $menu_model); $wndMain->add_accel_group( $menu->{accel_group} ); # create and arrange box containers my $vbxMainBox = Gtk2::VBox->new (FALSE, 3); my $hbxScriptBox = Gtk2::HBox->new (FALSE, 3); my $vbxScriptBox = Gtk2::VBox->new (FALSE, 10); my $hbxResultBox = Gtk2::HBox->new (FALSE, 3); my $vbxResultBox = Gtk2::VBox->new (FALSE, 3); $hbxScriptBox->pack_end($vscScript, TRUE, TRUE, 0); $vbxScriptBox->add($hbxScriptBox); $vbxScriptBox->pack_end($btnRun, FALSE, FALSE, 0); $vbxScriptBox->pack_end($chkWrap, FALSE, FALSE, 0); $vbxScriptBox->set_border_width(5); $hbxResultBox->pack_end($vscResult, TRUE, TRUE, 0); $vbxResultBox->add($hbxResultBox); $vbxResultBox->pack_start($rdbASCII, FALSE, FALSE, 0); $vbxResultBox->pack_start($rdbHex, FALSE, FALSE, 0); $vbxResultBox->set_border_width(5); # create and fill frame containers my $frmScript = Gtk2::Frame->new ("Script"); my $frmResult = Gtk2::Frame->new ("Result"); $frmScript->set_border_width(5); $frmScript->add ($vbxScriptBox); $frmResult->set_border_width(5); $frmResult->add ($vbxResultBox); my $hpaned = Gtk2::HPaned->new; $hpaned->set_border_width(5); my $hbxBox = Gtk2::HBox->new (FALSE, 10); $hpaned->add1($frmScript); $hpaned->add2($frmResult); $hbxBox->add($hpaned); $vbxMainBox->pack_start ($menu->{widget}, FALSE, FALSE, 1); $vbxMainBox->add ($hbxBox); $vbxMainBox->pack_end ($txtStatus, FALSE, FALSE, 0); $vbxMainBox->show_all(); $wndMain->add($vbxMainBox); } # Connect widgets together $chkWrap->signal_connect('toggled', sub { $txtScript->set_wrap_mode($chkWrap->get_active ? 'GTK_WRAP_CHAR' : 'GTK_WRAP_NONE'); } ); $wndMain->signal_connect ("delete_event", \&CloseAppWindow); $btnRun->signal_connect ("clicked", \&RunScript); $rdbASCII->signal_connect('toggled', \&RefreshResult); $rdbHex->signal_connect('toggled', \&RefreshResult); arrange_widgets(); ReadConfigFile(); ############################### create widgets ############################### $txtStatus->set_text ("welcome to the $strAppName application! - not connected"); # Show up everything as we should be ready by now $wndMain->show_all(); if (exists $hConfig{'script'}) { unless (ReadScriptFile ($hConfig{'script'})) { MessageBox ("Can not open $hConfig{'script'}: $!", "error", ['OK']); delete $hConfig{'script'}; } } Gtk2->main; exit; ######################### application engine follows ######################### sub MessageBox { my $strMessage = shift; my $icon_text = shift; my @ButtonDescr = @_; my $refCurButtonDescr; my $last_button; # Choose a nice title to our message box @ButtonDescr = ["OK"] unless @ButtonDescr; my $dlgDialog = Gtk2::Dialog->new; my $hbxButtonBox = Gtk2::HButtonBox->new; my $hbxTextBox = Gtk2::HBox->new (FALSE, 0); my $hbox = Gtk2::HBox->new (FALSE, 12); $dlgDialog->vbox->add ($hbox); $hbox->set_border_width(12); my $icon = Gtk2::Image->new_from_stock("gtk-dialog-" . $icon_text, "GTK_ICON_SIZE_DIALOG"); $hbox->add ($icon); # build up all the buttons and associate callbacks foreach $refCurButtonDescr (reverse @ButtonDescr) { croak ("invalid button name") unless scalar ($$refCurButtonDescr[0]); $$refCurButtonDescr[1] = sub { $dlgDialog->destroy(); } if $#$refCurButtonDescr == 0; warn ("button named '$$refCurButtonDescr[0]' has an invalid callback: '$$refCurButtonDescr[1]'") unless ref ($$refCurButtonDescr[1]); # Each button is constructed from its label my $btnTmpButton; $btnTmpButton = Gtk2::Button->new_from_stock("gtk-ok") if ($$refCurButtonDescr[0] =~ m/OK/); $btnTmpButton = Gtk2::Button->new_from_stock("gtk-cancel") if ($$refCurButtonDescr[0] =~ m/CANCEL/); $btnTmpButton = Gtk2::Button->new($$refCurButtonDescr[0]) unless defined $btnTmpButton; $btnTmpButton->can_default(1); # A mouse click on the button triggers a call to its embedded # callback then destroys the dialog box $btnTmpButton->signal_connect ("clicked", sub { &{$$refCurButtonDescr[1]}; $dlgDialog->destroy(); }); $hbxButtonBox->pack_end($btnTmpButton, FALSE, FALSE, 12); $last_button = $btnTmpButton; } $dlgDialog->action_area->pack_start($hbxButtonBox, 1, 1, 0); my $label = Gtk2::Label->new($strMessage); $hbxTextBox->pack_start($label, FALSE, FALSE, 0); $hbox->add ($hbxTextBox); # the last button is the default one $last_button->grab_default(); # Finally show up everything $dlgDialog->set_default_size (0, 0); $dlgDialog->set_modal(1); $dlgDialog->show_all(); } sub ReaderConfig { my $dlgReaderConfig = Gtk2::Dialog->new; my $txtReader = Gtk2::Label->new ("Reader"); my $cboReaders = Gtk2::ComboBox->new_text; my $rdbT0 = Gtk2::RadioButton->new(undef, "T=0"); my $rdbT1 = Gtk2::RadioButton->new($rdbT0->get_group(), "T=1"); my $rdbT01 = Gtk2::RadioButton->new($rdbT0->get_group(), "T=0 or T=1"); my $rdbRAW = Gtk2::RadioButton->new($rdbT0->get_group(), "T=RAW"); my $txtProtocol = Gtk2::Label->new ("Protocol"); my @readers_list = $hContext->ListReaders; # This sub adjusts the content of $hConfig with the selected protocol my $subProtocol = sub { $hConfig{'protocol'} = $Chipcard::PCSC::SCARD_PROTOCOL_RAW if ($rdbRAW->get_active()); $hConfig{'protocol'} = $Chipcard::PCSC::SCARD_PROTOCOL_T0 if ($rdbT0->get_active()); $hConfig{'protocol'} = $Chipcard::PCSC::SCARD_PROTOCOL_T1 if ($rdbT1->get_active()); $hConfig{'protocol'} = $Chipcard::PCSC::SCARD_PROTOCOL_T0|$Chipcard::PCSC::SCARD_PROTOCOL_T1 if ($rdbT01->get_active()); }; $rdbT0->signal_connect ('toggled', $subProtocol); $rdbT1->signal_connect ('toggled', $subProtocol); $rdbT01->signal_connect ('toggled', $subProtocol); $rdbRAW->signal_connect('toggled', $subProtocol); my $btnOK = Gtk2::Button->new_from_stock("gtk-ok"); $btnOK->signal_connect( "clicked", sub { $hConfig{'reader'} = @readers_list[$cboReaders->get_active()]; $hConfig{'reader'} = "" if (! defined $hConfig{'reader'}); $hCard->Disconnect($Chipcard::PCSC::SCARD_UNPOWER_CARD); $dlgReaderConfig->destroy(); }); $btnOK->can_default(1); my $btnCancel = Gtk2::Button->new_from_stock("gtk-cancel"); $btnCancel->signal_connect( "clicked", sub { $dlgReaderConfig->destroy(); }); $btnCancel->can_default(1); my $hbxReaderBox = Gtk2::HBox->new (FALSE, 10); my $hbxButtonBox = Gtk2::HButtonBox->new; my $hbxStatusBox = Gtk2::Table->new (4, 2, 1); $hbxReaderBox->pack_start ($txtReader, FALSE, FALSE, 10); $hbxReaderBox->pack_start ($cboReaders, 1, 1, 10); $hbxButtonBox->pack_end($btnCancel, FALSE, FALSE, 10); $hbxButtonBox->pack_end($btnOK, FALSE, FALSE, 10); # Select the last prefered protocol if any if (defined $hConfig {'protocol'}) { $rdbT0->set_active(1) if ($hConfig {'protocol'} == $Chipcard::PCSC::SCARD_PROTOCOL_T0); $rdbT1->set_active(1) if ($hConfig {'protocol'} == $Chipcard::PCSC::SCARD_PROTOCOL_T1); $rdbT01->set_active(1) if ($hConfig {'protocol'} == ($Chipcard::PCSC::SCARD_PROTOCOL_T0 | $Chipcard::PCSC::SCARD_PROTOCOL_T1)); $rdbRAW->set_active(1) if ($hConfig {'protocol'} == $Chipcard::PCSC::SCARD_PROTOCOL_RAW); } $hbxStatusBox->attach_defaults ($txtProtocol, 0, 1, 0, 1); $hbxStatusBox->attach_defaults ($rdbT0, 1, 2, 0, 1); $hbxStatusBox->attach_defaults ($rdbT1, 1, 2, 1, 2); $hbxStatusBox->attach_defaults ($rdbT01, 1, 2, 2, 3); $hbxStatusBox->attach_defaults ($rdbRAW, 1, 2, 3, 4); $cboReaders->append_text($_) for (@readers_list); # We select the first reader (default choice) $cboReaders->set_active(0); # Select the last prefered reader if any if (defined $hConfig {'reader'}) { my $i = 0; my $reader = $hConfig {'reader'}; # escape metacharacters $reader =~ s/\[/\\\[/g; $reader =~ s/\]/\\\]/g; $reader =~ s/\(/\\\[/g; $reader =~ s/\)/\\\)/g; for (@readers_list) { $cboReaders->set_active($i) if (m/$reader/); $i++; } } $dlgReaderConfig->action_area->pack_start($hbxButtonBox, TRUE, TRUE, 0); $dlgReaderConfig->vbox->set_spacing(6); $dlgReaderConfig->vbox->add ($hbxReaderBox); $dlgReaderConfig->vbox->add (new Gtk2::HSeparator()); $dlgReaderConfig->vbox->add ($hbxStatusBox); # the OK button is the default one $btnOK->grab_default(); $dlgReaderConfig->set_default_size (0, 0); $dlgReaderConfig->set_modal(1); $dlgReaderConfig->show_all(); } sub ReadConfigFile { if (-f $strConfigFileName) { die ("Can't open $strConfigFileName for reading: $!\n") unless (open (FILE, "<$strConfigFileName")); while () { chomp; next if /^#/; next if /^\s*$/; if (/^\s*(\S+)\s*=\s*\'(.*)\'$/) { $hConfig{$1} = $2; } else { print STDERR "Error while parsing $strConfigFileName\n\t'$_'\n"; } } close (FILE); } else { print STDERR "Couldn't read from $strConfigFileName. Using default configuration\n"; } $vscScript->set_size_request(split / /, $hConfig{'ScriptSize'}) if (defined $hConfig{'ScriptSize'}); $vscResult->set_size_request(split / /, $hConfig{'ResultSize'}) if (defined $hConfig{'ResultSize'}); } sub WriteConfigFile { my $strTmpKey; $hConfig{'ScriptSize'} = join (" ", $vscScript->get_size_request()); $hConfig{'ResultSize'} = join (" ", $vscResult->get_size_request()); die ("Can't open $strConfigFileName for writing: $!\n") unless (open (FILE, ">$strConfigFileName")); print FILE "# This file is automatically generated\n# Do not edit unless you know what you are doing\n\n"; foreach $strTmpKey (sort keys %hConfig) { print FILE "$strTmpKey = \'$hConfig{$strTmpKey}\'\n" if (defined $hConfig{$strTmpKey}); } close (FILE); } sub ReadScriptFile { my $strScriptFileName = shift; my $strBaseFileName = basename ($strScriptFileName); return 0 unless open (FILE, "<$strScriptFileName"); $txtScript->get_buffer->set_text(""); ($txtScript->get_buffer)->insert_at_cursor($_) while (); close (FILE); # Upon succesfull completion, we also set the window title as well # as the current ScriptfileName in the configuration hash $hConfig{'script'} = $strScriptFileName; $wndMain->set_title ("$strAppName - <$strBaseFileName>"); return 1; } sub WriteScriptFile { my $strScriptFileName = shift; my $strBaseFileName = basename ($strScriptFileName); return 0 unless open (FILE, ">$strScriptFileName"); my $textbuffer = $txtScript->get_buffer; print FILE $textbuffer->get_text($textbuffer->get_start_iter, $textbuffer->get_end_iter, 1); close (FILE); # Upon successfull completion, we also set the window title as well # as the current ScriptfileName in the configuration hash $hConfig{'script'} = $strScriptFileName; $wndMain->set_title ("$strAppName - <$strBaseFileName>"); return 1; } sub NewScriptFile { if ($txtScript->get_buffer->get_char_count()) { # That call to MessageBox will connect the OK button with a callback # that actually erase the script MessageBox( "The current work will be lost!\nAre you sure you want to continue?", "question", ["OK", sub { EraseCurrentScript(); } ], ["CANCEL"] ); } } sub EraseCurrentScript { $txtScript->get_buffer->set_text(""); $wndMain->set_title ("$strAppName"); delete $hConfig{'script'}; } sub SaveScriptFile { my ($widget) = @_; if (exists $hConfig{'script'}) { WriteScriptFile ($hConfig{'script'}); } else { SaveAsScriptFile ($widget); } } sub SaveAsScriptFile { my ($widget) = @_; my $file_dialog = Gtk2::FileSelection->new( "Save File" ); $file_dialog->ok_button->signal_connect( "clicked", sub { my $file = $file_dialog->get_filename(); if (-d $file) { MessageBox ("Can't open $file: file is a directory", "error", ['OK']); } else { MessageBox ("Can't open $file: $!", "error", ['OK']) unless WriteScriptFile ($file); } $file_dialog->destroy(); }); $file_dialog->cancel_button->signal_connect( "clicked", sub { $file_dialog->destroy(); }); if (exists $hConfig{'script'}) { $file_dialog->set_filename(dirname($hConfig{'script'})) } $file_dialog->show(); } sub LoadScriptFile { if ($txtScript->get_buffer->get_char_count()) { MessageBox( "The current work will be lost!\nAre you sure you want to continue?", "question", ["OK", \&LoadScript], ["CANCEL"] ); } else { LoadScript(); } } sub LoadScript { my $file_dialog = Gtk2::FileSelection->new( "Load File" ); $file_dialog->ok_button->signal_connect( "clicked", sub { my $file = $file_dialog->get_filename(); if (-d $file) { MessageBox ("Can't open $file: file is a directory", "error", ['OK']); } else { MessageBox ("Can't open $file: $!", "error", ['OK']) unless ReadScriptFile ($file); } $file_dialog->destroy(); }); $file_dialog->cancel_button->signal_connect( "clicked", sub { $file_dialog->destroy(); }); if (exists $hConfig{'script'}) { $file_dialog->set_filename(dirname($hConfig{'script'})."/"); } $file_dialog->show(); } sub ClearResult { $txtResult->get_buffer->set_text(""); @ResultStruct = (); } sub RefreshResult { my $tmpLine; my $tmp_value; my $textbuffer = $txtResult->get_buffer; my $tmp_text; my $state = 0; # text $textbuffer->set_text (""); my $iter = $textbuffer->get_end_iter; foreach $tmpLine (@ResultStruct) { if (ref $tmpLine) { $tmp_text = ""; if ($rdbHex->get_active) { my $c = 0; for (@$tmpLine) { $tmp_text .= sprintf ("%02X ", $_); if (++$c == 16) { $tmp_text .= "\n"; $c = 0; } } } else { my $c = 0; foreach $tmp_value (@$tmpLine) { # TODO: enhance filtering of non-printable chars # TODO: how can we use array_to_ascii here??? maybe # enhence the function so it can directly receive # ranges of chars to ignore optionally? if (($tmp_value >= 0x20) && ($tmp_value < 0x80)) { $tmp_text .= chr ($tmp_value); } else { $tmp_text .= "."; } if ($c++ == 32) { $tmp_text .= "\n"; $c = 0; } } } $textbuffer->insert_with_tags_by_name ($iter, "$tmp_text\n", "monospace", $state ? "red" : "blue"); } else { $textbuffer->insert ($iter, $tmpLine); $state = 0; $state = 1 if ($tmpLine =~ m/Received/); } } } sub ConnectDefaultReader { my @readers_list = $hContext->ListReaders; # if only one reader is found use it unconditionally $hConfig{'reader'} = $readers_list[0] if ($#readers_list == 0); if (defined $hConfig{'reader'} && !($hConfig{'reader'} eq '')) { if (defined $hCard->{hCard}) { MessageBox ( "The card is already connected...\nDo you want to reconnect?", "question", ['OK', \&ReconnectDefaultReader], ['CANCEL'] ); } else { $hCard->Connect ($hConfig{'reader'}, $Chipcard::PCSC::SCARD_SHARE_SHARED, $hConfig{'protocol'}); if (defined $hCard->{hCard}) { $txtStatus->set_text ("Connected to '$hConfig{'reader'}'"); } else { MessageBox ("Can not connect to the reader named '$hConfig{'reader'}':\n$Chipcard::PCSC::errno", "error", ['OK']) }; } } else { MessageBox ("No default reader has been configured.\nPlease make sure you have configured a reader first.", "error", ['OK']); } } sub ReconnectDefaultReader { if (defined $hCard->{hCard}) { $hCard->Reconnect ($Chipcard::PCSC::SCARD_SHARE_SHARED, $hConfig{'protocol'}, $Chipcard::PCSC::SCARD_RESET_CARD); if (defined $hCard->{hCard}) { $txtStatus->set_text ("Connected to '$hConfig{'reader'}'"); } else { MessageBox ("Can not reconnect to the reader named '$hConfig{'reader'}':\n$Chipcard::PCSC::errno", "error", ['OK']) }; } else { # we just propose to connect but do not call Reconnect() # afterwards that would be quite useless MessageBox ("The Chipcard::PCSC:Card object is not connected...\n Do you want to connect?", "question", ['OK', sub {ConnectDefaultReader();}], ['CANCEL']); } } sub DisconnectDefaultReader { if (defined $hCard->{hCard}) { $hCard->Disconnect($Chipcard::PCSC::SCARD_UNPOWER_CARD); if (defined $hCard->{hCard}) { MessageBox ("Can not disconnect from the reader named '$hConfig{'reader'}':\n$Chipcard::PCSC::errno", "error", ['OK']); } else { $txtStatus->set_text ("not connected"); }; } else { MessageBox ("The Chipcard::PCSC::Card object is not connected!", "error", ['OK']); } } sub DefaultReaderStatus { if (defined $hCard->{hCard}) { my @StatusResult = $hCard->Status(); if (defined $StatusResult[0]) { #TODO This stinks can't I rewrite it so it looks better&shorter my $tmpVal = 0; my $MessageString = "You are connected to reader $StatusResult[0].\n"; $MessageString .= "Card status (". sprintf ("0x%04X", $StatusResult[1]) ."): "; # Verbosely describes the Card Status (powered, inserted, etc.) if ($StatusResult[1] & $Chipcard::PCSC::SCARD_UNKNOWN) { $MessageString .= "'Unknown state', "; } if ($StatusResult[1] & $Chipcard::PCSC::SCARD_ABSENT) { $MessageString .= "Absent, "; } if ($StatusResult[1] & $Chipcard::PCSC::SCARD_PRESENT) { $MessageString .= "Present, "; } if ($StatusResult[1] & $Chipcard::PCSC::SCARD_SWALLOWED) { $MessageString .= "Swallowed, "; } if ($StatusResult[1] & $Chipcard::PCSC::SCARD_POWERED) { $MessageString .= "Powered, "; } if ($StatusResult[1] & $Chipcard::PCSC::SCARD_NEGOTIABLE) { $MessageString .= "'PTS is negotiable', "; } if ($StatusResult[1] & $Chipcard::PCSC::SCARD_SPECIFIC) { $MessageString .= "'PTS has been set', "; } # remove the trailing ", " substr ($MessageString, -2) = ""; # Verbosely describes the currently active protocol $MessageString .= ".\nThe active protocol (".sprintf ("0x%04X", $StatusResult[2]).") is "; if (($StatusResult[2] & $Chipcard::PCSC::SCARD_PROTOCOL_T0)) { $MessageString .= "T=0 "; } elsif (($StatusResult[2] & $Chipcard::PCSC::SCARD_PROTOCOL_T1)) { $MessageString .= "T=1 "; } elsif (($StatusResult[2] & $Chipcard::PCSC::SCARD_PROTOCOL_RAW)) { $MessageString .= "RAW "; } else { $MessageString .= "unknown"; } # Displays the ATR (if available) $MessageString .= ".\nThe ATR is "; if (defined $StatusResult[3]) { foreach (@{$StatusResult[3]}) { $MessageString .= sprintf ("%02X ", $_); } } else { $MessageString .= "not available"; } MessageBox ($MessageString, "info", ['OK']); } else { MessageBox ("Can not retrieve reader status:\n$Chipcard::PCSC::errno", "error", ['OK']) } } else { MessageBox ("The reader is not connected...\n Do you want to connect?", "question", ['OK', sub {ConnectDefaultReader(); if (defined $hCard->{hCard}) {DefaultReaderStatus();}}], ['CANCEL']); } } sub RunScript { if (defined $hCard->{hCard}) { my $textbuffer = $txtScript->get_buffer; my @tmpCommandArray = split /\n/, $textbuffer->get_text($textbuffer->get_start_iter, $textbuffer->get_end_iter, 1); my $raCurrentResult; my $nIndex; my $cmd; push @ResultStruct, "Beginning script execution...\n\n"; foreach $_ (@tmpCommandArray) { # this variable has to be inside the loop as we store a # reference to it in the result struct... it has to be # unique for each command line my $raCurrentCommand; # Skip blank lines and comments next if /^\s*$/; next if /^#/; if (/reset/i) { push @ResultStruct, "[Reset]\n"; ReconnectDefaultReader(); push @ResultStruct, "ATR: "; my @s = $hCard->Status(); push @ResultStruct, \@{$s[3]}; push @ResultStruct, "\n"; next; } # if the command does not contains spaces (00A4030000) we expand it s/(..)/$1 /g if (! m/ /); # continue if line ends in \ if (m/\\$/) { chop; # remove the \ s/ *$/ /; # replace any spaces by ONE space $cmd .= $_; next; # read next line } $cmd .= $_; # Extract bytes from the ascii string $raCurrentCommand = Chipcard::PCSC::ascii_to_array($cmd); $cmd = ""; # push them in the Display structure push @ResultStruct, "Sending: "; push @ResultStruct, $raCurrentCommand; # Transmit them to the card $raCurrentResult = $hCard->Transmit ($raCurrentCommand); if (ref $raCurrentResult) { push @ResultStruct, "Received: "; push @ResultStruct, $raCurrentResult; push @ResultStruct, Chipcard::PCSC::Card::ISO7816Error(substr Chipcard::PCSC::array_to_ascii($raCurrentResult), -5), "\n\n"; } else { MessageBox ("Transmit failed: $Chipcard::PCSC::errno\nStopping script execution", "error", ['OK']); push @ResultStruct, "\nErrors During Script Execution: $Chipcard::PCSC::errno\n"; last; } } push @ResultStruct, "Script was executed without error...\n"; } else { MessageBox ("The Chipcard::PCSC::Card object is not connected!\nDo you want to connect?", "question", ['CANCEL'], ['OK', sub {ConnectDefaultReader(); if (defined $hCard->{hCard}) {RunScript();}}]); } RefreshResult(); } sub CloseAppWindow { undef $hCard; undef $hContext; WriteConfigFile(); Gtk2->main_quit(); } sub Help { MessageBox (" $strAppName is coded by Lionel VICTOR, (C) 2001. and debugged & improved by Ludovic ROUSSEAU, (C) 2001-2006. $strAppName is (was) a small application written in Perl. It basically demonstrates how easy it is to develop a quick working prototype that uses smartcards in Perl. ", "info", ['OK'] ); } # End of File #