firewalld support from Gerry Reno
[fwknop.git] / perl / legacy / fwknop / install.pl
1 #!/usr/bin/perl -w
2 #
3 #############################################################################
4 #
5 # File: install.pl
6 #
7 # URL: http://www.cipherdyne.org/fwknop
8 #
9 # Purpose: Installer for fwknop
10 #
11 # Credits:  (see the CREDITS file)
12 #
13 # Copyright (C) 2004-2008 Michael Rash (mbr@cipherdyne.org)
14 #
15 # License (GNU Public License):
16 #
17 #    This program is distributed in the hope that it will be useful,
18 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
19 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 #    GNU General Public License for more details.
21 #
22 #    You should have received a copy of the GNU General Public License
23 #    along with this program; if not, write to the Free Software
24 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
25 #    USA
26 #
27 #############################################################################
28 #
29 # $Id: install.pl 1409 2009-02-23 05:38:33Z mbr $
30 #
31
32 use Cwd;
33 use File::Copy;
34 use File::Path;
35 use Getopt::Long;
36 use Sys::Hostname;
37 use strict;
38
39 #========================== config ===========================
40 my $USRBIN_DIR  = '/usr/bin';
41 my $USRSBIN_DIR = '/usr/sbin';
42
43 my $fwknop_conf_file = 'fwknop.conf';
44
45 ### system binaries
46 my $chkconfigCmd = '/sbin/chkconfig';
47 my $rcupdateCmd  = '/sbin/rc-update';  ### Gentoo
48 my $updatercdCmd = '/usr/sbin/update-rc.d';  ### Ubuntu
49 my $makeCmd      = '/usr/bin/make';
50 my $perlCmd      = '/usr/bin/perl';
51 my $gzipCmd      = '/bin/gzip';
52 my $killallCmd   = '/usr/bin/killall';
53 my $mknodCmd     = '/bin/mknod';
54 my $ifconfigCmd  = '/sbin/ifconfig';
55 my $runlevelCmd  = '/sbin/runlevel';
56 #======================== end config =========================
57
58 ### main configuration hash
59 my %config = ();
60
61 my $client_install = 0;
62 my $bsd_install    = 0;
63 my $cygwin_install = 0;
64 my $homedir = '';
65 my $distro  = '';
66 my $print_help  = 0;
67 my $uninstall   = 0;
68 my $syslog_conf = '';
69 my $data_method = '';
70 my $runlevel = -1;
71 my $deps_dir = 'deps';
72 my $init_dir = '/etc/init.d';
73 my $init_name = 'fwknop';
74 my $force_mod_re = '';
75 my $exclude_mod_re = '';
76 my $force_path_update = 0;
77 my $sniff_interface   = '';
78 my $cmdline_force_install = 0;
79 my $skip_module_install = 0;
80 my $install_syslog_fifo = 0;
81 my $single_module_install = '';
82 my $force_defaults  = 0;
83 my $cmdline_os_type = '';
84 my $os_type = 0;
85 my $locale = 'C';  ### default LC_ALL env variable
86 my $no_locale = 0;
87
88 ### unless --OS-type is used, install.pl will try to figure out the
89 ### OS where fwknop is being installed (this is usually best).
90 my $OS_LINUX  = 1;
91 my $OS_BSD    = 2;
92 my $OS_CYGWIN = 3;
93 my $OS_DARWIN = 4;  ### Mac OS X
94
95 my %os_types = (
96     'linux'  => $OS_LINUX,
97     'bsd'    => $OS_BSD,
98     'cygwin' => $OS_CYGWIN,
99     'darwin' => $OS_DARWIN
100 );
101
102 my %exclude_cmds = (
103     'gpg'         => '',
104     'mail'        => '',
105     'fwknop'      => '',
106     'fwknopd'     => '',
107     'fwknop_serv' => '',
108     'knopmd'      => '',
109     'knoptm'      => '',
110     'knopwatchd'  => '',
111 );
112
113 ### perl module directories
114 my @required_perl_modules = (
115     {   'module'              =>'Class::MethodMaker', ### GnuPG::Interface dependency
116         'force-install'       => 0,
117         'client-mode-install' => 1,
118         'mod-dir'             => 'Class-MethodMaker'
119     },
120     {   'module'              => 'GnuPG::Interface',
121         'force-install'       => 0,
122         'client-mode-install' => 1,
123         'mod-dir'             => 'GnuPG-Interface'
124     },
125     {   'module'              => 'Unix::Syslog',
126         'force-install'       => 0,
127         'client-mode-install' => 0,
128         'mod-dir'             => 'Unix-Syslog'
129     },
130     {   'module'              => 'Net::IPv4Addr',
131         'force-install'       => 0,
132         'client-mode-install' => 1,
133         'mod-dir'             => 'Net-IPv4Addr'
134     },
135     {   'module'              => 'Net::Pcap',
136         'force-install'       => 0,
137         'client-mode-install' => 0,
138         'mod-dir'             => 'Net-Pcap'
139     },
140     {   'module'              => 'Net::RawIP',
141         'force-install'       => 0,
142         'client-mode-install' => 1,
143         'mod-dir'             => 'Net-RawIP'
144     },
145     {   'module'              => 'Net::Ping::External',
146         'force-install'       => 0,
147         'client-mode-install' => 1,
148         'mod-dir'             => 'Net-Ping-External'
149     },
150     {   'module'              => 'Digest::SHA',
151         'force-install'       => 0,
152         'client-mode-install' => 1,
153         'mod-dir'             => 'Digest-SHA'
154     },
155     {   'module'              => 'Crypt::Rijndael',
156         'force-install'       => 0,
157         'client-mode-install' => 1,
158         'mod-dir'             => 'Crypt-Rijndael'
159     },
160     {   'module'              => 'Crypt::CBC',
161         'force-install'       => 0,
162         'client-mode-install' => 1,
163         'mod-dir'             => 'Crypt-CBC'
164     },
165     {   'module'              => 'Term::ReadKey',
166         'force-install'       => 0,
167         'client-mode-install' => 1,
168         'mod-dir'             => 'TermReadKey'
169     },
170     {   'module'              => 'IPTables::Parse',
171         'force-install'       => 1,
172         'client-mode-install' => 0,
173         'mod-dir'             => 'IPTables-Parse'
174     },
175     {   'module'              => 'IPTables::ChainMgr',
176         'force-install'       => 1,
177         'client-mode-install' => 0,
178         'mod-dir'             => 'IPTables-ChainMgr'
179     }
180 );
181
182 my %cmds = (
183     'make'     => $makeCmd,
184     'perl'     => $perlCmd,
185     'gzip'     => $gzipCmd,
186     'killall'  => $killallCmd,
187     'mknod'    => $mknodCmd,
188     'ifconfig' => $ifconfigCmd,
189     'runlevel' => $runlevelCmd
190 );
191
192 my @cmd_search_paths = qw(
193     /bin
194     /sbin
195     /usr/bin
196     /usr/sbin
197     /usr/local/bin
198     /usr/local/sbin
199 );
200
201 ### for user answers
202 my $ACCEPT_YES_DEFAULT = 1;
203 my $ACCEPT_NO_DEFAULT  = 2;
204 my $NO_ANS_DEFAULT     = 0;
205
206 ### make Getopts case sensitive
207 Getopt::Long::Configure('no_ignore_case');
208
209 &usage(1) unless (GetOptions(
210     'Single-mod-install=s'  => \$single_module_install,  ### install a single module
211     'force-mod-install' => \$cmdline_force_install,  ### force install of all modules
212     'Force-mod-regex=s' => \$force_mod_re, ### force specific mod install with regex
213     'Exclude-mod-regex=s' => \$exclude_mod_re, ### exclude a particular perl module
214     'Skip-mod-install' => \$skip_module_install,
215     'OS-type=s'        => \$cmdline_os_type,
216     'Cygwin-install'   => \$cygwin_install,
217     'BSD-install'   => \$bsd_install,
218     'Defaults'      => \$force_defaults,
219     'client-only'   => \$client_install, # Force client-only installation
220     'path-update'   => \$force_path_update,
221     'uninstall'     => \$uninstall,       # Uninstall fwknop.
222     'syslog-conf=s' => \$syslog_conf,     # Specify path to syslog config file.
223     'interface=s'   => \$sniff_interface, # Specify interface to sniff from
224     'init-dir=s'    => \$init_dir,
225     'init-name=s'   => \$init_name,
226     'install-syslog-fifo' => \$install_syslog_fifo,
227     'runlevel=i'    => \$runlevel,
228     'Home-dir=s'    => \$homedir, # specify home directory manually
229     'LC_ALL=s'      => \$locale,
230     'no-LC_ALL'     => \$no_locale,
231     'help'          => \$print_help # Display help.
232 ));
233 &usage(0) if $print_help;
234
235 $force_mod_re = qr|$force_mod_re| if $force_mod_re;
236 $exclude_mod_re = qr|$exclude_mod_re| if $exclude_mod_re;
237 $single_module_install = qr|$single_module_install| if $single_module_install;
238
239 ### set LC_ALL env variable
240 $ENV{'LC_ALL'} = $locale unless $no_locale;
241
242 &handle_cmd_line();
243
244 ### import paths from default fwknopd.conf
245 &import_config();
246
247 ### see if the deps/ directory exists, and if not then we are installing
248 ### from the -nodeps sources so don't install any perl modules
249 $skip_module_install = 1 unless -d $deps_dir;
250
251 ### check to see if we are installing as a non-root user
252 &check_non_root_user() unless $client_install;
253
254 ### get the OS type
255 &get_os() unless $os_type;
256
257 if ($os_type == $OS_LINUX) {
258     print "[+] OS: Linux\n";
259 } elsif ($os_type == $OS_CYGWIN) {
260     print "[+] OS: Cygwin\n";
261 } elsif ($os_type == $OS_DARWIN) {
262     print "[+] OS: Darwin\n";
263 } elsif ($os_type == $OS_BSD) {
264     print "[+] OS: BSD\n";
265 }
266
267 if ($client_install) {
268
269     ### we are installing as a normal user instead of root, so see
270     ### if it is ok to install within the user's home directory
271     unless ($homedir) {
272         $homedir = $ENV{'HOME'} or die '[*] Could not get home ',
273             "directory, use --Home-dir <directory>";
274     }
275
276     print
277 "    The fwknop client will be installed at $homedir/bin/fwknop, and a few\n",
278 "    perl modules needed by fwknop will be installed in $homedir/lib/fwknop/.\n\n",
279
280     $config{'FWKNOP_MOD_DIR'} = "$homedir/lib/fwknop";
281     $USRBIN_DIR = "$homedir/bin";
282 }
283
284 if ($os_type == $OS_LINUX) {
285
286     $distro = &get_linux_distro();
287
288     if ($distro eq 'redhat' or $distro eq 'fedora') {
289         ### add chkconfig only if we are runing on a redhat distro
290         $cmds{'chkconfig'} = $chkconfigCmd;
291     } elsif ($distro eq 'gentoo') {
292         ### add rc-update if we are running on a gentoo distro
293         $cmds{'rc-update'} = $rcupdateCmd;
294     } elsif ($distro eq 'ubuntu') {
295         ### add update-rc.d if we are running on an ubuntu distro
296         $cmds{'update-rc.d'} = $updatercdCmd;
297     }
298     print "[+] Distro: $distro\n";
299 }
300
301 ### make sure the system binaries are where we expect
302 ### them to be.
303 &check_commands();
304
305 my $hostname = hostname();
306
307 my $src_dir = getcwd() or die "[*] Could not get current working directory.";
308
309 if (not $uninstall) {
310     &install();
311 } else {
312     &uninstall();
313 }
314 exit 0;
315 #======================= end main ==========================
316
317 sub install() {
318     print "[+] Installing fwknop on $hostname\n";
319
320     if ($homedir) {
321         die "[*] $homedir does not exist" unless -d $homedir;
322     }
323
324     my $preserve_rv = 0;
325     unless ($client_install) {
326         if (&ask_to_stop_fwknop()) {
327             &stop_fwknop();
328         }
329
330         for my $dir qw| /usr/lib /var/run /var/log /var/lib | {
331             unless (-d $dir) {
332                 mkdir $dir or die "[*] Could not mkdir $dir: $!";
333             }
334         }
335         unless (-d $USRSBIN_DIR) {
336             mkdir $USRSBIN_DIR or die "[*] Could not mkdir $USRSBIN_DIR: $!";
337         }
338         for my $dir qw/FWKNOP_DIR FWKNOP_RUN_DIR
339                     FWKNOP_LIB_DIR/ {
340             unless (-d $config{$dir}) {
341                 mkdir $config{$dir}, 0500 or
342                     die "[*] Could not mkdir $config{$dir}: $!";
343             }
344         }
345     }
346     unless (-d $USRBIN_DIR) {
347         print "[+] Creating: $USRBIN_DIR\n";
348         mkdir $USRBIN_DIR or die "[*] Could not mkdir $USRBIN_DIR: $!";
349     }
350
351     ### config directory
352     unless ($client_install) {
353         unless (-d $config{'FWKNOP_CONF_DIR'}) {
354             ### Note that root will only be able to view files in
355             ### /etc/fwknop since fwknop only needs to view fwknop.conf
356             ### when being run as a daemon.
357             print "[+] Creating config directory: ",
358                 "$config{'FWKNOP_CONF_DIR'}\n";
359             mkdir $config{'FWKNOP_CONF_DIR'}, 0500
360                 or die "[*] Could not mkdir $config{'FWKNOP_CONF_DIR'}: $!";
361         }
362
363         ### archive directory for previously installed config files
364         unless (-d "$config{'FWKNOP_CONF_DIR'}/archive") {
365             print "[+] Creating config{'FWKNOP_CONF_DIR'}/archive ",
366                 "directory: $config{'FWKNOP_CONF_DIR'}/archive\n";
367             mkdir "$config{'FWKNOP_CONF_DIR'}/archive", 0500 or die
368                 "[*] Could not mkdir $config{'FWKNOP_CONF_DIR'}/archive: $!";
369         }
370     }
371
372     print "[+] Several perl modules needed by fwknop will be installed in\n",
373         "    $config{'FWKNOP_MOD_DIR'}. Installing them here will keep the ",
374         "system perl\n    library tree clean.\n";
375
376     ### make our library directory (for perl modules)
377     if ($client_install) {
378         unless (-d "$homedir/lib") {
379             print "[+] Creating directory $homedir/lib\n";
380             mkdir "$homedir/lib", 0755
381                 or die "[*] Could not mkdir $homedir/lib: $!";
382         }
383     }
384     unless (-d $config{'FWKNOP_MOD_DIR'}) {
385         print "[+] Creating directory $config{'FWKNOP_MOD_DIR'}\n";
386         mkdir $config{'FWKNOP_MOD_DIR'}, 0755
387             or die "[*] Could not mkdir $config{'FWKNOP_MOD_DIR'}: $!";
388     }
389
390     ### install perl modules
391     unless ($skip_module_install) {
392         for my $mod_href (@required_perl_modules) {
393             &install_perl_module($mod_href);
394         }
395     }
396     chdir $src_dir or die "[*] Could not chdir $src_dir: $!";
397
398     if ($single_module_install) {
399         print "    Finished module installation, exiting.\n";
400         exit 0;
401     }
402
403     unless ($client_install) {
404
405         ### install man pages
406         &install_manpage('knopmd.8');
407         &install_manpage('knopwatchd.8');
408         &install_manpage('fwknop.8');
409         &install_manpage('fwknopd.8');
410
411         if (-e "$config{'FWKNOP_CONF_DIR'}/fwknop.conf") {
412             $preserve_rv = &query_preserve_config();
413         }
414
415         print "[+] Compiling knopmd and knopwatchd daemons:\n";
416
417         ### remove any previously compiled knopmd
418         unlink 'knopmd' if -e 'knopmd';
419
420         ### remove any previously compiled knopwatchd
421         unlink 'knopwatchd' if -e 'knopwatchd';
422
423         ### compile the C fwknop daemons
424         system $cmds{'make'};
425
426         unless (-e 'knopmd' and -e 'knopwatchd') {
427             die "[*] Compilation failed.";
428         }
429
430         ### install fwknop server-side daemons/programs
431         for my $daemon qw(fwknopd knopmd knopwatchd knoptm fwknop_serv) {
432             if ($daemon eq 'fwknopd' or $daemon eq 'knoptm') {
433                 unless (((system "$cmds{'perl'} -c $daemon")>>8) == 0) {
434                     die "[*] $daemon does not compile with \"perl -c\".  ",
435                         "Download the latest sources ",
436                         "from:\n\nhttp://www.cipherdyne.org/\n";
437                 }
438             }
439             print "[+] Copying $daemon -> $USRSBIN_DIR\n";
440             copy $daemon, $USRSBIN_DIR or
441                 die "[*] Could not cp $daemon to $USRSBIN_DIR: $!";
442             chmod 0500, "$USRSBIN_DIR/$daemon" or
443                 die "[*] Could not chmod 500 $USRSBIN_DIR/$daemon: $!";
444         }
445     }
446
447     print "[+] Copying fwknop -> $USRBIN_DIR\n";
448     copy 'fwknop', $USRBIN_DIR or
449         die "[*] Could not cp fwknop to $USRBIN_DIR: $!";
450
451     if ($client_install) {
452         open F, "< $USRBIN_DIR/fwknop" or die "[*] Could not open ",
453             "$USRBIN_DIR/fwknop: $!";
454         my @lines = <F>;
455         close F;
456         open P, "> $USRBIN_DIR/fwknop.tmp" or die "[*] Could not open ",
457             "$USRBIN_DIR/fwknop.tmp: $!";
458         for my $line (@lines) {
459             ### change the lib dir to new homedir path
460             if ($line =~ m|^\s*use\s+lib\s+\'/usr/lib/fwknop\';|) {
461                 print P "use lib '", $config{'FWKNOP_MOD_DIR'}, "';\n";
462             } elsif ($line =~ m|^\s*my\s+\$lib_dir\s+=\s+\'/usr/lib/fwknop\';|) {
463                 print P 'my $lib_dir = ' . "'"
464                     . $config{'FWKNOP_MOD_DIR'} . "';\n";
465             } else {
466                 print P $line;
467             }
468         }
469         close P;
470         move "$USRBIN_DIR/fwknop.tmp", "$USRBIN_DIR/fwknop" or die "[*] Could ",
471             "not move $USRBIN_DIR/fwknop.tmp -> $USRBIN_DIR/fwknop: $!";
472         chmod 0700, "$USRBIN_DIR/fwknop" or
473             die "[*] Could not chmod 755 $USRBIN_DIR/fwknop: $!";
474     } else {
475         chmod 0755, "$USRBIN_DIR/fwknop" or
476             die "[*] Could not chmod 755 $USRBIN_DIR/fwknop: $!";
477     }
478
479     unless (((system "$cmds{'perl'} -c $USRBIN_DIR/fwknop")>>8) == 0) {
480         die "[*] $USRBIN_DIR/fwknop does not compile with \"perl -c\".  ",
481             "Download the latest sources ",
482             "from:\n\nhttp://www.cipherdyne.org/\n";
483     }
484
485
486     if ($client_install) {
487         print
488 "\n[+] fwknop has been installed at $USRBIN_DIR/fwknop.  Since this is a\n",
489 "    client-only install, the man pages could not be installed.  For more\n",
490 "    information about how to use fwknop, execute \"$USRBIN_DIR/fwknop -h\" or\n",
491 "    refer to:\n\n",
492 "        http://www.cipherdyne.org/fwknop/docs/manpages/index.html\n\n";
493
494     } else {
495
496         ### install config and access files
497         for my $file qw(fwknop.conf access.conf pf.os) {
498             if (-e "$config{'FWKNOP_CONF_DIR'}/$file") {
499                 &archive("$config{'FWKNOP_CONF_DIR'}/$file");
500                 if ($preserve_rv) {
501                     if ($file eq 'access.conf') {
502                         ### access.conf can have missing fields (i.e.
503                         ### REQUIRE_OS_REGEX and REQUIRE_USERNAME),
504                         ### and also it can have multiple sequences
505                         ### defined.  Hence we just use the old one.
506                         print "[+] Using original access.conf\n";
507                     } elsif ($file ne 'pf.os') {
508                         &preserve_config($file);
509                     }
510                 } else {
511                     print "[+] Copying $file -> $config{'FWKNOP_CONF_DIR'}\n";
512                     copy $file, $config{'FWKNOP_CONF_DIR'} or
513                         die "[*] Could not cp $file to $config{'FWKNOP_CONF_DIR'}";
514                 }
515             } else {
516                 print "[+] Copying $file -> $config{'FWKNOP_CONF_DIR'}\n";
517                 copy $file, $config{'FWKNOP_CONF_DIR'} or
518                     die "[*] Could not cp $file to $config{'FWKNOP_CONF_DIR'}";
519             }
520
521             if ($force_path_update or not $preserve_rv) {
522                 &update_command_paths("$config{'FWKNOP_CONF_DIR'}/$file")
523                     if ($file eq 'fwknop.conf');
524             }
525
526             if ($file eq 'fwknop.conf') {
527                 &set_hostname("$config{'FWKNOP_CONF_DIR'}/$file");
528             }
529             chmod 0600, "$config{'FWKNOP_CONF_DIR'}/$file" or die
530                 "[*] Could not chmod(600, $config{'FWKNOP_CONF_DIR'}/$file: $!";
531             chown 0, 0, "$config{'FWKNOP_CONF_DIR'}/$file" or die
532                 "[*] Could not chown 0,0, $config{'FWKNOP_CONF_DIR'}/$file: $!";
533         }
534
535         ### get data acquisition method (e.g. syslogd, sysylog-ng, ulogd
536         ### or pcap)
537         $data_method = &query_data_method();
538
539         if ($data_method =~ /syslog/) {
540
541             &syslog_reconfig() if $install_syslog_fifo;
542
543         } elsif ($data_method =~ /pcap/i or $data_method =~ /ulog/i) {
544
545             if ($os_type == $OS_BSD or $os_type == $OS_DARWIN) {
546                 ### update to use the ipfw firewall on *BSD systems
547                 &put_string('FIREWALL_TYPE', 'ipfw',
548                     "$config{'FWKNOP_CONF_DIR'}/fwknop.conf");
549             }
550
551             ### we are using a pcap method
552             if ($data_method eq 'ulogd' or $data_method eq 'file_pcap') {
553                 print
554 "[+] By default, fwknop uses the file /var/log/sniff.pcap in order to\n",
555 "    acquire packet data logged via a sniffer (or ulogd) to a pcap file,\n",
556 "    but this path may be changed by altering the PCAP_PKT_FILE keyword\n",
557 "    in $config{'FWKNOP_CONF_DIR'}/fwknop.conf.\n\n";
558
559                 if ($data_method eq 'file_pcap') {
560                     &put_string('AUTH_MODE', 'FILE_PCAP',
561                         "$config{'FWKNOP_CONF_DIR'}/fwknop.conf");
562                 } else {
563                     &put_string('AUTH_MODE', 'ULOG_PCAP',
564                         "$config{'FWKNOP_CONF_DIR'}/fwknop.conf");
565                 }
566             } else {
567                 unless ($sniff_interface) {
568                     $sniff_interface = &get_pcap_intf();
569                 }
570                 if ($sniff_interface) {
571                     &put_string('PCAP_INTF', $sniff_interface,
572                         "$config{'FWKNOP_CONF_DIR'}/fwknop.conf");
573                 } else {
574 print "[-] Could not get sniffing interface, edit the PCAP_INTF var in\n",
575     "    config{'FWKNOP_CONF_DIR'}/fwknop.conf\n";
576                 }
577             }
578         } else {
579             ### it is a client-only install, so don't reconfigure syslog
580             ### or anything.
581         }
582
583         unless ($preserve_rv) {
584             my $email_str = &query_email();
585             if ($email_str) {
586                 for my $file qw(fwknop.conf) {
587                     &put_string('EMAIL_ADDRESSES', $email_str,
588                         "$config{'FWKNOP_CONF_DIR'}/$file");
589                 }
590             }
591         }
592
593
594         if ($client_install) {
595             print "\n[+] fwknop has been installed!\n\n";
596         } else {
597
598             &get_init_dir();
599             if (-d $init_dir) {
600                 &enable_fwknop_at_boot($distro);
601             }
602
603             print "\n[+] fwknop has been installed!";
604
605             if ($os_type == $OS_LINUX) {
606                 print "  To start in server mode, run\n\n",
607                     "    \"$init_dir/$init_name start\"\n\n";
608             } else {
609                 print "\n\n";
610             }
611             print
612 "    You may want to consider running the fwknop test suite in the test/\n",
613 "    directory to ensure that fwknop will function correctly on your system.\n\n",
614
615 "    Note: You will need to edit $config{'FWKNOP_CONF_DIR'}/access.conf for fwknop to\n",
616 "    function properly in server mode.  More information can be found in\n",
617 "    the fwknopd(8) manpage.\n\n";
618             if ($os_type == $OS_BSD or $os_type == $OS_DARWIN) {
619                 print
620 "    You may need to update your /etc/syslog.conf file to log local info\n",
621 "    messages to a file in the /var/log/ directory in order to see syslog\n",
622 "    messages from the fwknop daemons.\n\n";
623             }
624         }
625     }
626     return;
627 }
628
629 sub syslog_reconfig() {
630
631     ### create the named pipe
632     unless (-e $config{'KNOPMD_FIFO'} and -p $config{'KNOPMD_FIFO'}) {
633         unlink $config{'KNOPMD_FIFO'} if -e $config{'KNOPMD_FIFO'};
634         print "[+] Creating named pipe $config{'KNOPMD_FIFO'}\n";
635         my $created_pipe = 1;
636         unless (((system "$cmds{'mknod'} -m 600 $config{'KNOPMD_FIFO'} p")>>8)
637                 == 0) {
638             $created_pipe = 0;
639         }
640         unless (-e $config{'KNOPMD_FIFO'} and -p $config{'KNOPMD_FIFO'}) {
641             $created_pipe = 0;
642         }
643         unless ($created_pipe) {
644             die
645 "[*] Could not create the named pipe \"$config{'KNOPMD_FIFO'}\"!\n",
646 "[*] fwknop requires this file to exist!  Aborting install.\n";
647         }
648     }
649
650     unless (-e "$config{'FW_DATA_FILE'}") {
651         print "[+] Creating $config{'FW_DATA_FILE'} file\n";
652         open F, "> $config{'FW_DATA_FILE'}" or die "[*] Could not open ",
653             "$config{'FW_DATA_FILE'}: $!";
654         close F;
655         &perms_ownership("$config{'FW_DATA_FILE'}", 0600);
656     }
657
658     ### we are acquiring data via syslog
659     &put_string('AUTH_MODE', 'KNOCK',
660         "$config{'FWKNOP_CONF_DIR'}/fwknop.conf");
661
662     if ($os_type == $OS_BSD or $os_type == $OS_DARWIN) {
663         ### update to use the ipfw firewall on *BSD systems
664         &put_string('FIREWALL_TYPE', 'ipfw',
665             "$config{'FWKNOP_CONF_DIR'}/fwknop.conf");
666     }
667
668     &put_string('SYSLOG_DAEMON', $data_method,
669         "$config{'FWKNOP_CONF_DIR'}/fwknop.conf");
670
671     my $restarted_syslog = 0;
672     if ($data_method eq 'syslogd') {
673         if (-e $syslog_conf) {
674             &append_fifo_syslog($syslog_conf);
675             if (((system "$cmds{'killall'} -HUP syslogd " .
676                     "2> /dev/null")>>8) == 0) {
677                 print "[+] HUP signal sent to syslogd.\n";
678                 $restarted_syslog = 1;
679             }
680         }
681     } elsif ($data_method eq 'syslog-ng') {
682         if (-e $syslog_conf) {
683             &append_fifo_syslog_ng($syslog_conf);
684             if (((system "$cmds{'killall'} -HUP syslog-ng " .
685                     "2> /dev/null")>>8) == 0) {
686                 print "[+] HUP signal sent to syslog-ng.\n";
687                 $restarted_syslog = 1;
688             }
689         }
690     }
691
692     unless ($restarted_syslog) {
693         print "[-] Could not restart any syslog daemons.\n";
694     }
695     return;
696 }
697
698 sub uninstall() {
699
700     print "[+] Uninstalling fwknop...\n";
701
702     ### stop any running fwknop daemons.
703     &stop_fwknop();
704
705     ### get the init directory
706     &get_init_dir();
707
708     for my $daemon qw(fwknopd knopmd knopwatchd knoptm fwknop_serv) {
709         unlink "$USRSBIN_DIR/$daemon" if -e "$USRSBIN_DIR/$daemon";
710     }
711     unlink "$USRBIN_DIR/fwknop" if -e "$USRBIN_DIR/fwknop";
712     unlink "$init_dir/$init_name" if -e "$init_dir/$init_name";
713     rmtree $config{'FWKNOP_CONF_DIR'}, 1, 1 if -d $config{'FWKNOP_CONF_DIR'};
714     rmtree $config{'FWKNOP_LIB_DIR'}, 1, 1 if -d $config{'FWKNOP_LIB_DIR'};
715     rmtree $config{'FWKNOP_MOD_DIR'}, 1, 1 if -d $config{'FWKNOP_MOD_DIR'};
716
717     return;
718 }
719
720 sub get_init_dir() {
721     return if $client_install;
722
723     if ($os_type == $OS_DARWIN) {
724
725         ### Mac OS X init directory for user programs
726         $init_dir = '/Library/StartupItems';
727         die "[*] The $init_dir directory does not exist."
728             unless -d $init_dir;
729         return;
730     }
731
732     ### accommodates Linux and BSD systems
733     unless (-d $init_dir) {
734         if (-d '/etc/rc.d/init.d') {
735             $init_dir = '/etc/rc.d/init.d';
736         } elsif (-d '/etc/rc.d') {
737             $init_dir = '/etc/rc.d';
738         } elsif (-d '/etc/init.d') {
739             $init_dir = '/etc/init.d';
740         } else {
741             die "[*] Cannot find the init script directory, use ",
742                 "--init-dir <path>\n";
743         }
744     }
745     return;
746 }
747
748 ### check paths to commands and attempt to correct if any are wrong.
749 sub check_commands() {
750
751     return if $client_install;
752
753     if ($os_type == $OS_LINUX or $os_type == $OS_CYGWIN) {
754         $exclude_cmds{'ipfw'} = '';
755     } else {
756         ### we are installing on BSD or Mac OS X
757         $exclude_cmds{'iptables'} = '';
758
759         ### the runlevel does not apply here
760         $exclude_cmds{'runlevel'} = '';
761     }
762
763     CMD: for my $cmd (keys %cmds) {
764         unless (-x $cmds{$cmd}) {
765             my $found = 0;
766             PATH: for my $dir (@cmd_search_paths) {
767                 if (-x "${dir}/${cmd}") {
768                     $cmds{$cmd} = "${dir}/${cmd}";
769                     $found = 1;
770                     last PATH;
771                 }
772             }
773             unless ($found) {
774                 next CMD if defined $exclude_cmds{$cmd};
775                 if ($cmd eq 'runlevel') {
776                     if ($runlevel > 0) {
777                         next CMD;
778                     } else {
779                         die "[*] Could not find the $cmd command, ",
780                             "use --runlevel <N>";
781                     }
782                 }
783                 die "[*] Could not find $cmd anywhere. ",
784                     "Please edit the config section to include the path to ",
785                     "$cmd.\n";
786             }
787         }
788         unless (-x $cmds{$cmd}) {
789             die "[*] $cmd is located at ",
790                 "$cmds{$cmd} but is not executable by uid: $<\n"
791                 unless $client_install;
792         }
793     }
794     return;
795 }
796
797 sub archive() {
798     my $file = shift;
799     return unless -e $file;
800     my $curr_pwd = cwd();
801     chdir "$config{'FWKNOP_CONF_DIR'}/archive" or die $!;
802     my ($filename) = ($file =~ m|.*/(.*)|);
803     my $base = "${filename}.old";
804     for (my $i = 5; $i > 1; $i--) {  ### keep five copies of old config files
805         my $j = $i - 1;
806         unlink "${base}${i}.gz" if -e "${base}${i}.gz";
807         move "${base}${j}.gz", "${base}${i}.gz" if -e "${base}${j}.gz";
808     }
809     print "[+] Archiving $file -> ${base}1\n";
810     unlink "${base}1.gz" if -e "${base}1.gz";
811     copy $file, "${base}1";   ### move $file into the archive directory
812     system "$cmds{'gzip'} ${base}1";
813     chdir $curr_pwd or die $!;
814     return;
815 }
816
817 sub preserve_config() {
818     my $file = shift;
819
820     my %preserved_lines = ();
821
822     open C, "< $file" or die "[*] Could not open $file: $!";
823     my @new_lines = <C>;
824     close C;
825
826     open CO, "< $config{'FWKNOP_CONF_DIR'}/$file" or die "[*] Could not open ",
827         "$config{'FWKNOP_CONF_DIR'}}/$file: $!";
828     my @orig_lines = <CO>;
829     close CO;
830
831     print "[+] Preserving existing config: $config{'FWKNOP_CONF_DIR'}/$file\n";
832     ### write to a tmp file and then move so any running fwknop daemon will
833     ### re-import a full config file if a HUP signal is received during
834     ### the install.
835     open CONF, "> $config{'FWKNOP_CONF_DIR'}/${file}.new" or die "[*] Could not open ",
836         "$config{'FWKNOP_CONF_DIR'}/${file}.new: $!";
837     for my $new_line (@new_lines) {
838         if ($new_line =~ /^\s*#/) {
839             print CONF $new_line;  ### take comments from new file.
840         } elsif ($new_line =~ /^\s*(\S+)/) {
841             my $var = $1;
842             my $found = 0;
843             my $ctr = 0;
844             for my $orig_line (@orig_lines) {
845                 if ($orig_line =~ /^\s*$var\s/) {
846                     next if defined $preserved_lines{$ctr};
847                     $preserved_lines{$ctr} = '';
848                     print CONF $orig_line;
849                     $found = 1;
850                     last;
851                 }
852                 $ctr++;
853             }
854             unless ($found) {
855                 print CONF $new_line;
856             }
857         } else {
858             print CONF $new_line;
859         }
860     }
861     close CONF;
862     move "$config{'FWKNOP_CONF_DIR'}/${file}.new",
863             "$config{'FWKNOP_CONF_DIR'}/$file" or die "[*] ",
864         "Could not move $config{'FWKNOP_CONF_DIR'}/${file}.new -> ",
865         "$config{'FWKNOP_CONF_DIR'}/$file: $!";
866     return;
867 }
868
869 sub append_fifo_syslog() {
870     print "[+] Modifying /etc/syslog.conf to write kern.info messages to\n",
871         "    $config{'KNOPMD_FIFO'}\n";
872     unless (-e '/etc/syslog.conf.orig') {
873         copy '/etc/syslog.conf', '/etc/syslog.conf.orig';
874     }
875     &archive('/etc/syslog.conf');
876     open RS, '< /etc/syslog.conf' or
877         die "[*] Unable to open /etc/syslog.conf: $!\n";
878     my @slines = <RS>;
879     close RS;
880     open SYSLOG, '> /etc/syslog.conf' or
881         die "[*] Unable to open /etc/syslog.conf: $!\n";
882     for my $line (@slines) {
883         unless ($line =~ /fwknopfifo/) {
884             print SYSLOG $line;
885         }
886     }
887     ### reinstate kernel logging to our named pipe
888     print SYSLOG '### Send kern.info messages to fwknopfifo for ',
889         "analysis by knopmd\n",
890         "kern.info\t\t|$config{'KNOPMD_FIFO'}\n";
891     close SYSLOG;
892     return;
893 }
894
895 sub append_fifo_syslog_ng() {
896     print "[+] Modifying /etc/syslog-ng/syslog-ng.conf to write kern.info ",
897         "messages to\n";
898     print "    $config{'KNOPMD_FIFO'}\n";
899     unless (-e '/etc/syslog-ng/syslog-ng.conf.orig') {
900         copy '/etc/syslog-ng/syslog-ng.conf',
901             '/etc/syslog-ng/syslog-ng.conf.orig';
902     }
903     &archive('/etc/syslog-ng/syslog-ng.conf');
904     open RS, '< /etc/syslog-ng/syslog-ng.conf' or
905         die "[*]  Unable to open /etc/syslog-ng/syslog-ng.conf: $!\n";
906     my @slines = <RS>;
907     close RS;
908
909     my $found_fifo = 0;
910     for my $line (@slines) {
911         $found_fifo = 1 if ($line =~ /fwknopfifo/);
912     }
913
914     unless ($found_fifo) {
915         open SYSLOGNG, '>> /etc/syslog-ng/syslog-ng.conf' or
916             die "[*] Unable to open /etc/syslog-ng/syslog-ng.conf: $!\n";
917         print SYSLOGNG "\n",
918             "destination fwknoppipe { pipe(\"/var/lib/fwknop/fwknopfifo\"); };\n",
919             "filter f_kerninfo { facility(kern) and level(info); };\n",
920             "log { source(src); filter(f_kerninfo); destination(fwknoppipe); };\n";
921         close SYSLOGNG;
922     }
923     return;
924 }
925
926 sub check_non_root_user() {
927
928     unless ($< == 0 && $> == 0) {
929
930         print
931 "[+] It looks like you are installing fwknop as a non-root user.  This will\n",
932 "    result in fwknop being installed in your local home directory.\n",
933 "    If you want fwknop to act as a server (i.e. monitor the network for\n",
934 "    Single Packet Authorization activity and modify firewall access or\n",
935 "    execute commands accordingly), you will need to install as root.\n\n";
936
937         $client_install = 1;
938     }
939     return;
940 }
941
942 sub get_os() {
943
944     ### This function implements a set of simple heuristics to determine
945     ### the OS type.  Note that the user can always just set the OS from
946     ### the command line with --OS-type
947
948     ### get OS output from uname
949     open UNAME, 'uname |' or die "[*] Could not execute 'uname', use ",
950         "--OS-type.";
951     while (<UNAME>) {
952
953         if (/Darwin/) {
954
955             print
956 "[+] It looks like you are installing fwknop on a Mac OS X system.\n",
957 "    Installation of iptables perl modules will be skipped.\n";
958             $os_type = $OS_DARWIN;
959
960         } elsif (/[a-z]BSD/) {
961
962             print
963 "[+] It looks like you are installing fwknop on a *BSD system. Installation\n",
964 "    of iptables perl modules will be skipped.\n";
965             $os_type = $OS_BSD;
966         }
967         last;
968     }
969     close UNAME;
970
971     unless ($os_type) {
972         ### 'uname -o' does not work on Mac OS X or FreeBSD, so we had to check
973         ### for this above
974
975         open UNAME, 'uname -o |' or die "[*] Could not execute 'uname -o', ",
976             "use --OS-type.";
977         while (<UNAME>) {
978
979             if (/Cygwin/ or /Gygwin/) {
980
981                 print
982 "[+] It looks like you are installing fwknop in a Cygwin environment, so the\n",
983 "    fwknop client will be installed (the fwknopd server does not yet\n",
984 "    function with a Windows-based firewall).\n\n";
985
986                 $client_install = 1;
987                 $os_type = $OS_CYGWIN;
988             }
989             last;
990         }
991         close UNAME;
992     }
993
994     ### default to Linux
995     $os_type = $OS_LINUX unless $os_type;
996
997     unless ($client_install) {
998
999         my $have_iptables = 0;
1000         $have_iptables = 1 if (-e '/usr/sbin/iptables' or -e '/sbin/iptables');
1001
1002         if ($os_type == $OS_LINUX) {
1003             unless ($have_iptables) {
1004                 die "[*] iptables does not seem to be installed, ",
1005                     "use --OS-type.";
1006             }
1007         } elsif ($os_type == $OS_BSD or $os_type == $OS_DARWIN) {
1008             if ($have_iptables) {
1009                 die "[*] iptables exists on what looks like a non-Linux ",
1010                     "system, use --OS-type.";
1011             }
1012         }
1013     }
1014     return;
1015 }
1016
1017 sub get_linux_distro() {
1018     return 'gentoo' if -e '/etc/gentoo-release';
1019     if (-e '/etc/issue') {
1020         ### Red Hat Linux release 6.2 (Zoot)
1021         open ISSUE, '< /etc/issue' or
1022             die "[*] Could not open /etc/issue: $!";
1023         my @lines = <ISSUE>;
1024         close ISSUE;
1025         for my $line (@lines) {
1026             chomp $line;
1027             return 'redhat' if $line =~ /red\s*hat/i;
1028             return 'fedora' if $line =~ /fedora/i;
1029             return 'ubuntu' if $line =~ /ubuntu/i;
1030         }
1031     }
1032     return 'NA';
1033 }
1034
1035 sub install_manpage() {
1036     my $manpage = shift;
1037
1038     ### default location to put man pages, but check with
1039     ### /etc/man.config
1040     my $mpath = '/usr/share/man/man8';
1041     if (-e '/etc/man.config') {
1042         ### prefer to install $manpage in /usr/local/man/man8 if
1043         ### this directory is configured in /etc/man.config
1044         open M, '< /etc/man.config' or
1045             die "[*] Could not open /etc/man.config: $!";
1046         my @lines = <M>;
1047         close M;
1048         ### prefer the path "/usr/share/man"
1049         my $found = 0;
1050         for my $line (@lines) {
1051             chomp $line;
1052             if ($line =~ m|^MANPATH\s+/usr/share/man|) {
1053                 $found = 1;
1054                 last;
1055             }
1056         }
1057         ### try to find "/usr/local/man" if we didn't find /usr/share/man
1058         unless ($found) {
1059             for my $line (@lines) {
1060                 chomp $line;
1061                 if ($line =~ m|^MANPATH\s+/usr/local/man|) {
1062                     $mpath = '/usr/local/man/man8';
1063                     $found = 1;
1064                     last;
1065                 }
1066             }
1067         }
1068         ### if we still have not found one of the above man paths,
1069         ### just select the first one out of /etc/man.config
1070         unless ($found) {
1071             for my $line (@lines) {
1072                 chomp $line;
1073                 if ($line =~ m|^MANPATH\s+(\S+)|) {
1074                     $mpath = $1;
1075                     last;
1076                 }
1077             }
1078         }
1079     }
1080     mkdir $mpath, 0755 unless -d $mpath;
1081     my $mfile = "${mpath}/${manpage}";
1082     print "[+] Installing $manpage man page at $mfile\n";
1083     copy $manpage, $mfile or die "[*] Could not copy $manpage to ",
1084         "$mfile: $!";
1085     &perms_ownership($mfile, 0644);
1086     print "[+] Compressing manpage $mfile\n";
1087     ### remove the old one so gzip doesn't prompt us
1088     unlink "${mfile}.gz" if -e "${mfile}.gz";
1089     system "$cmds{'gzip'} $mfile";
1090     return;
1091 }
1092
1093 sub enable_fwknop_at_boot() {
1094     my $distro = shift;
1095
1096     if (&query_yes_no("[+] Enable fwknop at boot time ([y]/n)?  ",
1097             $ACCEPT_YES_DEFAULT)) {
1098
1099         if ($os_type == $OS_LINUX) {
1100
1101         } elsif ($os_type == $OS_DARWIN) {
1102             my $dir = "$init_dir/Fwknop";
1103             unless (-d $dir) {
1104                 print "[+] mkdir $dir\n";
1105                 mkdir $dir, 0755 or die "[*] Could not mkdir $dir";
1106             }
1107             copy "init-scripts/OS_X/Fwknop", $dir or die "[*] Could not ",
1108                 "copy init-scripts/OS_X/Fwknop -> $dir";
1109             copy "init-scripts/OS_X/StartupParameters.plist", $dir or die "[*] ",
1110                 "Could not copy init-scripts/OS_X/StartupParameters.plist -> $dir";
1111
1112         } elsif ($os_type == $OS_BSD) {
1113             print "[+] Copying init-scripts/fwknop-init.freebsd ",
1114                 "-> ${init_dir}/$init_name\n";
1115             copy "init-scripts/fwknop-init.freebsd", "${init_dir}/$init_name"
1116                 or die "[*] Could not cp fwknop init script to $init_dir: $!";
1117             &perms_ownership("${init_dir}/$init_name", 0744);
1118         }
1119
1120         if ($os_type == $OS_LINUX) {
1121
1122             my $init_file = '';
1123
1124             if ($distro eq 'redhat') {
1125                 $init_file = 'fwknop-init.redhat-chkconfig-enable';
1126             } elsif ($distro eq 'fedora') {
1127                 $init_file = 'fwknop-init.fedora';
1128             } elsif ($distro eq 'gentoo') {
1129                 $init_file = 'fwknop-init.gentoo';
1130             } else {
1131                 $init_file = 'fwknop-init.generic';
1132             }
1133
1134             copy "init-scripts/$init_file", "${init_dir}/$init_name"
1135                 or die "[*] Could not cp fwknop init script to $init_dir: $!";
1136             &perms_ownership("${init_dir}/$init_name", 0744);
1137
1138             if ($distro eq 'redhat' or $distro eq 'fedora') {
1139                 system "$cmds{'chkconfig'} --add $init_name";
1140             } elsif ($distro eq 'gentoo') {
1141                 system "$cmds{'rc-update'} add $init_name default";
1142             } elsif ($distro eq 'ubuntu') {
1143                 system "$cmds{'update-rc.d'} $init_name defaults 99";
1144             } else {
1145
1146                 ### get the current run level
1147                 &get_runlevel();
1148
1149                 if ($runlevel) {
1150                     ### the link already exists, so don't re-create it
1151                     if (-d '/etc/rc.d' and -d "/etc/rc.d/rc${runlevel}.d") {
1152                         unless (-e "/etc/rc.d/rc${runlevel}.d/S99$init_name") {
1153                             symlink "$init_dir/$init_name",
1154                                 "/etc/rc.d/rc${runlevel}.d/S99$init_name";
1155                         }
1156                     } else {
1157                         print "[-] The /etc/rc.d/rc${runlevel}.d directory does ",
1158                             "exist, not sure how to enable fwknop at boot time.";
1159                     }
1160                 }
1161             }
1162         }
1163     }
1164     return;
1165 }
1166
1167 sub install_perl_module() {
1168     my $mod_hr = shift;
1169
1170     chdir $src_dir or die "[*] Could not chdir $src_dir: $!";
1171     chdir $deps_dir or die "[*] Could not chdir($deps_dir): $!";
1172
1173     for my $key qw/module force-install client-mode-install mod-dir/ {
1174         die "[*] Missing $key key in required_perl_modules hash."
1175             unless defined $mod_hr->{$key};
1176     }
1177     my $mod_name = $mod_hr->{'module'};
1178
1179     if ($client_install) {
1180         return unless $mod_hr->{'client-mode-install'};
1181     }
1182
1183     if ($os_type != $OS_LINUX) {
1184         return if $mod_name eq 'IPTables::Parse'
1185             or $mod_name eq 'IPTables::ChainMgr';
1186     }
1187
1188     if ($exclude_mod_re and $exclude_mod_re =~ /$mod_name/) {
1189         print "[+] Excluding installation of $mod_name module.\n";
1190         return;
1191     }
1192
1193     if ($single_module_install and $mod_name !~ /$single_module_install/) {
1194         print "[+] Excluding installation of $mod_name module.\n";
1195         return;
1196     }
1197
1198     my $version = '(NA)';
1199
1200     my $mod_dir = $mod_hr->{'mod-dir'};
1201
1202     if (-e "$mod_dir/VERSION") {
1203         open F, "< $mod_dir/VERSION" or
1204             die "[*] Could not open $mod_dir/VERSION: $!";
1205         $version = <F>;
1206         close F;
1207         chomp $version;
1208     } else {
1209         print "[-] Warning: VERSION file does not exist in $mod_dir\n";
1210     }
1211
1212     my $install_module = 0;
1213
1214     if ($mod_hr->{'force-install'}
1215             or $cmdline_force_install) {
1216         ### install regardless of whether the module may already be
1217         ### installed (this module may be a CPAN module that has been
1218         ### modified specifically for this project, or is a dedicated
1219         ### module for this project).
1220         $install_module = 1;
1221     } elsif ($force_mod_re and $force_mod_re =~ /$mod_name/) {
1222         print "[+] Forcing installation of $mod_name module.\n";
1223         $install_module = 1;
1224     } else {
1225         if (has_perl_module($mod_name)) {
1226             print "[+] Module $mod_name is already installed in the ",
1227                 "system perl tree, skipping.\n";
1228         } else {
1229             ### install the module in the /usr/lib/fwknop directory because
1230             ### it is not already installed.
1231             $install_module = 1;
1232         }
1233     }
1234
1235     if ($install_module) {
1236         unless (-d $config{'FWKNOP_MOD_DIR'}) {
1237             print "[+] Creating $config{'FWKNOP_MOD_DIR'}\n";
1238             mkdir $config{'FWKNOP_MOD_DIR'}, 0755
1239                 or die "[*] Could not mkdir $config{'FWKNOP_MOD_DIR'}: $!";
1240         }
1241         print "[+] Installing $mod_name $version perl module in ",
1242             "$config{'FWKNOP_MOD_DIR'}/\n";
1243         chdir $mod_dir or die "[*] Could not chdir to ",
1244             "$mod_dir: $!";
1245         unless (-e 'Makefile.PL') {
1246             die "[*] Your $mod_name source directory appears to be incomplete!\n",
1247                 "    Download the latest sources from ",
1248                 "http://www.cipherdyne.org\n";
1249         }
1250         system "$cmds{'make'} clean" if -e 'Makefile';
1251         system "$cmds{'perl'} Makefile.PL PREFIX=$config{'FWKNOP_MOD_DIR'} " .
1252             "LIB=$config{'FWKNOP_MOD_DIR'}";
1253         system $cmds{'make'};
1254 #        system "$cmds{'make'} test";
1255         system "$cmds{'make'} install";
1256
1257         print "\n\n";
1258     }
1259     chdir $src_dir or die "[*] Could not chdir $src_dir: $!";
1260     return;
1261 }
1262
1263 sub set_hostname() {
1264     my $file = shift;
1265     if (-e $file) {
1266         open P, "< $file" or die "[*] Could not open $file: $!";
1267         my @lines = <P>;
1268         close P;
1269         ### replace the "HOSTNAME           CHANGE_ME" line
1270         open PH, "> $file" or die "[*] Could not open $file: $!";
1271         for my $line (@lines) {
1272             chomp $line;
1273             if ($line =~ /^\s*HOSTNAME(\s+)_?CHANGE.?ME_?/) {
1274                 print PH "HOSTNAME${1}$hostname;\n";
1275             } else {
1276                 print PH "$line\n";
1277             }
1278         }
1279         close PH;
1280     } else {
1281         die "[*] Your source directory appears to be incomplete!  $file ",
1282             "is missing.\n    Download the latest sources from ",
1283             "http://www.cipherdyne.org\n";
1284     }
1285     return;
1286 }
1287
1288 sub query_preserve_config() {
1289     print "\n",
1290 "[+] It appears that there is an existing fwknop installation on the system.\n";
1291
1292     return &query_yes_no("    Would you like to merge the " .
1293         "config from the existing\n    fwknop installation ([y]/n)?  ",
1294         $ACCEPT_YES_DEFAULT);
1295 }
1296
1297 sub query_email() {
1298     my $email_str = '';
1299     open F, "< $config{'FWKNOP_CONF_DIR'}/fwknop.conf" or die "[*] Could not open ",
1300         "$config{'FWKNOP_CONF_DIR'}/fwknop.conf: $!";
1301     my @clines = <F>;
1302     close F;
1303     my $email_addresses;
1304     for my $line (@clines) {
1305         chomp $line;
1306         if ($line =~ /^\s*EMAIL_ADDRESSES\s+(.+);/) {
1307             $email_addresses = $1;
1308             last;
1309         }
1310     }
1311     unless ($email_addresses) {
1312         return '';
1313     }
1314     print "[+] fwknop access alerts will be sent to:\n\n",
1315         "       $email_addresses\n\n";
1316
1317     if ($force_defaults) {
1318         print "    Setting default [$email_addresses].\n";
1319         return $email_addresses;
1320     }
1321
1322     if (&query_yes_no("[+] Would you like access alerts sent to a " .
1323                 "different address ([y]/n)?  ", $ACCEPT_YES_DEFAULT)) {
1324
1325         print "\n",
1326 "[+] To which email address(es) would you like fwknop alerts to be sent?\n",
1327 "    You can enter as many email addresses as you like; each on its own line.\n\n",
1328 "    End with a \".\" on a line by itself.\n\n";
1329         my $ans = '';
1330         while ($ans !~ /^\s*\.\s*$/) {
1331             print "    Email Address: ";
1332             $ans = <STDIN>;
1333             chomp $ans;
1334             if ($ans =~ m|^\s*(\S+\@\S+)$|) {
1335                 $email_str .= "$1, ";
1336             } elsif ($ans !~ /^\s*\.\s*$/) {
1337                 print "[-] Invalid email address \"$ans\"\n";
1338             }
1339         }
1340         $email_str =~ s/\,\s*$//;
1341     }
1342     return $email_str;
1343 }
1344
1345 sub ask_to_stop_fwknop() {
1346
1347     if (&is_fwknop_running()) {
1348         print "[+] An existing fwknop process is running.\n";
1349
1350         if (&query_yes_no("    Can I stop the existing fwknop " .
1351                 "process ([y]/n)?  ", $ACCEPT_YES_DEFAULT)) {
1352             return 1;
1353         } else {
1354             die "[*] Aborting install (you can run ./install.pl again).";
1355         }
1356
1357     }
1358     return 0;
1359 }
1360
1361 sub is_fwknop_running() {
1362     for my $pid_file ($config{'FWKNOP_PID_FILE'},
1363             $config{'KNOPTM_PID_FILE'},
1364             $config{'KNOPMD_PID_FILE'},
1365             $config{'TCPSERV_PID_FILE'}) {
1366
1367         next unless -e $pid_file;
1368         open P, "< $pid_file" or die "[*] Could not open $pid_file: $!";
1369         my $pid = <P>;
1370         close P;
1371         return 1 if kill 0, $pid;
1372     }
1373     return 0;
1374 }
1375
1376 sub stop_fwknop() {
1377
1378     my $ctr = 0;
1379     while (&is_fwknop_running()) {
1380
1381         if (-d $init_dir and -e "$init_dir/$init_name") {
1382             system "$init_dir/$init_name stop";
1383         }
1384
1385         if (&is_fwknop_running()) {
1386             system "fwknopd -K";
1387         }
1388
1389         $ctr++;
1390         if ($ctr >= 5) {
1391             print "[-] Could not stop running fwknop processes, ",
1392                 "continuing anyway";
1393             return;
1394         }
1395     }
1396     return;
1397 }
1398
1399 sub get_pcap_intf() {
1400     print
1401 "\n[+] It appears that the following network interfaces are attached to the\n",
1402 "    system:\n";
1403     open IFC, "$cmds{'ifconfig'} -a |" or die "[*] Could not execute ",
1404         "$cmds{'ifconfig'} -a: $!";
1405     my @ifconfig_out = <IFC>;
1406     close IFC;
1407     my %interfaces = ();
1408     my $default_intf = '';
1409
1410     if ($os_type == $OS_BSD or $os_type == $OS_DARWIN) {
1411         for my $line (@ifconfig_out) {
1412             if ($line =~ /^\s*(\w+):\s+flags=/) {
1413                 $interfaces{$1} = '';
1414                 print "        $1\n";
1415                 $default_intf = $1 unless $default_intf;
1416             }
1417         }
1418     } else {
1419         for my $line (@ifconfig_out) {
1420             if ($line =~ /^\s*(\w+)\s+Link/) {
1421                 $interfaces{$1} = '';
1422                 print "        $1\n";
1423                 $default_intf = $1 unless $default_intf;
1424             }
1425         }
1426     }
1427
1428     ### return the default interface
1429     if ($force_defaults) {
1430         if (defined $interfaces{'eth0'}) {
1431             $default_intf = 'eth0';
1432         }
1433         print "    Setting default [$default_intf]\n";
1434         return $default_intf;
1435     }
1436
1437     my $ans = '';
1438     my $loop_ctr = 0;
1439     while (not defined $interfaces{$ans}) {
1440         print
1441 "    Which network interface would you like fwknop to sniff packets from?  ";
1442         $ans = <STDIN>;
1443         chomp $ans;
1444         $loop_ctr++;
1445         if ($loop_ctr >= 10) {
1446             return '';
1447         }
1448     }
1449     return $ans;
1450 }
1451
1452 sub query_data_method() {
1453     print
1454 "[+] fwknop can act as a server (i.e. monitoring authentication packets\n",
1455 "    and sequences, and taking the appropriate action on the local system\n",
1456 "    to alter the firewall policy or execute commands), or as a client (i.e.\n",
1457 "    by manufacturing authentication packets and sequences).\n\n";
1458     my $ans = '';
1459
1460     print
1461 "    In which mode will fwknop be executed on the local system?  (Note that\n",
1462 "    fwknop can still be used as a client even if you select \"server\" here).\n";
1463
1464     if ($force_defaults) {
1465         print "    Setting default [server] mode.\n";
1466         return 'server';
1467     }
1468
1469     while ($ans ne 'client' and $ans ne 'server') {
1470         print "    (client/[server]): ";
1471         $ans = <STDIN>;
1472         if ($ans eq "\n") {
1473             $ans = 'server';
1474         }
1475         $ans =~ s/\s*//g;
1476     }
1477     if ($ans eq 'client') {
1478         $client_install = 1;
1479         return $ans;
1480     }
1481
1482     print
1483 "\n[+] In server mode, fwknop can acquire packet through a pcap file that is\n",
1484 "    generated by a sniffer (or through the Netfilter ulogd pcap writer), or\n",
1485 "    by sniffing packets directly off the wire via the Net::Pcap perl module.\n",
1486 "    Fwknop can also acquire packet data from iptables syslog messages, but\n",
1487 "    this is only supported for the legacy port knocking mode; Single Packet\n",
1488 "    Authorization (SPA), which is used in the pcap modes, is a better\n",
1489 "    authorization strategy from every perspective (see the fwknop man page for\n",
1490 "    more information). If you intend to use iptables log messages (only makes\n",
1491 "    sense for the legacy port knocking mode), then fwknop will need to\n",
1492 "    reconfigure your syslog daemon to write kern.info messages to the\n",
1493 "    $config{'KNOPMD_FIFO'} named pipe. It is highly recommended\n",
1494 "    to use one of the pcap modes unless you really want the old port knocking\n",
1495 "    method.\n\n";
1496
1497     $ans = '';
1498
1499     if ($force_defaults) {
1500         print "    Setting default [pcap] mode.\n";
1501         return 'pcap';
1502     }
1503
1504         print
1505 "    Which of the following data acquistion methods would you like to use?\n";
1506     while ($ans ne 'syslogd' and $ans ne 'syslog-ng' and $ans ne 'ulogd'
1507             and $ans ne 'pcap' and $ans ne 'file_pcap') {
1508
1509         print "    ([pcap], file_pcap, ulogd, syslogd, syslog-ng): ";
1510         $ans = <STDIN>;
1511
1512         return 'pcap' if $ans eq "\n";
1513         $ans =~ s/\s*//g;
1514
1515         if ($ans eq 'syslogd' or $ans eq 'syslog-ng') {
1516             if ($ans eq 'syslogd') {
1517                 ### allow command line --syslog-conf arg to take over
1518                 $syslog_conf = '/etc/syslog.conf' unless $syslog_conf;
1519             } elsif ($ans eq 'syslog-ng') {
1520                 ### allow command line --syslog-conf arg to take over
1521                 $syslog_conf = '/etc/syslog-ng/syslog-ng.conf' unless $syslog_conf;
1522             }
1523             if ($syslog_conf and not -e $syslog_conf) {
1524                 die
1525 "[-] The config file $syslog_conf does not exist. Re-run install.pl\n",
1526 "    with the --syslog-conf argument to specify the path to the syslog\n",
1527 "    daemon config file.\n";
1528             }
1529         }
1530         print "[-] Invalid acquistion method \"$ans\"\n"
1531         unless ($ans and
1532             ($ans eq 'syslogd' or $ans eq 'syslog-ng'
1533             or $ans eq 'file_pcap' or $ans eq 'ulogd' or $ans eq 'pcap'));
1534     }
1535     return $ans;
1536 }
1537
1538 sub put_string() {
1539     my ($var, $value, $file) = @_;
1540     open RF, "< $file" or die "[*] Could not open $file: $!";
1541     my @lines = <RF>;
1542     close RF;
1543     open F, "> $file" or die "[*] Could not open $file: $!";
1544     for my $line (@lines) {
1545         if ($line =~ /^\s*$var\s+.*;/) {
1546             printf F "%-28s%s;\n", $var, $value;
1547         } else {
1548             print F $line;
1549         }
1550     }
1551     close F;
1552     return;
1553 }
1554
1555 sub perms_ownership() {  ### only gets called in full-installation mode
1556     my ($file, $perm_value) = @_;
1557     chmod $perm_value, $file or die "[*] Could not ",
1558         "chmod($perm_value, $file): $!";
1559     ### root (maybe should take the group assignment out?)
1560     chown 0, 0, $file or die "[*] Could not chown 0,0,$file: $!";
1561     return;
1562 }
1563
1564 sub has_perl_module() {
1565     my $module = shift;
1566
1567     # 5.8.0 has a bug with require Foo::Bar alone in an eval, so an
1568     # extra statement is a workaround.
1569     my $file = "$module.pm";
1570     $file =~ s{::}{/}g;
1571     eval { require $file };
1572
1573     return $@ ? 0 : 1;
1574 }
1575
1576 sub update_command_paths() {
1577     my $file = shift;
1578
1579     open F, "< $file" or die "[*] Could not open file: $!";
1580     my @lines = <F>;
1581     close F;
1582
1583     my @newlines = ();
1584     my $new_cmd = 0;
1585     CMD: for my $line (@lines) {
1586         my $found = 0;
1587         if ($line =~ /^\s*(\w+)Cmd(\s+)(\S+);/) {
1588             my $cmd    = $1;
1589             my $spaces = $2;
1590             my $path   = $3;
1591             unless (-e $path and -x $path) {
1592                 ### the command is not at this path, try to find it
1593                 my $cmd_minor_name = $cmd;
1594                 if ($path =~ m|.*/(\S+)|) {
1595                     $cmd_minor_name = $cmd if $cmd ne $1;
1596                 }
1597                 DIR: for my $dir (@cmd_search_paths) {
1598                     if (-e "$dir/$cmd_minor_name"
1599                             and -x "$dir/$cmd_minor_name") {
1600                         ### found the command
1601                         push @newlines,
1602                             "${cmd}Cmd${spaces}${dir}/${cmd_minor_name};\n";
1603                         $found   = 1;
1604                         $new_cmd = 1;
1605                         last DIR;
1606                     }
1607                 }
1608                 unless ($found) {
1609                     next CMD if ($cmd eq 'iptables' and $os_type != $OS_LINUX);
1610                     next CMD if ($cmd eq 'ipfw' and $os_type == $OS_LINUX);
1611                     print
1612 "[-] Could not find the path to the $cmd command, you will need to manually\n",
1613 "    edit the path for the ${cmd}Cmd variable in $file\n";
1614                 }
1615             }
1616         }
1617         unless ($found) {
1618             push @newlines, $line;
1619         }
1620     }
1621     if ($new_cmd) {
1622         open C, "> $file" or die "[*] Could not open file: $!";
1623         print C for @newlines;
1624         close C;
1625     }
1626     return;
1627 }
1628
1629 sub import_config() {
1630     open C, "< $fwknop_conf_file"
1631         or die "[*] Could not open $fwknop_conf_file: $!";
1632     while (<C>) {
1633         next if /^\s*#/;
1634         if (/^\s*(\S+)\s+(.*?)\;/) {
1635             my $varname = $1;
1636             my $val     = $2;
1637             if ($val =~ m|/.+| and $varname =~ /^\s*(\S+)Cmd$/) {
1638                 ### found a command
1639                 $cmds{$1} = $val;
1640             } else {
1641                 $config{$varname} = $val;
1642             }
1643         }
1644     }
1645     close C;
1646
1647     ### resolve internal vars within variable values
1648     &expand_vars();
1649
1650     &required_vars();
1651
1652     return;
1653 }
1654
1655 sub expand_vars() {
1656
1657     my $has_sub_var = 1;
1658     my $resolve_ctr = 0;
1659
1660     while ($has_sub_var) {
1661         $resolve_ctr++;
1662         $has_sub_var = 0;
1663         if ($resolve_ctr >= 20) {
1664             die "[*] Exceeded maximum variable resolution counter.";
1665         }
1666         for my $hr (\%config, \%cmds) {
1667             for my $var (keys %$hr) {
1668                 my $val = $hr->{$var};
1669                 if ($val =~ m|\$(\w+)|) {
1670                     my $sub_var = $1;
1671                     die "[*] sub-ver $sub_var not allowed within same ",
1672                         "variable $var" if $sub_var eq $var;
1673                     if (defined $config{$sub_var}) {
1674                         $val =~ s|\$$sub_var|$config{$sub_var}|;
1675                         $hr->{$var} = $val;
1676                     } else {
1677                         die "[*] sub-var \"$sub_var\" not defined in ",
1678                             "config for var: $var."
1679                     }
1680                     $has_sub_var = 1;
1681                 }
1682             }
1683         }
1684     }
1685     return;
1686 }
1687
1688 sub handle_cmd_line() {
1689     if ($bsd_install and $cygwin_install) {
1690         die "[*] Cannot use --BSD-install and --Cygwin-install at the same time.";
1691     }
1692
1693     if ($bsd_install) {
1694
1695         $os_type = $OS_BSD;
1696
1697     } elsif ($cygwin_install) {
1698
1699         $os_type = $OS_CYGWIN;
1700
1701     } elsif ($cmdline_os_type) {
1702
1703         my $found = 0;
1704         for my $type (keys %os_types) {
1705             if ($cmdline_os_type =~ /$type/i) {
1706                 $os_type = $os_types{$type};
1707             }
1708         }
1709         unless ($found) {
1710             print "[*] --OS-type accepts the following operating systems:\n";
1711             for my $type (keys %os_types) {
1712                 print "    $type\n";
1713             }
1714             exit 1;
1715         }
1716     }
1717
1718     return;
1719 }
1720
1721 sub get_runlevel() {
1722     die "[*] The runlevel cannot be greater than 6"
1723         if $runlevel > 6;
1724
1725     return if $runlevel > 0;
1726
1727     open RUN, "$cmds{'runlevel'} |" or die "[*] Could not execute the runlevel ",
1728         "command, use --runlevel <N>";
1729     while (<RUN>) {
1730         if (/^\s*\S+\s+(\d+)/) {
1731             $runlevel = $1;
1732             last;
1733         }
1734     }
1735     close RUN;
1736     return;
1737 }
1738
1739 sub required_vars() {
1740     my @vars = qw(
1741         FWKNOP_DIR FWKNOP_RUN_DIR FWKNOP_LIB_DIR KNOPMD_FIFO FWKNOP_PID_FILE
1742         FWKNOP_CONF_DIR FW_DATA_FILE FWKNOP_MOD_DIR
1743     );
1744     for my $var (@vars) {
1745         die "[*] Missing required var: $var in $fwknop_conf_file"
1746             unless defined $config{$var};
1747     }
1748     return;
1749 }
1750
1751 sub query_yes_no() {
1752     my ($msg, $style) = @_;
1753     my $ans = '';
1754
1755     if ($force_defaults) {
1756         if ($style == $ACCEPT_YES_DEFAULT or $style == $NO_ANS_DEFAULT) {
1757             print "    Setting default [y]\n";
1758             return 1;
1759         } elsif ($ACCEPT_NO_DEFAULT) {
1760             print "    Setting default [n]\n";
1761             return 0;
1762         }
1763     }
1764
1765     while ($ans ne 'y' and $ans ne 'n') {
1766         print $msg;
1767         $ans = lc(<STDIN>);
1768         if ($style == $ACCEPT_YES_DEFAULT) {
1769             return 1 if $ans eq "\n";
1770         } elsif ($style == $ACCEPT_NO_DEFAULT) {
1771             return 0 if $ans eq "\n";
1772         }
1773         chomp $ans;
1774     }
1775     return 1 if $ans eq 'y';
1776     return 0;
1777 }
1778
1779 sub usage() {
1780     my $exit_status = shift;
1781     print <<_HELP_;
1782
1783 Usage: install.pl [options]
1784
1785     -c, --client-only         - Only install fwknop client.
1786     -u, --uninstall           - Uninstall fwknop.
1787     -O, --OS-type <OS>        - Specify installation target OS (e.g.
1788                                 "linux", "cygwin", or "darwin").
1789     --Single-mod-install <re> - Install a particular perl module.
1790     --force-mod-install       - Force all perl modules to be installed
1791                                 even if some already exist in the system
1792                                 /usr/lib/perl5 tree.
1793     --Force-mod-regex <re>    - Specify a regex to match a module name
1794                                 and force the installation of such modules.
1795     -D, --Defaults            - Force all default values without asking.
1796     -p, --path-update         - Run path update code regardless of whether
1797                                 a previous config is being merged.
1798     --interface <intf>        - Manually specify an interface to sniff
1799                                 from.
1800     --init-dir <path>         - Specify path to the init directory (the
1801                                 default is $init_dir).
1802     --init-name <name>        - Specify the name for the fwknop init
1803                                 script (the default is $init_name).
1804     -r, --runlevel <N>        - Specify the current system runlevel.
1805     --Skip-mod-install        - Do not install any perl modules.
1806     -s, --syslog <file>       - Specify path to syslog.conf file.
1807     -L, --LANG <locale>       - Specify LANG env variable (actually the
1808                                 LC_ALL variable).
1809     -n, --no-LANG             - Do not export the LANG env variable.
1810     -H, --Home-dir <dir>      - Manually specify the home directory for
1811                                 client-mode installs.
1812     -h  --help                - Prints this help message.
1813
1814 _HELP_
1815     exit $exit_status;
1816 }