OSDN Git Service

Updated l7directord man page.
[ultramonkey-l7/ultramonkey-l7-v2.git] / l7directord / l7directord
1 #!/usr/bin/perl
2 ######################################################################
3 # l7directord
4 # Linux Director Daemon - run "perldoc l7directord" for details
5 #
6 # 2005-2008 (C) NTT COMWARE
7 #
8 # License:   GNU General Public License (GPL)
9 #
10 # This program is developed on similar lines of ldirectord. It handles
11 # l7vsadm and monitoring of real servers.
12 #
13 # The version of ldirectord used as a reference for this l7directord is
14 # ldirectord,v 1.77.2.32 2005/09/21 04:00:41
15 #
16 # Note : * The existing code of ldirectord that is not required for
17 #          l7directord is also maintained in the program but is
18 #          commented out.
19 #
20 # This program is free software; you can redistribute it and/or
21 # modify it under the terms of the GNU General Public License as
22 # published by the Free Software Foundation; either version 2 of the
23 # License, or (at your option) any later version.
24
25 # This program is distributed in the hope that it will be useful, but
26 # WITHOUT ANY WARRANTY; without even the implied warranty of
27 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
28 # General Public License for more details.
29
30 # You should have received a copy of the GNU General Public License
31 # along with this program; if not, write to the Free Software
32 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
33 # 02110-1301 USA
34 ######################################################################
35
36 # Revision History :
37 #   0.5.0-0: Added code related to Sorry server and Max connection
38 #            - 2006/11/03 NTT COMWARE
39 #   1.0.0-0: Added code related to weight of real server and QoS
40 #            - 2007/10/12 NTT COMWARE
41 #   1.0.1-0: Added the code below.
42 #            configuration of realdowncallback, realrecovercallback,
43 #            and sessionless module.
44 #            - 2007/12/28 NTT COMWARE
45 #   1.0.2-0: Added the code below.
46 #            cookie insert with X-Forwarded-For module(cinsert_xf)
47 #            - 2008/1/14 Shinya TAKEBAYASHI
48 #   2.0.0-0: Added code related to sslid module.
49 #            cinsert_xf module is marged into cinsert module.
50 #            Added code related to syntax test of configuration.
51 #            Expanded checkcount setting to all service check.
52 #            - 2008/03/25 Norihisa NAKAI
53 #   2.1.0-0: Changed helthcheck logic to multi-process.
54 #            - 2008/12/17 NTT COMWARE
55 #   2.1.1-0: Fix 'Range iterator outside integer range' in parse_real.
56 #            - 2009/01/06 NTT COMWARE
57 #   2.1.2-0: Added code related to some module. See below.
58 #            (cpassive, crewrite, pfilter, url, ip)
59 #            Add custom healthcheck.
60 #            (checktype=custom, customcheck=exec_command)
61 #            - 2009/02/14 NTT COMWARE
62
63 use 5.006;
64 use strict;
65 use warnings;
66 use Getopt::Long qw(:config posix_default);
67 use Sys::Hostname;
68 use POSIX qw(:sys_wait_h :signal_h);
69 use Sys::Syslog qw(:DEFAULT setlogsock);
70 use English;
71 use Fatal qw(open close);
72 use Cwd qw(abs_path);
73 use Data::Dumper;
74 use Time::HiRes qw(sleep);
75 use IO::Handle;
76
77 # current version
78 our $VERSION     = '2.1.2-0';
79 our $COPYRIGHT   = 'Copyright (C) 2009 NTT COMWARE CORPORATION';
80
81 # default global config values
82 our %GLOBAL = (
83     logfile          => '/var/log/l7vs/l7directord.log',
84     autoreload       => 0,
85     checkcount       => 1,
86     checkinterval    => 10,
87     retryinterval    => 10,
88     configinterval   => 5,
89     checktimeout     => 5,
90     negotiatetimeout => 5,
91     supervised       => 0,
92     quiescent        => 1,
93     virtual          => undef,
94     execute          => undef,
95     fallback         => undef,
96     callback         => undef,
97     );
98
99 # default virtual config values
100 our %VIRTUAL = (
101     real                => undef,
102     module              => { name => 'sessionless', key => q{} },
103     scheduler           => 'rr',
104     protocol            => 'tcp',
105     checktype           => 'negotiate',
106     service             => undef,
107     checkport           => undef,
108     maxconn             => 0,
109     qosup               => 0,
110     qosdown             => 0,
111     sorryserver         => undef,
112     request             => undef,
113     receive             => undef,
114     httpmethod          => 'GET',
115     virtualhost         => undef,
116     login               => q{},
117     passwd              => q{},
118     database            => q{},
119     realdowncallback    => undef,
120     realrecovercallback => undef,
121     customcheck         => undef,
122     # can override
123     checkcount          => undef,
124     checkinterval       => undef,
125     retryinterval       => undef,
126     checktimeout        => undef,
127     negotiatetimeout    => undef,
128     quiescent           => undef,
129     fallback            => undef,
130     );
131
132 # default real config values
133 our %REAL = (
134     weight              => 1,
135     forward             => 'masq',
136     # can override
137     request             => undef,
138     receive             => undef,
139     );
140
141 # current config data
142 our %CONFIG = %GLOBAL;
143
144 # config file data
145 our %CONFIG_FILE = (
146     path            => undef,
147     filename        => undef,
148     checksum        => undef,
149     stattime        => undef,
150     );
151
152 # process environment
153 our %PROC_ENV = (
154     l7directord => $0,
155     l7vsadm     => undef,
156     pid_prefix  => '/var/run/l7directord',
157     hostname    => undef,
158     );
159
160 # process status
161 our %PROC_STAT = (
162     pid             => $PID,
163     initialized     => 0,
164     log_opened      => 0,
165     health_checked  => 0,
166     halt            => undef,
167     reload          => undef,
168     );
169
170 # debug level
171 our $DEBUG_LEVEL = 0;
172
173 # health check process data
174 our %HEALTH_CHECK  = ();
175
176 # real server health flag
177 our $SERVICE_UP   = 0;
178 our $SERVICE_DOWN = 1;
179
180 # section virtual sub config prefix
181 our $SECTION_VIRTUAL_PREFIX = "    ";
182
183 main();
184
185 # main
186 # Main method of this program.
187 # parse command line and run each command method.
188 sub main {
189     my $cmd_func = {
190         start         => \&cmd_start,
191         stop          => \&cmd_stop,
192         restart       => \&cmd_restart,
193         'try-restart' => \&cmd_try_restart,
194         reload        => \&cmd_reload,
195         status        => \&cmd_status,
196         configtest    => \&cmd_configtest,
197         version       => \&cmd_version,
198         help          => \&cmd_help,
199         usage         => \&cmd_usage,
200         };
201
202     # change program name for removing `perl' string from `ps' command result.
203     my $ps_name = @ARGV ? $PROGRAM_NAME . " @ARGV"
204                         : $PROGRAM_NAME;
205     $PROGRAM_NAME = $ps_name;
206
207     my $cmd_mode = parse_cmd();
208     if ( !defined $cmd_mode || !exists $cmd_func->{$cmd_mode} ) {
209         $cmd_mode = 'usage';
210     }
211     if ($cmd_mode ne 'help' && $cmd_mode ne 'version' && $cmd_mode ne 'usage') {
212         initial_setting();
213     }
214
215     # execute command.
216     my $cmd_result = &{ $cmd_func->{$cmd_mode} }();
217
218     ld_exit( $cmd_result, _message_only('INF0008') );
219 }
220
221 # parse_cmd
222 # Parse command line (ARGV)
223 sub parse_cmd {
224     # configtest or help command
225     my $cmd_mode = parse_option();
226
227     # other command
228     if (!defined $cmd_mode && @ARGV) {
229         $cmd_mode = pop @ARGV;
230     }
231     return $cmd_mode;
232 }
233
234 # parse_option
235 # Parse option strings by Getopt::Long
236 sub parse_option {
237     my $cmd_mode = undef;
238
239     # default option value
240     my $debug   = undef;
241     my $help    = undef;
242     my $test    = undef;
243     my $version = undef;
244
245     # parse command line options
246     my $result = GetOptions(
247         'd:3'       => \$debug,   # debug mode, arg: debug level (default 3)
248         'h|help'    => \$help,    # show help message
249         't'         => \$test,    # config syntax test
250         'v|version' => \$version, # show version
251         );
252
253     if ($result) {
254         # set debug level
255         if (defined $debug) {
256             $DEBUG_LEVEL = $debug;
257         }
258
259         # set command mode
260         if (defined $help) {
261             $cmd_mode = 'help';
262         }
263         elsif (defined $version) {
264             $cmd_mode = 'version';
265         }
266         elsif (defined $test) {
267             $cmd_mode = 'configtest';
268         }
269     }
270     else {
271         $cmd_mode = 'usage';
272     }
273
274     return $cmd_mode;
275 }
276
277 # initial_setting
278 # Initialize file path settings.
279 sub initial_setting {
280     # search config and l7vsadm
281     $PROC_ENV{l7vsadm} = search_l7vsadm_file();
282     $CONFIG_FILE{path} = search_config_file();
283
284     # get config file name exclude `.cf' or `.conf'
285     ( $CONFIG_FILE{filename} )
286         = $CONFIG_FILE{path} =~ m{([^/]+?)(?:\.cf|\.conf)?$};
287
288     # get hostname
289     $PROC_ENV{hostname}
290         = defined $ENV{HOSTNAME} ? $ENV{HOSTNAME}
291         :                          ( POSIX::uname() )[1]
292         ;
293 }
294
295 # search_config_file
296 # Search l7directord.cf file from search path.
297 sub search_config_file {
298     my $config_file = undef;
299     my @search_path = qw(
300         ./l7directord.cf
301         /etc/ha.d/l7directord.cf
302         /etc/ha.d/conf/l7directord.cf
303         );
304
305     if (@ARGV) {
306         $config_file = $ARGV[0];
307         if (!-f $ARGV[0]) {
308             init_error( _message_only('ERR0404', $config_file) );
309         }
310     }
311     else {
312         for my $file (@search_path) {
313             if (-f $file) {
314                 $config_file = $file;
315                 last;
316             }
317         }
318         if (!defined $config_file) {
319             init_error( _message_only('ERR0405', $config_file) );
320         }
321     }
322
323     return abs_path($config_file);
324 }
325
326 # search_l7vsadm_file
327 # Search l7vsadm file from search path.
328 sub search_l7vsadm_file {
329     my $l7vsadm_file = undef;
330     my @search_path = qw(
331         ./l7vsadm
332         /usr/sbin/l7vsadm
333         /sbin/l7vsadm
334         );
335
336     for my $file (@search_path) {
337         if (-x $file) {
338             $l7vsadm_file = $file;
339             last;
340         }
341     }
342     if (!defined $l7vsadm_file) {
343         init_error( _message_only('ERR0406', $l7vsadm_file) );
344     }
345
346     return abs_path($l7vsadm_file);
347 }
348
349 # cmd_start
350 # Start process
351 # Called if command argument is start
352 # return: 0 if success
353 #         1 if old process id is found.
354 sub cmd_start {
355     set_ld_handler();
356     read_config();
357
358     ld_log( _message('INF0001', $PROGRAM_NAME) );
359
360     ld_setup();
361
362     my $oldpid = read_pid();
363
364     # already other process is running
365     if ($oldpid) {
366         print {*STDERR} _message_only('INF0103', $oldpid) . "\n";
367         return 1;
368     }
369     
370     # supervised or debug mode (not daemon)
371     if ($CONFIG{supervised} || $DEBUG_LEVEL > 0) {
372         ld_log( _message( 'INF0002', $VERSION, $PID, $CONFIG_FILE{path} ) );
373     }
374     # otherwise (daemon)
375     else {
376         ld_daemon();
377         ld_log( _message( 'INF0003', $VERSION, $CONFIG_FILE{path} ) );
378     }
379
380     write_pid( $PROC_STAT{pid} );
381     ld_cmd_children('start');
382     ld_main();
383     ld_cmd_children('stop');
384     remove_pid();
385
386     return 0;
387 }
388
389 # cmd_stop
390 # Send stop signal (TERM)
391 # Called if command argument is stop
392 # return: 0 if success
393 #         2 if old process id is not found.
394 #         3 if signal failed.
395 sub cmd_stop {
396     my ($oldpid, $stalepid) = read_pid();
397
398     # process is not running
399     if (!$oldpid) {
400         if ($stalepid) {
401             my $pid_file = $PROC_ENV{pid_prefix} . q{.} . $CONFIG_FILE{filename} . '.pid';
402             print {*STDERR} _message_only('INF0102', $pid_file, $CONFIG_FILE{path}) . "\n";
403         }
404         print {*STDERR} _message_only('INF0104') . "\n";
405         return 2;
406     }
407
408     # signal TERM
409     my $signaled = kill 15, $oldpid;
410     if ($signaled != 1) {
411         print {*STDERR} _message('WRN0003', $oldpid);
412         return 3;
413     }
414     return 0;
415 }
416
417 # cmd_restart
418 # Restart process
419 # Called if command argument is try-restart
420 # return: see cmd_start return
421 sub cmd_restart {
422     # stop and ignore result
423     cmd_stop();
424
425     # wait for pid file
426     sleep 1;
427
428     # start
429     my $status = cmd_start();
430
431     return $status;
432 }
433
434 # cmd_try_restart
435 # Trying restart process
436 # Called if command argument is try-restart
437 # return: see cmd_start, cmd_stop return
438 sub cmd_try_restart {
439     # stop
440     my $stop_result = cmd_stop();
441
442     # start only if stop succeed
443     if ($stop_result != 0) {
444         return $stop_result;
445     }
446
447     # wait for pid file
448     sleep 1;
449
450     # start
451     my $status = cmd_start();
452
453     return $status;
454 }
455
456 # cmd_reload
457 # Send reload signal (HUP)
458 # Called if command argument is reload
459 # return: 0 if success
460 #         2 if old process id is not found.
461 #         3 if signal failed.
462 sub cmd_reload {
463     read_config();
464     my ($oldpid, $stalepid) = read_pid();
465     if (!$oldpid) {
466         if ($stalepid) {
467             my $pid_file = $PROC_ENV{pid_prefix} . q{.} . $CONFIG_FILE{filename} . '.pid';
468             print {*STDERR} _message_only( 'INF0102', $pid_file, $CONFIG_FILE{path} ) . "\n";
469         }
470         print {*STDERR} _message_only('INF0104') . "\n";
471         return 2;
472     }
473
474     # signal HUP
475     my $signaled = kill 1, $oldpid;
476     if ($signaled != 1) {
477         print {*STDERR} _message('WRN0004', $oldpid);
478         return 3;
479     }
480     return 0;
481 }
482
483 # cmd_status
484 # Show process id of running
485 # Called if command argument is status
486 # return: 0 if success
487 #         2 if old process id is not found.
488 sub cmd_status {
489     my ($oldpid, $stalepid) = read_pid();
490     if (!$oldpid) {
491         if ($stalepid) {
492             my $pid_file = $PROC_ENV{pid_prefix} . q{.} . $CONFIG_FILE{filename} . '.pid';
493             print {*STDERR} _message_only('INF0102', $pid_file, $CONFIG_FILE{path}) . "\n";
494         }
495         print {*STDERR} _message_only('INF0104') . "\n";
496         ld_cmd_children('status');
497
498         return 2;
499     }
500
501     print {*STDERR} _message_only('INF0101', $CONFIG_FILE{path}, $oldpid) . "\n";
502
503     read_config();
504     ld_cmd_children('status');
505
506     return 0;
507 }
508
509 # cmd_version
510 # Configuration syntax check
511 # Called if command argument is configtest
512 # return: 0 if syntax ok
513 #         otherwise, exit by read_config
514 sub cmd_configtest {
515     read_config();
516     print {*STDOUT} "Syntax OK\n";
517     return 0;
518 }
519
520 # cmd_version
521 # Show program version.
522 # Called if command argument is version
523 # return: 0
524 sub cmd_version {
525     print {*STDOUT} "l7directord, version $VERSION\n$COPYRIGHT\n";
526     return 0;
527 }
528
529 # cmd_help
530 # Show command manual.
531 # Called if command argument is help
532 # return: 0
533 sub cmd_help {
534     system_wrapper( '/usr/bin/perldoc ' . $PROC_ENV{l7directord} );
535     return 0;
536 }
537
538 # cmd_usage
539 # Show command usage.
540 # Called if command argument is unknown or not specified.
541 # return: 0
542 sub cmd_usage {
543     print {*STDERR} 
544         "Usage: l7directord {start|stop|restart|try-restart|reload|status|configtest}\n"
545       . "Try `l7directord --help' for more information.\n";
546     return 0;
547 }
548
549 # set_ld_handler
550 # Set signal handler function.
551 sub set_ld_handler {
552     $SIG{ INT  } = \&ld_handler_term;
553     $SIG{ QUIT } = \&ld_handler_term;
554     $SIG{ ILL  } = \&ld_handler_term;
555     $SIG{ ABRT } = \&ld_handler_term;
556     $SIG{ FPE  } = \&ld_handler_term;
557     $SIG{ SEGV } = \&ld_handler_term;
558     $SIG{ TERM } = \&ld_handler_term;
559     $SIG{ BUS  } = \&ld_handler_term;
560     $SIG{ SYS  } = \&ld_handler_term;
561     $SIG{ XCPU } = \&ld_handler_term;
562     $SIG{ XFSZ } = \&ld_handler_term;
563     # HUP is actually used
564     $SIG{ HUP  } = \&ld_handler_hup;
565     # This used to call a signal handler, that logged a message
566     # However, this typically goes to syslog and if syslog
567     # is playing up a loop will occur.
568     $SIG{ PIPE } = 'IGNORE';
569     # handle perl warn signal
570     $SIG{__WARN__} = \&ld_handler_perl_warn;
571 }
572
573 # ld_handler_perl_warn
574 # Handle Perl warnings for logging file.
575 sub ld_handler_perl_warn {
576     my $warning = join q{, }, @_;
577     $warning =~ s/[\r\n]//g;
578     ld_log( _message('WRN0301', $warning) );
579 }
580
581 # read_pid
582 # Read pid file and check if pid (l7directord) is still running
583 sub read_pid {
584     my $old_pid  = undef;
585     my $file_pid = undef;
586     my $pid_file = $PROC_ENV{pid_prefix} . q{.} . $CONFIG_FILE{filename} . '.pid';
587     eval {
588         open my $pid_handle, '<', $pid_file;
589         $file_pid = <$pid_handle>;
590         close $pid_handle;
591         chomp $file_pid;
592
593         # Check to make sure this isn't a stale pid file
594         my $proc_file = "/proc/$file_pid/cmdline";
595         open my $proc_handle, '<', $proc_file;
596         my $line = <$proc_handle>;
597         if ($line =~ /l7directord/) {
598             $old_pid = $file_pid;
599         }
600         close $proc_handle;
601     };
602     
603     return wantarray ? ($old_pid, $file_pid) : $old_pid;
604 }
605
606 # write_pid
607 # Write pid number to pid file.
608 sub write_pid {
609     my $pid = shift;
610
611     my $pid_file = $PROC_ENV{pid_prefix} . q{.} . $CONFIG_FILE{filename} . '.pid';
612     if (!defined $pid || $pid !~ /^\d+$/ || $pid < 1) {
613         $pid = defined $pid ? $pid : 'undef';
614         init_error( _message_only('ERR0412', $pid) );
615     }
616     eval {
617         open my $pid_handle, '>', $pid_file;
618         print {$pid_handle} $pid . "\n";
619         close $pid_handle;
620     };
621     if ($EVAL_ERROR) {
622         init_error( _message_only('ERR0409', $pid_file, $EVAL_ERROR) );
623     }
624 }
625
626 # remove_pid
627 # Remove pid file.
628 sub remove_pid {
629     my $pid_file = $PROC_ENV{pid_prefix} . q{.} . $CONFIG_FILE{filename} . '.pid';
630     ld_rm_file($pid_file);
631 }
632
633 # init_error
634 # Handle error during initialization and exit.
635 sub init_error {
636     my $msg = shift;
637     if (defined $msg) {
638         if ($DEBUG_LEVEL == 0) {
639             print {*STDERR} $msg . "\n";
640         }
641         ld_log( _message('ERR0001', $msg) );
642     }
643     ld_exit( 4, _message_only('INF0004') );
644 }
645
646 # ld_handler_term
647 # If we get a sinal then put a halt flag up
648 sub ld_handler_term {
649     my $signal = shift;
650     $PROC_STAT{halt} = defined $signal ? $signal : 'undef';
651 }
652
653 # ld_handler_hup
654 # If we get a sinal then put a reload flag up
655 sub ld_handler_hup {
656     my $signal = shift;
657     $PROC_STAT{reload} = defined $signal ? $signal : 'undef';
658 }
659
660 # reread_config
661 # Re-read config, and then re-setup l7vsd and child process.
662 sub reread_config {
663     my $old_virtual = defined $CONFIG{virtual} ? [ @{ $CONFIG{virtual} } ]
664                     :                            []
665                     ;
666     my %old_sub_config = defined $CONFIG{execute} ? %{ $CONFIG{execute} }
667                        :                            ()
668                        ;
669
670     %CONFIG = %GLOBAL;
671     $CONFIG{old_virtual} = $old_virtual;
672
673     # analyze config and catch format error
674     eval {
675         read_config();
676         ld_setup();
677         ld_start();
678     };
679     if ($EVAL_ERROR) {
680         my $exception = $EVAL_ERROR;
681         chomp $exception;
682         ld_log( _message('ERR0122', $exception) );
683         $CONFIG{virtual} = [ @{ $CONFIG{old_virtual} } ];
684         $CONFIG{execute} = \%old_sub_config;
685     }
686
687     my %new_sub_config = defined $CONFIG{execute} ? %{ $CONFIG{execute} }
688                        :                            ()
689                        ;
690     for my $sub_config ( keys %old_sub_config ) {
691         if ( exists $new_sub_config{$sub_config} ) {
692             if ( system_wrapper($PROC_ENV{l7directord} . " $sub_config reload") ) {
693                  system_wrapper($PROC_ENV{l7directord} . " $sub_config start");
694             }
695             delete $new_sub_config{$sub_config};
696             delete $old_sub_config{$sub_config};
697         }
698     }
699     ld_cmd_children('stop',  \%old_sub_config);
700     ld_cmd_children('start', \%new_sub_config);
701 }
702
703 # read_config
704 # Read configuration and parse settings.
705 sub read_config {
706     my $line = 0;
707     my $current_global_name = q{};
708     my $config_handle;
709
710     eval {
711         open $config_handle, '<', $CONFIG_FILE{path};
712     };
713     if ($EVAL_ERROR) {
714         config_error( 0, 'ERR0407', $CONFIG_FILE{path} );
715     }
716
717     while (my $config_line = <$config_handle>) {
718         $line++;
719         chomp $config_line;
720         $config_line =~ s/#.*//mg; # remove comment (FIXME optimize regex for "foo='#'")
721         $config_line =~ s/^\t/$SECTION_VIRTUAL_PREFIX/mg; # convert tab to prefix
722
723         next if ($config_line =~ /^(?:$SECTION_VIRTUAL_PREFIX)?\s*$/);
724
725         # section global
726         if ($config_line !~ /^$SECTION_VIRTUAL_PREFIX/) {
727             my ($name, $value) = validate_config($line, $config_line);
728             $current_global_name = $name;
729             if ($name eq 'virtual') {
730                 my %virtual = %VIRTUAL;
731                 $virtual{server} = $value;
732                 push @{ $CONFIG{virtual} }, \%virtual;
733                 _ld_service_resolve(\%virtual, $value->{port});
734             }
735             elsif ($name eq 'execute') {
736                 $CONFIG{execute}{$value} = 1;
737             }
738             else {
739                 $CONFIG{$name} = $value;
740             }
741         }
742         # section virtual
743         else {
744             if ($current_global_name ne 'virtual') {
745                 config_error($line, 'ERR0119', $config_line);
746             }
747             my ($name, $value) = validate_config($line, $config_line);
748             if ($name eq 'real' && defined $value) {
749                 push @{ $CONFIG{virtual}[-1]{real} }, @$value;
750             }
751             elsif (defined $value) {
752                 $CONFIG{virtual}[-1]{$name} = $value;
753             }
754         }
755     }
756
757     eval {
758         close $config_handle;
759     };
760     if ($EVAL_ERROR) {
761         config_error( 0, 'ERR0408', $CONFIG_FILE{path} );
762     }
763
764     ld_openlog( $CONFIG{logfile} ) if !$PROC_STAT{log_opened};
765     check_require_module();
766     undef $CONFIG_FILE{checksum};
767     undef $CONFIG_FILE{stattime};
768     check_cfgfile();
769
770     $PROC_STAT{initialized} = 1;
771 }
772
773 # validate_config
774 # Validation check of configuration.
775 sub validate_config {
776     my ($line, $config) = @_;
777     my ($name, $value) = split /\s*=\s*/, $config, 2;
778     if (defined $value) {
779         $value =~ s/\s*$//;
780         $value =~ s/^("|')(.*)\1$/$2/;
781     }
782
783     # section global validate
784     if ($name !~ /^$SECTION_VIRTUAL_PREFIX/) {
785         if (!exists $GLOBAL{$name}) {
786             config_error($line, 'ERR0120', $config);
787         }
788         if ($name eq 'virtual') {
789             $value = ld_gethostservbyname($value, 'tcp');
790             if (!defined $value) {
791                 config_error($line, 'ERR0114', $config);
792             }
793         }
794         elsif (    $name eq 'checktimeout'
795                 || $name eq 'negotiatetimeout'
796                 || $name eq 'checkinterval'
797                 || $name eq 'retryinterval'
798                 || $name eq 'configinterval'
799                 || $name eq 'checkcount'      ) {
800             if (!defined $value || $value !~ /^\d+$/ || $value == 0 ) {
801                 config_error($line, 'ERR0101', $config);
802             }
803         }
804         elsif (    $name eq 'autoreload'
805                 || $name eq 'quiescent'  ) {
806             $value = defined $value && $value =~ /^yes$/i ? 1
807                    : defined $value && $value =~ /^no$/i  ? 0
808                    :                                     undef
809                    ;
810             if (!defined $value) {
811                 config_error($line, 'ERR0102', $config);
812             }
813         }
814         elsif ($name eq 'fallback') {
815             my $fallback = parse_fallback($line, $value, $config);
816             $value = {tcp => $fallback};
817         }
818         elsif ($name eq 'callback') {
819             if (!defined $value || !-f $value || !-x $value) {
820                 config_error($line, 'ERR0117', $config);
821             }
822         }
823         elsif ($name eq 'execute') {
824             if (!defined $value || !-f $value) {
825                 config_error($line, 'ERR0116', $config);
826             }
827         }
828         elsif ($name eq 'logfile') {
829             if (!defined $value || ld_openlog($value) ) {
830                 config_error($line, 'ERR0118', $config);
831             }
832         }
833         elsif ($name eq 'supervised') {
834             $value = 1;
835         }
836     }
837     # section virtual validate
838     else {
839         $name =~ s/^$SECTION_VIRTUAL_PREFIX\s*//g;
840         if (!exists $VIRTUAL{$name}) {
841             config_error($line, 'ERR0120', $config);
842         }
843         if ($name eq 'real') {
844             $value = parse_real($line, $value, $config);
845         }
846         elsif (    $name eq 'request'
847                 || $name eq 'receive'
848                 || $name eq 'login'
849                 || $name eq 'passwd'
850                 || $name eq 'database'
851                 || $name eq 'customcheck'
852                 || $name eq 'virtualhost' ) {
853             if (!defined $value || $value !~ /^.+$/) {
854                 config_error($line, 'ERR0103', $config);
855             }
856         }
857         elsif ($name eq 'checktype') {
858             my $valid_type = qr{custom|connect|negotiate|ping|off|on|\d+};
859             $value = lc $value;
860             if (!defined $value || $value !~ /^(?:$valid_type)$/) {
861                 config_error($line, 'ERR0104', $config);
862             }
863             if ($value =~ /^\d+$/ && $value == 0) {
864                 config_error($line, 'ERR0104', $config);
865             }
866         }
867         elsif (    $name eq 'checktimeout'
868                 || $name eq 'negotiatetimeout'
869                 || $name eq 'checkinterval'
870                 || $name eq 'retryinterval'
871                 || $name eq 'checkcount'
872                 || $name eq 'maxconn'         ) {
873             if (!defined $value || $value !~ /^\d+$/ || ($name ne 'maxconn' && $value == 0) ) {
874                 config_error($line, 'ERR0101', $config);
875             }
876         }
877         elsif ($name eq 'checkport') {
878             if (!defined $value || $value !~ /^\d+$/ || $value == 0 || $value > 65535) {
879                 config_error($line, 'ERR0108', $config);
880             }
881         }
882         elsif ($name eq 'scheduler') {
883             my $valid_scheduler = qr{lc|rr|wrr};
884             $value = lc $value;
885             if (!defined $value || $value !~ /^(?:$valid_scheduler)$/) {
886                 config_error($line, 'ERR0105', $config);
887             }
888         }
889         elsif ($name eq 'protocol') {
890             $value = lc $value;
891             if (!defined $value || $value !~ /^tcp$/) {
892                 config_error($line, 'ERR0109', $config);
893             }
894         }
895         elsif ($name eq 'service') {
896             $value = lc $value;
897             my $valid_service = qr{http|https|ldap|ftp|smtp|pop|imap|nntp|dns|mysql|pgsql|sip|none};
898             if (!defined $value || $value !~ /^(?:$valid_service)$/) {
899                 config_error($line, 'ERR0106', $config);
900             }
901         }
902         elsif ($name eq 'httpmethod') {
903             my $valid_method = qr{GET|HEAD};
904             $value = uc $value;
905             if (!defined $value || $value !~ /^(?:$valid_method)$/) {
906                 config_error($line, 'ERR0110', $config);
907             }
908         }
909         elsif ($name eq 'fallback') {
910             my $fallback = parse_fallback($line, $value, $config);
911             $value = {tcp => $fallback};
912         }
913         elsif ($name eq 'quiescent') {
914             $value = defined $value && $value =~ /^yes$/i ? 1
915                    : defined $value && $value =~ /^no$/i  ? 0
916                    :                                     undef
917                    ;
918             if (!defined $value) {
919                 config_error($line, 'ERR0102', $config);
920             }
921         }
922         elsif ($name eq 'module') {
923             my %key_option = ( url         => ['--pattern-match', '--uri-pattern-match', '--host-pattern-match'],
924                                pfilter     => ['--pattern-match'],
925                                sessionless => [],
926                                ip          => [],
927                                sslid       => [],
928                              );
929             my $module = undef;
930             my $option = undef;
931             my $key    = q{};
932             if (defined $value) {
933                 $value =~ s/["']//g;
934                 ($module, $option) = split /\s+/, $value, 2;
935             }
936             $module = lc $module;
937             if ( !defined $module || !exists $key_option{$module} ) {
938                 config_error($line, 'ERR0111', $config);
939             }
940             for my $key_opt ( @{$key_option{$module}} ) {
941                 if (defined $option && $option =~ /$key_opt\s+(\S+)/) {
942                     $key .= q{ } if $key;
943                     $key .= $key_opt . q{ } . $1;
944                 }
945             }
946             if ( !$key && @{$key_option{$module}} ) {
947                 # when omit cookie module key option
948                 my $key_opt = join q{' or `}, @{$key_option{$module}};
949                 config_error($line, 'ERR0112', $module, $key_opt, $config);
950             }
951             $value = {name => $module, option => $option, key => $key};
952         }
953         elsif ($name eq 'sorryserver') {
954             my $sorry_server = ld_gethostservbyname($value, 'tcp');
955             if (!defined $sorry_server) {
956                 config_error($line, 'ERR0114', $config);
957             }
958             $value = $sorry_server;
959         }
960         elsif (    $name eq 'qosup'
961                 || $name eq 'qosdown' ) {
962             $value = uc $value;
963             if ( !defined $value || ($value ne '0' && $value !~ /^[1-9]\d{0,2}[KMG]$/) ) {
964                 config_error($line, 'ERR0113', $config);
965             }
966         }
967         elsif (    $name eq 'realdowncallback'
968                 || $name eq 'realrecovercallback' ) {
969             if (!defined $value || !-f $value || !-x $value) {
970                 config_error($line, 'ERR0117', $config);
971             }
972         }
973     }
974
975     return ($name, $value);
976 }
977
978 # check_require_module
979 # Check service setting and require module.
980 sub check_require_module {
981     my %require_module = (
982         http    => [ qw( LWP::UserAgent LWP::Debug ) ],
983         https   => [ qw( LWP::UserAgent LWP::Debug Crypt::SSLeay ) ],
984         ftp     => [ qw( Net::FTP ) ],
985         smtp    => [ qw( Net::SMTP ) ],
986         pop     => [ qw( Net::POP3 ) ],
987         imap    => [ qw( Mail::IMAPClient ) ],
988         ldap    => [ qw( Net::LDAP ) ],
989         nntp    => [ qw( IO::Socket IO::Select ) ],
990         dns     => [ qw( Net::DNS ) ],
991         mysql   => [ qw( DBI DBD::mysql ) ],
992         pgsql   => [ qw( DBI DBD::Pg ) ],
993         sip     => [ qw( IO::Socket::INET ) ],
994         ping    => [ qw( Net::Ping ) ],
995         connect => [ qw( IO::Socket::INET ) ],
996     );
997             
998     for my $v ( @{ $CONFIG{virtual} } ) {
999         next if !defined $v;
1000         next if ( !defined $v->{service} || !defined $v->{checktype} );
1001         my $check_service = q{};
1002         if ( $v->{checktype} eq 'negotiate' && $require_module{ $v->{service} } ) {
1003             $check_service = $v->{service};
1004         }
1005         elsif ($v->{checktype} eq 'ping' || $v->{checktype} eq 'connect') {
1006             $check_service = $v->{checktype};
1007         }
1008         else {
1009             next;
1010         }
1011         for my $module ( @{ $require_module{$check_service} } ) {
1012             my $module_path = $module . '.pm';
1013             $module_path =~ s{::}{/}g;
1014             eval {
1015                 require $module_path;
1016             };
1017             if ($EVAL_ERROR) {
1018                 config_error(0, 'ERR0123', $module, $check_service);
1019             }
1020         }
1021     }
1022 }
1023
1024 # _ld_service_resolve
1025 # Set service name from port number
1026 # pre: vsrv: Virtual Service to resolve port
1027 #      port: port in the form
1028 # post: If $vsrv->{service} is not set, then set it to "http",
1029 #       "https", "ftp", "smtp", "pop", "imap", "ldap", "nntp" or "none"
1030 #       if $vsrv->{port} is 80, 443, 21, 25, 110, 143, 389 or
1031 #       any other value, respectivley
1032 # return: none
1033 sub _ld_service_resolve {
1034     my ($vsrv, $port) = @_;
1035
1036     my %servname;
1037     my @p = qw( 80   443   21  25   110 119  143  389  53  3306  5432  5060 );
1038     my @s = qw( http https ftp smtp pop nntp imap ldap dns mysql pgsql sip  );
1039     @servname{@p} = @s;
1040
1041     if (defined $vsrv && !defined $vsrv->{service} && defined $port) {
1042         $vsrv->{service} = exists $servname{$port} ? $servname{$port}
1043                          :                           'none'
1044                          ;
1045     }
1046 }
1047
1048 # parse_fallback
1049 # Parse a fallback server
1050 # pre: line: line number fallback server was read from
1051 #      fallback: Should be of the form
1052 #                ip_address|hostname[:port|:service_name] masq
1053 #      config_line: line read from configuration file
1054 # post: fallback is parsed
1055 # return: Reference to hash of the form
1056 #         { server => blah, forward => blah }
1057 #         Debugging message will be reported and programme will exit
1058 #         on error.
1059 sub parse_fallback {
1060     my ($line, $fallback, $config_line) = @_;
1061
1062     if (!defined $fallback || $fallback !~ /^(\S+)(?:\s+(\S+))?$/) {
1063         config_error($line, 'ERR0114', $config_line);
1064     }
1065     my ($ip_port, $forward) = ($1, $2);
1066     $ip_port = ld_gethostservbyname($ip_port, 'tcp');
1067     if ( !defined $ip_port ) {
1068         config_error($line, 'ERR0114', $config_line);
1069     }
1070     if (defined $forward && $forward !~ /^masq$/i) {
1071         config_error($line, 'ERR0107', $config_line);
1072     }
1073
1074     my %fallback = %REAL;
1075     $fallback{server} = $ip_port;
1076     if (defined $forward) {
1077         $fallback{forward} = $forward;
1078     }
1079
1080     return \%fallback;
1081 }
1082
1083 # parse_real
1084 # Parse a real server
1085 # pre: line: line number real server was read from
1086 #      real: Should be of the form
1087 #                ip_address|hostname[:port|:service_name] masq
1088 #      config_line: line read from configuration file
1089 # post: real is parsed
1090 # return: Reference to array include real server hash reference
1091 #         [ {server...}, {server...} ... ]
1092 #         Debugging message will be reported and programme will exit
1093 #         on error.
1094 sub parse_real {
1095     my ($line, $real, $config_line) = @_;
1096     
1097     my $ip_host = qr{\d+\.\d+\.\d+\.\d+|[a-z0-9.-]+};
1098     my $port_service = qr{\d+|[a-z0-9-]+};
1099     if (    !defined $real
1100          || $real !~ /^
1101                       ($ip_host)             # ip or host
1102                       (?:->($ip_host))?      # range (optional)
1103                       (?::($port_service))?  # port or service (optional)
1104                       (?:\s+([a-z]+))?       # forwarding mode (optional)
1105                       (?:\s+(\d+))?          # weight (optional)
1106                       (?:\s+
1107                          ([^,\s]+)           # "request
1108                          \s*[ ,]\s*          #  separater
1109                          (\S+)               #  receive"
1110                       )?                     # (optional)
1111                       $/ix) {
1112         config_error($line, 'ERR0114', $config_line);
1113     }
1114     my ($ip1, $ip2, $port, $forward, $weight, $request, $receive)
1115      = (  $1,   $2,    $3,       $4,      $5,       $6,       $7);
1116
1117     # set forward, weight and request-receive pair.
1118     my %real = %REAL;
1119     if (defined $forward) {
1120         $forward = lc $forward;
1121         if ($forward !~ /^masq$/) {
1122             config_error($line, 'ERR0107', $config_line);
1123         }
1124         $real{forward} = $forward;
1125     }
1126     if (defined $weight) {
1127         $real{weight} = $weight;
1128     }
1129     if (defined $request && defined $receive) {
1130         $request =~ s/^\s*("|')(.*)\1\s*/$2/;
1131         $receive =~ s/^\s*("|')(.*)\1\s*/$2/;
1132         $real{request} = $request;
1133         $real{receive} = $receive;
1134     }
1135
1136     my $resolved_port = undef;
1137     if (defined $port) {
1138         $resolved_port = ld_getservbyname($port);
1139         if (!defined $resolved_port) {
1140             config_error($line, 'ERR0108', $config_line);
1141         }
1142     }
1143
1144     my $resolved_ip1 = ld_gethostbyname($ip1);
1145     if (!defined $resolved_ip1) {
1146         config_error($line, 'ERR0114', $config_line);
1147     }
1148
1149     my $resolved_ip2 = $resolved_ip1;
1150     if (defined $ip2) {
1151         $resolved_ip2 = ld_gethostbyname($ip2);
1152         if (!defined $resolved_ip2) {
1153             config_error($line, 'ERR0114', $config_line);
1154         }
1155     }
1156
1157     my $int_ip1 = ip_to_int($resolved_ip1);
1158     my $int_ip2 = ip_to_int($resolved_ip2);
1159     if ($int_ip1 > $int_ip2) {
1160         config_error($line, 'ERR0115', $resolved_ip1, $resolved_ip2, $config_line);
1161     }
1162
1163     my @reals = ();
1164     for (my $int_ip = $int_ip1; $int_ip <= $int_ip2; $int_ip++) {
1165         my %new_real = %real;
1166         $new_real{server}{ip  } = int_to_ip($int_ip);
1167         $new_real{server}{port} = $resolved_port;
1168         push @reals, \%new_real;
1169     }
1170     return \@reals;
1171 }
1172
1173 # config_error
1174 # Handle error during read configuration and validation check
1175 sub config_error {
1176     my ($line, $msg_code, @msg_args) = @_;
1177
1178     if ($DEBUG_LEVEL > 0 || $PROC_STAT{initialized} == 0) {
1179         my $msg = _message_only($msg_code, @msg_args);
1180         if (defined $line && $line > 0) {
1181             print {*STDERR} _message_only('ERR0121', $CONFIG_FILE{path}, $line, $msg) . "\n";
1182         }
1183         else {
1184             print {*STDERR} $msg . "\n";
1185         }
1186     }
1187     else {
1188         if ($line > 0) {
1189             ld_log( _message('ERR0121', $CONFIG_FILE{path}, $line, q{}) );
1190         }
1191         ld_log( _message($msg_code, @msg_args) );
1192     }
1193     if ( $PROC_STAT{initialized} == 0 ) {
1194         ld_exit(5, _message_only('ERR0002') );
1195     }
1196     else {
1197         die "Configuration error.\n";
1198     }
1199 }
1200
1201 # ld_setup
1202 # Check configuration value and set default value, overwrite global config value and so on.
1203 sub ld_setup {
1204     if ( defined $CONFIG{virtual} ) {
1205         for my $v ( @{ $CONFIG{virtual} } ) {
1206             next if !defined $v;
1207             if (defined $v->{protocol} && $v->{protocol} eq 'tcp') {
1208                 $v->{option}{protocol} = "-t";
1209             }
1210     
1211             if ( defined $v->{option} && defined $v->{option}{protocol} && defined $v->{module} && defined $v->{module}{name} ) {
1212                 my $module_option = $v->{module}{name};
1213                 if ( defined $v->{module}{option} ) {
1214                     $module_option .= q{ } . $v->{module}{option};
1215                 }
1216                 $v->{option}{main} = sprintf "%s %s -m %s", $v->{option}{protocol}, get_ip_port($v), $module_option;
1217                 $v->{option}{flags} = $v->{option}{main};
1218                 if ( defined $v->{scheduler} ) {
1219                     $v->{option}{flags} .= ' -s ' . $v->{scheduler};
1220                 }
1221                 if ( defined $v->{maxconn} ) {
1222                     $v->{option}{flags} .= ' -u ' . $v->{maxconn};
1223                 }
1224                 if ( defined $v->{sorryserver} && defined $v->{sorryserver}{ip} && defined $v->{sorryserver}{port} ) {
1225                     $v->{option}{flags} .= ' -b ' . $v->{sorryserver}{ip} . ':' . $v->{sorryserver}{port};
1226                 }
1227                 if ( defined $v->{qosup} ) {
1228                     $v->{option}{flags} .= ' -Q ' . $v->{qosup};
1229                 }
1230                 if ( defined $v->{qosdown} ) {
1231                     $v->{option}{flags} .= ' -q ' . $v->{qosdown};
1232                 }
1233             }
1234     
1235             if ( !defined $v->{fallback} && defined $CONFIG{fallback} ) {
1236                 $v->{fallback} = { %{ $CONFIG{fallback} } };
1237             }
1238             if ( defined $v->{fallback} ) {
1239                 for my $proto ( keys %{ $v->{fallback} } ) {
1240                     $v->{fallback}{$proto}{option}{flags} = '-r ' . get_ip_port( $v->{fallback}{$proto} );
1241                 }
1242             }
1243             if (defined $v->{checktype} && $v->{checktype} =~ /^\d+$/) {
1244                 $v->{num_connects} = $v->{checktype};
1245                 $v->{checktype} = 'combined';
1246             }
1247     
1248             if ( defined $v->{login} && $v->{login} eq q{} ) {
1249                 $v->{login} = defined $v->{service} && $v->{service} eq 'ftp' ? 'anonymous'
1250                             : defined $v->{service} && $v->{service} eq 'sip' ? 'l7directord@' . $PROC_ENV{hostname}
1251                             :                                                   q{}
1252                             ;
1253             }
1254             if ( defined $v->{passwd} && $v->{passwd} eq q{} ) {
1255                 $v->{passwd} = defined $v->{service} && $v->{service} eq 'ftp' ? 'l7directord@' . $PROC_ENV{hostname}
1256                              :                                                   q{}
1257                              ;
1258             }
1259     
1260             if ( defined $v->{real} ) {
1261                 for my $r ( @{ $v->{real} } ) {
1262                     next if !defined $r;
1263                     if ( defined $r->{forward} ) {
1264                         $r->{option}{forward} = get_forward_flag( $r->{forward} );
1265                     }
1266                     if ( !defined $r->{weight} || $r->{weight} !~ /^\d+$/ ) {
1267                         $r->{weight} = 1;
1268                     }
1269         
1270                     if ( !defined $r->{server}{port} ) {
1271                         $r->{server}{port} = $v->{server}{port};
1272                     }
1273
1274                     $r->{option}{flags} = '-r ' . get_ip_port($r);
1275         
1276                     # build request URL
1277                     if ( defined $v->{service} && defined $r->{server} ) {
1278                         my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
1279                         $r->{url} = sprintf "%s://%s:%s/",
1280                                             $v->{service}, $r->{server}{ip}, $port;
1281                     }
1282                     if ( !defined $r->{request} && defined $v->{request} ) {
1283                         $r->{request} = $v->{request};
1284                     }
1285                     if ( !defined $r->{receive} && defined $v->{receive} ) {
1286                         $r->{receive} = $v->{receive};
1287                     }
1288                     if ( defined $r->{request} ) {
1289                         my $uri = $r->{request};
1290                         my $service = $v->{service};
1291                         if ( defined $v->{service} && $uri =~ m{^$service://} ) {
1292                             $r->{url} = $uri;
1293                         }
1294                         else {
1295                             $uri =~ s{^/+}{}g;
1296                             $r->{url} .= $uri;
1297                         }
1298                     }
1299                     
1300                     # set connect count for combine check
1301                     if (defined $v->{checktype} && $v->{checktype} eq 'combined') {
1302                         $r->{num_connects} = undef;
1303                     }
1304         
1305                     $r->{fail_counts} = 0;
1306                     $r->{healthchecked} = 0;
1307                 }
1308             }
1309             if ( !defined $v->{checkcount} || $v->{checkcount} <= 0 ) {
1310                 $v->{checkcount} = $CONFIG{checkcount};
1311             }
1312             if ( !defined $v->{checktimeout} || $v->{checktimeout} <= 0 ) {
1313                 $v->{checktimeout} = $CONFIG{checktimeout};
1314             }
1315             if ( !defined $v->{negotiatetimeout} || $v->{negotiatetimeout} <= 0 ) {
1316                 $v->{negotiatetimeout} = $CONFIG{negotiatetimeout};
1317             }
1318             if ( !defined $v->{checkinterval} || $v->{checkinterval} <= 0 ) {
1319                 $v->{checkinterval} = $CONFIG{checkinterval};
1320             }
1321             if ( !defined $v->{retryinterval} || $v->{retryinterval} <= 0 ) {
1322                 $v->{retryinterval} = $CONFIG{retryinterval};
1323             }
1324             if ( !defined $v->{quiescent} ) {
1325                 $v->{quiescent} = $CONFIG{quiescent};
1326             }
1327         }
1328     }
1329
1330     if (defined $CONFIG{fallback}) {
1331         $CONFIG{fallback}{tcp}{option}{flags} = '-r ' . get_ip_port( $CONFIG{fallback}{tcp} );
1332     }
1333 }
1334
1335 # Removed persistent and netmask related hash entries from the structure of l7vsadm since it is not used - NTT COMWARE
1336 # ld_read_l7vsadm
1337 # Parses the output of "l7vsadm -K -n" and puts into a structure of
1338 # the following from:
1339 #
1340 # {
1341 #   (vip_address:vport) protocol module_name module_key_value => {
1342 #     "scheduler" => scheduler,
1343 #     "real" => {
1344 #       rip_address:rport => {
1345 #         "forward" => forwarding_mechanism,
1346 #         "weight"  => weight
1347 #       },
1348 #       ...
1349 #     }
1350 #   },
1351 #   ...
1352 # }
1353 #
1354 # where:
1355 #   vip_address: IP address of virtual service
1356 #   vport: Port of virtual service
1357 #   module_name: Depicts the name of the module (For example, pfilter)
1358 #   module_key_value: Depicts the module key values (For example, --path-match xxxx)
1359 #   scheduler: Scheduler for virtual service
1360 #
1361 #   rip_address: IP address of real server
1362 #   rport: Port of real server
1363 #   forwarding_mechanism: Forwarding mechanism for real server. This would be only masq.
1364 #   weight: Weight of real server
1365 #
1366 # pre: none
1367 # post: l7vsadm -K -n is parsed
1368 # result: reference to structure detailed above.
1369 sub ld_read_l7vsadm {
1370     my $current_service = {};
1371     my $vip_id;
1372
1373     if ( !-f $PROC_ENV{l7vsadm} || !-x $PROC_ENV{l7vsadm} ) {
1374         ld_log( _message( 'FTL0101', $PROC_ENV{l7vsadm} ) );
1375         return $current_service;
1376     }
1377     # read status of current l7vsadm -K -n
1378     # -K indicates Key parameters of the module included.
1379     my $list_command = $PROC_ENV{l7vsadm} . " -K -n";
1380     my $cmd_result = qx{$list_command};
1381     my @list_line = split /\n/, $cmd_result;
1382
1383     # skip below header
1384     # [cf] Layer-7 Virtual Server version 2.0.0-0
1385     # [cf] Prot LocalAddress:Port ProtoMod Scheduler Reschedule Protomod_key_string
1386     # [cf]   -> RemoteAddress:Port           Forward Weight ActiveConn InactConn
1387     shift @list_line; shift @list_line; shift @list_line;
1388
1389     for my $line (@list_line) {
1390         # check virtual service line format
1391         # [cf] TCP 192.168.0.4:12121 cinsert rr 0 --cookie-name CookieName
1392         if ($line =~ /
1393                 ^           # top
1394                 (\w+) \s+   # 'TCP'
1395                 (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}) \s+ # ip port
1396                 (\w+) \s+   # protocol module
1397                 \w+ \s+     # scheduler
1398                 (?:0|1) \s+ # reschedule flag
1399                 (.*)        # module key
1400                 $           # end
1401                 /x
1402             ) {
1403             my ($proto, $ip_port, $module, $key) = ($1, $2, $3, $4);
1404             # vip_id MUST be same format as get_virtual_id_str
1405             $proto = lc $proto;
1406             $vip_id = "$proto:$ip_port:$module $key";
1407             $vip_id =~ s/\s+$//;
1408             $current_service->{$vip_id} = undef;
1409             next;
1410         }
1411         # check real server line format
1412         # [cf] -> 192.168.0.4:7780             Masq    1     10     123456      
1413         if (defined $vip_id && $line =~ /
1414                 ^           # top
1415                 \s+ -> \s+  # arrow
1416                 (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d{1,5}) \s+ # ip port
1417                 (\w+) \s+   # 'Masq'
1418                 (\d+) \s+   # weight
1419                 \d+ \s+     # active connections
1420                 \d+ \s*     # inactive connections
1421                 $           # end
1422                 /x
1423             ) {
1424             my ($ip, $port, $forward, $weight) = ($1, $2, $3, $4);
1425             my $ip_port = "$ip:$port";
1426             my $real = {
1427                     server  => { ip => $ip, port => $port },
1428                     weight  => $weight,
1429                     forward => $forward,
1430                     option  => {
1431                                 flags   => "-r $ip_port",
1432                                 forward => get_forward_flag($forward),
1433                                 },
1434             };
1435             $current_service->{$vip_id}{$ip_port} = $real;
1436         }
1437     }
1438
1439     return $current_service;
1440 }
1441
1442 # ld_operate_virtual
1443 # Operate virtual service on l7vsd by l7vsadm command.
1444 sub ld_operate_virtual {
1445     my ($v, $option, $success_code, $error_code) = @_;
1446     if (!defined $v || !defined $option || !defined $success_code || !defined $error_code) {
1447         ld_log( _message('ERR0501') );
1448         return;
1449     }
1450
1451     my $command = $PROC_ENV{l7vsadm} . " $option ";
1452     if ($option ne '-D') {
1453         $command .= $v->{option}{flags};
1454     }
1455     else {
1456         $command .= $v->{option}{main};
1457     }
1458     $command .= ' 2>&1';
1459
1460     my ($result, $output) = command_wrapper($command);
1461
1462     my $module_key = $v->{module}{name};
1463     if ( defined $v->{module}{key} ) {
1464         $module_key .= q{ } . $v->{module}{key};
1465     }
1466     if ($result == 0) {
1467         ld_log( _message($success_code, get_ip_port($v), $module_key) );
1468     }
1469     else {
1470         ($output) = split /\n/, $output, 2;
1471         ld_log( _message($error_code, get_ip_port($v), $module_key, $output) );
1472     }
1473 }
1474
1475 # ld_add_virtual
1476 # Call operate virtual with add option.
1477 sub ld_add_virtual {
1478     my $v = shift;
1479     ld_operate_virtual($v, '-A', 'INF0201', 'ERR0201');
1480 }
1481
1482 # ld_edit_virtual
1483 # Call operate virtual with edit option.
1484 sub ld_edit_virtual {
1485     my $v = shift;
1486     ld_operate_virtual($v, '-E', 'INF0202', 'ERR0202');
1487 }
1488
1489 # ld_delete_virtual
1490 # Call operate virtual with delete option.
1491 sub ld_delete_virtual {
1492     my $v = shift;
1493     ld_operate_virtual($v, '-D', 'INF0203', 'ERR0203');
1494 }
1495
1496 # ld_operate_real
1497 # Operate real server on l7vsd by l7vsadm command.
1498 sub ld_operate_real {
1499     my ($v, $r, $weight, $option, $success_code, $error_code) = @_;
1500     if (!defined $v || !defined $r || !defined $option || !defined $success_code || !defined $error_code) {
1501         ld_log( _message('ERR0501') );
1502         return;
1503     }
1504
1505     my $command
1506         = $PROC_ENV{l7vsadm} . " $option " . $v->{option}{main} . q{ } . $r->{option}{flags};
1507
1508     # replace weight value
1509     if (defined $weight) {
1510         $command .= ' -w ' . $weight;
1511     }
1512     $command .= ' 2>&1';
1513
1514     my ($result, $output) = command_wrapper($command);
1515
1516     my $module_key = $v->{module}{name};
1517     if ( defined $v->{module}{key} ) {
1518         $module_key .= q{ } . $v->{module}{key};
1519     }
1520     if ($result == 0) {
1521         ld_log( _message($success_code, get_ip_port($r), get_ip_port($v), $module_key, $weight) );
1522     }
1523     else {
1524         ($output) = split /\n/, $output, 2;
1525         ld_log( _message($error_code, get_ip_port($r), get_ip_port($v), $module_key, $output) );
1526     }
1527 }
1528
1529 # ld_add_real
1530 # Call operate real with add option.
1531 sub ld_add_real {
1532     my ($v, $r, $weight) = @_;
1533     ld_operate_real($v, $r, $weight, '-a', 'INF0204', 'ERR0204');
1534 }
1535
1536 # ld_edit_real
1537 # Call operate real with edit option.
1538 sub ld_edit_real {
1539     my ($v, $r, $weight) = @_;
1540     ld_operate_real($v, $r, $weight, '-e', 'INF0205', 'ERR0205');
1541 }
1542
1543 # ld_delete_real
1544 # Call operate real with delete option.
1545 sub ld_delete_real {
1546     my ($v, $r) = @_;
1547     ld_operate_real($v, $r, undef, '-d', 'INF0206', 'ERR0206');
1548 }
1549
1550 # ld_start
1551 # Check l7vsd by l7vsadm command and create virtual service on l7vsd.
1552 sub ld_start {
1553     # read status of current l7vsadm -K -n
1554     my $current_service = ld_read_l7vsadm();
1555     if (!defined $current_service) {
1556         ld_log( _message('FTL0201') );
1557         return;
1558     }
1559
1560     my %old_health_check = %HEALTH_CHECK;
1561     %HEALTH_CHECK = ();
1562
1563     # make sure virtual servers are up to date
1564     if ( defined $CONFIG{virtual} ) {
1565         for my $nv ( @{ $CONFIG{virtual} } ) {
1566             my $vip_id = get_virtual_id_str($nv);
1567             if (!defined $vip_id) {
1568                 ld_log( _message('ERR0502') );
1569                 return;
1570             }
1571     
1572             if ( exists( $current_service->{$vip_id} ) ) {
1573                 # service already exists, modify it
1574                 ld_edit_virtual($nv);
1575             }
1576             else {
1577                 # no such service, create a new one
1578                 ld_add_virtual($nv);
1579             }
1580     
1581             my $or = $current_service->{$vip_id} || {};
1582     
1583             # Not delete fallback server from l7vsd if exist
1584             my $fallback = fallback_find($nv);
1585             if (defined $fallback) {
1586                 my $fallback_ip_port = get_ip_port( $fallback->{ $nv->{protocol} } );
1587                 delete $or->{$fallback_ip_port};
1588                 fallback_on($nv);
1589             }
1590     
1591             if ( defined $nv->{real} ) {
1592                 CHECK_REAL:
1593                 for my $nr ( @{ $nv->{real} } ) {
1594                     delete $or->{ get_ip_port($nr) };
1595         
1596                     my $health_check_id = get_health_check_id_str($nv, $nr);
1597                     if (!defined $health_check_id) {
1598                         ld_log( _message('ERR0503') );
1599                         return;
1600                     }
1601         
1602                     # search same health check process
1603                     if ( exists $HEALTH_CHECK{$health_check_id} ) {
1604                         # same health check process exist
1605                         # then check real server and virtual service ($r, $v)
1606                         for my $v_r_pair ( @{ $HEALTH_CHECK{$health_check_id}{manage} } ) {
1607                             # completely same. check next real server
1608                             next CHECK_REAL if ($nv eq $v_r_pair->[0] && $nr eq $v_r_pair->[1]);
1609                         }
1610         
1611                         # add real server and virtual service to management list
1612                         push @{ $HEALTH_CHECK{$health_check_id}{manage} }, [$nv, $nr];
1613                     }
1614                     else {
1615                         # add to health check process list
1616                         $HEALTH_CHECK{$health_check_id}{manage} = [ [$nv, $nr] ];
1617                     }
1618                 }
1619             }
1620     
1621             # remove remaining entries for real servers
1622             for my $remove_real_ip_port (keys %$or) {
1623                 ld_delete_real( $nv, $or->{$remove_real_ip_port} );
1624                 delete $or->{$remove_real_ip_port};
1625             }
1626     
1627             delete $current_service->{$vip_id};
1628         }
1629     }
1630
1631     # terminate old health check process
1632     # TODO should compare old and new, and only if different then re-create process...
1633     for my $id (keys %old_health_check) { 
1634         # kill old health check process
1635         if ( defined $old_health_check{$id}{pid} ) {
1636             # TODO cannot kill process during pinging to unreachable host?
1637             {
1638                 local $SIG{ALRM} = sub { die; };
1639                 kill 15, $old_health_check{$id}{pid};
1640                 eval {
1641                     alarm 3;
1642                     waitpid $old_health_check{$id}{pid}, 0;
1643                     alarm 0;
1644                 };
1645                 alarm 0;
1646                 if ($EVAL_ERROR) {
1647                     kill 9, $old_health_check{$id}{pid};
1648                     waitpid $old_health_check{$id}{pid}, WNOHANG;
1649                 }
1650             }
1651         }
1652     }
1653
1654     # remove remaining entries for virtual servers
1655     if ( defined $CONFIG{old_virtual} ) {
1656         for my $nv ( @{ $CONFIG{old_virtual} } ) {
1657             my $vip_id = get_virtual_id_str($nv);
1658             if ( exists $current_service->{$vip_id} ) {
1659                 # service still exists, remove it
1660                 ld_delete_virtual($nv);
1661             }
1662         }
1663     }
1664     delete $CONFIG{old_virtual};
1665 }
1666
1667 # ld_cmd_children
1668 # Run l7directord command to child process.
1669 # Child process is not health check process,
1670 # but sub config (specified by configuration with `execute') process.
1671 sub ld_cmd_children {
1672     my $command_type = shift;
1673     my $execute = shift;
1674
1675     # instantiate other l7directord, if specified
1676     if (!defined $execute) {
1677         if ( defined $CONFIG{execute} ) {
1678             for my $sub_config ( keys %{ $CONFIG{execute} } ) {
1679                 if (defined $command_type && defined $sub_config) {
1680                     my $command = $PROC_ENV{l7directord} . " $sub_config $command_type";
1681                     system_wrapper($command);
1682                 }
1683             }
1684         }
1685     }
1686     else {
1687         for my $sub_config ( keys %$execute ) {
1688             if (defined $command_type && defined $sub_config) {
1689                 my $command = $PROC_ENV{l7directord} . " $sub_config $command_type";
1690                 system_wrapper($command);
1691             }
1692         }
1693     }
1694 }
1695
1696 # ld_stop
1697 # Remove virtual service for stopping this program.
1698 sub ld_stop {
1699     my $srv = ld_read_l7vsadm();
1700     if (!defined $srv) {
1701         ld_log( _message('FTL0201') );
1702         return;
1703     }
1704     if ( defined $CONFIG{virtual} ) {
1705         for my $v ( @{ $CONFIG{virtual} } ) {
1706             my $vid = get_virtual_id_str($v);
1707             if (!defined $vid) {
1708                 ld_log( _message('ERR0502') );
1709                 return;
1710             }
1711             if ( exists $srv->{$vid} ) {
1712                 for my $rid ( keys %{ $srv->{$vid} } ) {
1713                     ld_delete_real( $v, $srv->{$vid}{$rid} );
1714                 }
1715             }
1716             ld_delete_virtual($v);
1717         }
1718     }
1719 }
1720
1721 # ld_main
1722 # Main function of this program.
1723 # Create virtual service and loop below 3 steps.
1724 # 1. Check health check sub process and (re-)create sub process as needed
1725 # 2. Check signal in sleep and start to terminate program or reload config as needed
1726 # 3. Check config file and reload config as needed
1727 sub ld_main {
1728     ld_start();
1729
1730     # Main failover checking code
1731     MAIN_LOOP:
1732     while (1) {
1733         # manage real server check process.
1734         REAL_CHECK:
1735         while (1) {
1736             my @id_lists = check_child_process();
1737             # if child process is not running
1738             if (@id_lists) {
1739                 create_check_process(@id_lists);
1740             }
1741             my $signal = sleep_and_check_signal( $CONFIG{configinterval} );
1742             last MAIN_LOOP  if defined $signal && $signal eq 'halt';
1743             last REAL_CHECK if defined $signal && $signal eq 'reload';
1744             last REAL_CHECK if check_cfgfile();
1745         }
1746
1747         # reload config
1748         reread_config();
1749     }
1750
1751     # signal TERM to child process
1752     for my $id (keys %HEALTH_CHECK) {
1753         if ( defined $HEALTH_CHECK{$id}{pid} ) {
1754             # TODO cannot kill process during pinging to unreachable host?
1755             {
1756                 local $SIG{ALRM} = sub { die; };
1757                 kill 15, $HEALTH_CHECK{$id}{pid};
1758                 eval {
1759                     alarm 3;
1760                     waitpid $HEALTH_CHECK{$id}{pid}, 0;
1761                     alarm 0;
1762                 };
1763                 alarm 0;
1764                 if ($EVAL_ERROR) {
1765                     kill 9, $HEALTH_CHECK{$id}{pid};
1766                     waitpid $HEALTH_CHECK{$id}{pid}, WNOHANG;
1767                 }
1768             }
1769         }
1770     }
1771     ld_stop();
1772 }
1773
1774 # check_child_process
1775 # Check health check process by signal zero.
1776 # return: Health check id list that (re-)created later.
1777 sub check_child_process {
1778     my @down_process_ids = ();
1779     for my $id (sort keys %HEALTH_CHECK) {
1780         if ( !defined $HEALTH_CHECK{$id}{pid} ) {
1781             # not create ever
1782             ld_log( _message('INF0401', $id) );
1783             push @down_process_ids, $id;
1784             next;
1785         }
1786         # non-blocking wait for zombie process
1787         waitpid(-1, WNOHANG); # TODO should move to sigchld handler?
1788         # signal 0
1789         my $signaled = kill 0, $HEALTH_CHECK{$id}{pid};
1790         if ($signaled != 1) {
1791             # maybe killed from outside
1792             ld_log( _message('ERR0603', $HEALTH_CHECK{$id}{pid}, $id) );
1793             push @down_process_ids, $id;
1794             next;
1795         }
1796     }
1797     return @down_process_ids;
1798 }
1799
1800 # create_check_process
1801 # Fork health check sub process.
1802 # And health check sub process run health_check sub function.
1803 sub create_check_process {
1804     my @id_lists = @_;
1805     for my $health_check_id (@id_lists) {
1806         my $pid = fork();
1807         if ($pid > 0) {
1808             ld_log( _message('INF0402', $pid, $health_check_id) );
1809             $HEALTH_CHECK{$health_check_id}{pid} = $pid;
1810         }
1811         elsif ($pid == 0) {
1812             $PROC_STAT{parent_pid} = $PROC_STAT{pid};
1813             $PROC_STAT{pid} = $PID;
1814             health_check( $HEALTH_CHECK{$health_check_id}{manage} );
1815         }
1816         else {
1817             ld_log( _message('ERR0604', $health_check_id) );
1818         }
1819         sleep 1;
1820     }
1821 }
1822
1823 # health_check
1824 # Main function of health check process.
1825 # Loop below.
1826 # 1. Health check.
1827 # 2. Status change and reflect to l7vsd as needed.
1828 # 3. Check signal in sleep.
1829 # pre: v_r_list: reference list of virtual service and real server pair
1830 #     $v_r_list = [ [$virtual, $real], [$virtual, $real], ... ];
1831 # return: none
1832 #         MUST use POSIX::_exit when terminate sub process.
1833 sub health_check {
1834     my $v_r_list = shift;
1835     if (!defined $v_r_list) {
1836         ld_log( _message('ERR0501') );
1837         ld_log( _message('FTL0001') );
1838         POSIX::_exit(1);
1839     }
1840
1841     # you can use any virtual, real pair in $v_r_list.
1842     my ($v, $r) = @{ $v_r_list->[0] };
1843     if (!defined $v || !defined $r) {
1844         ld_log( _message('FTL0002') );
1845         POSIX::_exit(2);
1846     }
1847
1848     my $health_check_func = get_check_func($v);
1849     my $current_status = get_status($v_r_list);
1850
1851     my $status = 'STARTING';
1852     my $type = $v->{checktype} eq 'negotiate' ? $v->{service}
1853              : $v->{checktype} eq 'combined'  ? $v->{service} . '(combined)'
1854              :                                  $v->{checktype}
1855              ;
1856     $PROGRAM_NAME = 'l7directord: ' . $type . ':' . get_ip_port($r) . ':' . $status;
1857     
1858     while (1) {
1859         # health check
1860         my $service_status = &$health_check_func($v, $r);
1861         
1862         if ($service_status == $SERVICE_DOWN) {
1863             if (!defined $current_status || $current_status == $SERVICE_UP) {
1864                 $r->{fail_counts}++;
1865                 undef $r->{num_connects};
1866                 if ($r->{fail_counts} >= $v->{checkcount}) {
1867                     ld_log( _message( 'ERR0602', get_ip_port($r) ) );
1868                     service_set($v_r_list, 'down');
1869                     $current_status = $SERVICE_DOWN;
1870                     $status = 'DOWN';
1871                     $r->{fail_counts} = 0;
1872                 }
1873                 else {
1874                     ld_log( _message( 'WRN1001', get_ip_port($r), $v->{checkcount} - $r->{fail_counts} ) );
1875                     $status = sprintf "NG[%d/%d]", $r->{fail_counts}, $v->{checkcount};
1876                 }
1877             }
1878         }
1879         if ($service_status == $SERVICE_UP) {
1880             $r->{fail_counts} = 0;
1881             if (!defined $current_status || $current_status == $SERVICE_DOWN) {
1882                 ld_log( _message( 'ERR0601', get_ip_port($r) ) );
1883                 service_set($v_r_list, 'up');
1884                 $current_status = $SERVICE_UP;
1885             }
1886             $status = 'UP';
1887         }
1888
1889         $PROGRAM_NAME = 'l7directord: ' . $type . ':' . get_ip_port($r) . ':' . $status;
1890
1891         my $sleeptime = $r->{fail_counts} ? $v->{retryinterval} : $v->{checkinterval};
1892         last if (sleep_and_check_signal($sleeptime, 1) eq 'halt');
1893
1894         my $parent_process = kill 0, $PROC_STAT{parent_pid};
1895         if ($parent_process != 1) {
1896             ld_log( _message( 'FTL0003', $PROC_STAT{parent_pid} ) );
1897             POSIX::_exit(3);
1898         }
1899     }
1900
1901     ld_log( _message('INF0007') );
1902     POSIX::_exit(0);
1903 }
1904
1905 # sleep_and_check_signal
1906 # Check signal flag each 0.1 secound with sleeping specified seconds.
1907 sub sleep_and_check_signal {
1908     my ($sec, $is_child) = @_;
1909     if (!defined $sec || $sec !~ /^\d+$/) {
1910         ld_log( _message('ERR0501') );
1911         return 'halt';
1912     }
1913
1914     my $sleeped = 0;
1915     while ($sec > $sleeped) {
1916         if ($is_child) {
1917             if ( defined $PROC_STAT{halt} ) { 
1918                 ld_log( _message( 'WRN0001', $CONFIG_FILE{path}, $PROC_STAT{halt} ) );
1919                 return 'halt';
1920             }
1921         }
1922         else {
1923             if ( defined $PROC_STAT{halt} ) { 
1924                 ld_log( _message( 'WRN0001', $CONFIG_FILE{path}, $PROC_STAT{halt} ) );
1925                 return 'halt';
1926             }
1927             if ( defined $PROC_STAT{reload} ) {
1928                 ld_log( _message( 'WRN0002', $CONFIG_FILE{path}, $PROC_STAT{reload} ) );
1929                 undef $PROC_STAT{reload};
1930                 return 'reload';
1931             }
1932         }
1933         sleep 0.1;
1934         $sleeped += 0.1;
1935     }
1936     return 'run';
1937 }
1938
1939 # get_check_func
1940 # Determine check function by checktype and service.
1941 sub get_check_func {
1942     my $v = shift;
1943     if (!defined $v) {
1944         ld_log( _message('ERR0501') );
1945         return \&check_off;
1946     }
1947
1948     my $type = $v->{checktype};
1949     my $service_func = {
1950         http  => \&check_http,
1951         https => \&check_http,
1952         pop   => \&check_pop,
1953         imap  => \&check_imap,
1954         smtp  => \&check_smtp,
1955         ftp   => \&check_ftp,
1956         ldap  => \&check_ldap,
1957         nntp  => \&check_nntp,
1958         dns   => \&check_dns,
1959         sip   => \&check_sip,
1960         mysql => \&check_mysql,
1961         pgsql => \&check_pgsql,
1962     };
1963
1964     if ( defined $type && ($type eq 'negotiate' || $type eq 'combined') ) {
1965         if (defined $v->{service} && exists $service_func->{ $v->{service} } ) {
1966             my $negotiate_func = $service_func->{ $v->{service} };
1967             if ($type eq 'negotiate') {
1968                 return $negotiate_func;
1969             }
1970             elsif ($type eq 'combined') {
1971                 my $combined_func =  make_combined_func($negotiate_func);
1972                 return $combined_func;
1973             }
1974         }
1975         else {
1976             return \&check_none;
1977         }
1978     }
1979
1980     if (defined $type && $type eq 'custom') {
1981         my $custom_func = make_custom_func( $v->{customcheck} );
1982         return $custom_func;
1983     }
1984
1985     if (defined $type && $type eq 'connect') {
1986         if (defined $v->{protocol} && $v->{protocol} eq 'tcp') {
1987             return \&check_connect;
1988         }
1989         else {
1990             return \&check_ping;
1991         }
1992     }
1993
1994     if (defined $type && $type eq 'ping') {
1995         return \&check_ping;
1996     }
1997
1998     if (defined $type && $type eq 'off') {
1999         return \&check_off;
2000     }
2001
2002     if (defined $type && $type eq 'on') {
2003         return \&check_on;
2004     }
2005
2006     return \&check_none;
2007 }
2008
2009 # make_combined_func
2010 # Create combined function.
2011 sub make_combined_func {
2012     my $negotiate_func = shift;
2013     if (!defined $negotiate_func) {
2014         ld_log( _message('ERR0504') );
2015         return \&check_connect;
2016     }
2017
2018     # closure
2019     my $combined_func = sub {
2020         my ($v, $r) = @_;
2021         my $timing    = $v->{num_connects};
2022         my $connected = $r->{num_connects};
2023
2024         if (!defined $connected ||
2025             (defined $timing && $timing <= $connected) ) {
2026             $r->{num_connects} = 0;
2027             return &$negotiate_func($v, $r);
2028         }
2029         else {
2030             $r->{num_connects}++;
2031             return check_connect($v, $r);
2032         }
2033     };
2034
2035     return $combined_func;
2036 }
2037
2038 # make_custom_func
2039 # Create custom check function.
2040 sub make_custom_func {
2041     my $customcheck = shift;
2042     if (!defined $customcheck) {
2043         ld_log( _message('ERR0505') );
2044         return \&check_off;
2045     }
2046
2047     # closure
2048     my $custom_func = sub {
2049         my ($v, $r) = @_;
2050         my $status = get_status([[$v, $r]]);
2051         my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2052         my $ip_port  = $r->{server}{ip} . ':' . $port;
2053
2054         # expand macro
2055         $customcheck =~ s/_IP_/$r->{server}{ip}/g;
2056         $customcheck =~ s/_PORT_/$port/g;
2057
2058         my $res;
2059         {
2060             local $SIG{__DIE__} = 'DEFAULT';
2061             local $SIG{ALRM} = sub { die "custom check timeout\n"; };
2062             eval {
2063                 alarm $v->{negotiatetimeout};
2064                 $res = system_wrapper($customcheck);
2065                 alarm 0;
2066             };
2067             alarm 0;
2068             if ($EVAL_ERROR) {
2069                 ld_log( _message('WRN3301', $v->{checktimeout}, $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2070                 return $SERVICE_DOWN;
2071             }
2072         }
2073         if ($res) {
2074                 ld_log( _message('WRN3302', $customcheck, $res) ) if (!defined $status || $status eq $SERVICE_UP);
2075                 return $SERVICE_DOWN;
2076         }
2077         ld_log( _message('WRN0215', $ip_port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2078         return $SERVICE_UP;
2079     };
2080
2081     return $custom_func;
2082 }
2083
2084 # check_http
2085 # HTTP service health check.
2086 # Send GET/HEAD request, and check response
2087 sub check_http {
2088     require LWP::UserAgent;
2089     require LWP::Debug;
2090     if ( $DEBUG_LEVEL > 2 ) {
2091         LWP::Debug::level('+');
2092     }
2093     my ( $v, $r ) = @_;
2094     my $status = get_status([[$v, $r]]);
2095
2096     my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2097
2098     if ( $r->{url} !~ m{^https?://([^:/]+)} ) {
2099         ld_log( _message( 'WRN1101', $r->{url}, $r->{server}{ip}, $port ) ) if (!defined $status || $status eq $SERVICE_UP);
2100         return $SERVICE_DOWN;
2101     }
2102     my $host = $1;
2103     my $virtualhost = defined $v->{virtualhost} ? $v->{virtualhost} : $host;
2104
2105     ld_debug(2, "check_http: url=\"$r->{url}\" " . "virtualhost=\"$virtualhost\"");
2106
2107     my $ua = LWP::UserAgent->new( timeout => $v->{negotiatetimeout} );
2108     my $req = new HTTP::Request( $v->{httpmethod}, $r->{url}, [ Host => $virtualhost ] );
2109     my $res;
2110     {
2111         # LWP makes ungaurded calls to eval
2112         # which throw a fatal exception if they fail
2113         local $SIG{__DIE__} = 'DEFAULT';
2114         local $SIG{ALRM} = sub { die "Can't connect to $r->{server}{ip}:$port (connect: timeout)\n"; };
2115         eval {
2116             alarm $v->{negotiatetimeout};
2117             $res = $ua->request($req);
2118             alarm 0;
2119         };
2120         alarm 0;
2121     }
2122
2123     my $status_line = $res->status_line;
2124     $status_line =~ s/[\r\n]//g;
2125
2126     my $recstr = $r->{receive};
2127     if (!$res->is_success) {
2128         ld_log( _message( 'WRN1102', $status_line, $r->{server}{ip}, $port ) ) if (!defined $status || $status eq $SERVICE_UP);
2129         return $SERVICE_DOWN;
2130     }
2131     elsif (defined $recstr && $res->as_string !~ /$recstr/) {
2132         ld_log( _message( 'WRN1103', $recstr, $r->{server}{ip}, $port ) ) if (!defined $status || $status eq $SERVICE_UP);
2133         ld_debug(3, "Headers " . $res->headers->as_string);
2134         ld_debug(2, "check_http: $r->{url} is down\n");
2135         return $SERVICE_DOWN;
2136     }
2137
2138     ld_debug(2, "check_http: $r->{url} is up\n");
2139     ld_log( _message( 'WRN0203', $status_line, $r->{server}{ip}, $port ) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2140     return $SERVICE_UP;
2141 }
2142
2143 # check_smtp
2144 # SMTP service health check.
2145 # Connect SMTP server and check first response
2146 sub check_smtp {
2147     require Net::SMTP;
2148     my ($v, $r) = @_;
2149     my $status = get_status([[$v, $r]]);
2150
2151     my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2152
2153     ld_debug(2, "Checking http: server=$r->{server}{ip} port=$port");
2154     my $debug_flag = $DEBUG_LEVEL ? 1 : 0;
2155
2156     my $smtp = Net::SMTP->new(
2157         $r->{server}{ip},
2158         Port    => $port,
2159         Timeout => $v->{negotiatetimeout},
2160         Debug   => $debug_flag,
2161     );
2162     if (!$smtp) {
2163         ld_log( _message('WRN1201', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2164         return $SERVICE_DOWN;
2165     }
2166     $smtp->quit;
2167
2168     ld_log( _message('WRN0204', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2169     return $SERVICE_UP;
2170 }
2171
2172 # check_pop
2173 # POP3 service health check.
2174 # Connect POP3 server and login if user-pass specified.
2175 sub check_pop {
2176     require Net::POP3;
2177     my ($v, $r) = @_;
2178     my $status = get_status([[$v, $r]]);
2179
2180     my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2181
2182     ld_debug(2, "Checking pop server=$r->{server}{ip} port=$port");
2183     my $debug_flag = $DEBUG_LEVEL ? 1 : 0;
2184
2185     my $pop = Net::POP3->new(
2186         $r->{server}{ip},
2187         Port    => $port,
2188         Timeout => $v->{negotiatetimeout},
2189         Debug   => $debug_flag,
2190     );
2191     if (!$pop) {
2192         ld_log( _message('WRN1301', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2193         return $SERVICE_DOWN;
2194     }
2195
2196     if ( defined $v->{login} && defined $v->{passwd} && $v->{login} ne q{} ) {
2197         $pop->user( $v->{login} );
2198         my $num = $pop->pass( $v->{passwd} );
2199         if (!defined $num) {
2200             ld_log( _message('WRN1302', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2201             $pop->quit();
2202             return $SERVICE_DOWN;
2203         }
2204     }
2205     $pop->quit();
2206
2207     ld_log( _message('WRN0205', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2208     return $SERVICE_UP;
2209 }
2210
2211 # check_imap
2212 # IMAP service health check.
2213 # Connect IMAP server and login if user-pass specified.
2214 sub check_imap {
2215     require Mail::IMAPClient;
2216     my ($v, $r) = @_;
2217     my $status = get_status([[$v, $r]]);
2218
2219     my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2220
2221     ld_debug(2, "Checking imap server=$r->{server}{ip} port=$port");
2222     my $debug_flag = $DEBUG_LEVEL ? 1 : 0;
2223
2224     my $imap;
2225     {
2226         local $SIG{ALRM} = sub { die "Connection timeout\n"; };
2227         eval {
2228             alarm $v->{negotiatetimeout};
2229             $imap = Mail::IMAPClient->new(
2230                 Server   => $r->{server}{ip},
2231                 Port     => $port,
2232                 Timeout  => $v->{negotiatetimeout},
2233                 Debug    => $debug_flag,
2234             );
2235             alarm 0;
2236         };
2237         alarm 0;
2238         if ($EVAL_ERROR) {
2239             ld_log( _message('WRN1403', $v->{negotiatetimeout}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2240             return $SERVICE_DOWN;
2241         }
2242     }
2243     if (!$imap) {
2244         ld_log( _message('WRN1401', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2245         return $SERVICE_DOWN;
2246     }
2247
2248     if ( defined $v->{login} && defined $v->{passwd} && $v->{login} ne q{} ) {
2249         $imap->User( $v->{login} );
2250         $imap->Password( $v->{passwd} );
2251         my $authres = $imap->login();
2252         if (!$authres) {
2253             ld_log( _message('WRN1402', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2254             $imap->logout();
2255             return $SERVICE_DOWN;
2256         }
2257     }
2258     $imap->logout();
2259
2260     ld_log( _message('WRN0206', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2261     return $SERVICE_UP;
2262 }
2263
2264 # check_ldap
2265 # LDAP service health check.
2266 # Connect LDAP server and search if base-DN specified by 'request'
2267 sub check_ldap {
2268     require Net::LDAP;
2269     my ($v, $r) = @_;
2270     my $status = get_status([[$v, $r]]);
2271
2272     my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2273
2274     ld_debug(2, "Checking ldap server=$r->{server}{ip} port=$port");
2275     my $debug_flag = $DEBUG_LEVEL ? 15 : 0;
2276
2277     my $ldap = Net::LDAP->new(
2278         $r->{server}{ip},
2279         port    => $port,
2280         timeout => $v->{negotiatetimeout},
2281         debug   => $debug_flag,
2282     );
2283     if (!$ldap) {
2284         ld_log( _message('WRN1501', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2285         return $SERVICE_DOWN;
2286     }
2287
2288     my $mesg;
2289     {
2290         local $SIG{ALRM} = sub { die "Connection timeout\n"; };
2291         eval {
2292             alarm $v->{negotiatetimeout};
2293             $mesg = $ldap->bind;
2294             alarm 0;
2295         };
2296         alarm 0;
2297         if ($EVAL_ERROR) {
2298             ld_log( _message('WRN1502', $v->{negotiatetimeout}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2299             return $SERVICE_DOWN;
2300         }
2301     }
2302     if ($mesg->is_error) {
2303         ld_log( _message('WRN1503', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2304         return $SERVICE_DOWN;
2305     }
2306
2307     if ( defined $r->{request} && $r->{request} ne q{} ) {
2308         ld_debug( 4, "Base : " . $r->{request} );
2309         my $result = $ldap->search(
2310             base   => $r->{request},
2311             scope  => 'base',
2312             filter => '(objectClass=*)',
2313         );
2314     
2315         if ($result->count != 1) {
2316             ld_log( _message('WRN1504', $result->count, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2317             $ldap->unbind;
2318             return $SERVICE_DOWN;
2319         }
2320     
2321         if ( defined $r->{receive} ) {
2322             my $href       = $result->as_struct;
2323             my @arrayOfDNs = keys %$href;
2324             my $recstr = $r->{receive};
2325             if ($recstr =~ /.+/ && $arrayOfDNs[0] !~ /$recstr/) {
2326                 ld_log( _message('WRN1505', $recstr, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2327                 $ldap->unbind;
2328                 return $SERVICE_DOWN;
2329             }
2330         }
2331     }
2332     $ldap->unbind;
2333
2334     ld_log( _message('WRN0207', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2335     return $SERVICE_UP;
2336 }
2337
2338 # check_nntp
2339 # NNTP service health check.
2340 # Connect NNTP server and check response start with '2**'
2341 sub check_nntp {
2342     require IO::Socket;
2343     require IO::Select;
2344     my ($v, $r) = @_;
2345     my $status = get_status([[$v, $r]]);
2346
2347     my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2348
2349     ld_debug(2, "Checking nntp server=$r->{server}{ip} port=$port");
2350
2351     my $sock = ld_open_socket( $r->{server}{ip}, $port, $v->{protocol}, $v->{negotiatetimeout} );
2352     if (!$sock) {
2353         ld_log( _message('WRN1601', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2354         return $SERVICE_DOWN;
2355     }
2356
2357     ld_debug(3, "Connected to $r->{server}{ip} (port $port)");
2358     my $select = IO::Select->new();
2359     $select->add($sock);
2360     if ( !defined $select->can_read( $v->{negotiatetimeout} ) ) {
2361         ld_log( _message('WRN1602', $v->{negotiatetimeout}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2362         $select->remove($sock);
2363         $sock->close;
2364         return $SERVICE_DOWN;
2365     }
2366
2367     my $buf;
2368     sysread $sock, $buf, 64;
2369     $select->remove($sock);
2370     $sock->close;
2371     my ($response) = split /[\r\n]/, $buf;
2372
2373     if ($response !~ /^2/) {
2374         ld_log( _message('WRN1603', $response, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2375         return $SERVICE_DOWN;
2376     }
2377
2378     ld_log( _message('WRN0208', $response, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2379     return $SERVICE_UP;
2380 }
2381
2382 # check_mysql
2383 # MySQL service health check.
2384 # call check_sql and use MySQL driver
2385 sub check_mysql {
2386     return check_sql(@_, 'mysql', 'database');
2387 }
2388
2389 # check_pgsql
2390 # PostgreSQL service health check.
2391 # call check_sql and use PostgreSQL driver
2392 sub check_pgsql {
2393     return check_sql(@_, 'Pg', 'dbname');
2394 }
2395
2396 # check_sql
2397 # DBI service health check.
2398 # Login DB and send query if query specified by 'request', check result row number same as 'receive'
2399 sub check_sql {
2400     require DBI;
2401     my ($v, $r, $dbd, $dbname) = @_;
2402     my $status = get_status([[$v, $r]]);
2403
2404     my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2405
2406     if ( !defined $v->{login} || !defined $v->{passwd} || !defined $v->{database} ||
2407            $v->{login} eq q{} || $v->{database} eq q{} ) {
2408         ld_log( _message('WRN1701', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2409         return $SERVICE_DOWN;
2410     }
2411
2412     ld_debug(2, "Checking $v->{server}{ip} server=$r->{server}{ip} port=$port\n");
2413
2414     my $mask = POSIX::SigSet->new(SIGALRM);
2415     my $action = POSIX::SigAction->new(
2416         sub { die "Connection timeout\n" },
2417         $mask,
2418     );
2419     my $oldaction = POSIX::SigAction->new();
2420     sigaction(SIGALRM, $action, $oldaction);
2421
2422     my $dbh;
2423     eval {
2424         alarm $v->{negotiatetimeout};
2425
2426         DBI->trace(15) if $DEBUG_LEVEL;
2427         $dbh = DBI->connect( "dbi:$dbd:$dbname=$v->{database};host=$r->{server}{ip};port=$port", $v->{login}, $v->{passwd} );
2428         DBI->trace(0);
2429
2430         if (!defined $dbh) {
2431             alarm 0;
2432             sigaction(SIGALRM, $oldaction);
2433             ld_log( _message('WRN1702', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2434             die;
2435         }
2436
2437         local $dbh->{TraceLevel} = $DEBUG_LEVEL ? 15 : 0;
2438
2439         my $rows = 0;
2440     
2441         if ( defined $r->{request} && $r->{request} ne q{} ) {
2442             my $sth  = $dbh->prepare( $r->{request} );
2443             $rows = $sth->execute;
2444             $sth->finish;
2445         }
2446     
2447         $dbh->disconnect;
2448     
2449         alarm 0;
2450         sigaction(SIGALRM, $oldaction);
2451
2452         if ( defined $r->{request} && $r->{request} ne q{} ) {
2453             ld_debug(4, "Database search returned $rows rows");
2454             if ($rows == 0) {
2455                 ld_log( _message('WRN1703', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2456                 die;
2457             }
2458             # If user defined a receive string (number of rows returned), only do
2459             # the check if the previous fetchall_arrayref succeeded.
2460             if (defined $r->{receive} && $r->{receive} =~ /^\d+$/) {
2461                 # Receive string specifies an exact number of rows
2462                 if ( $rows ne $r->{receive} ) {
2463                     ld_log( _message('WRN1704', $r->{receive}, $rows, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2464                     die;
2465                 }
2466             }
2467         }
2468     };
2469     alarm 0;
2470     sigaction(SIGALRM, $oldaction);
2471     if ($EVAL_ERROR) {
2472         if ($EVAL_ERROR eq "Connection timeout\n") {
2473             ld_log( _message('WRN1705', $v->{negotiatetimeout}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2474         }
2475         return $SERVICE_DOWN;
2476     }
2477
2478     ld_log( _message('WRN0209', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2479     return $SERVICE_UP;
2480 }
2481
2482 # check_connect
2483 # Connect service health check.
2484 # Just connect port and close.
2485 sub check_connect {
2486     my ($v, $r) = @_;
2487     my $status = get_status([[$v, $r]]);
2488
2489     my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2490
2491     ld_debug(2, "Checking connect: real server=$r->{server}{ip}:$port");
2492
2493     my $sock = ld_open_socket( $r->{server}{ip}, $port, $v->{protocol}, $v->{checktimeout} );
2494     if (!defined $sock) {
2495         ld_log( _message('WRN3201', $ERRNO, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2496         return $SERVICE_DOWN;
2497     }
2498     close($sock);
2499
2500     ld_debug(3, "Connected to: (port $port)");
2501
2502     ld_log( _message('WRN0210', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2503     return $SERVICE_UP;
2504 }
2505
2506 # check_sip
2507 # SIP service health check.
2508 # Send SIP OPTIONS request and check 200 response
2509 sub check_sip {
2510     my ($v, $r) = @_;
2511     my $status = get_status([[$v, $r]]);
2512
2513     my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port};
2514
2515     ld_debug(2, "Checking sip server=$r->{server}{ip} port=$port");
2516
2517     if ( !defined $v->{login} ) {
2518         ld_log( _message('WRN1801', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2519         return $SERVICE_DOWN;
2520     }
2521
2522     my $sock = ld_open_socket( $r->{server}{ip}, $port, $v->{protocol}, $v->{negotiatetimeout} );
2523     if (!defined $sock) {
2524         ld_log( _message('WRN1802', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2525         return $SERVICE_DOWN;
2526     }
2527
2528     my $sip_s_addr = $sock->sockhost;
2529     my $sip_s_port = $sock->sockport;
2530
2531     ld_debug(3, "Connected from $sip_s_addr:$sip_s_port to " . $r->{server} . ":$port");
2532
2533     my $id = $v->{login};
2534     my $request =
2535           "OPTIONS sip:$id SIP/2.0\r\n"
2536         . "Via: SIP/2.0/UDP $sip_s_addr:$sip_s_port;branch=z9hG4bKhjhs8ass877\r\n"
2537         . "Max-Forwards: 70\r\n"
2538         . "To: <sip:$id>\r\n"
2539         . "From: <sip:$id>;tag=1928301774\r\n"
2540         . "Call-ID: a84b4c76e66710\r\n"
2541         . "CSeq: 63104 OPTIONS\r\n"
2542         . "Contact: <sip:$id>\r\n"
2543         . "Accept: application/sdp\r\n"
2544         . "Content-Length: 0\r\n"
2545         . "\r\n";
2546
2547     ld_debug(3, "Request:\n$request");
2548
2549     my $response;
2550     eval {
2551         local $SIG{__DIE__} = 'DEFAULT';
2552         local $SIG{ALRM   } = sub { die "Connection timeout\n"; };
2553         ld_debug(4, "Timeout is $v->{negotiatetimeout}");
2554         alarm $v->{negotiatetimeout};
2555
2556         print {$sock} $request;
2557         $response = <$sock>;
2558         close $sock;
2559         alarm 0;
2560
2561         ld_debug(3, "Response:\n$response");
2562
2563         if ( $response !~ m{^SIP/2\.0 200 OK} ) {
2564             ld_log( _message('WRN1803', $response, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2565             die;
2566         }
2567     };
2568     alarm 0;
2569     if ($EVAL_ERROR) {
2570         if ($EVAL_ERROR eq "Connection timeout\n") {
2571             ld_log( _message('WRN1804', $v->{negotiatetimeout}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2572         }
2573         return $SERVICE_DOWN;
2574     }
2575
2576     ld_log( _message('WRN0211', $response, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2577     return $SERVICE_UP;
2578 }
2579
2580 # check_ftp
2581 # FTP service health check.
2582 # Login server and get file if 'request' specified, and check file include 'receive' string
2583 sub check_ftp {
2584     require Net::FTP;
2585     my ($v, $r) = @_;
2586     my $status = get_status([[$v, $r]]);
2587
2588     my $ip_port = get_ip_port($r, $v->{checkport});
2589
2590     ld_debug(2, "Checking ftp server=$ip_port");
2591     my $debug_flag = $DEBUG_LEVEL ? 1 : 0;
2592
2593     if ( !defined $v->{login} || !defined $v->{passwd} || $v->{login} eq q{} ) {
2594         ld_log( _message('WRN1901', $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2595         return $SERVICE_DOWN;
2596     }
2597
2598     my $ftp = Net::FTP->new(
2599             $ip_port,
2600             Timeout => $v->{negotiatetimeout},
2601             Passive => 1,
2602             Debug   => $debug_flag,
2603          );
2604     if (!defined $ftp) {
2605         ld_log( _message('WRN1902', $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2606         return $SERVICE_DOWN;
2607     }
2608     if ( !$ftp->login( $v->{login}, $v->{passwd} ) ) {
2609         ld_log( _message('WRN1903', $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2610         $ftp->quit();
2611         return $SERVICE_DOWN;
2612     }
2613     if ( !$ftp->cwd('/') ) {
2614         ld_log( _message('WRN1904', $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2615         $ftp->quit();
2616         return $SERVICE_DOWN;
2617     }
2618     if ( $r->{request} ) {
2619         my $fail_flag = 0;
2620         eval {
2621             local $SIG{__DIE__} = 'DEFAULT';
2622             local $SIG{ALRM   } = sub { die "Connection timeout\n"; };
2623             alarm $v->{negotiatetimeout};
2624
2625             open my $tmp, '+>', undef;
2626             $ftp->binary();
2627             if ( !$ftp->get( $r->{request}, *$tmp ) ) {
2628                 alarm 0;
2629                 ld_log( _message('WRN1905', $r->{request}, $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2630                 close $tmp;
2631                 $ftp->quit();
2632                 $fail_flag = 1;
2633             }
2634             elsif ( $r->{receive} ) {
2635                 seek $tmp, 0, 0;
2636                 local $/;
2637                 my $memory = <$tmp>;
2638                 close $tmp;
2639                 if ($memory !~ /$r->{receive}/) {
2640                     alarm 0;
2641                     $ftp->quit();
2642                     ld_log( _message('WRN1906', $r->{receive}, $r->{request}, $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2643                     $fail_flag = 1;
2644                 }
2645             }
2646         };
2647         alarm 0;
2648         if ($EVAL_ERROR) {
2649             $ftp->quit();
2650             my $error_message = $EVAL_ERROR;
2651             $error_message =~ s/[\r\n]//g;
2652             if ($error_message eq 'Connection timeout') {
2653                 ld_log( _message('WRN1908', $v->{negotiatetimeout}, $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2654             }
2655             else {
2656                 ld_log( _message('WRN1907', $error_message, $ip_port) ) if (!defined $status || $status eq $SERVICE_UP);
2657             }
2658             return $SERVICE_DOWN;
2659         }
2660         if ($fail_flag) {
2661             $ftp->quit();
2662             return $SERVICE_DOWN;
2663         }
2664     }
2665     $ftp->quit();
2666
2667     ld_log( _message('WRN0212', $ip_port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2668     return $SERVICE_UP;
2669 }
2670
2671 # check_dns
2672 # DNS service health check.
2673 # Connect server and search 'request' A or PTR record and check result include 'response' string 
2674 sub check_dns {
2675     my ($v, $r) = @_;
2676     my $status = get_status([[$v, $r]]);
2677
2678     my $port = defined $v->{checkport} ? $v->{checkport} : $r->{server}{port}; 
2679
2680     {
2681         # Net::DNS makes ungaurded calls to eval
2682         # which throw a fatal exception if they fail
2683         local $SIG{__DIE__} = 'DEFAULT';
2684         require Net::DNS;
2685     }
2686     my $res = Net::DNS::Resolver->new();
2687
2688     if ($DEBUG_LEVEL) {
2689         $res->debug(1);
2690     }
2691
2692     if ( !defined $r->{request} || $r->{request} eq q{} || !defined $r->{receive} || $r->{receive} eq q{} ) {
2693         ld_log( _message('WRN2001', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2694         return $SERVICE_DOWN;
2695     }
2696     ld_debug( 2, qq(Checking dns: request="$r->{request}" receive="$r->{receive}"\n) );
2697
2698     my $packet;
2699     eval {
2700         local $SIG{__DIE__} = 'DEFAULT';
2701         local $SIG{ALRM   } = sub { die "Connection timeout\n"; };
2702         alarm $v->{negotiatetimeout};
2703         $res->nameservers( $r->{server}{ip} );
2704         $res->port($port);
2705         $packet = $res->search( $r->{request} );
2706         alarm 0;
2707     };
2708     alarm 0;
2709     if ($EVAL_ERROR) {
2710         if ($EVAL_ERROR eq "Connection timeout\n") {
2711             ld_log( _message('WRN2002', $v->{negotiatetimeout}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2712         }
2713         else {
2714             ld_log( _message('WRN2003', $EVAL_ERROR, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2715         }
2716         return $SERVICE_DOWN;
2717     }
2718     if (!$packet) {
2719         ld_log( _message('WRN2004', $r->{request}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2720         return $SERVICE_DOWN;
2721     }
2722
2723     my $match = 0;
2724     for my $rr ($packet->answer) {
2725         if (   ( $rr->type eq 'A'   && $rr->address  eq $r->{receive} )
2726             || ( $rr->type eq 'PTR' && $rr->ptrdname eq $r->{receive} ) ) {
2727             $match = 1;
2728             last;
2729         }
2730     }
2731     if (!$match) {
2732         ld_log( _message('WRN2005', $r->{receive}, $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_UP);
2733         return $SERVICE_DOWN;
2734     }
2735
2736     ld_log( _message('WRN0213', $r->{server}{ip}, $port) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2737     return $SERVICE_UP;
2738 }
2739
2740 # check_ping
2741 # ICMP ping service health check.
2742 # Ping server and check response.
2743 sub check_ping {
2744     require Net::Ping;
2745     my ($v, $r) = @_;
2746     my $status = get_status([[$v, $r]]);
2747
2748     ld_debug( 2, qq(Checking ping: host="$r->{server}{ip}" checktimeout="$v->{checktimeout}"\n) );
2749
2750     my $p = Net::Ping->new('icmp', 1, 64);
2751     if ( !$p->ping( $r->{server}{ip}, $v->{checktimeout} ) ) {
2752         ld_log( _message('WRN3101', $v->{checktimeout}, $r->{server}{ip}) ) if (!defined $status || $status eq $SERVICE_UP);
2753         return $SERVICE_DOWN;
2754     }
2755
2756     ld_log( _message('WRN0214', $r->{server}{ip}) ) if (!defined $status || $status eq $SERVICE_DOWN || $r->{fail_counts} > 0);
2757     return $SERVICE_UP;
2758 }
2759
2760 # check_none
2761 # Dummy function to check service if service type is none.
2762 # Just activates the real server
2763 sub check_none {
2764     my ($v, $r) = @_;
2765     ld_debug(2, "Checking none");
2766     return $SERVICE_UP;
2767 }
2768
2769 # check_off
2770 # Check nothing and always return $SERVICE_DOWN
2771 sub check_off {
2772     my ($v, $r) = @_;
2773     return $SERVICE_DOWN;
2774 }
2775
2776 # check_on
2777 # Check nothing and always return $SERVICE_UP
2778 sub check_on {
2779     my ($v, $r) = @_;
2780     return $SERVICE_UP;
2781 }
2782
2783 # service_set
2784 # Used to bring up and down real servers.
2785 # This is the function you should call if you want to bring a real
2786 # server up or down.
2787 # This function is safe to call regrdless of the current state of a
2788 # real server.
2789 # Do _not_ call _service_up or _service_down directly.
2790 # pre: v_r_list: virtual and real pair list
2791 #                [ [$v, $r], [$v, $r] ... ]
2792 #      state: up or down
2793 #             up to bring the real service up
2794 #             down to bring the real service up
2795 # post: The real server is brough up or down for each virtual service
2796 #       it belongs to.
2797 # return: none
2798 sub service_set {
2799     my ($v_r_list, $state) = @_;
2800
2801     if (defined $state && $state eq 'up') {
2802         _service_up($v_r_list);
2803     }
2804     elsif (defined $state && $state eq 'down') {
2805         _service_down($v_r_list);
2806     }
2807 }
2808
2809 # _service_up
2810 # Bring a real service up if it is down
2811 # Should be called by service_set only
2812 # I.e. If you want to change the state of a real server call service_set.
2813 #      If you call this function directly then l7directord will lose track
2814 #      of the state of real servers.
2815 # pre: v_r_list: virtual and real pair list
2816 #                [ [$v, $r], [$v, $r] ... ]
2817 # post: real service is taken up from the respective virtual service
2818 #       if it is inactive
2819 # return: none
2820 sub _service_up {
2821     my $v_r_list = shift;
2822     if ( !_status_up($v_r_list) ) {
2823         return;
2824     }
2825
2826     for my $v_r_pair (@$v_r_list) {
2827         my ($v, $r) = @$v_r_pair;
2828         _restore_service($v, $r, 'real');
2829         fallback_off($v);
2830     }
2831 }
2832
2833 # _service_down
2834 # Bring a real service down if it is up
2835 # Should be called by service_set only
2836 # I.e. if you want to change the state of a real server call service_set.
2837 #      If you call this function directly then l7directord will lose track
2838 #      of the state of real servers.
2839 # pre: v_r_list: virtual and real pair list
2840 #                [ [$v, $r], [$v, $r] ... ]
2841 # post: real service is taken down from the respective virtual service
2842 #       if it is active
2843 # return: none
2844 sub _service_down {
2845     my $v_r_list = shift;
2846     if ( !_status_down($v_r_list) ) {
2847         return;
2848     }
2849
2850     for my $v_r_pair (@$v_r_list) {
2851         my ($v, $r) = @$v_r_pair;
2852         _remove_service($v, $r, 'real');
2853         fallback_on($v);
2854     }
2855 }
2856
2857 # _status_up
2858 # Set the status of a server as up
2859 # Should only be called from _service_up or fallback_on
2860 sub _status_up {
2861     my ($v_r_list, $is_fallback) = @_;
2862     if (!defined $v_r_list) {
2863         return 0;
2864     }
2865
2866     if (!$is_fallback) {
2867         my $current_status = get_status($v_r_list);
2868         if (defined $current_status && $current_status eq $SERVICE_UP) {
2869             return 0;
2870         }
2871     
2872         my $id = get_health_check_id_str( @{ $v_r_list->[0] } );
2873         if (!defined $id) {
2874             ld_log( _message('ERR0503') );
2875             return 0;
2876         }
2877         $HEALTH_CHECK{$id}{status} = $SERVICE_UP;
2878     
2879         return 1;
2880     }
2881     else {
2882         my $current_service = ld_read_l7vsadm();
2883         if (!defined $current_service) {
2884             ld_log( _message('FTL0201') );
2885             return 0;
2886         }
2887         my $vid = get_virtual_id_str( $v_r_list->[0][0] );
2888         if ( exists $current_service->{$vid} ) {
2889             # no real server
2890             if ( !defined $current_service->{$vid} ) {
2891                 return 1;
2892             }
2893             my $weight = 0;
2894             # all real server's weight are zero.
2895             for my $real ( keys %{ $current_service->{$vid} } ) {
2896                 # already added fallback server.
2897                 if ( $real eq get_ip_port( $v_r_list->[0][1] ) ) {
2898                     return 0;
2899                 }
2900                 $weight += $current_service->{$vid}{$real}{weight};
2901             }
2902             if ($weight == 0) {
2903                 return 1;
2904             }
2905         }
2906         return 0;
2907     }
2908 }
2909
2910 # _status_down
2911 # Set the status of a server as down
2912 # Should only be called from _service_down or _ld_stop
2913 sub _status_down {
2914     my ($v_r_list, $is_fallback) = (@_);
2915     if (!defined $v_r_list) {
2916         return 0;
2917     }
2918
2919     if (!$is_fallback) {
2920         my $current_status = get_status($v_r_list);
2921         if ($current_status && $current_status eq $SERVICE_DOWN) {
2922             return 0;
2923         }
2924     
2925         my $id = get_health_check_id_str( @{ $v_r_list->[0] } );
2926         if (!defined $id) {
2927             ld_log( _message('ERR0503') );
2928             return 0;
2929         }
2930         $HEALTH_CHECK{$id}{status} = $SERVICE_DOWN;
2931     
2932         return 1;
2933     }
2934     else {
2935         my $current_service = ld_read_l7vsadm();
2936         if (!defined $current_service) {
2937             ld_log( _message('FTL0201') );
2938             return 0;
2939         }
2940         my $vid = get_virtual_id_str( $v_r_list->[0][0] );
2941         if ( defined $current_service->{$vid} ) {
2942             my $weight = 0;
2943             my $fallback_exist = 0;
2944             # any real server has weight.
2945             for my $real ( keys %{ $current_service->{$vid} } ) {
2946                 if ( $real eq get_ip_port( $v_r_list->[0][1] ) ) {
2947                     $fallback_exist = 1;
2948                 }
2949                 $weight += $current_service->{$vid}{$real}{weight};
2950             }
2951             if ($fallback_exist && $weight) {
2952                 return 1;
2953             }
2954         }
2955         return 0;
2956     }
2957 }
2958
2959 # get_status
2960 # Get health check server status
2961 # return $SERVICE_UP / $SERVICE_DOWN
2962 sub get_status {
2963     my $v_r_list = shift;
2964
2965     my $id = get_health_check_id_str( @{ $v_r_list->[0] } );
2966     if (!defined $id) {
2967         ld_log( _message('ERR0503') );
2968         return 0;
2969     }
2970     return $HEALTH_CHECK{$id}{status};
2971 }
2972
2973 # _remove_service
2974 # Remove a real server by either making it quiescent or deleteing it
2975 # Should be called by _service_down or fallback_off
2976 # I.e. If you want to change the state of a real server call service_set.
2977 #      If you call this function directly then l7directord will lose track
2978 #      of the state of real servers.
2979 # If the real server exists (which it should) make it quiescent or
2980 # delete it, depending on the global and per virtual service quiecent flag.
2981 # If it # doesn't exist, just leave it as it will be added by the
2982 # _service_up code as appropriate.
2983 # pre: v: reference to virtual service to with the real server belongs
2984 #      rservice: service to restore. Of the form server:port for tcp
2985 #      rforw: Forwarding mechanism of service. Should be only "-m"
2986 #    rforw is kept as it is, even though not used - NTT COMWARE
2987 #      tag: Tag to use for logging. Should be either "real" or "fallback"
2988 # post: real service is taken up from the respective virtual service
2989 #       if it is inactive
2990 # return: none
2991 sub _remove_service {
2992     my ($v, $r, $tag) = @_;
2993     if (!defined $v || !defined $r) {
2994         ld_log( _message('ERR0501') );
2995         return;
2996     }
2997
2998     my $vip_id = get_virtual_id_str($v);
2999     if (!defined $vip_id) {
3000         ld_log( _message('ERR0502') );
3001         return;
3002     }
3003     my $oldsrv = ld_read_l7vsadm();
3004     if (!defined $oldsrv) {
3005         ld_log( _message('FTL0201') );
3006         return;
3007     }
3008
3009     if ( !exists $oldsrv->{$vip_id} ) {
3010         ld_log( _message( 'ERR0208', get_ip_port($r), get_ip_port($v) ) );
3011         return;
3012     }
3013
3014     # quiescent check
3015     my $is_quiescent = 0;
3016     if (!defined $tag || $tag ne 'fallback') {
3017         if ( defined $v->{quiescent} && $v->{quiescent} ) {
3018             $is_quiescent = 1;
3019         }
3020     }
3021
3022     my $or = $oldsrv->{$vip_id}{ get_ip_port($r) };
3023     # already removed server
3024     if (!defined $or && !$is_quiescent) {
3025         my $module_key = $v->{module}{name} . q{ } . $v->{module}{key};
3026         ld_log( _message( 'ERR0210', get_ip_port($r), get_ip_port($v), $module_key ) );
3027         return;
3028     }
3029     # already quiescent server
3030     if ( defined $or && $is_quiescent && $or->{weight} == 0 &&
3031          $or->{option}{forward} eq $r->{option}{forward} ) {
3032         my $module_key = $v->{module}{name} . q{ } . $v->{module}{key};
3033         ld_log( _message( 'ERR0211', get_ip_port($r), get_ip_port($v), $module_key ) );
3034         return;
3035     }
3036
3037     if ($is_quiescent) {
3038         if (defined $or) {
3039             ld_edit_real($v, $r, 0);
3040         }
3041         else {
3042             ld_add_real($v, $r, 0);
3043         }
3044         if (!defined $tag || $tag eq 'real') {
3045             ld_log( _message( 'INF0303', get_ip_port($r) ) );
3046         }
3047         elsif ($tag eq 'fallback') {
3048             ld_log( _message( 'INF0304', get_ip_port($r) ) );
3049         } 
3050     }
3051     else {
3052         ld_delete_real($v, $r);
3053         if (!defined $tag || $tag eq 'real') {
3054             ld_log( _message( 'INF0305', get_ip_port($r) ) );
3055         }
3056         elsif ($tag eq 'fallback') {
3057             ld_log( _message( 'INF0306', get_ip_port($r) ) );
3058         } 
3059     }
3060
3061     if ( defined $v->{realdowncallback} && $r->{healthchecked} ) {
3062         system_wrapper( $v->{realdowncallback}, get_ip_port($r) );
3063         ld_log( _message( 'INF0501',  $v->{realdowncallback}, get_ip_port($r) ) );
3064     }
3065     $r->{healthchecked} = 1;
3066 }
3067
3068 # _restore_service
3069 # Make a retore a real server. The opposite of _quiescent_server.
3070 # Should be called by _service_up or fallback_on
3071 # I.e. If you want to change the state of a real server call service_set.
3072 #      If you call this function directly then l7directord will lose track
3073 #      of the state of real servers.
3074 # If the real server exists (which it should) make it quiescent. If it
3075 # doesn't exist, just leave it as it will be added by the _service_up code
3076 # as appropriate.
3077 # pre: v: reference to virtual service to with the real server belongs
3078 #      r: reference to real server to restore.
3079 #      tag: Tag to use for logging. Should be either "real" or "fallback"
3080 # post: real service is taken up from the respective virtual service
3081 #       if it is inactive
3082 # return: none
3083 sub _restore_service {
3084     my ($v, $r, $tag) = @_;
3085     if (!defined $v || !defined $r) {
3086         ld_log( _message('ERR0501') );
3087         return;
3088     }
3089
3090     my $vip_id = get_virtual_id_str($v);
3091     if (!defined $vip_id) {
3092         ld_log( _message('ERR0502') );
3093         return;
3094     }
3095     my $oldsrv = ld_read_l7vsadm();
3096     if (!defined $oldsrv) {
3097         ld_log( _message('FTL0201') );
3098         return;
3099     }
3100
3101     if ( !exists $oldsrv->{$vip_id} ) {
3102         ld_log( _message( 'ERR0207', get_ip_port($r), get_ip_port($v) ) );
3103         return;
3104     }
3105
3106     my $or = $oldsrv->{$vip_id}{ get_ip_port($r) };
3107     # already completely same server exist
3108     if ( defined $or &&
3109          $or->{weight} eq $r->{weight} &&
3110          $or->{option}{forward} eq $r->{option}{forward} ) {
3111         my $module_key = $v->{module}{name} . q{ } . $v->{module}{key};
3112         ld_log( _message( 'ERR0209', get_ip_port($r), get_ip_port($v), $module_key ) );
3113         return;
3114     }
3115
3116     if (defined $or) {
3117         ld_edit_real( $v, $r, $r->{weight} );
3118     }
3119     else {
3120         ld_add_real( $v, $r, $r->{weight} );
3121     }
3122
3123     if (!defined $tag || $tag eq 'real') {
3124         ld_log( _message( 'INF0301', get_ip_port($r) ) );
3125     }
3126     elsif ($tag eq 'fallback') {
3127         ld_log( _message( 'INF0302', get_ip_port($r) ) );
3128     } 
3129
3130     if ( defined $v->{realrecovercallback} && $r->{healthchecked} ){
3131         system_wrapper( $v->{realrecovercallback}, get_ip_port($r) );
3132         ld_log( _message( 'INF0502',  $v->{realrecovercallback}, get_ip_port($r) ) );
3133     }
3134     $r->{healthchecked} = 1;
3135 }
3136
3137 # fallback_on
3138 # Turn on the fallback server for a virtual service if it is inactive
3139 # pre: v: virtual to turn fallback service on for
3140 # post: fallback server is turned on if it was inactive
3141 # return: none
3142 sub fallback_on {
3143     my $v = shift;
3144
3145     my $fallback = fallback_find($v);
3146     if (defined $fallback) {
3147         my $v_r_list = [ [ $v, $fallback->{tcp} ] ];
3148         if ( _status_up($v_r_list, 'fallback') ) {
3149             _restore_service($v, $fallback->{tcp}, 'fallback');
3150         }
3151     }
3152 }
3153
3154 # fallback_off
3155 # Turn off the fallback server for a virtual service if it is active
3156 # pre: v: virtual to turn fallback service off for
3157 # post: fallback server is turned off if it was active
3158 # return: none
3159 sub fallback_off {
3160     my $v = shift;
3161
3162     my $fallback = fallback_find($v);
3163     if (defined $fallback) {
3164         my $v_r_list = [ [ $v, $fallback->{tcp} ] ];
3165         if ( _status_down($v_r_list, 'fallback') ) {
3166             _remove_service($v, $fallback->{tcp}, 'fallback');
3167         }
3168     }
3169 }
3170
3171 # fallback_find
3172 # Determine the fallback for a virtual service
3173 # pre: v: reference to a virtual service
3174 # post: none
3175 # return: $v->{fallback} if defined
3176 #         else undef
3177 sub fallback_find {
3178     my $v = shift;
3179     if (!defined $v) {
3180         ld_log( _message('ERR0501') );
3181         return;
3182     }
3183     return $v->{fallback};
3184 }
3185
3186 # check_cfgfile
3187 # Check configfile change.
3188 # pre: none
3189 # post: check configfile size, and then check md5 sum
3190 # return: 1 if notice file change
3191 #         0 if not notice or not change
3192 sub check_cfgfile {
3193     if (!defined $CONFIG_FILE{path}) {
3194         ld_log( _message('FTL0102') );
3195         return 0;
3196     }
3197
3198     my $mtime = (stat $CONFIG_FILE{path})[9];
3199     if (!defined $mtime) {
3200         ld_log( _message( 'ERR0410', $CONFIG_FILE{path} ) );
3201         return 0;
3202     }
3203
3204     if ( defined $CONFIG_FILE{stattime} && $mtime == $CONFIG_FILE{stattime} ) {
3205         # file mtime is not change
3206         return 0;
3207     }
3208     $CONFIG_FILE{stattime} = $mtime;
3209
3210     my $digest = undef;;
3211     eval {
3212         require Digest::MD5;
3213
3214         my $ctx = Digest::MD5->new();
3215         open my $config, '<', $CONFIG_FILE{path};
3216         $ctx->addfile($config);
3217         $digest = $ctx->hexdigest;
3218         close $config;
3219     };
3220     if ($EVAL_ERROR) {
3221         ld_log( _message( 'ERR0407', $CONFIG_FILE{path} ) );
3222         return 0;
3223     }
3224
3225     if (defined $CONFIG_FILE{checksum} && $digest && 
3226                 $CONFIG_FILE{checksum} ne $digest ) {
3227         ld_log( _message('WRN0101', $CONFIG_FILE{path}) );
3228         $CONFIG_FILE{checksum} = $digest;
3229
3230         if ( defined $CONFIG{callback} && -x $CONFIG{callback} ) {
3231             system_wrapper( $CONFIG{callback} . q{ } . $CONFIG_FILE{path} );
3232             ld_log( _message( 'INF0503',  $CONFIG{callback}, $CONFIG_FILE{path} ) );
3233         }
3234
3235         if ( $CONFIG{autoreload} ) {
3236             ld_log( _message('WRN0102') );
3237             return 1;
3238         }
3239         else {
3240             ld_log( _message('WRN0103') );
3241             return 0;
3242         }
3243     }
3244
3245     $CONFIG_FILE{checksum} = $digest;
3246     return 0;
3247 }
3248
3249 # ld_openlog
3250 # Open logger
3251 # make log rotation work
3252 # pre: log setting
3253 # post: If logger is a file, it opened and closed again as a test
3254 #       If logger is syslog, it is opened so it can be used without
3255 #       needing to be opened again.
3256 #       Otherwiese, nothing is done.
3257 # return: 0 on success
3258 #         1 on error
3259 sub ld_openlog {
3260     my $log_config = shift;
3261     if (!defined $log_config) {
3262         ld_log( _message('ERR0501') );
3263         return 1;
3264     }
3265
3266     if ( $DEBUG_LEVEL > 0 or $CONFIG{supervised} ) {
3267         # Instantly do nothing
3268         return 0;
3269     }
3270
3271     if ( $log_config =~ m{^/}) {
3272         # Open and close the file as a test.
3273         # We open the file each time we want to log to it
3274         eval {
3275             open my $log_file, ">>", $log_config;
3276             close $log_file;
3277         };
3278         if ($EVAL_ERROR) {
3279             ld_log( _message('ERR0118', $log_config) );
3280             return 1;
3281         }
3282     }
3283     else {
3284         # Assume $log_config is a logfacility, log to syslog
3285         setlogsock("unix");
3286         openlog("l7directord", "pid", $log_config);
3287         # FIXME "closelog" not found
3288     }
3289
3290     $PROC_STAT{log_opened} = 1;
3291     return 0;
3292 }
3293
3294 # ld_log
3295 # Log a message.
3296 # pre: message: Message to write
3297 # post: message and timetsamp is written to loged
3298 #       If logger is a file, it is opened and closed again as a
3299 #       primative means to make log rotation work
3300 # return: 0 on success
3301 #         1 on error
3302 sub ld_log {
3303     my $message = shift;
3304     if (!defined $message) {
3305         ld_log( _message('ERR0501') );
3306         return 1;
3307     }
3308
3309     ld_debug(2, $message);
3310     chomp $message;
3311
3312     if ( !$PROC_STAT{log_opened} ) {
3313         return 1;
3314     }
3315
3316     my $now = localtime();
3317     my $line_header = sprintf "[%s|%d] ", $now, $PROC_STAT{pid};
3318     $message =~ s/^/$line_header/mg;
3319
3320     if ( $CONFIG{supervised} ) {
3321         print {*STDOUT} $message . "\n";
3322     }
3323     elsif ( $CONFIG{logfile} =~ m{^/} ) {
3324         eval {
3325             open my $log_file, '>>', $CONFIG{logfile};
3326             flock $log_file, 2; # LOCK_EX
3327             print {$log_file} $message . "\n";
3328             close $log_file;
3329         };
3330         if ($EVAL_ERROR) {
3331             print {*STDERR} _message_only( 'FTL0103', $CONFIG{logfile}, $message ) . "\n";
3332             return 1;
3333         }
3334     }
3335     else {
3336         # Assume LOGFILE is a logfacility, log to syslog
3337         syslog('info', $message);
3338     }
3339     return 0;
3340 }
3341
3342 # ld_debug
3343 # Log a message to a STDOUT.
3344 # pre: priority: priority of message
3345 #      message: Message to write
3346 # post: message is written to STDOUT if $DEBUG_LEVEL >= priority
3347 # return: none
3348 sub ld_debug {
3349     my ($priority, $message) = @_;
3350
3351     if (defined $priority && $priority =~ /^\d+$/ &&
3352         defined $message  && $DEBUG_LEVEL >= $priority) {
3353         chomp $message;
3354         $message =~ s/^/DEBUG[$priority]: /mg;
3355         print {*STDERR} $message . "\n";
3356     }
3357 }
3358
3359 # command_wrapper
3360 # Wrapper around command(qx) to get output
3361 # pre: command to execute
3362 # post: execute command and if it returns non-zero a failure
3363 #       message is logged
3364 # return: return value of command, and output
3365 sub command_wrapper {
3366     my $command = shift;
3367
3368     if ($DEBUG_LEVEL > 2) {
3369         ld_log( _message( 'INF0506', $command) );
3370     }
3371
3372     $command =~ s/([{}\\])/\\$1/g;
3373     my $output = qx($command);
3374     if ($CHILD_ERROR != 0) {
3375         ld_log( _message('ERR0303', $command, $CHILD_ERROR) );
3376     }
3377     return ($CHILD_ERROR, $output);
3378 }
3379
3380 # system_wrapper
3381 # Wrapper around system() to log errors
3382 # pre: LIST: arguments to pass to system()
3383 # post: system() is called and if it returns non-zero a failure
3384 #       message is logged
3385 # return: return value of system()
3386 sub system_wrapper {
3387     my @args = @_;
3388
3389     if ($DEBUG_LEVEL > 2) {
3390         ld_log( _message( 'INF0504', join(q{ }, @args) ) );
3391     }
3392     my $status = system(@args);
3393     if ($DEBUG_LEVEL > 2) {
3394         if ($status != 0) {
3395             ld_log( _message('ERR0301', join(q{ }, @args), $status) );
3396         }
3397     }
3398     return $status;
3399 }
3400
3401 # exec_wrapper
3402 # Wrapper around exec() to log errors
3403 # pre: LIST: arguments to pass to exec()
3404 # post: exec() is called and if it returns non-zero a failure
3405 #       message is logged
3406 # return: return value of exec() on failure
3407 #         does not return on success
3408 sub exec_wrapper {
3409     my @args = @_;
3410
3411     if ($DEBUG_LEVEL > 2) {
3412         ld_log( _message( 'INF0505', join(q{ }, @args) ) );
3413     }
3414     my $status = exec(@args);
3415     if (!$status) {
3416         ld_log( _message('ERR0302', join(q{ }, @args), $status) );
3417     }
3418     return $status;
3419 }
3420
3421 # ld_rm_file
3422 # Remove a file, symink, or anything that isn't a directory
3423 # and exists
3424 # pre: filename: file to delete
3425 # post: If filename does not exist or is a directory an
3426 #       error state is reached
3427 #       Else filename is delete
3428 #       If $DEBUG_LEVEL >=2 errors are logged
3429 # return:  0 on success
3430 #         -1 on error
3431 sub ld_rm_file {
3432     my $filename = shift;
3433     if (!defined $filename) {
3434         ld_log( _message('ERR0411') );
3435         return -1;
3436     }
3437     if (-d $filename) {
3438         ld_log( _message('ERR0401', $filename) );
3439         return -1;
3440     }
3441     if (!-e $filename) {
3442         ld_log( _message('ERR0402', $filename) );
3443         return -1;
3444     }
3445     my $status = unlink $filename;
3446     if ($status != 1) {
3447         ld_log( _message('ERR0403', $filename, $ERRNO) );
3448         return -1;
3449     }
3450     return 0;
3451 }
3452
3453 # is_octet
3454 # See if a number is an octet, that is >=0 and <=255
3455 # pre: alleged_octet: the octect to test
3456 # post: alleged_octect is checked to see if it is valid
3457 # return: 1 if the alleged_octet is an octet
3458 #         0 otherwise
3459 sub is_octet {
3460     my $alleged_octet = shift;
3461     if (!defined $alleged_octet || $alleged_octet !~ /^\d+$/ || $alleged_octet > 255) {
3462         ld_log( _message('ERR0501') );
3463         return 0;
3464     }
3465     return 1;
3466 }
3467
3468 # is_ip
3469 # Check that a given string is an IP address
3470 # pre: alleged_ip: string representing ip address
3471 # post: alleged_ip is checked to see if it is valid
3472 # return: 1 if alleged_ip is a valid ip address
3473 #         0 otherwise
3474 sub is_ip {
3475     my $alleged_ip = shift;
3476
3477     # If we don't have four, . delimited numbers then we have no hope
3478     if (!defined $alleged_ip || $alleged_ip !~ m/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/) {
3479         ld_log( _message('ERR0501') );
3480         return 0;
3481     }
3482
3483     # Each octet must be >=0 and <=255
3484     is_octet($1) or return 0;
3485     is_octet($2) or return 0;
3486     is_octet($3) or return 0;
3487     is_octet($4) or return 0;
3488
3489     return 1;
3490 }
3491
3492 # ip_to_int
3493 # Turn an IP address given as a dotted quad into an integer
3494 # pre: ip_address: string representing IP address
3495 # post: post ip_address is converted to an integer
3496 # return: -1 if an error occurs
3497 #         integer representation of IP address otherwise
3498 sub ip_to_int {
3499     my $ip_address = shift;
3500
3501     if ( !is_ip($ip_address) ) {
3502         return -1;
3503     }
3504     my ($oct1, $oct2, $oct3, $oct4)
3505         = $ip_address =~ m/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
3506
3507     my $result = ($oct1 << 24) + ($oct2 << 16) + ($oct3 << 8) + $oct4;
3508     return $result;
3509 }
3510
3511 # int_to_ip
3512 # Turn an IP address given as an integer into a dotted quad
3513 # pre: ip_address: integer representation of IP address
3514 # post: Decimal is converted to a dotted quad
3515 # return: string representing IP address
3516 sub int_to_ip {
3517     my $ip_address = shift;
3518     if (!defined $ip_address || $ip_address !~ /^\d+$/) {
3519         ld_log( _message('ERR0501') );
3520         return;
3521     }
3522
3523     my $result = sprintf "%d.%d.%d.%d",
3524                      ($ip_address >> 24) & 255,
3525                      ($ip_address >> 16) & 255,
3526                      ($ip_address >> 8 ) & 255,
3527                      ($ip_address      ) & 255;
3528     return $result;
3529 }
3530
3531 # get_ip_port
3532 # Get the service for a virtual or a real
3533 # pre: host: virtual or real to get the service for
3534 # post: none
3535 # return: ip_address:port
3536 sub get_ip_port {
3537     my ($host, $checkport) = @_;
3538     my $server = defined $host && defined $host->{server} && defined $host->{server}{ip}
3539                     ? $host->{server}{ip  } : q{};
3540     my $port   = defined $checkport ? $checkport
3541                : defined $host && defined $host->{server} && defined $host->{server}{port}
3542                     ? $host->{server}{port} : q{};
3543
3544     my $ip_port = $server ne q{} && $port ne q{} ? "$server:$port" : q{};
3545     return $ip_port;
3546 }
3547
3548 # get_health_check_id_str
3549 # Get an id string for a health check process
3550 # pre: r: Real service.
3551 #      v: Virtual service
3552 # post: none
3553 # return: Id string for the health check process
3554 sub get_health_check_id_str {
3555     my ($v, $r) = @_;
3556     if ( !defined $v || !defined $r || !defined $r->{server} ) {
3557         ld_log( _message('ERR0501') );
3558         return;
3559     }
3560
3561     my $ip   = defined $r->{server}{ip  } ? $r->{server}{ip  } : q{};
3562     my $port = defined $v->{checkport   } ? $v->{checkport   } :
3563                defined $r->{server}{port} ? $r->{server}{port} : q{};
3564     my $checktype    = defined $v->{checktype   } ? $v->{checktype   } : q{};
3565     my $service      = defined $v->{service     } ? $v->{service     } : q{};
3566     my $protocol     = defined $v->{protocol    } ? $v->{protocol    } : q{};
3567     my $num_connects = defined $v->{num_connects} ? $v->{num_connects} : q{};
3568     my $request      = defined $r->{request     } ? $r->{request     } : q{};
3569     my $receive      = defined $r->{receive     } ? $r->{receive     } : q{};
3570     my $httpmethod   = defined $v->{httpmethod  } ? $v->{httpmethod  } : q{};
3571     my $virtualhost  = defined $v->{virtualhost } ? $v->{virtualhost } : q{};
3572     my $login        = defined $v->{login       } ? $v->{login       } : q{};
3573     my $password     = defined $v->{passwd      } ? $v->{passwd      } : q{};
3574     my $database     = defined $v->{database    } ? $v->{database    } : q{};
3575     my $customcheck  = defined $v->{customcheck } ? $v->{customcheck } : q{};
3576     my $checkinterval    = defined $v->{checkinterval    } ? $v->{checkinterval    } : q{};
3577     my $checkcount       = defined $v->{checkcount       } ? $v->{checkcount       } : q{};
3578     my $checktimeout     = defined $v->{checktimeout     } ? $v->{checktimeout     } : q{};
3579     my $negotiatetimeout = defined $v->{negotiatetimeout } ? $v->{negotiatetimeout } : q{};
3580     my $retryinterval    = defined $v->{retryinterval    } ? $v->{retryinterval    } : q{};
3581
3582     # FIXME SHOULD change separator. (request, receive, login, passwd ,database may include ':')
3583     my $id = "$ip:$port:$checktype:$service:$protocol:$num_connects:$request:$receive:" .
3584              "$httpmethod:$virtualhost:$login:$password:$database:$customcheck:" .
3585              "$checkinterval:$checkcount:$checktimeout:$negotiatetimeout:$retryinterval";
3586
3587     return $id;
3588 }
3589
3590 # get_virtual_id_str
3591 # Get an id string for a virtual service
3592 # pre: v: Virtual service
3593 # post: none
3594 # return: Id string for the virtual service
3595 sub get_virtual_id_str {
3596     my $v = shift;
3597     if ( !defined $v || !defined $v->{module} ) {
3598         ld_log( _message('ERR0501') );
3599         return;
3600     }
3601
3602     my $ip_port     = get_ip_port($v);
3603     my $protocol    = defined $v->{protocol    } ? $v->{protocol    } : q{};
3604     my $module_name = defined $v->{module}{name} ? $v->{module}{name} : q{};
3605     my $module_key  = defined $v->{module}{key } ? $v->{module}{key } : q{};
3606
3607     my $id = "$protocol:$ip_port:$module_name $module_key";
3608     $id =~ s/ +$//;
3609
3610     return $id;
3611     # [cf] id = "tcp:127.0.0.1:80:cinsert --cookie-name 'monkey'"
3612 }
3613
3614 # get_forward_flag
3615 # Get the l7vsadm flag corresponging to a forwarding mechanism
3616 # pre: forward: Name of forwarding mechanism.
3617 #               Should be masq
3618 # post: none
3619 # return: l7vsadm flag corresponding to the forwading mechanism
3620 #         " " if $forward is unknown
3621 sub get_forward_flag {
3622     my $forward = shift;
3623
3624     if (defined $forward && $forward =~ /^masq$/i) {
3625         return '-m';
3626     }
3627     return q{};
3628 }
3629
3630 # ld_exit
3631 # Exit and log a message
3632 # pre: exit_status: Integer exit status to exit with
3633 #                   0 wiil be used if parameter is omitted
3634 #      message: Message to log when exiting. May be omitted
3635 # post: If exit_status is non-zero or $DEBUG_LEVEL>2 then
3636 #       message logged.
3637 #       Programme exits with exit_status
3638 # return: does not return
3639 sub ld_exit {
3640     my ($exit_status, $message) = @_;
3641     if (defined $exit_status && defined $message) {
3642         ld_log( _message('INF0006', $exit_status, $message) );
3643     }
3644     exit $exit_status;
3645 }
3646
3647 # ld_open_socket
3648 # Open a socket connection
3649 # pre: remote: IP address as a dotted quad of remote host to connect to
3650 #      port: port to connect to
3651 #      protocol: Prococol to use. Should be either "tcp" or "udp"
3652 # post: A Socket connection is opened to the remote host
3653 # return: Open socket
3654 sub ld_open_socket {
3655     require IO::Socket::INET;
3656     my ($remote, $port, $protocol, $timeout) = @_;
3657
3658     my $sock_handle = IO::Socket::INET->new(
3659             PeerAddr => $remote,
3660             PeerPort => $port,
3661             Proto    => $protocol,
3662             Timeout  => $timeout,
3663         );
3664     return $sock_handle;
3665 }
3666
3667 # daemon
3668 # Close and fork to become a daemon.
3669 #
3670 # Notes from unix programmer faq
3671 # http://www.landfield.com/faqs/unix-faq/programmer/faq/
3672 #
3673 # Almost none of this is necessary (or advisable) if your daemon is being
3674 # started by `inetd'.  In that case, stdin, stdout and stderr are all set up
3675 # for you to refer to the network connection, and the `fork()'s and session
3676 # manipulation should *not* be done (to avoid confusing `inetd').  Only the
3677 # `chdir()' step remains useful.
3678 sub ld_daemon {
3679     ld_daemon_become_child();
3680
3681     if (POSIX::setsid() < 0) {
3682         ld_exit( 7, _message_only('ERR0702') );
3683     }
3684
3685     ld_daemon_become_child();
3686
3687     if (chdir('/') < 0) {
3688         ld_exit( 8, _message_only('ERR0703') );
3689     }
3690
3691     close *STDIN;
3692     close *STDOUT;
3693     close *STDERR;
3694
3695     eval { open  *STDIN, '<', '/dev/null'; };
3696     ld_exit(9, _message_only('ERR0704') ) if ($EVAL_ERROR);
3697     eval { open *STDOUT, '>>', '/dev/console'; };
3698     ld_exit(10, _message_only('ERR0705') ) if ($EVAL_ERROR);
3699     eval { open *STDERR, '>>', '/dev/console'; };
3700     ld_exit(10, _message_only('ERR0705') ) if ($EVAL_ERROR);
3701 }
3702
3703 # ld_daemon_become_child
3704 # Fork, kill parent and return child process
3705 # pre: none
3706 # post: process forkes and parent exits
3707 #       All preocess exit with exit status -1 if an error occurs
3708 # return: parent: exits
3709 #         child: none  (this is the process that returns)
3710 sub ld_daemon_become_child {
3711     my $status = fork();
3712     $PROC_STAT{pid} = $PID;
3713
3714     if ($status < 0) {
3715         ld_exit( 6, _message_only('ERR0701', $ERRNO) );
3716     }
3717     if ($status > 0) {
3718         ld_exit( 0, _message_only('INF0005') );
3719     }
3720 }
3721
3722 # ld_gethostbyname
3723 # Wrapper to gethostbyname. Look up the/an IP address of a hostname
3724 # If an IP address is given is it returned
3725 # pre: name: Hostname of IP address to lookup
3726 # post: gethostbyname is called to find an IP address for $name
3727 #       This is converted to a string
3728 # return: IP address
3729 #         undef on error
3730 sub ld_gethostbyname {
3731     my $name = shift;
3732     $name = q{} if !defined $name;
3733     my $addrs = ( gethostbyname($name) )[4] or return;
3734     return Socket::inet_ntoa($addrs);
3735 }
3736
3737 # ld_getservbyname
3738 # Wraper for getservbyname. Look up the port for a service name
3739 # If a port is given it is returned.
3740 # pre: name: Port or Service name to look up
3741 # post: if $name is a number
3742 #         if 0<=$name<=65536 $name is returned
3743 #         else undef is returned
3744 #       else getservbyname is called to look up the port for the service
3745 # return: Port
3746 #         undef on error
3747 sub ld_getservbyname {
3748     my ($name, $protocol) = @_;
3749     $name     = q{} if !defined $name;
3750     $protocol = q{} if !defined $protocol;
3751
3752     if ($name =~ /^\d+$/) {
3753         if ($name > 65535) {
3754             return;
3755         }
3756         return $name;
3757     }
3758
3759     my $port = ( getservbyname($name, $protocol) )[2] or return;
3760     return $port;
3761 }
3762
3763 # ld_gethostservbyname
3764 # Wraper for ld_gethostbyname and ld_getservbyname. Given a server of the
3765 # form ip_address|hostname:port|servicename return hash refs of ip_address and port
3766 # pre: hostserv: Servver of the form ip_address|hostname:port|servicename
3767 #      protocol: Protocol for service. Should be either "tcp" or "udp"
3768 # post: lookups performed as per ld_getservbyname and ld_gethostbyname
3769 # return: { ip => ip_address, port => port }
3770 #         undef on error
3771 sub ld_gethostservbyname {
3772     my ($hostserv, $protocol) = @_;
3773
3774     if (!defined $hostserv || $hostserv !~ /
3775             ^
3776             (\d+\.\d+\.\d+\.\d+|[a-z0-9.-]+) # host or ip
3777             :                                # colon
3778             (\d+|[a-z0-9-]+)                 # serv or port
3779             $
3780         /ix) {
3781         return;
3782     }
3783     my $ip   = $1;
3784     my $port = $2;
3785     $ip   = ld_gethostbyname($ip)              or return;
3786     $port = ld_getservbyname($port, $protocol) or return;
3787
3788     return {ip => $ip, port => $port};
3789 }
3790
3791 # _message_only
3792 # Create message only.
3793 sub _message_only {
3794     my ($code, @message_args) = @_;
3795
3796     my $message_list = {
3797         # health check process exit
3798         FTL0001 => "health_check argument is invalid. Exit this monitor process with status: 1",
3799         FTL0002 => "health_check argument pair, virtual or real structure is invalid. Exit this monitor process with status: 2",
3800         FTL0003 => "Detected down management process (pid: %s). Exit this monitor process with status: 3",
3801         # file fatal error
3802         FTL0101 => "l7vsadm file `%s' is not found or cannot execute.",
3803         FTL0102 => "Config file is not defined. So cannot check configuration change.",
3804         FTL0103 => "Cannot open logfile `%s'. Log message: `%s'",
3805         # command fatal error
3806         FTL0201 => "Result of read from l7vsadm is not defined.",
3807
3808         # exit
3809         ERR0001 => "Initialization error: %s",
3810         ERR0002 => "Configuration error and exit.",
3811         # validation error
3812         ERR0101 => "Invalid value (set natural number) `%s'.",
3813         ERR0102 => "Invalid value (set `yes' or `no') `%s'.",
3814         ERR0103 => "Invalid value (set any word) `%s'.",
3815         ERR0104 => "Invalid value (set `custom', `connect', `negotiate', `ping', `off', `on' "
3816                  . "or positive number) `%s'.",
3817         ERR0105 => "Invalid value (set `lc', `rr' or `wrr') `%s'.",
3818         ERR0106 => "Invalid value (set `http', `https', `ftp', `smtp', `pop', `imap', "
3819                  . "`ldap', `nntp', `dns', `mysql', `pgsql', `sip', or `none') `%s'.",
3820         ERR0107 => "Invalid value (forwarding mode must be `masq') `%s'.",
3821         ERR0108 => "Invalid port number `%s'.",
3822         ERR0109 => "Invalid protocol (protocol must be `tcp') `%s'.",
3823         ERR0110 => "Invalid HTTP method (set `GET' or `HEAD') `%s'.",
3824         ERR0111 => "Invalid module (set `url', `pfilter', `ip', `sslid' or `sessionless') `%s'.",
3825         # ERR0111 => "Invalid module (set `cinsert', `cpassive', `crewrite', `url', `pfilter', `ip', `sslid' or `sessionless') `%s'.",
3826         ERR0112 => "Invalid module key option (`%s' module must set `%s' option) `%s'.",
3827         ERR0113 => "Invalid QoS value (set 0 or 1-999[KMG]. must specify unit(KMG)) `%s'.",
3828         ERR0114 => "Invalid address `%s'.",
3829         ERR0115 => "Invalid address range (first value(%s) must be less than or equal to the second value(%s)) `%s'.",
3830         ERR0116 => "File not found `%s'.",
3831         ERR0117 => "File not found or cannot execute `%s'.",
3832         ERR0118 => "Unable to open logfile `%s'.",
3833         ERR0119 => "Virtual section not found for `%s'.",
3834         ERR0120 => "Unknown config `%s'.",
3835         ERR0121 => "Configuration error. Reading file `%s' at line %d: %s",
3836         ERR0122 => "Caught exception during re-read config file and re-setup l7vsd. (message: %s) "
3837                  . "So config setting will be rollbacked.",
3838         ERR0123 => "`%s' is a required module for checking %s service.",
3839         # operate l7vsd error
3840         ERR0201 => "Failed to add virtual service to l7vsd: `%s %s', output: `%s'",
3841         ERR0202 => "Failed to edit virtual service on l7vsd: `%s %s', output: `%s'",
3842         ERR0203 => "Failed to delete virtual service from l7vsd: `%s %s', output: `%s'",
3843         ERR0204 => "Failed to add server to l7vsd: `%s' ( x `%s %s'), output: `%s'",
3844         ERR0205 => "Failed to edit server on l7vsd: `%s' ( x `%s %s'), output: `%s'",
3845         ERR0206 => "Failed to delete server from l7vsd: `%s' ( x `%s %s'), output: `%s'",
3846         ERR0207 => "Trying add server `%s', but virtual service `%s' is not found.",
3847         ERR0208 => "Trying delete server `%s', but virtual service `%s' is not found.",
3848         ERR0209 => "`%s' was already existed on l7vsd. ( x `%s %s')",
3849         ERR0210 => "`%s' was already deleted on l7vsd. ( x `%s %s')",
3850         ERR0211 => "`%s' was already changed to quiescent state on l7vsd. ( x `%s %s')",
3851         # command error
3852         ERR0301 => "Failed to system `%s' with return: %s",
3853         ERR0302 => "Failed to exec `%s' with return: %s",
3854         ERR0303 => "Failed to command `%s' with return: %s",
3855         # file error
3856         ERR0401 => "Failed to delete file `%s': `Is a directory'",
3857         ERR0402 => "Failed to delete file `%s': `No such file'",
3858         ERR0403 => "Failed to delete file `%s': `%s'",
3859         ERR0404 => "Config file `%s' is not found.",
3860         ERR0405 => "`l7directord.cf' is not found at default search paths.",
3861         ERR0406 => "`l7vsadm' file is not found at default search paths.",
3862         ERR0407 => "Cannot open config file `%s'.",
3863         ERR0408 => "Cannot close config file `%s'.",
3864         ERR0409 => "Cannot open pid file (%s): %s",
3865         ERR0410 => "Cannot get mtime of configuration file `%s'",
3866         ERR0411 => "No delete file specified.",
3867         ERR0412 => "Invalid pid specified. (pid: %s)",
3868         # undefined
3869         ERR0501 => "Some method arguments are undefined.",
3870         ERR0502 => "VirtualService ID is undefined.",
3871         ERR0503 => "HealthCheck ID is undefined.",
3872         ERR0504 => "negotiate function is undefined. So use check_connect function.",
3873         ERR0505 => "custom check script is undefined. So use check_off function.",
3874         # health check process
3875         ERR0601 => "Service up detected. (Real server `%s')",
3876         ERR0602 => "Service down detected. (Real server `%s')",
3877         ERR0603 => "Detected down monitor process (pid: %s). Prepare to re-start health check process. (id: `%s')",
3878         ERR0604 => "Failed to fork() on sub process creation. (id: `%s')",
3879         # daemon
3880         ERR0701 => "Cannot fork for become daemon (errno: `%s') and exit.",
3881         ERR0702 => "Cannot setsid for become daemon and exit.",
3882         ERR0703 => "Cannot chdir for become daemon and exit.",
3883         ERR0704 => "Cannot open /dev/null for become daemon and exit.",
3884         ERR0705 => "Cannot open /dev/console for become daemon and exit.",
3885
3886         # signal
3887         WRN0001 => "l7directord `%s' received signal: %s. Terminate process.",
3888         WRN0002 => "l7directord `%s' received signal: %s. Reload configuration.",
3889         WRN0003 => "Signal TERM send error(pid: %d)",
3890         WRN0004 => "Signal HUP send error(pid: %d)",
3891         # config
3892         WRN0101 => "Configuration file `%s' has changed on disk.",
3893         WRN0102 => "Reread new configuration.",
3894         WRN0103 => "Ignore new configuration.",
3895         # service check OK
3896         WRN0203 => "Service check OK. HTTP response is valid. HTTP response status line is `%s' (real - `%s:%s')",
3897         WRN0204 => "Service check OK. Successfully connect SMTP server. (real - `%s:%s')",
3898         WRN0205 => "Service check OK. Successfully connect POP3 server. (real - `%s:%s')",
3899         WRN0206 => "Service check OK. Successfully connect IMAP server. (real - `%s:%s')",
3900         WRN0207 => "Service check OK. Successfully bind LDAP server. (real - `%s:%s')",
3901         WRN0208 => "Service check OK. NNTP response is valid. `%s' (real - `%s:%s')",
3902         WRN0209 => "Service check OK. Database response is valid. (real - `%s:%s')",
3903         WRN0210 => "Service check OK. Successfully connect socket to server. (real - `%s:%s')",
3904         WRN0211 => "Service check OK. SIP response is valid. `%s' (real - `%s:%s')",
3905         WRN0212 => "Service check OK. Successfully login FTP server. (real - `%s')",
3906         WRN0213 => "Service check OK. Successfully lookup DNS. (real - `%s:%s')",
3907         WRN0214 => "Service check OK. Successfully receive ping response. (real - `%s')",
3908         WRN0215 => "Custom check result OK. (real - `%s')",
3909         # perl warn
3910         WRN0301 => "Perl warning: `%s'",
3911         # service check NG
3912         WRN1001 => "Retry service check `%s' %d more time(s).",
3913         # - http
3914         WRN1101 => "Service check NG. Check URL `%s' is not valid. (real - `%s:%s')",
3915         WRN1102 => "Service check NG. HTTP response is not ok. Response status line is `%s' (real - `%s:%s')",
3916         WRN1103 => "Service check NG. Check string `%s' is not found in HTTP response. (real - `%s:%s')",
3917         # - smtp
3918         WRN1201 => "Service check NG. Cannot connect SMTP server. (real - `%s:%s')",
3919         # - pop3
3920         WRN1301 => "Service check NG. Cannot connect POP3 server. (real - `%s:%s')",
3921         WRN1302 => "Service check NG. Cannot login POP3 server. (real - `%s:%s')",
3922         # - imap
3923         WRN1401 => "Service check NG. Cannot connect IMAP server. (real - `%s:%s')",
3924         WRN1402 => "Service check NG. Cannot login IMAP server. (real - `%s:%s')",
3925         WRN1403 => "Service check NG. Connection timeout from IMAP server in %d seconds. (real - `%s:%s')",
3926         # - ldap
3927         WRN1501 => "Service check NG. Cannot connect LDAP server. (real - `%s:%s')",
3928         WRN1502 => "Service check NG. Connection timeout from LDAP server in %d seconds. (real - `%s:%s')",
3929         WRN1503 => "Service check NG. LDAP bind error. (real - `%s:%s')",
3930         WRN1504 => "Service check NG. Exists %d results (not one) on search Base DN. (real - `%s:%s')",
3931         WRN1505 => "Service check NG. Check string `%s' is not found in Base DN search result. (real - `%s:%s')",
3932         # - nntp
3933         WRN1601 => "Service check NG. Cannot connect NNTP server. (real - `%s:%s')",
3934         WRN1602 => "Service check NG. Connection timeout from NNTP server in %d seconds. (real - `%s:%s')",
3935         WRN1603 => "Service check NG. NNTP response is not ok. `%s' (real - `%s:%s')",
3936         # - sql
3937         WRN1701 => "Service check NG. SQL check must set `database', `login', `passwd' by configuration. (real - `%s:%s')",
3938         WRN1702 => "Service check NG. Cannot connect database or cannot login database. (real - `%s:%s')",
3939         WRN1703 => "Service check NG. Query result has no row. (real - `%s:%s')",
3940         WRN1704 => "Service check NG. Expected %d rows of query results, but got %d rows. (real - `%s:%s')",
3941         WRN1705 => "Service check NG. Connection timeout from database in %d seconds. (real - `%s:%s')",
3942         # - sip
3943         WRN1801 => "Service check NG. SIP check must set `login' by configuration. (real - `%s:%s')",
3944         WRN1802 => "Service check NG. Cannot connect SIP server. (real - `%s:%s')",
3945         WRN1803 => "Service check NG. SIP response is not ok. `%s' (real - `%s:%s')",
3946         WRN1804 => "Service check NG. Connection timeout from SIP server in %d seconds. (real - `%s:%s')",
3947         # - ftp
3948         WRN1901 => "Service check NG. FTP check must set `login', `passwd' by configuration. (real - `%s')",
3949         WRN1902 => "Service check NG. Cannot connect FTP server. (real - `%s')",
3950         WRN1903 => "Service check NG. Cannot login FTP server. (real - `%s')",
3951         WRN1904 => "Service check NG. Cannot chdir to / of FTP server. (real - `%s')",
3952         WRN1905 => "Service check NG. Cannot get file `%s' (real - `%s')",
3953         WRN1906 => "Service check NG. Check string `%s' is not found in file `%s' (real - `%s')",
3954         WRN1907 => "Service check NG. Exception occur during FTP check `%s' (real - `%s')",
3955         WRN1908 => "Service check NG. Connection timeout from FTP server in %d seconds. (real - `%s')",
3956         # - dns
3957         WRN2001 => "Service check NG. DNS check must set `request', `receive' by configuration. (real - `%s:%s')",
3958         WRN2002 => "Service check NG. Connection timeout from DNS server in %d seconds. (real - `%s:%s')",
3959         WRN2003 => "Service check NG. Net::DNS exception occur `%s' (real - `%s:%s')",
3960         WRN2004 => "Service check NG. DNS search `%s' not respond. (real - `%s:%s')",
3961         WRN2005 => "Service check NG. Check string `%s' is not found in search result. (real - `%s:%s')",
3962         # - ping
3963         WRN3101 => "Service check NG. Ping timeout in %d seconds. (real - `%s')",
3964         # - connect
3965         WRN3201 => "Service check NG. Cannot connect socket to server. (errno: `%s') (real - `%s:%s')",
3966         # - custom
3967         WRN3301 => "Custom check NG. Check timeout in %d seconds. (real - `%s')",
3968         WRN3302 => "Custom check NG. `%s' returns %d",
3969
3970         # start stop
3971         INF0001 => "Starting program with command: `%s'",
3972         INF0002 => "Starting l7directord v%s with pid: %d (configuration: `%s')",
3973         INF0003 => "Starting l7directord v%s as daemon. (configuration: `%s')",
3974         INF0004 => "Exit by initialize error.",
3975         INF0005 => "Exit parent process for become daemon",
3976         INF0006 => "Exiting with exit status %d: %s",
3977         INF0007 => "Detected halt flag. Exit this monitor process with status: 0",
3978         INF0008 => "Reached end of `main'",
3979         # stderr
3980         INF0101 => "l7directord for `%s' is running with pid: %d",
3981         INF0102 => "l7directord stale pid file %s for %s",
3982         INF0103 => "Other l7directord process is running. (pid: %d)",
3983         INF0104 => "l7directord process is not running.",
3984         # l7vsd
3985         INF0201 => "Add virtual service to l7vsd: `%s %s'",
3986         INF0202 => "Edit virtual service on l7vsd: `%s %s'",
3987         INF0203 => "Delete virtual service from l7vsd: `%s %s'",
3988         INF0204 => "Add server to l7vsd: `%s' ( x `%s %s') (weight set to %d)",
3989         INF0205 => "Edit server on l7vsd: `%s' ( x `%s %s') (weight set to %d)",
3990         INF0206 => "Delete server from l7vsd: `%s' ( x `%s %s')",
3991         # server change
3992         INF0301 => "Added real server. (`%s')",
3993         INF0302 => "Added fallback server. (`%s')",
3994         INF0303 => "Changed real server to quiescent state. (`%s')",
3995         INF0304 => "Changed fallback server to quiescent state. (`%s')",
3996         INF0305 => "Deleted real server. (`%s')",
3997         INF0306 => "Deleted fallback server. (`%s')",
3998         # health check
3999         INF0401 => "Prepare to start health check process. (id: `%s')",
4000         INF0402 => "Create health check process with pid: %d. (id `%s')",
4001         # run
4002         INF0501 => "Real server down shell execute: `%s %s'",
4003         INF0502 => "Real server recovery shell execute: `%s %s'",
4004         INF0503 => "Config callback shell execute: `%s %s'",
4005         INF0504 => "Running system: `%s'",
4006         INF0505 => "Running exec: `%s'",
4007         INF0506 => "Running command: `%s'",
4008         };
4009
4010     my $message
4011         = exists $message_list->{$code} ? sprintf $message_list->{$code}, @message_args
4012         : "Unknown message. (code:[$code] args:[" . join(q{, }, @message_args) . '])';
4013
4014     return $message;
4015 }
4016
4017 # _message
4018 # Create message by _message_only and add code header.
4019 sub _message {
4020     my ($code, @message_args) = @_;
4021     my $message = _message_only($code, @message_args);
4022     $message = "[$code] $message";
4023     return $message;
4024 }
4025
4026 1;
4027
4028 __END__
4029
4030 =head1 NAME
4031
4032 l7directord - UltraMonkey-L7 Director Daemon
4033
4034 Daemon to monitor remote services and control UltraMonkey-L7
4035
4036
4037 =head1 SYNOPSIS
4038
4039 B<l7directord> [B<-d>] [I<configuration>] {B<start>|B<stop>|B<restart>|B<try-restart>|B<reload>|B<status>|B<configtest>}
4040
4041 B<l7directord> B<-t> [I<configuration>]
4042
4043 B<l7directord> B<-h|--help>
4044
4045 B<l7directord> B<-v|--version>
4046
4047 =head1 DESCRIPTION
4048
4049 B<l7directord> is a daemon to monitor and administer real servers in a
4050 cluster of load balanced virtual servers. B<l7directord> is similar to B<ldirectord>
4051 in terms of functionality except that it triggers B<l7vsadm>.
4052 B<l7directord> typically is started from command line but can be included
4053 to start from heartbeat. On startup B<l7directord> reads the file
4054 B</etc/ha.d/conf/>I<configuration>.
4055 After parsing the file, entries for virtual servers are created on the UltraMonkey-L7.
4056 Now at regular intervals the specified real servers are monitored and if
4057 they are considered alive, added to a list for each virtual server. If a
4058 real server fails, it is removed from that list. Only one instance of
4059 B<l7directord> can be started for each configuration, but more instances of
4060 B<l7directord> may be started for different configurations. This helps to
4061 group clusters of services.  This can be done by putting an entry inside
4062 B</etc/ha.d/haresources>
4063
4064 I<nodename virtual-ip-address l7directord::configuration>
4065
4066 to start l7directord from heartbeat.
4067
4068
4069 =head1 OPTIONS
4070
4071 =over
4072
4073 =item I<configuration>:
4074
4075 This is the name for the configuration as specified in the file
4076 B</etc/ha.d/conf/>I<configuration>
4077
4078 =item B<-d>
4079
4080 Don't start as daemon. Useful for debugging.
4081
4082 =item B<-h>
4083
4084 Help. Print user manual of l7directord.
4085
4086 =item B<-v>
4087
4088 Version. Print version of l7directord.
4089
4090 =item B<-t>
4091
4092 Run syntax tests for configuration files only. The program immediately exits after these syntax parsing tests
4093 with either a return code of 0 (Syntax OK) or return code not equal to 0 (Syntax Error).
4094
4095 =item B<start>
4096
4097 Start the daemon for the specified configuration.
4098
4099 =item B<stop>
4100
4101 Stop the daemon for the specified configuration. This is the same as sending
4102 a TERM signal to the running daemon.
4103
4104 =item B<restart>
4105
4106 Restart the daemon for the specified configuration. The same as stopping and starting.
4107
4108 =item B<try-restart>
4109
4110 Try to restart the daemon for the specified configuration. If l7directord is already running for the
4111 specified configuration, then the same is stopped and started (Similar to restart).
4112 However, if l7directord is not already running for the specified configuration, then an error message
4113 is thrown and the program exits.
4114
4115 =item B<reload>
4116
4117 Reload the configuration file. This is only useful for modifications
4118 inside a virtual server entry. It will have no effect on adding or
4119 removing a virtual server block. This is the same as sending a HUP signal to
4120 the running daemon.
4121
4122 =item B<status>
4123
4124 Show status of the running daemon for the specified configuration.
4125
4126 =item B<configtest>
4127
4128 This is the same as B<-t>.
4129
4130 =back
4131
4132
4133 =head1 SYNTAX
4134
4135 =head2 Description how to write configuration files
4136
4137 =over
4138
4139 =item B<virtual = >I<(ip_address|hostname:portnumber|servicename)>
4140
4141 Defines a virtual service by IP-address (or hostname) and port (or
4142 servicename). All real services and flags for a virtual
4143 service must follow this line immediately and be indented.
4144 For ldirectord, Firewall-mark settings could be set. But for l7directord
4145 Firewall-mark settings cannot be set.
4146
4147 =item B<checktimeout = >I<n>
4148
4149 Timeout in seconds for connect checks. If the timeout is exceeded then the
4150 real server is declared dead.  Default is 5 seconds. If defined in virtual
4151 server section then the global value is overridden.
4152
4153 =item B<negotiatetimeout = >I<n>
4154
4155 Timeout in seconds for negotiate checks. Default is 5 seconds.
4156 If defined in virtual server section then the global value is overridden.
4157
4158 =item B<checkinterval = >I<n>
4159
4160 Defines the number of second between server checks. Default is 10 seconds.
4161 If defined in virtual server section then the global value is overridden.
4162
4163 =item B<retryinterval = >I<n>
4164
4165 Defines the number of second between server checks when server status is NG.
4166 Default is 10 seconds. If defined in virtual server section then the global
4167 value is overridden.
4168
4169 =item B<checkcount = >I<n>
4170
4171 The number of times a check will be attempted before it is considered
4172 to have failed. Note that the checktimeout is additive, so if checkcount
4173 is 3 and checktimeout is 2 seconds and retryinterval is 1 second,
4174 then a total of 8 seconds (2 + 1 + 2 + 1 + 2) worth of timeout will occur
4175 before the check fails. Default is 1. If defined in virtual server section
4176 then the global value is overridden.
4177
4178 =item B<configinterval = >I<n>
4179
4180 Defines the number of second between configuration checks.
4181 Default is 5 seconds.
4182
4183 =item B<autoreload = >[B<yes>|B<no>]
4184
4185 Defines if <l7directord> should continuously check the configuration file
4186 for modification each B<configinterval> seconds. If this is set to B<yes>
4187 and the configuration file changed on disk and its modification time (mtime)
4188 is newer than the previous version, the configuration is automatically reloaded.
4189 Default is B<no>.
4190
4191 =item B<callback = ">I</path/to/callback>B<">
4192
4193 If this directive is defined, B<l7directord> automatically calls
4194 the executable I</path/to/callback> after the configuration
4195 file has changed on disk. This is useful to update the configuration
4196 file through B<scp> on the other heartbeated host. The first argument
4197 to the callback is the name of the configuration.
4198
4199 This directive might also be used to restart B<l7directord> automatically
4200 after the configuration file changed on disk. However, if B<autoreload>
4201 is set to B<yes>, the configuration is reloaded anyway.
4202
4203 =item B<fallback = >I<ip_address|hostname[:portnumber|servicename]> [B<masq>]
4204
4205 the server onto which a web service is redirected if all real
4206 servers are down. Typically this would be 127.0.0.1 with
4207 an emergency page.
4208
4209 This directive may also appear within a virtual server, in which
4210 case it will override the global fallback server, if set.
4211 Only a value of B<masq> can be specified here. The default is I<masq>.
4212
4213 =item B<logfile = ">I</path/to/logfile>B<">|syslog_facility
4214
4215 An alternative logfile might be specified with this directive. If the logfile
4216 does not have a leading '/', it is assumed to be a syslog(3) facility name.
4217
4218 The default is to log directly to the file I</var/log/l7vs/l7directord.log>.
4219
4220 =item B<execute = ">I<configuration>B<">
4221
4222 Use this directive to start an instance of l7directord for
4223 the named I<configuration>.
4224
4225 =item B<supervised>
4226
4227 If this directive is specified, the daemon does not go into background mode.
4228 All log-messages are redirected to stdout instead of a logfile.
4229 This is useful to run B<l7directord> supervised from daemontools.
4230 See http://untroubled.org/rpms/daemontools/ or http://cr.yp.to/daemontools.html
4231 for details.
4232
4233 =item B<quiescent = >[B<yes>|B<no>]
4234
4235 If B<yes>, then when real or fallback servers are determined
4236 to be down, they are not actually removed from the UltraMonkey-L7,
4237 but set weight to zero.
4238 If B<no>, then the real or fallback servers will be removed
4239 from the UltraMonkey-L7. The default is B<yes>.
4240
4241 This directive may also appear within a virtual server, in which
4242 case it will override the global fallback server, if set.
4243
4244 =back
4245
4246
4247 =head2 Section virtual
4248
4249 The following commands must follow a B<virtual> entry and must be indented
4250 with a minimum of 4 spaces or one tab.
4251
4252 =over
4253
4254 =item B<real => I<ip_address|hostname[-E<gt>ip_address|hostname][:portnumber|servicename>] [B<masq>] [I<n>] [B<">I<request>B<", ">I<receive>B<">]
4255
4256 Defines a real service by IP-address (or hostname) and port (or
4257 servicename). If the port is omitted then a 0 will be used.
4258 Optionally a range of IP addresses (or two hostnames) may be
4259 given, in which case each IP address in the range will be treated as a real
4260 server using the given port. The second argument defines the forwarding
4261 method, it must be B<masq> only.  The third argument defines the weight of
4262 each real service. This argument is optional. Default is 1. The last two
4263 arguments are optional too. They define a request-receive pair to be used to
4264 check if a server is alive. They override the request-receive pair in the
4265 virtual server section. These two strings must be quoted. If the request
4266 string starts with I<http://...> the IP-address and port of the real server
4267 is overridden, otherwise the IP-address and port of the real server is used.
4268
4269 =item B<module => I<proto-module module-args [opt-module-args]>
4270
4271 Indicates the module parameter of B<l7directord>. Here B<proto-module>
4272 denotes the protocol module name (For example, pfilter). B<module-args> denotes the
4273 arguments for the protocol module (For example, --pattern-match '*.html*').
4274 B<module-args> is optional only when set B<sessionless>, B<ip> and B<sslid> module to B<proto-module>.
4275 The last argument is optional (For example, --reschedule).
4276
4277 =back
4278
4279 =head2 More than one of these entries may be inside a virtual section:
4280
4281 =over
4282
4283 =item B<maxconn => I<n>
4284
4285 Defines the maximum connection that the virtual service can handle. If the number of
4286 requests cross the maxconn limit, the requests would be redirected to the
4287 sorry server.
4288
4289 =item B<qosup => I<n>[B<K>|B<M>|B<G>]
4290
4291 Defines the bandwidth quota size in bps for up stream. If the number of the
4292 bandwidth is over the qosup limit, a packet to the virtual service will be delayed
4293 until the number of bandwidth become below the qosup limit.
4294 B<K>(kilo), B<M>(mega) and B<G>(giga) unit are available.
4295
4296 =item B<qosdown => I<n>[B<K>|B<M>|B<G>]
4297
4298 Defines the bandwidth quota size in bps for down stream. If the number of the
4299 bandwidth is over the qosdown limit, a packet to the client will be delayed
4300 until the number of bandwidth become below the qosdown limit.
4301 B<K>(kilo), B<M>(mega) and B<G>(giga) unit are available.
4302
4303 =item B<sorryserver =>I<ip_address|hostname[:portnumber|servicename]>
4304
4305 Defines a sorry server by IP-address (or hostname) and port (or
4306 servicename). Firewall-mark settings cannot be set.
4307 If the number of requests to the virtual service cross the maxconn limit, the requests would be
4308 redirected to the sorry server.
4309
4310 =item B<checktype = negotiate>|B<connect>|I<N>|B<ping>|B<custom>|B<off>|B<on>
4311
4312 Type of check to perform. Negotiate sends a request and matches a receive
4313 string. Connect only attempts to make a TCP/IP connection, thus the
4314 request and receive strings may be omitted.  If checktype is a number then
4315 negotiate and connect is combined so that after each N connect attempts one
4316 negotiate attempt is performed. This is useful to check often if a service
4317 answers and in much longer intervals a negotiating check is done. Ping
4318 means that ICMP ping will be used to test the availability of real servers.
4319 Ping is also used as the connect check for UDP services. Custom means that
4320 custom command will be used to test the availability of real servers.
4321 Off means no checking will take place and no real or fallback servers will
4322 be activated.  On means no checking will take place and real servers will
4323 always be activated. Default is I<negotiate>.
4324
4325 =item B<service = ftp>|B<smtp>|B<http>|B<pop>|B<nntp>|B<imap>|B<ldap>|B<https>|B<dns>|B<mysql>|B<pgsql>|B<sip>|B<none>
4326
4327 The type of service to monitor when using checktype=negotiate. None denotes
4328 a service that will not be monitored. If the port specified for the virtual
4329 server is 21, 25, 53, 80, 110, 119, 143, 389, 443, 3306, 5432 or 5060 then
4330 the default is B<ftp>, B<smtp>, B<dns>, B<http>, B<pop>, B<nntp>, B<imap>,
4331 B<ldap>, B<https>, B<mysql>, B<pgsql> or B<sip> respectively.  Otherwise the
4332 default service is B<none>.
4333
4334 =item B<checkport = >I<n>
4335
4336 Number of port to monitor. Sometimes check port differs from service port.
4337 Default is port specified for the real server.
4338
4339 =item B<request = ">I<uri to requested object>B<">
4340
4341 This object will be requested each checkinterval seconds on each real
4342 server.  The string must be inside quotes. Note that this string may be
4343 overridden by an optional per real-server based request-string.
4344
4345 For a DNS check this should the name of an A record, or the address
4346 of a PTR record to look up.
4347
4348 For a MySQL or PostgreSQL checks, this should be a SQL query.
4349 The data returned is not checked, only that the
4350 answer is one or more rows.  This is a required setting.
4351
4352 =item B<receive = ">I<regexp to compare>B<">
4353
4354 If the requested result contains this I<regexp to compare>, the real server
4355 is declared alive. The regexp must be inside quotes. Keep in mind that
4356 regexps are not plain strings and that you need to escape the special
4357 characters if they should as literals. Note that this regexp may be
4358 overridden by an optional per real-server based receive regexp.
4359
4360 For a DNS check this should be any one the A record's addresses or
4361 any one of the PTR record's names.
4362
4363 For a MySQL check, the receive setting is not used.
4364
4365 =item B<httpmethod = GET>|B<HEAD>
4366
4367 Sets the HTTP method, which should be used to fetch the URI specified in
4368 the request-string. GET is the method used by default if the parameter is
4369 not set. If HEAD is used, the receive-string should be unset.
4370
4371 =item B<virtualhost = ">I<hostname>B<">
4372
4373 Used when using a negotiate check with HTTP or HTTPS. Sets the host header
4374 used in the HTTP request.  In the case of HTTPS this generally needs to
4375 match the common name of the SSL certificate. If not set then the host
4376 header will be derived from the request url for the real server if present.
4377 As a last resort the IP address of the real server will be used.
4378
4379 =item B<login = ">I<username>B<">
4380
4381 Username to use to login to FTP, POP, IMAP, MySQL and PostgreSQL servers.
4382 For FTP, the default is anonymous. For POP and IMAP, the default is the
4383 empty string, in which case authentication will not be attempted.
4384 For a MySQL and PostgreSQL, the username must be provided.
4385
4386 For SIP the username is used as both the to and from address
4387 for an OPTIONS query. If unset it defaults to l7directord\@<hostname>,
4388 hostname is derived as per the passwd option below.
4389
4390 =item B<passwd = ">I<password>B<">
4391
4392 Password to use to login to FTP, POP, IMAP, MySQL and PostgreSQL servers.
4393 Default is for FTP is l7directord\@<hostname>, where hostname is the
4394 environment variable HOSTNAME evaluated at run time, or sourced from uname
4395 if unset. The default for all other services is an empty password, in the
4396 case of MySQL and PostgreSQL this means authentication will not be
4397 performed.
4398
4399 =item B<database = ">I<databasename>B<">
4400
4401 Database to use for MySQL and PostgreSQL servers, this is the database that
4402 the query (set by B<receive> above) will be performed against.  This is a
4403 required setting.
4404
4405 =item B<scheduler => I<scheduler_name>
4406
4407 Scheduler to be used by UltraMonkey-L7 for load balancing.
4408 The available schedulers are only B<lc> and B<rr>. The default is I<rr>.
4409
4410 =item B<protocol = tcp>
4411
4412 Protocol to be used. B<l7vsadm> supports only B<tcp>.
4413 Since the virtual is specified as an IP address and port, it would be tcp
4414 and will default to tcp.
4415
4416 =item B<realdowncallback = ">I</path/to/realdowncallback>B<">
4417
4418 If this directive is defined, B<l7directord> automatically calls
4419 the executable I</path/to/realdowncallback> after a real server's status
4420 changes to down. The first argument to the realdowncallback is the real 
4421 server's IP-address and port (ip_address:portnumber).
4422
4423 =item B<realrecovercallback = ">I</path/to/realrecovercallback>B<">
4424
4425 If this directive is defined, B<l7directord> automatically calls
4426 the executable I</path/to/realrecovercallback> after a real server's status
4427 changes to up. The first argument to the realrecovercallback is the real 
4428 server's IP-address and port (ip_address:portnumber).
4429
4430 =item B<customcheck = ">I<custom check command>B<">
4431
4432 If this directive is defined and set B<checktype> to custom, B<l7directord>
4433 exec custom command for real servers health checking. Only if custom command
4434 returns 0, real servers will change to up. Otherwise real servers will change
4435 to down. Custom check command has some macro string. See below.
4436
4437 =over
4438
4439 =item B<_IP_>
4440
4441 Change to real server IP address.
4442
4443 =item B<_PORT_>
4444
4445 Change to real server port number.
4446
4447 =back
4448
4449 =back
4450
4451
4452 =head1 FILES
4453
4454 B</etc/ha.d/conf/l7directord.cf>
4455
4456 B</var/log/l7vs/l7directord.log>
4457
4458 B</var/run/l7directord.>I<configuration>B<.pid>
4459
4460 B</etc/services>
4461
4462 =head1 SEE ALSO
4463
4464 L<l7vsadm>, L<heartbeat>
4465
4466
4467 =head1 AUTHORS
4468
4469 NTT COMWARE
4470
4471 =cut