#!/usr/bin/perl -w use strict; use Curses; use Curses::Widgets; use Curses::Widgets::TextField; use Curses::Widgets::ButtonSet; use Curses::Widgets::ListBox; use Curses::Widgets::TextMemo; use Curses::Widgets::ComboBox; my $Version = 0.3; my ($mwh, $actions, $filename, $ok_edit, $info, $filelist, $s_or_r, $recipient, $sign_menu); # Widget handles my ($loop_control, $first_action, $maxy, $maxx, $fname, $file_sel, $output, $infile, $options, $srchoice, $sign_choice, $r_name); # Usual Scalars my (@files); # List of files in cur dir # Get list of files in current dir. @files = (`find . -maxdepth 1 -type f \! -name '.*' -print`); # Put a space as first item in file list for use as an 'empty' selection unshift(@files, ' '); # Instantiate new Curses object and initialize $mwh = new Curses; noecho(); halfdelay(5); $mwh->keypad(1); curs_set(0); # Check that size of terminal window is OK $mwh->getmaxyx($maxy, $maxx); if ($maxx < 80 || $maxy < 24) { $mwh->erase(); endwin(); die ("Terminal is $maxx x $maxy; must be 80 x 24 min; aborting...\n"); } # Create the widgets that are used for all actions; others when needed $actions = action_widget(); $filename = file_in(); $ok_edit = ok_or_edit(); $info = info_win(); $filelist = filelist(); # Retrieve value from 'actions' widget and select appropriate routine $actions->execute($mwh); $first_action = $actions->getField('VALUE'); # Blank 'actions' widget to give a clear screen for other widgets erase_widget($actions, $mwh); if ($first_action == 0) { # Action is 'Verify' $loop_control = 1; while ($loop_control) { # 'while' loop allows 'OK/Edit' option # Use 'list_files' subroutine to select a filename $file_sel = list_files($filelist, $filename, $info, $mwh); # Set selected name in 'filename' widget window $filename->setField(VALUE => $file_sel); # Display 'filename' widget and accept or edit final filename $filename->execute($mwh); $fname = $filename->getField('VALUE'); # Set 'verify' option for gpg $options = "--verify "; # Use the retrieved values to show gpg command to be executed, then # display OK/Edit button for final action $loop_control = final_phase($options, $fname, $info, $ok_edit, $mwh); } # End of while loop } elsif ($first_action == 1) { # Action is 'Encrypt' # Instantiate special widgets $s_or_r = s_or_r(); $recipient = param_in(); $loop_control = 1; while ($loop_control) { # Use 'list_files' subroutine to select a filename $file_sel = list_files($filelist, $filename, $info, $mwh); # Set selected name in 'filename' widget window $filename->setField(VALUE => $file_sel); $filename->execute($mwh); # Get final filename and test for wildcards $fname = $filename->getField('VALUE'); if ($fname =~ /[\*\?]/) { $options = "--encrypt-files "; } else { $options = "--encrypt "; } # Display 'recipient choice' menu $s_or_r->execute($mwh); # Get choice of recipient; display 'recipient ID' widget if needed $srchoice = $s_or_r->getField('VALUE'); if ($srchoice) { # Display 'recipient' box for Recipient ID $recipient->execute($mwh); $recipient = $recipient->getField('VALUE'); $options = "-a -r $recipient " . $options; } # Use the retrieved values to show gpg command to be executed, then # display OK/Edit button for final action $loop_control = final_phase($options, $fname, $info, $ok_edit, $mwh); } # End of while loop } elsif ($first_action == 2) { # Action is 'Decrypt' $loop_control = 1; while ($loop_control) { # Use 'list_files' subroutine to select a filename $file_sel = list_files($filelist, $filename, $info, $mwh); # Set selected name in 'filename' widget window $filename->setField(VALUE => $file_sel); $filename->execute($mwh); # Get final filename and test for wildcards $fname = $filename->getField('VALUE'); if ($fname =~ /[\*\?]/) { $options = "--decrypt-files "; } else { $options = " "; } # Use the retrieved values to show gpg command to be executed, then # display OK/Edit button for final action $loop_control = final_phase($options, $fname, $info, $ok_edit, $mwh); } # End of while loop } elsif ($first_action == 3) { # Action is 'Sign' # Instantiate special widgets $sign_menu = sign_menu(); $recipient = param_in(); $s_or_r = s_or_r(); $loop_control = 1; while ($loop_control) { # Use 'list_files' subroutine to select a filename $file_sel = list_files($filelist, $filename, $info, $mwh); # Set selected name in 'filename' widget window $filename->setField(VALUE => $file_sel); $filename->execute($mwh); # Display 'sign_menu' for types of signatures and get a choice $sign_menu->execute($mwh); $sign_choice = $sign_menu->getField('VALUE'); # Note selected filename for use later $fname = $filename->getField('VALUE'); # Set 'options' for type of sig chosen if ($sign_choice == 0) { $options = "--clearsign "; } elsif ($sign_choice == 1) { $options = "--detach-sig "; } elsif ($sign_choice == 2) { $options = "--sign "; } elsif ($sign_choice == 3) { $options = "--sign --encrypt "; # Call routines to select a recipient; set options accordingly $s_or_r->execute($mwh); $srchoice = $s_or_r->getField('VALUE'); if ($srchoice) { $recipient->execute($mwh); $r_name = $recipient->getField('VALUE'); $options = "-a -r $r_name " . $options; } } else { # Indicates program error $mwh->erase(); endwin(); die "Button Selection/Program Error\n"; } # Use the retrieved values to show gpg command to be executed, then # display OK/Edit button for final action $loop_control = final_phase($options, $fname, $info, $ok_edit, $mwh); } # End of 'while' loop } elsif ($first_action == 4) { # List Keys # Instantiate special widget $recipient = param_in(); $loop_control = 1; while ($loop_control) { # Action is 'List Keys' # Modify caption of 'recipient' widget and display $recipient->setField('CAPTION' => 'ID of Keys to List/blank = All'); $recipient->execute($mwh); # Erase 'recipient' widget for now erase_widget($recipient, $mwh); # Get key IDs to list; store in the 'fname' var $fname = $recipient->getField('VALUE'); # Add a 'less' pipe if all keys are to be listed if ($fname eq " " || $fname eq "") { $fname = "|less"; } # Set 'list-keys' option $options = "--list-keys "; # Use the retrieved values to show gpg command to be executed, then # display OK/Edit button for final action $loop_control = final_phase($options, $fname, $info, $ok_edit, $mwh); } # End of while loop } elsif ($first_action == 5) { # Edit Keys # Instantiate special widget $recipient = param_in(); $loop_control = 1; while ($loop_control) { # Action is 'Edit Key' # Modify caption of 'recipient' widget and display $recipient->setField('CAPTION' => 'Key ID to Edit'); $recipient->execute($mwh); # Erase 'recipient' widget for now erase_widget($recipient, $mwh); # Get key ID to edit $fname = $recipient->getField('VALUE'); # Set 'edit-key' option $options = "--edit-key "; # Use the retrieved values to show gpg command to be executed, then # display OK/Edit button for final action $loop_control = final_phase($options, $fname, $info, $ok_edit, $mwh); } # End of while loop } else { # Indicates Program Error $mwh->erase(); endwin(); die "Button Selection/Program Error\n"; } # Clean up Curses stuff $mwh->erase(); endwin(); # Leave the perl process and execute the gpg command in terminal exec("gpg $options $fname"); exit; # For insurance # Subroutine definitions follow # Erase widget and reset window background to black # Usage: blank_widget($widget_handle, $window_handle) sub erase_widget { my($w_h, $mwh) = @_; $w_h->_init($mwh); bkgd($mwh, COLOR_BLACK); return; } # Instantiate main menu sub action_widget { my $act = Curses::Widgets::ButtonSet->new({ LENGTH => 15, VALUE => 0, INPUTFUNC => \&scankey, FOREGROUND => 'blue', BACKGROUND => 'white', BORDER => 1, FOCUSSWITCH => "\t\n", HORIZONTAL => 0, X => 1, Y => 1, LABELS => [('Verify','Encrypt','Decrypt','Sign','List Keys','Edit Keys')], }); return($act); } # Instantiate menu for type of sig wanted sub sign_menu { my $act = Curses::Widgets::ButtonSet->new({ LENGTH => 15, VALUE => 0, INPUTFUNC => \&scankey, FOREGROUND => 'blue', BACKGROUND => 'white', BORDER => 1, FOCUSSWITCH => "\t\n", HORIZONTAL => 0, X => 1, Y => 13, LABELS => [('Clear Sign','Detached Sig','Sign','Sign/Encrypt')], }); return($act); } # Instantiate menu for the usual 'OK/Edit' choice buttons sub ok_or_edit { my $okq = Curses::Widgets::ButtonSet->new({ LENGTH => 15, VALUE => 0, FOREGROUND => 'red', BACKGROUND => 'white', BORDER => 0, FOCUSSWITCH => "\n", HORIZONTAL => 1, PADDING => 5, X => 22, Y => 22, LABELS => [('OK','Edit Input')], }); return($okq); } # Instantiate menu to show/edit final file name sub file_in { my $file_in; $file_in = Curses::Widgets::TextField->new({ X => 32, Y => 1, FOREGROUND => 'blue', BACKGROUND => 'white', MAXLENGTH => $maxx, COLUMNS => 24, CAPTION => 'File Selected for Action' }); return($file_in); } # Instantiate menu for Recipient ID or Key ID sub param_in { my $param_in = Curses::Widgets::TextField->new({ X => 32, Y => 8, FOREGROUND => 'blue', BACKGROUND => 'white', MAXLENGTH => $maxx, COLUMNS => 32, CAPTION => 'Recipient ID' }); return($param_in); } # Instantiate 'info' widget to show operation status and gpg command sub info_win { my $info_win; $info_win = Curses::Widgets::TextMemo->new({ CAPTION => 'Progress...', COLUMNS => 50, LINES => 4, VALUE => '', FOREGROUND => 'blue', BACKGROUND => 'white', BORDER => 1, FOCUSSWITCH => "\t", CURSORPOS => 0, TEXTSTART => 0, X => 22, Y => 12, READONLY => 0, }); return($info_win); } # Instantiate menu to select encrypt 'to self' or 'to named recipient' sub s_or_r { my $act; $act = Curses::Widgets::ButtonSet->new({ LENGTH => 16, VALUE => 0, INPUTFUNC => \&scankey, FOREGROUND => 'blue', BACKGROUND => 'white', BORDER => 1, FOCUSSWITCH => "\t\n", HORIZONTAL => 0, X => 60, Y => 1, LABELS => [('to Self', 'to Named ID')], }); return($act); } # Instantiate scrolling widget to show files in current dir sub filelist { my $cb = Curses::Widgets::ComboBox->new({ CAPTION => 'Select the File', CAPTIONCOL => 'blue', COLUMNS => 22, MAXLENGTH => 80, MASK => undef, VALUE => 'Down Arrow for List', INPUTFUNC => \&scankey, FOREGROUND => 'blue', BACKGROUND => 'white', BORDER => 1, BORDERCOL => 'blue', FOCUSSWITCH => "\n\t", CURSORPOS => 0, TEXTSTART => 0, PASSWORD => 0, X => 1, Y => 1, READONLY => 0, LISTITEMS => \@files, }); return $cb; } # Subroutine displays list of files in current dir; returns name selected # Invoke as list_files($filelist, $filename, $info, $mwh) sub list_files { my ($filelist, $filename, $info, $mwh) = @_; my $file_sel; # Display the filelist widget $filelist->execute($mwh); # Get file selected $file_sel = $filelist->getField('VALUE'); # Erase filelist widget and redraw the others erase_widget($filelist, $mwh); $filename->draw($mwh, 0); $info->setField(VALUE => '...Waiting for remainder of input'); $info->draw($mwh, 0); # Return the selected filename return($file_sel); } # Subroutine to show command to be executed and offer OK/Edit button # for final action # Invoke as final_phase($options, $fname, $info, $ok_edit, $mwh) sub final_phase { # Use the retrieved values to show command to be executed my($options, $fname, $info, $ok_edit, $mwh) = @_; my $output = "\ngpg " . $options . $fname . "\nChoose OK or Edit; hit 'Return'"; $info->setField('VALUE' => $output); $info->setField('CAPTION' => ' >>> The gpg Command Will Be...'); $info->draw($mwh, 0); # Display OK/Edit button for final action $ok_edit->execute($mwh); #$loop_control = $ok_edit->getField('VALUE'); return($ok_edit->getField('VALUE')); } =pod =head1 NAME C - a terminal interface for the construction of routine gpg commands. =head1 DESCRIPTION This script uses a curses interface with a terminal or xterm window in a Q-and-A style to construct a command-line for common gpg operations. There is no need to remember the syntax of gpg commands or options. =head1 README Navigation: The file selection widgets are scrolled with the up- and down-arrow keys. To select a choice, hit 'Return.' To pass from one widget to the next, hit 'Return' or 'Tab.' C operates on the files in the current working directory, and saves output there as well. The text in the C widget box can be edited to include wildcards, or additional path info can be appended if desired. The C command that is run when the script exits uses the gpg defaults that are in the gpg config file, normally C<~/.gnupg/gpg.conf>. This includes the C, C, C and C options. However, during C and C operations, if a recipient other than C is chosen, we assume that an ASCII armored output is needed, and that option is included, overriding the setting in the C config file. =head1 PREREQUISITES =over =item strict =item Curses =item Curses::Widgets =item Curses::Widgets::TextField =item Curses::Widgets::ButtonSet =item Curses::Widgets::ListBox =item Curses::Widgets::TextMemo =item Curses::Widgets::ComboBox =item And, of course, a gpg executable is required. =back =head1 SCRIPT CATEGORIES UNIX : System_administration =head1 AUTHOR / COPYRIGHT Howard L. Arons, hlarons@CPAN.org Copyright (c) 2005 by Howard L. Arons. All Rights Reserved. This script is free software; you can redistribute it and/or modify it under the same terms as Perl itself. If you have suggestions for improvement, please e-mail me. If you make improvements, kindly send me a copy of your changes. Thanks. =cut