source: trunk/scripts/samhainadmin.pl.in@ 487

Last change on this file since 487 was 481, checked in by katerina, 9 years ago

Enhancements and fixes for tickets #374, #375, #376, #377, #378, and #379.

File size: 19.4 KB
Line 
1#! /usr/bin/perl
2
3# Copyright Rainer Wichmann (2004)
4#
5# License Information:
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19#
20
21use warnings;
22use strict;
23use Getopt::Long;
24use File::Basename;
25use File::Copy;
26use File::stat;
27use File::Temp qw/ tempfile tempdir unlink0 /;
28use IO::Handle;
29use Fcntl qw(:DEFAULT :flock);
30use Tie::File;
31
32# Do I/O to the data file in binary mode (so it
33# wouldn't complain about invalid UTF-8 characters).
34use bytes;
35
36File::Temp->safe_level( File::Temp::HIGH );
37
38my %opts = ();
39my $action;
40my $file1;
41my $file2;
42my $passphrase;
43my $secretkeyring;
44my $return_from_sign = 0;
45my $no_print_examine = 0;
46my $no_remove_lock = 0;
47my $base = basename($0);
48
49my $cfgfile = "@myconffile@";
50my $datafile = "@mydatafile@";
51my $daemon = "@sbindir@/@install_name@";
52my $gpg = "@mygpg@";
53
54my $TARGETKEYID = "@mykeyid@";
55my $KEYTAG = "@mykeytag@";
56
57$cfgfile =~ s/^REQ_FROM_SERVER//;
58$datafile =~ s/^REQ_FROM_SERVER//;
59
60$gpg = "gpg" if ($gpg eq "");
61
62sub check_gpg_agent() {
63 my $gpgconf = "$ENV{'HOME'}/.gnupg/gpg.conf";
64
65 if (!-f "$gpgconf") {
66 $gpgconf = "$ENV{'HOME'}/.gnupg/options";
67 }
68
69 if (-f $gpgconf) {
70
71 my @array = ();
72 tie @array, 'Tie::File', $gpgconf or die "Cannot tie ${gpgconf}: $!";
73 my @grep = grep(/^\s*use-agent/, @array);
74
75 # print "matches = $#grep\n";
76
77 if ($#grep >= 0)
78 {
79 if (exists $ENV{'GPG_AGENT_INFO'})
80 {
81 my $socke = $ENV{'GPG_AGENT_INFO'};
82 $socke =~ s/:.*//;
83
84 # print "socke = $socke\n";
85
86 if (! -S $socke)
87 {
88 print "--------------------------------------------------\n";
89 print "\n";
90 print " GPG is set to use gpg-agent, but GPG agent is";
91 print " not running, though GPG_AGENT_INFO is defined.\n\n";
92 print " Please restart gpg-agent, or remove the use-agent\n";
93 print " option from ${gpgconf} and unset GPG_AGENT_INFO\n\n";
94 print "--------------------------------------------------\n";
95 print "\n";
96 exit 1;
97 }
98 }
99 else
100 {
101 print "--------------------------------------------------\n";
102 print "\n";
103 print " GPG is set to use gpg-agent, but ";
104 print " GPG_AGENT_INFO is not defined.\n\n";
105 print " Please start gpg-agent, or remove the use-agent\n";
106 print " option from ${gpgconf}\n\n";
107 print "--------------------------------------------------\n";
108 print "\n";
109 exit 1;
110 }
111 }
112 untie @array;
113 }
114}
115
116
117sub usage() {
118 print "Usage:\n";
119 print " $base { -m F | --create-cfgfile } [options] [in.cfgfile]\n";
120 print " Sign the configuration file. If in.cfgfile is given, sign it\n";
121 print " and install it as configuration file.\n\n";
122
123 print " $base { -m f | --print-cfgfile } [options] \n";
124 print " Print the configuration file to stdout. Signatures are removed.\n\n";
125
126 print " $base { -m D | --create-datafile } [options] [in.datafile]\n";
127 print " Sign the database file. If in.datafile is given, sign it\n";
128 print " and install it as database file.\n\n";
129
130 print " $base { -m d | --print-datafile } [options] \n";
131 print " Print the database file to stdout. Signatures are removed. Use\n";
132 print " option --list to list files in database rather than printing the raw file.\n\n";
133
134 print " $base { -m R | --remove-signature } [options] file1 [file2 ...]\n";
135 print " Remove cleartext signature from input file(s). The file\n";
136 print " is replaced by the non-signed file.\n\n";
137
138 print " $base { -m E | --sign } [options] file1 [file2 ...]\n";
139 print " Sign file(s) with a cleartext signature. The file\n";
140 print " is replaced by the signed file.\n\n";
141
142 print " $base { -m e | --examine } [options] file1 [file2 ...]\n";
143 print " Report signature status of file(s).\n\n";
144
145 print " $base { -m G | --generate-keys } [options] \n";
146 print " Generate a PGP keypair to use for signing.\n\n";
147
148 print "Options:\n";
149 print " -c cfgfile --cfgfile cfgfile\n";
150 print " Select an alternate configuration file.\n\n";
151
152 print " -d datafile --datafile datafile\n";
153 print " Select an alternate database file.\n\n";
154
155 print " -p passphrase --passphrase passphrase\n";
156 print " Set the passphrase for gpg. By default, gpg will ask.\n\n";
157
158 print " -s gnupg_homedir --secretkeyring gnupg_homedir\n";
159 print " Select an alternate gpg homedirectory to locate the secret keyring.\n";
160 print " Will use '$ENV{'HOME'}/.gnupg/' by default.\n\n";
161
162 print " -k keyid --keyid keyid\n";
163 print " Select the keyid to use for signing.\n\n";
164
165 print " -l --list\n";
166 print " List the files in database rather than printing the raw file.\n\n";
167
168 print " -v --verbose\n";
169 print " Verbose output.\n\n";
170 return;
171}
172
173sub check_gpg_uid () {
174 if (0 != $>) {
175 print "--------------------------------------------------\n";
176 print "\n";
177 print " You are not root. Please remember that samhain/yule\n";
178 print " will use the public keyring of root to verify a signature.\n";
179 print "\n";
180 print "--------------------------------------------------\n";
181 } else {
182 if (!("@yulectl_prg@" =~ //)) {
183 print "--------------------------------------------------\n";
184 print "\n";
185 print " Please remember that yule will drop root after startup. Signature\n";
186 print " verification on SIGHUP will fail if you do not import the public key\n";
187 print " into the keyring of the non-root yule user.\n";
188 print "\n";
189 print "--------------------------------------------------\n";
190 }
191 }
192}
193
194sub check_gpg_sign () {
195 if ( defined($secretkeyring)) {
196 if ( (!-d "$secretkeyring")){
197 print "--------------------------------------------------\n";
198 print "\n";
199 print " Secret keyring $secretkeyring not found!\n";
200 print "\n";
201 print " Please check the path/name of the alternate secret keyring.\n";
202 print "\n";
203 print "--------------------------------------------------\n";
204 print "\n";
205 exit;
206 }
207 } else {
208 if ( (!-d "$ENV{'HOME'}/.gnupg") || (!-e "$ENV{'HOME'}/.gnupg/secring.gpg")) {
209 print "--------------------------------------------------\n";
210 print "\n";
211 if (!-d "$ENV{'HOME'}/.gnupg") {
212 print " Directory \$HOME/.gnupg not found!\n";
213 } else {
214 print " Secret keyring \$HOME/.gnupg/secring.gpg not found!\n";
215 }
216 print "\n";
217 print " This indicates that you have never created a \n";
218 print " public/private keypair, and thus cannot sign.\n";
219 print " \n";
220 print " Please use $0 --generate-keys or gpg --gen-key\n";
221 print " to generate a public/private keypair first.\n";
222 print "\n";
223 print "--------------------------------------------------\n";
224 print "\n";
225 exit;
226 }
227 }
228}
229
230sub check_gpg_verify () {
231 if ( (!-d "$ENV{'HOME'}/.gnupg") || (!-e "$ENV{'HOME'}/.gnupg/pubring.gpg")) {
232 print "--------------------------------------------------\n";
233 print "\n";
234 if (!-d "$ENV{'HOME'}/.gnupg") {
235 print " Directory \$HOME/.gnupg not found!\n";
236 } else {
237 print " Public keyring \$HOME/.gnupg/pubring.gpg not found!\n";
238 }
239 print "\n";
240 print " This indicates that you have never used gpg before \n";
241 print " and/or have no public keys to verify signatures.\n";
242 print " \n";
243 print " Please use 'gpg --export key_id' to export the public\n";
244 print " signing key of the user who is signing the\n";
245 print " configuration/database files.\n\n";
246 print " Then you can use 'gpg --import keyfile' to import the key\n";
247 print " into this user's public keyring.\n";
248 print "\n";
249 print "--------------------------------------------------\n";
250 print "\n";
251 exit;
252 }
253}
254
255
256sub generate () {
257 my $command = "$gpg --homedir $ENV{'HOME'}/.gnupg --gen-key";
258 check_gpg_uid();
259 system ($command) == 0
260 or die "system $command failed: $?";
261 exit;
262}
263
264sub examine () {
265 my $iscfg = 0;
266 my $have_fp = 0;
267 my $have_sig = 0;
268 my $message = '';
269 my $retval = 9;
270 my $fh;
271 my $filename;
272
273 if (!($file1 =~ /^\-$/)) {
274 die ("Cannot open $file1 for read: $!") unless ((-e $file1) && (-r _));
275 }
276 open FIN, "<$file1" or die "Cannot open $file1 for read: $!";
277
278 my $dir = tempdir( CLEANUP => 1 );
279 $filename = $dir . "/exa_jhfdbilw." . $$;
280 open $fh, ">$filename" or die "Cannot open $filename";
281 autoflush $fh 1;
282
283 while (<FIN>) {
284 print $fh $_;
285 if ($_ =~ /^\s*\[Misc\]/) {
286 $iscfg = 1;
287 }
288 }
289 if ($iscfg == 1) {
290 $message .= "File $file1 is a configuration file\n\n";
291 } else {
292 $message .= "File $file1 is a database file\n\n";
293 }
294
295
296 my $command = "$gpg --homedir $ENV{'HOME'}/.gnupg --status-fd 1 ";
297 $command .= "--verbose " if (defined($opts{'v'}));
298 $command .= "--verify $filename ";
299 if (defined($opts{'v'})) {
300 $command .= "2>&1";
301 } else {
302 $command .= "2>/dev/null";
303 }
304
305 print STDOUT "Using: $command\n\n" if (defined($opts{'v'}));
306 open GPGIN, "$command |" or die "Cannot fork: $!";
307
308 while (<GPGIN>) {
309 if ($_ =~ /^\[GNUPG:\] GOODSIG ([0-9A-F]+) (.*)$/) {
310 $message .= "GOOD signature with key: $1\n";
311 $message .= "Key owner: $2\n";
312 $have_sig = 1;
313 $retval = 0;
314 }
315 if ($_ =~ /^\[GNUPG:\] VALIDSIG ([0-9A-F]+) ([0-9\-]+)\s/) {
316 $message .= "Key fingerprint: $1\n";
317 $message .= "Signature generated on: $2\n\n";
318 $have_fp = 1;
319 $message .= "This file is signed with a valid signature.\n"
320 if ($have_sig == 1);
321 $have_sig = 1;
322 $have_fp = 1;
323 }
324 if ($_ =~ /^\[GNUPG:\] NODATA 1/) {
325 $message .= "NO signature found.\n\n";
326 $message .= "This file is not signed !!!\n";
327 $have_sig = 1;
328 $have_fp = 1;
329 $retval = 2;
330 }
331 if ($_ =~ /^\[GNUPG:\] BADSIG ([0-9A-F]+) (.*)$/) {
332 $message .= "BAD signature with key: $1\n";
333 $message .= "Key owner: $2\n\n";
334 $message .= "This file is signed with an invalid signature !!!\n";
335 $have_sig = 1;
336 $have_fp = 1;
337 $retval = 1;
338 }
339 if ($_ =~ /^\[GNUPG:\] NO_PUBKEY ([0-9A-F]+)/) {
340 $message .= "NOT CHECKED signature with key: $1\n\n";
341 $message .= "The signature of this file cannot be checked: no public key available !!!\n";
342 $have_sig = 1;
343 $have_fp = 1;
344 $retval = 1;
345 }
346 print STDOUT $_ if (defined($opts{'v'}));
347 }
348 close (GPGIN);
349 print STDOUT "\n" if (defined($opts{'v'}));
350 if ($have_sig == 0) {
351 $message .= "NO valid signature found\n";
352 }
353 elsif ($have_fp == 0) {
354 $message .= "NO fingerprint found\n";
355 }
356 close (FIN);
357 if ($no_print_examine == 0) {
358 print STDOUT $message;
359 }
360 unlink0( $fh, $filename ) or die "Cannot unlink $filename safely";
361 return $retval;
362}
363
364sub remove () {
365 my $bodystart = 1;
366 my $sigstart = 0;
367 my $sigend = 0;
368 my $filename = "";
369 my $fh;
370 my $stats;
371
372 open FH, "<$file1" or die "Cannot open file $file1 for read: $!";
373 if (!($file1 =~ /^\-$/)) {
374 flock(FH, LOCK_EX) unless ($no_remove_lock == 1);
375 my $dir = tempdir( CLEANUP => 1 ) or die "Tempdir failed";
376 $filename = $dir . "/rem_iqegBCQb." . $$;
377 open $fh, ">$filename" or die "Cannot open $filename";
378 $stats = stat($file1);
379 # ($fh, $filename) = tempfile(UNLINK => 1);
380 } else {
381 open $fh, ">$file1" or die "Cannot open file $file1 for write: $!";
382 }
383 autoflush $fh 1;
384 while (<FH>) {
385 if ($_ =~ /^-----BEGIN PGP SIGNED MESSAGE-----/) {
386 $sigstart = 1;
387 $bodystart = 0;
388 next;
389 } elsif (($sigstart == 1) && ($_ =~ /^\s+$/)) {
390 $sigstart = 0;
391 $bodystart = 1;
392 next;
393 } elsif ($_ =~ /^-----BEGIN PGP SIGNATURE-----/) {
394 $bodystart = 0;
395 $sigend = 1;
396 next;
397 } elsif (($sigend == 1) && ($_ =~ /^-----END PGP SIGNATURE-----/)) {
398 $sigend = 0;
399 $bodystart = 1;
400 next;
401 }
402 if ($bodystart == 1) {
403 print $fh $_;
404 }
405 }
406 if (!($file1 =~ /^\-$/)) {
407 copy("$filename", "$file1")
408 or die "Copy $filename to $file1 failed: $!";
409 chmod $stats->mode, $file1;
410 chown $stats->uid, $stats->gid, $file1;
411 flock(FH, LOCK_UN) unless ($no_remove_lock == 1);
412 close FH;
413 }
414 unlink0( $fh, $filename ) or die "Cannot unlink $filename safely";
415 return;
416}
417
418sub print_cfgfile () {
419 my $bodystart = 0;
420 my $sigstart = 0;
421
422 if (!defined($file2)) {
423 $file2 = '-';
424 }
425
426 open FH, "<$file1" or die "Cannot open file $file1 for read: $!";
427 open FO, ">$file2" or die "Cannot open file $file2 for write: $!";
428 while (<FH>) {
429 if ($_ =~ /^-----BEGIN PGP SIGNED MESSAGE-----/) {
430 $sigstart = 1;
431 next;
432 } elsif (($sigstart == 1) && ($_ =~ /^\s+$/)) {
433 $sigstart = 0;
434 $bodystart = 1;
435 next;
436 } elsif ($_ =~ /^-----BEGIN PGP SIGNATURE-----/) {
437 $bodystart = 0;
438 exit;
439 }
440 if ($bodystart == 1) {
441 print FO $_;
442 }
443 }
444 exit;
445}
446sub print_datafile () {
447 die ("Cannot find program $daemon")
448 unless (-e $daemon);
449 if (defined($opts{'v'})) {
450 open FH, "$daemon --full-detail -d $datafile |"
451 or die "Cannot open datafile $datafile for read: $!";
452 } else {
453 open FH, "$daemon -d $datafile |"
454 or die "Cannot open datafile $datafile for read: $!";
455 }
456 while (<FH>) {
457 print $_;
458 }
459 exit;
460}
461
462sub sign_file () {
463
464 my $fileout = '';
465 my $bodystart = 1;
466 my $sigstart = 0;
467 my $sigend = 0;
468 my $stats;
469 my $fh1;
470 my $filename1;
471 my $flag1 = 0;
472
473 check_gpg_uid();
474 check_gpg_agent();
475
476 if (!defined($file2)) {
477 $file2 = $file1;
478 }
479
480 if ($file1 =~ /^\-$/) {
481 my $dir = tempdir( CLEANUP => 1 ) or die "Tempdir failed";
482 $filename1 = $dir . "/sig_vs8827sd." . $$;
483 open $fh1, ">$filename1" or die "Cannot open $filename1";
484 $flag1 = 1;
485 # my ($fh1, $filename1) = tempfile(UNLINK => 1);
486
487 while (<STDIN>) {
488 if ($_ =~ /^-----BEGIN PGP SIGNED MESSAGE-----/) {
489 $sigstart = 1;
490 $bodystart = 0;
491 next;
492 } elsif (($sigstart == 1) && ($_ =~ /^\s+$/)) {
493 $sigstart = 0;
494 $bodystart = 1;
495 next;
496 } elsif ($_ =~ /^-----BEGIN PGP SIGNATURE-----/) {
497 $bodystart = 0;
498 $sigend = 1;
499 next;
500 } elsif (($sigend == 1) && ($_ =~ /^-----END PGP SIGNATURE-----/)) {
501 $sigend = 0;
502 $bodystart = 1;
503 next;
504 }
505 if ($bodystart == 1) {
506 print $fh1 $_;
507 }
508 #
509 # print $fh1 $_;
510 #
511 }
512 $file1 = $filename1;
513 $fileout = '-';
514 } else {
515 open (LOCKFILE, "<$file1") or die "Cannot open $file1: $!";
516 flock(LOCKFILE, LOCK_EX);
517 $no_print_examine = 1;
518 $no_remove_lock = 1;
519 if (examine() < 2) {
520 remove();
521 }
522 $fileout = $file1 . ".asc";
523 $stats = stat($file1)
524 or die "No file $file1: $!";
525 }
526
527 if (defined($passphrase)) {
528 local $SIG{PIPE} = 'IGNORE';
529 my $command = "$gpg --homedir $ENV{'HOME'}/.gnupg --passphrase-fd 0 -a ${KEYTAG} ${TARGETKEYID} --clearsign -o $fileout --not-dash-escaped ";
530 $command .= "--secret-keyring $secretkeyring " if (defined($opts{'s'}));
531 $command .= "$file1";
532 open (FH, "|$command") or die "can't fork: $!";
533 print FH "$passphrase" or die "can't write: $!";
534 close FH or die "can't close: status=$?";
535 } else {
536 my $command = "$gpg --homedir $ENV{'HOME'}/.gnupg -a ${KEYTAG} ${TARGETKEYID} --clearsign -o $fileout --not-dash-escaped ";
537 $command .= "--secret-keyring $secretkeyring " if (defined($opts{'s'}));
538 $command .= "$file1";
539 system("$command") == 0
540 or die "system $command failed: $?";
541 }
542
543 if (!($fileout =~ /^\-$/)) {
544 my $st_old = stat($file1)
545 or die "No file $file1: $!";
546 my $st_new = stat($fileout)
547 or die "No file $fileout: $!";
548 die ("Signed file is smaller than unsigned file")
549 unless ($st_new->size > $st_old->size);
550 move("$fileout", "$file2")
551 or die "Move $fileout to $file2 failed: $!";
552 chmod $stats->mode, $file2;
553 chown $stats->uid, $stats->gid, $file2;
554 flock(LOCKFILE, LOCK_UN);
555 }
556
557 if ($flag1 == 1) {
558 unlink0( $fh1, $filename1 ) or die "Cannot unlink $filename1 safely";
559 }
560 if ($return_from_sign == 1) {
561 return;
562 }
563 exit;
564}
565
566Getopt::Long::Configure ("posix_default");
567Getopt::Long::Configure ("bundling");
568# Getopt::Long::Configure ("debug");
569
570GetOptions (\%opts, 'm=s', 'h|help', 'v|verbose', 'l|list',
571 'c|cfgfile=s',
572 'd|datafile=s',
573 'p|passphrase=s',
574 's|secretkeyring=s',
575 'k|keyid=s',
576 'create-cfgfile', # -m F
577 'print-cfgfile', # -m f
578 'create-datafile', # -m D
579 'print-datafile', # -m d
580 'remove-signature',# -m R
581 'sign', # -m E
582 'examine', # -m e
583 'generate-keys'); # -m G
584
585if (defined ($opts{'h'})) {
586 usage();
587 exit;
588}
589
590if (defined($opts{'k'})) {
591 $TARGETKEYID = $opts{'k'};
592 $KEYTAG = "--default-key";
593}
594if (defined($opts{'c'})) {
595 $cfgfile = $opts{'c'};
596}
597if (defined($opts{'d'})) {
598 $datafile = $opts{'d'};
599}
600if (defined($opts{'p'})) {
601 $passphrase = $opts{'p'};
602}
603if (defined($opts{'s'})) {
604 $secretkeyring = $opts{'s'};
605}
606
607if (defined ($opts{'m'}) && ($opts{'m'} =~ /[FfDdREeG]{1}/) ) {
608 $action = $opts{'m'};
609}
610elsif (defined ($opts{'create-cfgfile'})) {
611 $action = 'F';
612}
613elsif (defined ($opts{'print-cfgfile'})) {
614 $action = 'f';
615}
616elsif (defined ($opts{'create-datafile'})) {
617 $action = 'D';
618}
619elsif (defined ($opts{'print-datafile'})) {
620 $action = 'd';
621}
622elsif (defined ($opts{'remove-signature'})) {
623 $action = 'R';
624}
625elsif (defined ($opts{'sign'})) {
626 $action = 'E';
627}
628elsif (defined ($opts{'examine'})) {
629 $action = 'e';
630}
631elsif (defined ($opts{'generate-keys'})) {
632 $action = 'G';
633}
634else {
635 usage();
636 die ("No valid action specified !");
637}
638
639if (defined($ARGV[0])) {
640 $file1 = $ARGV[0];
641}
642if (defined($ARGV[1])) {
643 $file2 = $ARGV[1];
644}
645
646
647if (($action =~ /[REe]{1}/) && !defined($file1)) {
648 usage();
649 die("Option -m $action requires a filename (or '-' for stdio)\n");
650}
651
652if ($action =~ /^F$/) {
653 if (!defined($file1)) {
654 $file1 = $cfgfile;
655 }
656 $file2 = $cfgfile;
657 sign_file ();
658}
659
660if ($action =~ /^D$/) {
661 if (!defined($file1)) {
662 $file1 = $datafile;
663 }
664 $file2 = $datafile;
665 sign_file ();
666}
667
668if ($action =~ /^R$/) {
669 # $file1 defined
670 my $i = 0;
671 while (defined($ARGV[$i])) {
672 $file1 = $ARGV[$i];
673 remove ();
674 ++$i;
675 }
676}
677
678if ($action =~ /^E$/) {
679 # $file1 defined
680 # default: $file2 = $file1
681 check_gpg_sign();
682 my $i = 0;
683 while (defined($ARGV[$i])) {
684 $file1 = $ARGV[$i];
685 $file2 = $file1;
686 $return_from_sign = 1;
687 sign_file ();
688 ++$i;
689 }
690}
691
692if ($action =~ /^e$/) {
693 # $file1 defined
694 # default: $file2 = stdout
695 check_gpg_verify();
696 my $i = 0;
697 my $ret = 0;
698 while (defined($ARGV[$i])) {
699 print "\n";
700 $file1 = $ARGV[$i];
701 $ret += examine ();
702 ++$i;
703 print "\n--------------------------------\n" if (defined($ARGV[$i]));
704 }
705 exit($ret);
706}
707
708if ($action =~ /^f$/) {
709 $file1 = $cfgfile;
710 $file2 = "-";
711 print_cfgfile ();
712}
713
714if ($action =~ /^d$/) {
715 # $file1 irrelevant
716 if (defined($opts{'l'})) {
717 print_datafile ();
718 } else {
719 $file1 = $datafile;
720 $file2 = "-";
721 print_cfgfile ();
722 }
723}
724
725
726
Note: See TracBrowser for help on using the repository browser.