3 # (c) 2008 spd-developer-team
4 # treibholz <treibholz@users.sourceforge.net>
5 # Z3po <Z3po@users.sourceforge.net>
6 # badticket <badticket@users.sourceforge.net>
9 use strict
; # strict handling
10 use warnings
; # show warnings
11 use Getopt
::Long
; # used to make -parameter possible
12 Getopt
::Long
::Configure
("bundling"); # you can combine parameters with it. -avx = -a -v -x
13 use GnuPG
::Interface
; # GPG Interface for perl
14 use IO
::Handle
; # module to open filehandles
15 use IO
::File
; # needed to open files directly
16 use Switch
; # module to handle switches
17 use File
::Temp qw
/ tempfile /; # needed to create secure temporary files
18 use File
::Copy
; # needed to copy files
19 use Term
::ANSIColor
qw(:constants); # make color possible
20 $Term::ANSIColor
::AUTORESET
= 1; # autoreset colorscheme after each \n
21 use Digest
::MD5
::File
qw(file_md5_hex); #
22 #use open ':utf8'; # all input and output strings shall be utf8 strings
23 #can not be used because of destroying the "--edit" procedure. umlauts are displayed mutated
25 my $GLOBALSETTINGS = {};
26 my $GLOBALPARAMETERS = {};
27 $main::GLOBALSETTINGS
->{HOME
} = $ENV{'HOME'};
28 $main::GLOBALSETTINGS
->{SPD_DIR
} = "$main::GLOBALSETTINGS->{HOME}/.spd";
29 $main::GLOBALSETTINGS
->{EDITOR
} = "vi";
30 $main::GLOBALSETTINGS
->{VERSION
} = "0.2-UNKNOWN";
31 $main::GLOBALSETTINGS
->{SYSTEM_CONFIG
} = "/etc/spd/spd.conf";
33 # Function to check if all needed variables are set and make them global accessible
34 # There are no parameters needed calling this function. It does not return anything.
37 my $passfile_not_forced = shift;
40 # check if configfile is present
41 if ( !$main::GLOBALSETTINGS
->{CONFIG_FILE
} ){ # if configfile did not get set with --config
42 $main::GLOBALSETTINGS
->{CONFIG_FILE
} = "$main::GLOBALSETTINGS->{SPD_DIR}/spd.conf"; # set default configfile
43 if ( -e
$main::GLOBALSETTINGS
->{CONFIG_FILE
} ){ # is configfile there?
44 $config=parse_config
($main::GLOBALSETTINGS
->{CONFIG_FILE
}); # get the config parameters
46 elsif ( -e
$main::GLOBALSETTINGS
->{SYSTEM_CONFIG
} ){ # check for system-wide configfile
47 $config=parse_config
($main::GLOBALSETTINGS
->{SYSTEM_CONFIG
}); # get the config parameters
50 require SPD
::unix_create_config
;
51 print "CONFIG FILE NOT FOUND!\n";
52 print "Do you want to create one with the wizard (w) or generate a default one (d) ?";
56 if ( $answer eq "w" ) {
59 elsif ( $answer eq "d" ) {
60 print "GENERATING A SAMPLE TO $main::GLOBALSETTINGS->{CONFIG_FILE}\n";
64 die "Did nothing. You cannot do anything without a working configfile.\n";
70 if ( !-f
$main::GLOBALSETTINGS
->{CONFIG_FILE
} ){ # checks wether configfile is a file
71 die "FILE \"$main::GLOBALSETTINGS->{CONFIG_FILE}\" NOT FOUND!\n"
74 $config=parse_config
($main::GLOBALSETTINGS
->{CONFIG_FILE
}); # get the config parameters
78 # is the EDITOR-variable defined? if not let it be "vi"
79 if ( $config->{EDITOR
} ){
80 $main::GLOBALSETTINGS
->{EDITOR
} = $config->{EDITOR
};
83 # is a shared PASSFILE defined? and readable?
84 if ( $config->{SHARED_PASSFILE
} && !$main::GLOBALPARAMETERS
->{LOCAL
} ){
85 if ( !-r
$config->{SHARED_PASSFILE
} && !$main::GLOBALPARAMETERS
->{CREATE
} ) {
86 die "PASSFILE $config->{SHARED_PASSFILE} NOT FOUND OR NOT READABLE!\nForgot --create?\nForgot --local?\n";
89 $main::GLOBALSETTINGS
->{SHARED_PASSFILE
} = $config->{SHARED_PASSFILE
};
93 # is PASSFILE defined? and readable?
94 if ( !$main::GLOBALSETTINGS
->{PASSFILE
} || $passfile_not_forced ){
95 if ( $config->{PASSFILE
} ){
96 $main::GLOBALSETTINGS
->{PASSFILE
} = $config->{PASSFILE
};
97 $passfile_not_forced = "1";
98 if ( !-r
$main::GLOBALSETTINGS
->{PASSFILE
} && !$main::GLOBALPARAMETERS
->{CREATE
} ) {
99 die "PASSFILE $main::GLOBALSETTINGS->{PASSFILE} NOT FOUND OR NOT READABLE!\nForgot --create?\n";
103 die "PASSFILE NOT DEFINED!\n";
107 if ( !-r
$main::GLOBALSETTINGS
->{PASSFILE
} && !$main::GLOBALPARAMETERS
->{CREATE
} ) {
108 die "PASSFILE $main::GLOBALSETTINGS->{PASSFILE} NOT FOUND OR NOT READABLE!\nForgot --create?\n"
112 # is YOUR_ID defined?
113 if ( $config->{YOUR_ID
} ) {
114 $main::GLOBALSETTINGS
->{YOUR_NAME
} = $config->{YOUR_ID
};
117 die "YOUR_ID NOT SET! CHECK YOUR CONFIGFILE\n";
119 if ( $config->{$main::GLOBALSETTINGS
->{YOUR_NAME
}} ) {
120 $main::GLOBALSETTINGS
->{YOUR_ID
} = $config->{$main::GLOBALSETTINGS
->{YOUR_NAME
}};
123 die "$main::GLOBALSETTINGS->{YOUR_NAME} NOT DEFINED!\n";
126 # get the gpg-key of TRUSTED_ID
127 if ( $config->{TRUSTED_IDS
} ){
128 @
{$main::GLOBALSETTINGS
->{IDS
}} = {};
129 my @name = split(/\s+/, $config->{TRUSTED_IDS
});
130 my $elements = @name;
131 for ( my $i=0; $i<$elements; $i++) {
132 if ( $config->{$name[$i]} ) {
133 ${$main::GLOBALSETTINGS
->{IDS
}}[$i] = "$config->{$name[$i]}";
136 die "$name[$i] NOT DEFINED! CHECK YOUR CONFIGFILE";
141 # get the lines which should be colored
142 if ( $config->{RED
} ){
143 $main::GLOBALSETTINGS
->{RED
} = $config->{RED
};
145 if ( $config->{YELLOW
} ){
146 $main::GLOBALSETTINGS
->{YELLOW
} = $config->{YELLOW
};
148 if ( $config->{BLUE
} ){
149 $main::GLOBALSETTINGS
->{BLUE
} = $config->{BLUE
};
151 if ( $config->{GREEN
} ){
152 $main::GLOBALSETTINGS
->{GREEN
} = $config->{GREEN
};
154 if ($config->{PASSWORD
} ){
155 $main::GLOBALSETTINGS
->{PASSWORDLINE
} = $config->{PASSWORD
};
158 # is an additional configfile defined?
159 if ( $config->{INCLUDE
} ){
160 $main::GLOBALSETTINGS
->{CONFIG_FILE
} = $config->{INCLUDE
};
161 check_config
($passfile_not_forced);
165 # decrypt the passfile
166 # given parameter is location of the passfile.
167 # it returns the decrypted passfile array
171 my $gnupg = GnuPG
::Interface
->new(); # load object
172 # $gnupg->options->hash_init(armor => 1); # set armor enabled (not needed here)
173 my $output = IO
::Handle
->new(); # output Handle
174 my $infile = IO
::File
->new( "<$file" ); # open $PASSFILE for reading
175 my $handles = GnuPG
::Handles
->new( stdin
=> $infile,
177 stderr
=> "/dev/null");
178 $handles->options( 'stdin' )->{direct
} = 1;
179 $gnupg->passphrase( my $passphrase ); # Load the passphrase from STDIN
180 my $pid = $gnupg->decrypt( handles
=> $handles ); # start the decryption
181 close $infile; # close $cipher_file
182 my @decrypted = <$output>; # insert output in array "@result"
183 close $output; # close $output
184 waitpid $pid, 0; # clean up the finished GnuPG process
185 return @decrypted; # return the decrypted passfile
189 # encrypt given array to $OUTFILE and move it to $PASSFILE
190 # parameter give is the location where the encrypted file should be saved (first) and the array to encrpyt (second)
191 # nothing is returned
194 my ($output_file_fh, $output_file) = tempfile
();
196 my @input_array = @_;
198 my $gnupg = GnuPG
::Interface
->new(); # load object
200 my $input = IO
::Handle
->new();
202 # We'll let the standard error of GnuPG pass through
203 # to our own standard error, by not creating
204 # a stderr-part of the $handles object.
206 my $handles = GnuPG
::Handles
->new( stdin
=> $input,
207 stdout
=> $output_file_fh);
209 $handles->options( 'stdout' )->{direct
} = 1; # think this is needed when using files
211 $gnupg->options->armor( 1 );
212 $gnupg->options->default_key ( $main::GLOBALSETTINGS
->{YOUR_ID
} ); # set default id here (if you have multiple ids you need that)
213 $gnupg->options->push_recipients ( $main::GLOBALSETTINGS
->{YOUR_ID
} ); # set own-id to encrypt file
215 if ( $main::GLOBALSETTINGS
->{IDS
} ) {
216 foreach my $id ( @
{$main::GLOBALSETTINGS
->{IDS
}} ) {
217 $gnupg->options->push_recipients ( $id ); # set all the other ids
221 # this sets up the communication
222 # Note that the recipients were specified earlier
223 # in the 'options' data member of the $gnupg object.
224 my $pid = $gnupg->sign_and_encrypt( handles
=> $handles );
226 print $input @input_array;
228 # this closes the communication channel,
229 # indicating we are done
231 close $output_file_fh;
233 waitpid $pid, 0; # clean up the finished GnuPG process
236 move
($output_file, $outfile) || die "WARNING!! File $output_file cannot be moved to $outfile.\n$outfile not writeable?\n";
237 print "seems to me that everything worked fine. Passfile encrypted.\n";
240 die "something went wrong, encryption stopped. hope you did not lose any changes?!?";
244 # encrypt given File to $OUTFILE and move it to $PASSFILE
245 # there are two parameters given. The file where the plaintext is in (first) and the location where the encrypted file should be saved (second)
246 # nothing is returned
249 my ($output_file_fh, $output_file) = tempfile
();
250 my $input_file = shift;
252 my $gnupg = GnuPG
::Interface
->new(); # load object
254 # We'll let the standard error of GnuPG pass through
255 # to our own standard error, by not creating
256 # a stderr-part of the $handles object.
257 my $infile = IO
::File
->new( "$input_file" );
259 my $handles = GnuPG
::Handles
->new( stdin
=> $infile,
260 stdout
=> $output_file_fh);
262 $handles->options( 'stdin' )->{direct
} = 1; # set option to directly use the filehandles
263 $handles->options( 'stdout' )->{direct
} = 1; # think this is needed when using files
265 $gnupg->options->armor( 1 );
266 $gnupg->options->default_key ( $main::GLOBALSETTINGS
->{YOUR_ID
} ); # set default id here (if you have multiple ids you need that)
267 $gnupg->options->push_recipients ( $main::GLOBALSETTINGS
->{YOUR_ID
} ); # set own-id to encrypt file
269 if ( $main::GLOBALSETTINGS
->{IDS
} ) {
270 foreach my $id ( @
{$main::GLOBALSETTINGS
->{IDS
}} ) {
271 $gnupg->options->push_recipients ( $id ); # set all the other ids
275 # this sets up the communication
276 # Note that the recipients were specified earlier
277 # in the 'options' data member of the $gnupg object.
278 my $pid = $gnupg->sign_and_encrypt( handles
=> $handles );
280 # this closes the communication channel,
281 # indicating we are done
283 close $output_file_fh;
285 waitpid $pid, 0; # clean up the finished GnuPG process
288 move
($output_file, $outfile) || die "WARNING!! File $output_file cannot be moved to $outfile.\n$outfile not writeable?\n";
289 print "seems to me that everything worked fine. Passfile encrypted.\n";
292 die "something went wrong, encryption stopped. hope you did not lose any changes?!?";
298 # check if the edited file is ok
299 # parameter is the location of the passfile
300 # it returns just successfull (0) or error (1)
304 open(my $filehandle,'<'.$file) || die "Cannot open $file: $! that shouldn't happen\n";
305 my @checkarray = <$filehandle>;
306 close($filehandle) || die "Cannot close $file ?!?!? WTF that shouldn happen\n";
308 my $max_desclength = 0;
310 if ( @checkarray ) { # check if something IS in $file
311 my @description = split(/\t/,$checkarray[0]); #save Description
312 shift ( @checkarray ); # delete description from array
314 my $quantity_desc = @description; # save Quantity of Array Description
316 foreach my $line (@checkarray){
318 my @result = split(/\t/,$line);
319 my $quantity_result = @result;
320 if ( $quantity_desc != $quantity_result ) { # do the check
321 print "ERROR - Quantity of description does not match quantity of results in line $counter\nof your datafile, there are $quantity_result instead of $quantity_desc items, which results in an ERROR\n";
327 print "$file has been empty. Return successfull state to go on.\n";
332 # sub to compare 2 files.
333 # there are two parameters given containing the location of the files
334 # it does not return anything but handle overwriting files internally
335 sub compare_passfiles
337 my $first_passfile = shift;
338 my $second_passfile = shift;
339 my $md5sum_first_passfile = file_md5_hex
($first_passfile);
340 my $md5sum_second_passfile = file_md5_hex
($second_passfile);
342 if ( $md5sum_first_passfile ne $md5sum_second_passfile ) {
343 print "size of $first_passfile does not match $second_passfile what would you like to do?\n";
344 print "a) overwrite $second_passfile with $first_passfile\n";
345 print "b) overwrite $first_passfile with $second_passfile\n";
346 print "c) do nothing at all\n";
347 my $selection = <STDIN
>;
350 switch
($selection) {
353 copy
($second_passfile, "$second_passfile.bak") || print "Could not make a copy of $second_passfile!\n";
354 copy
($first_passfile, $second_passfile) || die "Could not copy $second_passfile to $first_passfile. FAILED!\n$first_passfile not writeable?\n";
355 print "PASSFILE copied.\nBackup is $second_passfile.bak\nPress Enter to continue...";
356 my $buffer = <STDIN
>;
359 copy
($first_passfile, "$first_passfile.bak") || print "Could not backup $first_passfile\n";
360 copy
($second_passfile, $first_passfile) || die "Could not copy $first_passfile to $second_passfile, FAILED!\n$second_passfile not writeable?\n";
361 print "PASSFILE copied.\nBackup is $first_passfile.bak\nPress Enter to continue...";
362 my $buffer = <STDIN
>;
364 case
"c" { print "Did nothing.\n" }
365 else { print "OPTION unknown. Did nothing.\n" }
370 # function to get max length of string
371 # parameter given is a array
372 # nothing is returned
376 # get max length of "DESCRIPTION"
377 foreach my $part_of_string ( @string ) {
378 my $length_string = length $part_of_string;
379 if ( $length < $length_string ) {
380 $length = $length_string;
386 # function to join a given array to file
387 # parameters given are the location of the passfile (first) and the array i want to join in (second)
388 sub join_array_to_file
391 my @first_array = @_;
392 my @second_array = decrypt_passfile
($file);
394 my $diff_counter = 0;
397 my $quantity_first_array = @first_array;
398 my $quantity_second_array = @second_array;
400 for ( my $i = 0; $i < $quantity_first_array; $i++ ) {
401 for ( my $x = 0; $x < $quantity_second_array; $x++ ) {
402 if ( $first_array[$i] eq $second_array[$x] ) {
407 $diff_array[$diff_counter] = $first_array[$i];
417 my @result_array = (@second_array,@diff_array);
418 return @result_array;
421 # function to make string or array equal to max length
422 # parameters given are the length all columns should have (first) and the array itself (second)
423 # returned is the array, length adjusted
424 sub make_strings_equal
{
427 my $quantity_string = @string;
428 # make them all to one length
429 for ( my $i = 0; $i < $quantity_string; $i++ ) {
430 my $length_string = length $string[$i];
431 my $missing = $length - $length_string;
432 for ( my $x = 0; $x < $missing; $x++ ) {
433 $string[$i] = $string[$i] . " ";
439 # does string contain any of string?
440 # parameter given is the string i'm searching in (first) and the searchword (second)
441 # returned is just true or false
442 sub string_contains_string
{
446 if ( !$string2 ) { return 1}
447 elsif ($string =~ /$string2/i) {return 1;}
451 # does string contain any of array?
452 # parameters given are the string to search in (first) and the searchwords (second)
453 # returned is just true or false
454 sub string_equals_array
459 @array = split(/\s+/, $_[0]);
463 $string =~ s/\s+$//g; # remove whitespaces out of string
465 if ( $array[0] ne "" ) {
466 foreach my $array_element (@array) {
467 $array_element =~ s/::/ /g;
468 if ($string =~ /^$array_element+$/) {return 1;}}
477 # Procedure to check the configuration file and load the settings
478 # got this configuration parser from http://www.patshaping.de/hilfen_ta/codeschnipsel/perl-configparser.htm
479 # it needs the configfile as parameter and returns all the values
485 open(CF
,'<'.$file) || die "Open $file: $!\n";
486 read(CF
, my $data, -s
$file);
487 close(CF
) || die "could not close $file. that shouldn't happen\n";
489 my @lines = split(/\015\012|\012|\015/,$data);
493 foreach my $line(@lines)
497 next if($line =~ /^\s*#/);
498 next if($line !~ /^\s*\S+\s*=.*$/);
500 my ($key,$value) = split(/=/,$line,2);
502 # Remove whitespaces at the beginning and at the end
509 die "Configuration option '$key' defined twice in line $count of configuration file '$file'" if($config->{$key});
511 $config->{$key} = $value;
517 # dummy function to handle shared passfile or not before creating passfile.
518 # this function does not get any parameters nor returns any.
519 sub creating_passfile
521 if ( !$main::GLOBALSETTINGS
->{SHARED_PASSFILE
} || $main::GLOBALPARAMETERS
->{LOCAL
} ) {
522 create_passfile
($main::GLOBALSETTINGS
->{PASSFILE
}); # if wether shared passfile is set or --local is given
525 if ( -e
"$main::GLOBALSETTINGS->{SHARED_PASSFILE}.lock" && !$main::GLOBALPARAMETERS
->{FORCE
} ) { # if lockfile is already set return username
526 open (my $lockfile,"<$main::GLOBALSETTINGS->{SHARED_PASSFILE}.lock") || die "COULD NOT OPEN LOCKFILE $main::GLOBALSETTINGS->{SHARED_PASSFILE}.lock: $!. THAT SHOULDN'T HAPPEN!";
527 my $current_user = <$lockfile>;
528 close($lockfile) || die "COULD NOT CLOSE LOCKFILE $main::GLOBALSETTINGS->{SHARED_PASSFILE}.lock: $!. THAT SHOULDN'T HAPPEN!";
529 print "You cannot overwrite $main::GLOBALSETTINGS->{SHARED_PASSFILE} as it is currently processed by $current_user\ntry --force if you really want to ignore it (changes will be lost)\n";
532 if ( $main::GLOBALPARAMETERS
->{FORCE
} ) { # overwrite lockfile if set
533 if ( -e
"$main::GLOBALSETTINGS->{SHARED_PASSFILE}.lock" ) {
534 unlink("$main::GLOBALSETTINGS->{SHARED_PASSFILE}.lock");
537 open (my $lockfile,">$main::GLOBALSETTINGS->{SHARED_PASSFILE}.lock") || die "COULD NOT OPEN LOCKFILE $main::GLOBALSETTINGS->{SHARED_PASSFILE}.lock for writing: $!.\nDoing this would be insecure\n";
538 print $lockfile "$main::GLOBALSETTINGS->{YOUR_NAME} (creating passfile)";
539 close($lockfile) || die "COULD NOT CLOSE $main::GLOBALSETTINGS->{SHARED_PASSFILE}.lock after writing: $!.\n that REALLY shouldn't happen!\n";
540 create_passfile
($main::GLOBALSETTINGS
->{SHARED_PASSFILE
}); # added lockfile. Now do real adding.
541 unlink("$main::GLOBALSETTINGS->{SHARED_PASSFILE}.lock");
547 # Procedure to create a new passfile.
548 # it gets the passfile-location as parameter.
549 # it returns nothing.
556 print "PASSFILE $file exists, do you want to override(y/n)?";
557 my $answer = <STDIN
>;
559 if ( $answer eq "n" ) {
563 open(my $FH,'>'.$file) || die "Could not Open $file: $!\n";
565 print "Which Name should the first column have?";
566 my $answer = <STDIN
>;
568 $PASSFILE[0] = $answer;
571 while ( $answer ne "n" ) {
572 print "Do you want to add another column?(y/n)";
575 if ( $answer eq "y" ) {
576 print "what name should the column have?:";
579 $PASSFILE[0] = $PASSFILE[0] . "\t" . $answer;
583 $PASSFILE[0] = $PASSFILE[0] . "\n";
585 print "Do you want to create some entries right now?(y/n)";
588 if ( $answer eq "y" ) {
589 require SPD
::unix_adding
;
590 add_line
($file, @PASSFILE);
595 # no parameters needed nothing returned
599 print "SPD - Simple Password Displayer - Version $main::GLOBALSETTINGS->{VERSION}\n\n";
600 print "Syntax: spd [options]|<search patterns>\n\n";
601 print "main options:\n";
602 print " --help | -h : display this wonderful help\n";
603 print " --version | -v : shows version information\n";
604 print " --import | -i : import a cleartext-file\n";
605 print " --export : to export as a cleartext-file\n";
606 print " --config | -c : use configfile instead of the standard ones\n";
607 print " --passfile| -p : use passfile instead of the one set in spd.conf\n";
608 print " --local | -l : use local passfile instead of the shared one\n";
609 print " --force | -f : forcing some operations\n";
610 print " --wide | -w : give output in wide Format\n";
611 print " --create : create a passfile using the wizard\n\n";
612 print "passfile options:\n";
613 print " --delete-column : delete a column by number or word of the description\n";
614 print " Note: The ID Column cannot be deleted.\n";
615 print " --edit | -e : edit the password-file with your favourite editor\n";
616 print " --add | -a : interactive add a line\n\n";
617 print "import/export options:\n";
618 print " --field-delimiter : char which is used to delimiter the fields\n";
619 print " --column-delimiter : char which is used to delimiter the columns\n\n";
620 print "other options:\n";
621 print " --config-wizard : create a config using the wizard\n";
622 print " Note: will delete existing config if exist\n\n";
623 print "With no parameters it will print out everything!\n";
629 GetOptions
('help|h' => \
my $help,
630 'version|v' => \
my $version,
631 'import|i=s' => \
$main::GLOBALPARAMETERS
->{IMPORT
},
632 'export=s' => \
$main::GLOBALPARAMETERS
->{EXPORT
},
633 'config|c=s' => \
my $conffile,
634 'passfile|p=s' => \
my $passfile,
635 'local|l' => \
$main::GLOBALPARAMETERS
->{LOCAL
},
636 'force|f' => \
$main::GLOBALPARAMETERS
->{FORCE
},
637 'wide|w' => \
$main::GLOBALPARAMETERS
->{WIDE
},
638 'create' => \
$main::GLOBALPARAMETERS
->{CREATE
},
639 'delete-column=s' => \
my $delete_column,
640 'edit|e' => \
my $edit,
642 # 'checksign|cs' => \my $checksign,
643 'field-delimiter=s' => \
$main::GLOBALPARAMETERS
->{FIELD_DELIMITER
},
644 'column-delimiter=s' => \
$main::GLOBALPARAMETERS
->{COLUMN_DELIMITER
},
645 'config-wizard' => \
my $config_wizard);
652 print "SPD - Simple Password Displayer - Version $main::GLOBALSETTINGS->{VERSION}\n\n";
657 $main::GLOBALSETTINGS
->{CONFIG_FILE
} = $conffile
660 if ( $config_wizard ){
661 require SPD
::unix_create_config
;
667 $main::GLOBALSETTINGS
->{PASSFILE
} = $passfile
670 # we need to check the config before doing any of the following
677 if ( $main::GLOBALPARAMETERS
->{CREATE
} ) {
678 create_passfile
($main::GLOBALSETTINGS
->{PASSFILE
});
682 elsif ( $delete_column ){
683 require SPD
::deleting_column
;
684 deleting_column
($delete_column);
689 require SPD
::unix_adding
;
695 require SPD
::unix_editing
;
700 elsif ( $main::GLOBALPARAMETERS
->{IMPORT
} ){
701 require SPD
::importing
;
706 elsif ( $main::GLOBALPARAMETERS
->{EXPORT
} ){
707 require SPD
::exporting
;
713 require SPD
::unix_print_results
;