diff options
| author | fukachan <fukachan> | 2004-03-04 04:30:12 +0000 |
|---|---|---|
| committer | fukachan <fukachan> | 2004-03-04 04:30:12 +0000 |
| commit | 55e26f45fb57cb2a6e056af758a24c94ea15bf4a (patch) | |
| tree | dd022eb101ab7b3ad425e983c0668e34d5df02d4 /fml/lib/FML/Process | |
| parent | b74f9240b7cd9c74998b366db4227bb4f83d86cb (diff) | |
| download | fml8-55e26f45fb57cb2a6e056af758a24c94ea15bf4a.tar.gz fml8-55e26f45fb57cb2a6e056af758a24c94ea15bf4a.tar.bz2 fml8-55e26f45fb57cb2a6e056af758a24c94ea15bf4a.zip | |
merge new command mail framework [from command-reconstruct branch]
Diffstat (limited to 'fml/lib/FML/Process')
| -rw-r--r-- | fml/lib/FML/Process/Command.pm | 795 | ||||
| -rw-r--r-- | fml/lib/FML/Process/State.pm | 229 |
2 files changed, 400 insertions, 624 deletions
diff --git a/fml/lib/FML/Process/Command.pm b/fml/lib/FML/Process/Command.pm index ce7d73c1..2cd493ea 100644 --- a/fml/lib/FML/Process/Command.pm +++ b/fml/lib/FML/Process/Command.pm @@ -3,7 +3,7 @@ # Copyright (C) 2000,2001,2002,2003,2004 Ken'ichi Fukamachi # All rights reserved. # -# $FML: Command.pm,v 1.98 2004/01/31 04:06:31 fukachan Exp $ +# $FML: Command.pm,v 1.99.2.1 2004/03/04 04:03:58 fukachan Exp $ # package FML::Process::Command; @@ -14,7 +14,12 @@ use Carp; use FML::Log qw(Log LogWarn LogError); use FML::Config; use FML::Process::Kernel; -@ISA = qw(FML::Process::Kernel); +use FML::Process::State; +@ISA = qw(FML::Process::State FML::Process::Kernel); + + +my ($num_total, $num_error, $num_ignored, $num_processed) = (0, 0, 0, 0); +my %cc_recipient = (); =head1 NAME @@ -194,43 +199,13 @@ sub run if ($eval) { eval qq{ $eval; }; $curproc->logwarn($@) if $@; } unless ($curproc->is_refused()) { - # permit_xxx() sets the error reason at "check_restriction" in pcb. - if ($curproc->permit_command()) { - $curproc->_evaluate_command_lines(); - } - # XXX reject command use irrespective of requests from admins/users. - # XXX rejection of admin use occurs in _evaluate_command_lines() - # XXX not here. - # XXX possible cases are from "system_special_accounts" or - # XXX from a not member. - else { - # check the error reason by permit_command(). - my $reason = $pcb->get("check_restrictions", "deny_reason"); - if (defined($reason) && - ($reason eq 'reject_system_special_accounts')) { - my $s = "deny request from system accounts"; - $curproc->reply_message_nl("error.system_special_accounts",$s); - } - else { - $curproc->reply_message_nl("error.not_member", - "deny request from a not member"); - } - - # append the incoming message into the error message sent back - # as the reference. - my $msg = $curproc->incoming_message(); - $curproc->reply_message( $msg ); - - # add header info. - $curproc->reply_message_add_header_info(); - - unless (defined $reason) { $reason = 'unknown';} - $curproc->log("deny command. reason=$reason"); - } + $curproc->_command_process_loop(); + $curproc->_add_reply_message_trailor(); } else { $curproc->logerror("ignore this request."); } + $eval = $config->get_hook( 'command_run_end_hook' ); if ($eval) { eval qq{ $eval; }; $curproc->logwarn($@) if $@; } } @@ -288,659 +263,231 @@ sub finish } -# Descriptions: check message of the current process -# whether it contains keyword e.g. "confirm". -# Arguments: OBJ($curproc) ARRAY_REF($ra_data) -# Side Effects: none -# Return Value: HASH_REF -sub _check_context -{ - my ($curproc, $ra_data) = @_; +=head1 LOCAL LIBRARIES - # XXX $ra_data = [ split(/\n/, $message ) ]; - use FML::Command::DataCheck; - my $check = new FML::Command::DataCheck; - my $data = $check->find_special_keyword($curproc, $ra_data); +=cut - # current process tries to confirm the previous result e.g. subscribe. - $data->{ under_confirmation } = $data->{ confirm_keyword } ? 1 : 0; - # XXX-TODO: $data is validated already ? - # XXX-TODO: e.g. check if "confirm_id" is included in cache? - return $data; -} +# Descriptions: scan message body and call command switch (wrapper). +# Arguments: OBJ($curproc) +# Side Effects: loading FML::Command::command. +# prepare messages to return. +# Return Value: none +sub _command_process_loop +{ + my ($curproc) = @_; + my $config = $curproc->config(); + my $rbody = $curproc->incoming_message_body(); + my $msg = $rbody->find_first_plaintext_message(); + my $comlines = $msg->message_text_as_array_ref(); + my $context = {}; + # firstly, prompt (for politeness :) to show processing ... + $curproc->reply_message("result for your command requests follows:"); -# Descriptions: check command (specified in $opts) is valid and permitted -# in the configuration. -# Arguments: OBJ($curproc) STR($level) HASH_REF($opts) -# Side Effects: none -# Return Value: NUM(1 or 0) -sub _config_permit_command -{ - my ($curproc, $level, $opts) = @_; - my $config = $curproc->config(); - my $cred = $curproc->{ credential }; # user credential - my $prompt = $config->{ command_mail_reply_prompt } || '>>>'; - my $comname = $opts->{ comname }; - my $command = $opts->{ command }; + # the main loop to analyze each command at each line. + COMMAND: + for my $orig_command (@$comlines) { + next COMMAND if $orig_command =~ /^\s*$/o; # ignore empty lines - # XXX-TODO: case sensitive ? - # use of this command is allowed in FML::Config or not ? - if ($config->has_attribute("commands_for_$level", $comname)) { - return 1; # o.k. accpet this command. - } - else { - if ($level eq 'admin' || $level eq 'user') { - $curproc->log("commands_for_$level has no $comname"); - $curproc->reply_message("\n$prompt $command"); - $curproc->reply_message_nl('command.not_command', - "not command, ignored."); - } - elsif ($level eq 'stranger') { - ; # ignored. - } - else { - $curproc->logwarn("unknown level=$level"); - } + $num_total++; # the total numer of non null lines - return 0; - } -} + if ($debug) { # save raw command buffer. + $curproc->log("(debug) input[$num_total]: $orig_command"); + } + # XXX analyze the iput command and set info into $context. + $context = $curproc->command_context_init($orig_command); -# Descriptions: validate command syntax by FML::Restriction. -# Arguments: OBJ($curproc) HASH_REF($status) HASH_REF($cominfo) -# Side Effects: none -# Return Value: NUM(1 or 0) -sub _is_safe_syntax -{ - my ($curproc, $status, $cominfo) = @_; - my $config = $curproc->config(); - my $prompt = $config->{ command_mail_reply_prompt } || '>>>'; - my $level = $status->{ level }; - my $command = $cominfo->{ command }; + # XXX call command actually. + $curproc->_command_switch($context); - # simple command syntax check - use FML::Restriction::Command; - if (FML::Restriction::Command::is_secure_command_string( $command )) { - return 1; - } - else { - if ($level eq 'admin') { - $curproc->logerror("insecure command: $command"); - $curproc->reply_message("\n$prompt $command"); - $curproc->reply_message_nl('command.insecure', - "insecure, so ignored."); - } - elsif ($level eq 'stranger' || $level eq 'user') { - ; # ignored. - } - else { - $curproc->logwarn("unknown level=$level"); + # XXX error handlings. + # 1. command evaluation ends. + # it should be notified by using $curproc->stop_this_process(). + if ($curproc->command_context_get_stop_process()) { + $curproc->logerror("command processing stop."); + $curproc->reply_message_nl('command.stop', "stopped."); + last COMMAND; } - - return 0; } } -# Descriptions: parse command buffer to prepare several info -# after use. return info as HASH_REF. -# Arguments: OBJ($curproc) STR($fixed_command) +# Descriptions: check configuration. if ok, call each command +# via _command_execute(). +# Arguments: OBJ($curproc) HASH_REF($context) # Side Effects: none -# Return Value: HASH_REF -sub _parse_command_args +# Return Value: none +sub _command_switch { - my ($curproc, $fixed_command) = @_; + my ($curproc, $context) = @_; my $config = $curproc->config(); - my $ml_name = $config->{ ml_name }; - my $argv = $curproc->command_line_argv(); - - my ($comname, $comsubname) = _get_command_name($fixed_command); - - use FML::Command::DataCheck; - my $check = new FML::Command::DataCheck; - my $options = $check->parse_command_arguments($fixed_command, $comname); - - my $cominfo = { - command => $fixed_command, - comname => $comname, - comsubname => $comsubname, - options => $options, - - ml_name => $ml_name, - argv => $argv, - - msg_args => {}, - }; - - return $cominfo; -} - - -# Descriptions: return command name ( ^\S+ in $command ). -# remove the prepending strings such as \s, #, ... -# Arguments: STR($command) -# Side Effects: none -# Return Value: ARRAY -sub _get_command_name -{ - my ($command) = @_; - - use FML::Command::DataCheck; - my $check = new FML::Command::DataCheck; - $check->parse_command_buffer($command) -} - - -# Descriptions: authenticate the currrent process sender as an admin. -# Arguments: OBJ($curproc) HASH_REF($optargs) -# Side Effects: none -# Return Value: NUM(1 or 0) -sub _try_admin_auth -{ - my ($curproc, $optargs) = @_; - my $is_auth = 0; - my $obj = undef; - - eval q{ - use FML::Command::Auth; - $obj = new FML::Command::Auth; - }; - unless ($@) { - my $config = $curproc->config(); - my $rules = $config->get_as_array_ref('admin_command_restrictions'); - for my $rule (@$rules) { - $is_auth = $obj->$rule($curproc, $optargs); - - # reject as soon as possible - if ($is_auth eq '__LAST__') { - $curproc->log("admin: rejected by $rule"); - return 0; - } - elsif ($is_auth) { - $curproc->log("admin: auth by $rule"); - return $is_auth; - } - else { - $curproc->log("admin: not match rule=$rule") if $debug; - } - } - } - else { - $curproc->logerror("fail to load FML::Command::Auth"); - return 0; - } - - # deny transition to admin mode by default - return 0; -} - + my $prompt = $config->{ command_mail_reply_prompt } || '>>>'; + my $comname = $context->{ comname }; + my $pcb = $curproc->pcb(); -# Descriptions: determine $mode and $level for the current command (line). -# We apply this function for each line in command request. -# $mode and $level change line by line. -# Arguments: OBJ($curproc) HASH_REF($status) HAS_REF($command_info) -# Side Effects: update $status, $command_info -# Return Value: STR -sub _get_command_mode -{ - my ($curproc, $status, $command_info) = @_; - my $config = $curproc->config(); - my $command = $command_info->{ command }; - my $comname = $command_info->{ comname }; - my $comsubname = $command_info->{ comsubname }; - my $is_auth = $status->{ is_auth }; - my $is_admin = $status->{ is_admin }; - my $is_member = $status->{ is_member }; - my $confirm_id = $status->{ context }->{ confirm_keyword }; - my $is_confirm = $status->{ context }->{ under_confirmation }; - - # special traps are needed for "confirm" and "admin" commands. - my $confirm_prefix = $config->{ confirm_command_prefix }; - my $admin_prefix = $config->{ privileged_command_prefix }; - - # cheap sanity - return '__NEXT__' unless defined $command; - return '__NEXT__' unless $command; - - # XXX-TODO: $command is clean-ed up already here? - # XXX-TODO: If so, we should check $command =~ /^$confirm_prefix\s+/ ? - # XXX-TODO: it is easy for us not to use regexp in this case? - - # Case: "confirm" command. For examle, "confirm ..." or "> confirm ...", - # we need to trap all cases including confirm keyword. - # It is exceptional so that a stranger can use. - # We need to validate commands except for "confirmation" - # if $confirm_id is 1, this message must be confirmation reply. - if ($command =~ /$confirm_prefix\s+/ && $confirm_id) { - # XXX $command may be "> confirm chaddr ...". - $comname = $confirm_prefix; # comname = confirm - $command =~ s/^.*$comname/$comname/; # normalize $command - my $opts = { comname => $comname, command => $command }; - - # 1. $config permits this command for a stranger? - # 2. This condition permits "confirm" command for a member, - # since $commands_for_stranger contains "confirm" command :-) - # It is effective but wrong since we set $level = stranger - # though we should set up $level = user. - if ($curproc->_config_permit_command("stranger", $opts)) { - $status->{ mode } = 'user'; - $status->{ level } = 'stranger'; - } - else { - # no, we do not accept this command. - $curproc->log("invalid command: $command"); - return '__NEXT__'; - } + if ($debug) { + my $fixed_command = $context->{ fixed_command }; + $curproc->log("execute \"$fixed_command\""); } - # Case: "admin" command is exceptional. try priviledged mode. - elsif ($command =~ /$admin_prefix\s+/) { - if ($is_auth) { - $curproc->log("admin: auth-ed already. run <$command>") if $debug; - } - else { # for the first time ? - $curproc->log("admin: try auth"); - - my $sender = $curproc->{'credential'}->{'sender'}; - my $data = $command; - - # XXX-TODO: (password|pass) hard-coded. - $data =~ s/^.*(password|pass)\s+//; - my $optargs = { address => $sender, password => $data }; - - # XXX simple state machine: update $status->{ is_auth } - $is_auth = $curproc->_try_admin_auth($optargs); - $status->{ is_auth } = $is_auth; - $curproc->log("admin: o.k. auth-ed as an ML admin") if $is_auth; - } - - if ($is_admin && $is_auth) { - # XXX-TODO: we need the method normalize_command(). - $comname = $comsubname; - $command =~ s/^.*$comname/admin $comname/; - my $opts = { comname => $comname, command => $command }; - - my $xmode = 'privileged_user'; - if ($curproc->_config_permit_command($xmode, $opts)) { - $status->{ mode } = 'admin'; - $status->{ level } = 'admin'; - $command_info->{ command } = $command; - $command_info->{ comname } = $comname; - } - else { - # no, we do not accept this command. - $curproc->log("invalid command(priv mode): $command"); - return '__NEXT__'; - } - } - else { - # Exapmle: incorrect password - if ($is_admin && (! $is_auth)) { - $status->{ _stop_reason_key } = 'command.auth_fail'; - $status->{ _stop_reason_str } = "not authenticated."; - } - # Example: not admin member (not in members-admin) - elsif ((! $is_admin) && $is_auth) { - $status->{ _stop_reason_key } = 'error.not_admin_member'; - $status->{ _stop_reason_str } = "not admin member."; - $curproc->logerror("not admin member"); - } - # other reasons - else { - $status->{ _stop_reason_key } = 'command.auth_fail'; - $status->{ _stop_reason_str } = "not authenticated."; - } - $curproc->logerror("admin command not authenticated"); - return '__LAST__'; - } - } - # ignore all requests except for confirm command - # when we receive "confirm" command for better security. - elsif ($is_confirm) { - $curproc->log("ignore(confirm stage): $command"); - return '__NEXT__'; + # 1. anonymous user mode firstly. + # no check + if ($config->has_attribute("commands_for_stranger", $comname)) { + $curproc->_command_execute($context); } - # Case: use command (commands "a usual member" can use) - else { - if ($is_member) { - my $opts = { comname => $comname, command => $command }; - if ($curproc->_config_permit_command("user", $opts)) { - $status->{ mode } = 'user'; - $status->{ level } = 'user'; - } - else { - # no, we do not accept this command. - $curproc->log("invalid command: $command"); - return '__NEXT__'; - } + # 2. user mode command. + # admin command module called via user mode "admin" command. + # so admin_commad_restriction is applied in "admin" module hereafter. + elsif ($config->has_attribute("commands_for_user", $comname)) { + # permit_xxx() sets the error reason at "check_restriction" in pcb. + # apply "command_restrictions" here. + if ($curproc->permit_command()) { + $curproc->_command_execute($context); } else { - my $opts = { comname => $comname, command => $command }; - if ($curproc->_config_permit_command("stranger", $opts)) { - $status->{ mode } = 'user'; - $status->{ level } = 'stranger'; + # check the error reason by permit_command(). + my $reason = $pcb->get("check_restrictions", "deny_reason"); + if (defined($reason) && + ($reason eq 'reject_system_special_accounts')) { + my $s = "deny request from system accounts"; + $curproc->reply_message_nl("error.system_special_accounts",$s); } else { - # XXX invalid condition is satisfied. - # XXX emergency stop if admin mode. - if ($status->{ level } eq 'admin') { - $curproc->logerror("command from not member."); - $curproc->logerror("command processing stop."); - return '__LAST__'; - } - # XXX but just ignore this commnad unless admin mode. - else { - $curproc->log("(debug) ignore $command") if $debug; - return '__NEXT__'; - } + $curproc->reply_message_nl("error.not_member", + "deny request from a not member"); } - } - } - - return $status->{ mode }; -} + # append the incoming message into the error message sent back + # as the reference. + my $msg = $curproc->incoming_message(); + $curproc->reply_message( $msg ); -# Descriptions: this command is allowd under the current $mode and $level. -# Arguments: OBJ($curproc) -# STR($mode) -# HASH_REF($status) -# HASH_REF($command_info) -# Side Effects: none -# Return Value: NUM -sub _config_allow_command -{ - my ($curproc, $mode, $status, $command_info) = @_; - my $comname = $command_info->{ comname }; - my $config = $curproc->config(); - my $level = $status->{ level }; - - $curproc->log("(debug) mode=$mode level=$level") if $debug; + # add header info. + $curproc->reply_message_add_header_info(); - if ($config->has_attribute("commands_for_${level}", $comname)) { - $curproc->log("(debug) $comname o.k. under mode=$mode level=$level") if $debug; + unless (defined $reason) { $reason = 'unknown';} + $curproc->log("deny command. reason=$reason"); + } } + # 3. not matched. else { - $curproc->log("deny command: mode=$mode level=$level"); - return 0; - } - - 1; -} - - -=head1 $command_args HASH STRUCTURE - - $command_args = { - args => HASH_REF, - argv => ARRAY_REF, - command => SCALAR, - command_level => SCALAR, - command_mode => SCALAR, - comname => SCALAR, - comsubname => SCALAR, - ml_name => SCALAR, - msg_args => HASH_REF, - options => ARRAY_REF, - }; - -$command_args hash lives shortly within command processing. - -each module in FML::Command::${MODE}::${COMMAND} can use _VARIABLE -within $command_args to share some data between modules called in it. - -=cut - - -# Descriptions: build $command_args for FML::Command execution. -# Arguments: OBJ($curproc) HASH_REF($status) HASH_REF($cominfo) -# Side Effects: none -# Return Value: HASH_REF -sub _gen_command_args -{ - my ($curproc, $status, $cominfo) = @_; - my $xargs = $cominfo; - my $mode = $status->{ mode }; - $xargs->{ command_mode } = $status->{ mode }; - $xargs->{ command_level } = $status->{ level }; - - # we need to modify [ $comsubname, @options ] to [ @options ] - if ($mode eq 'admin' && @{ $xargs->{ options } }) { - shift @{ $xargs->{ options } }; + my $orig_command = $context->{ original_command } || ''; + $curproc->reply_message("\n$prompt $orig_command"); + $curproc->reply_message_nl("command.not_command", "\tno such commnd."); + $curproc->logerror("no such command: $comname"); + $num_ignored++; } - - return $xargs; } -# Descriptions: remove the superflous string before the actual command. -# Arguments: STR($buf) +# Descriptions: actually execute command via FML::Command. +# Arguments: OBJ($curproc) HASH_REF($command_args) # Side Effects: none -# Return Value: STR -sub __clean_up -{ - my ($buf) = @_; - $buf =~ s/^\W+//; - return $buf; -} - - -# Descriptions: set up error message to inform emergency stop. -# Arguments: OBJ($curproc) -# HASH_REF($status) HASH_REF($cominfo) STR($orig_command) -# Side Effects: update reply messages # Return Value: none -sub __stop_here +sub _command_execute { - my ($curproc, $status, $cominfo, $orig_command) = @_; - my $config = $curproc->config(); - my $prompt = $config->{ command_mail_reply_prompt } || '>>>'; - my $key = $status->{ _stop_reason_key }; - my $str = $status->{ _stop_reason_str }; + my ($curproc, $command_args) = @_; + my $config = $curproc->config(); + my $prompt = $config->{ command_mail_reply_prompt } || '>>>'; + my $cred = $curproc->{ credential }; + my $sender = $cred->sender(); + my $msg_args = $command_args->{ msg_args } || {}; use FML::Command; - my $obj = new FML::Command; - if (defined $obj) { - # rewrite prompt e.g. to hide the password - my $command_args = $curproc->_gen_command_args($status, $cominfo); - $obj->rewrite_prompt($curproc, $command_args, \$orig_command); - $curproc->reply_message("\n$prompt $orig_command"); - $curproc->reply_message_nl($key, $str); - $curproc->reply_message_nl('command.stop', "stopped."); - } -} - - -# Descriptions: scan message body and execute approviate command -# with dynamic loading of command definition. -# It resolves your customized command easily. -# Arguments: OBJ($curproc) -# Side Effects: loading FML::Command::command. -# prepare messages to return. -# Return Value: none -sub _evaluate_command_lines -{ - my ($curproc) = @_; - my $config = $curproc->config(); - my $ml_name = $config->{ ml_name }; - my $argv = $curproc->command_line_argv(); - my $prompt = $config->{ command_mail_reply_prompt } || '>>>'; - my $mode = 'unknown'; - my $rbody = $curproc->incoming_message_body(); - my $msg = $rbody->find_first_plaintext_message(); - - # preliminary scanning for message to find "confirm" or "admin" - my $command_lines = $msg->message_text_as_array_ref(); - my $context = $curproc->_check_context($command_lines); - - # [user credential check] - # is_admin: whether From: is a member of admin users. - # is_auth: authenticated or not by e.g. password - my $cred = $curproc->{ credential }; - my $sender = $cred->sender(); - my $is_member = $cred->is_member($sender); - my $is_admin = $cred->is_privileged_member($sender); - my $is_auth = 0; - my $status = { - is_auth => $is_auth, - is_admin => $is_admin, - is_member => $is_member, - mode => $mode, - level => 'unknown', - context => $context, - }; - - - my $eval = $config->get_hook( 'command_run_start_hook' ); - if ($eval) { eval qq{ $eval; }; $curproc->logwarn($@) if $@; } - - # firstly, prompt (for politeness :) to show processing ... - $curproc->reply_message("result for your command requests follows:"); - - # the main loop to analyze each command at each line. - my ($cominfo, $fixed_command); - my ($num_total, $num_ignored, $num_processed) = (0, 0, 0); - my $is_cc_recipient = 1; - my %cc_recipient = (); - COMMAND: - for my $orig_command (@$command_lines) { - next COMMAND if $orig_command =~ /^\s*$/; # ignore empty lines - - $num_total++; # the total numer of non null lines - - $curproc->log("(debug) input: $orig_command") if $debug; # log raw buffer - - # Example: if orig_command = "# help", comname = "help" - $fixed_command = __clean_up($orig_command); - $cominfo = $curproc->_parse_command_args($fixed_command); - $mode = $curproc->_get_command_mode($status, $cominfo); - - # 1. check $mode if the further processing is allowed - if ($mode eq '__NEXT__') { - $num_ignored++; - next COMMAND; - } - elsif ($mode eq '__LAST__') { - $curproc->logerror("command processing stop."); - $curproc->__stop_here($status, $cominfo, $orig_command); - last COMMAND; + my $dispatch = new FML::Command; + if (defined $dispatch) { + # XXX-TODO: configurable + # always cc to the sender. + if (1) { + $msg_args->{ always_cc } = $sender; } - - # [CAUTION] - # mode = { user, admin }; - # level = { strange, user, admin }; - - - # 1.3 valid mode - unless ($mode eq 'user' || $mode eq 'admin') { - $curproc->logerror("command processing stop."); - $curproc->__stop_here($status, $cominfo, $orig_command); - last COMMAND; + # command dependent rewrite prompt e.g. to hide the password + my $masked_command = $command_args->{ original_command }; + $dispatch->rewrite_prompt($curproc, $command_args, \$masked_command); + $command_args->{ masked_original_command } = $masked_command; + + # recipients depends on each command. The list is defined in + # each command module (e.g. FML::Command::User::*) + my $cclist = $dispatch->notice_cc_recipient($curproc, $command_args); + if (defined $cclist && @$cclist) { + my $primary_key = join("-", sort @$cclist); # XXX unique key. + $msg_args->{ recipient } = $cclist; + $cc_recipient{ $primary_key } = $cclist; } - # 2. check $level if this command is allowed in the current $mode ? - unless ($curproc->_config_allow_command($mode, $status, $cominfo)) { - $curproc->reply_message_nl("command.deny", - "\tyou cannot use this command."); - $curproc->log("(debug) ignore $fixed_command"); - $num_ignored++; - next COMMAND; - } + # bulid reply buffer, which is rewritten if needed above. + $curproc->reply_message("\n$prompt $masked_command", $msg_args); + $curproc->log("command: $masked_command"); - # 3. simple syntax check - unless ($curproc->_is_safe_syntax($status, $cominfo)) { - $curproc->logerror("invalid/unsafe syntax"); - $curproc->log("(debug) ignore $fixed_command"); - $num_ignored++; - next COMMAND; + # command dependent syntax checker. + unless ($dispatch->verify_syntax($curproc, $command_args)) { + $curproc->reply_message_nl('command.fail', "fail.", $msg_args); + $curproc->logerror("fail to execute \"$masked_command\""); + return 0; } - # o.k. here we go to execute this command - $curproc->log("execute \"$fixed_command\"") if $debug; - $num_processed++; - - use FML::Command; - my $obj = new FML::Command; - if (defined $obj) { - # arguments to pass off into each method - my $sender = $curproc->{'credential'}->{'sender'}; - my $command_args = $curproc->_gen_command_args($status, $cominfo); - my $msg_args = $command_args->{ msg_args }; - $msg_args->{ always_cc } = $sender; - - # rewrite prompt e.g. to hide the password - $obj->rewrite_prompt($curproc, $command_args, \$orig_command); - - # check if addresses to notice defined ? - # XXX The recipients are dependent on each command. - # XXX Defined in each command module (e.g. FML::Command::User::*) - { - my $a = $obj->notice_cc_recipient($curproc, $command_args); - if (defined $a) { - $msg_args->{ recipient } = $a; - $is_cc_recipient = 1; - $cc_recipient{ join("-", @$a) } = $a; - } - } + # execute $comname command within eval(). + # 1) $dispatch = FML::Command NOT FML::Command::$mode::$command + # 2) $comname must be valid since $comname is one of defined + # command list in $config (see _command_switch() method). + my $comname = $command_args->{ comname }; + eval q{ + $dispatch->$comname($curproc, $command_args); + }; + unless ($@) { + $num_processed++; + $curproc->reply_message_nl('command.ok', "ok.", $msg_args); + } + else { # error trap + my $reason = $@; + $curproc->log($reason); - # reply buffer - $curproc->reply_message("\n$prompt $orig_command", $msg_args); - $curproc->log($orig_command); - - # execute command ($comname method) under eval(). - # XXX $obj = FML::Command object NOT FML::Command::$mode::$command - # XXX-TODO: validate $comname is safe syntax or not. - my $comname = $cominfo->{ comname }; - eval q{ - $obj->$comname($curproc, $command_args); - }; - unless ($@) { - $curproc->reply_message_nl('command.ok', "ok.", $msg_args); - } - else { # error trap - my $reason = $@; - $curproc->log($reason); + $num_error++; - $curproc->reply_message_nl('command.fail', "fail.", $msg_args); - $curproc->logerror("command ${comname} fail"); + $curproc->reply_message_nl('command.fail', "fail.", $msg_args); + $curproc->logerror("command ${comname} fail"); - if ($reason =~ /^(.*)\s+at\s+/) { - my $reason = $1; - $curproc->log($reason); # pick up reason - } + if ($reason =~ /^(.*)\s+at\s+/) { + my $reason = $1; + $curproc->log($reason); # pick up reason } } - } # END OF FOR LOOP: for my $orig_command (@body) { ... } + } +} - $eval = $config->get_hook( 'command_run_end_hook' ); - if ($eval) { eval qq{ $eval; }; $curproc->logwarn($@) if $@; } + +# Descriptions: add closing message at the tail of result. +# Arguments: OBJ($curproc) +# Side Effects: none +# Return Value: none +sub _add_reply_message_trailor +{ + my ($curproc) = @_; # info $curproc->reply_message("\ncommand processing results:"); $curproc->reply_message(" processed = $num_processed"); + $curproc->reply_message(" error = $num_error"); $curproc->reply_message(" ignored = $num_ignored"); $curproc->reply_message(" total = $num_total"); # send back the original input message if needed. - { - my $msg = $curproc->incoming_message(); + my $msg = $curproc->incoming_message(); - # in the case "confirm" - if ($status->{ context }->{ under_confirmation }) { - # send back original message as a reference - $curproc->reply_message( $msg ); - } + # 1. send back original message as a reference in the case "confirm". + if ($curproc->command_context_get_need_confirm()) { + $curproc->reply_message( $msg ); + } - if (keys %cc_recipient) { - for my $k (keys %cc_recipient) { - my $ra_addr = $cc_recipient{ $k }; - $curproc->log("msg.cc: [ @$ra_addr ]"); - $curproc->reply_message( $msg , { recipient => $ra_addr }); - } + # 2. if cc (carbon copy) is needed. + if (keys %cc_recipient) { + for my $k (keys %cc_recipient) { + my $ra_addr = $cc_recipient{ $k }; + $curproc->log("msg.cc: [ @$ra_addr ]"); + $curproc->reply_message( $msg , { recipient => $ra_addr }); } } } diff --git a/fml/lib/FML/Process/State.pm b/fml/lib/FML/Process/State.pm new file mode 100644 index 00000000..c1089ee3 --- /dev/null +++ b/fml/lib/FML/Process/State.pm @@ -0,0 +1,229 @@ +#-*- perl -*- +# +# Copyright (C) 2004 Ken'ichi Fukamachi +# All rights reserved. This program is free software; you can +# redistribute it and/or modify it under the same terms as Perl itself. +# +# $FML: State.pm,v 1.1.2.1 2004/03/04 04:06:22 fukachan Exp $ +# + +package FML::Process::State; +use strict; +use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD); +use Carp; + +=head1 NAME + +FML::Process::State - interface to handle states within this process + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +=head1 METHODS + +=haed1 BASIC RESTRICTION STATES + +=cut + + + +=haed1 COMMAND PROCESSOER STATES + +=cut + + +# Descriptions: parse $orig_command and set up HASH_REF as +# base information for command processing. +# Arguments: OBJ($curproc) STR($orig_command) +# Side Effects: none +# Return Value: HASH_REF +sub command_context_init +{ + my ($curproc, $orig_command) = @_; + + # Example: if orig_command = "# help", comname = "help" + my $cleanstr = _command_string_clean_up($orig_command); + my $context = $curproc->_build_command_context_template($cleanstr); + + # save original string, set the command mode be "user" by default. + $context->{ command_mode } = "User"; + $context->{ original_command } = $orig_command; + + return $context; +} + + +# Descriptions: parse command buffer to prepare several info +# after use. return info as HASH_REF. +# Arguments: OBJ($curproc) STR($fixed_command) +# Side Effects: none +# Return Value: HASH_REF +sub _build_command_context_template +{ + my ($curproc, $fixed_command) = @_; + my $ml_name = $curproc->ml_name(); + my $ml_domain = $curproc->ml_domain(); + my $argv = $curproc->command_line_argv(); + + use FML::Command::DataCheck; + my $check = new FML::Command::DataCheck; + my ($comname, $comsubname) = $check->parse_command_buffer($fixed_command); + my $options = $check->parse_command_arguments($fixed_command, $comname); + my $cominfo = { + command => $fixed_command, + comname => $comname, + comsubname => $comsubname, + options => $options, + + ml_name => $ml_name, + ml_domain => $ml_domain, + argv => $argv, + + msg_args => {}, + }; + + return $cominfo; +} + + +# Descriptions: remove the superflous string before the actual command. +# Arguments: STR($buf) +# Side Effects: none +# Return Value: STR +sub _command_string_clean_up +{ + my ($buf) = @_; + $buf =~ s/^\W+//o; + return $buf; +} + + +# Descriptions: declare no more further command processing needed. +# Arguments: OBJ($curproc) +# Side Effects: update pcb. +# Return Value: NUM +sub command_context_set_stop_process +{ + my ($curproc) = @_; + my $pcb = $curproc->pcb(); + + $pcb->set("process_command", "stop_now", 1); +} + + +# Descriptions: we stop here or not ? +# Arguments: OBJ($curproc) +# Side Effects: none +# Return Value: NUM +sub command_context_get_stop_process +{ + my ($curproc) = @_; + my $pcb = $curproc->pcb(); + + return( $pcb->get("process_command", "stop_now") || 0 ); +} + + +# Descriptions: set "we need to send back confirmation". +# Arguments: OBJ($curproc) +# Side Effects: update pcb. +# Return Value: NUM +sub command_context_set_need_confirm +{ + my ($curproc) = @_; + my $pcb = $curproc->pcb(); + + $pcb->set("process_command", "need_confirm", 1); +} + + +# Descriptions: check if we need to send back confirmation ? +# Arguments: OBJ($curproc) +# Side Effects: none +# Return Value: NUM +sub command_context_get_need_confirm +{ + my ($curproc) = @_; + my $pcb = $curproc->pcb(); + + return( $pcb->get("process_command", "need_confirm") || 0 ); +} + + +# Descriptions: remote administrator is authenticated. +# Arguments: OBJ($curproc) +# Side Effects: update pcb. +# Return Value: NUM +sub command_context_set_admin_auth +{ + my ($curproc) = @_; + my $pcb = $curproc->pcb(); + + $pcb->set("process_command", "admin_auth", 1); +} + + +# Descriptions: check if remote administrator is authenticated. +# Arguments: OBJ($curproc) +# Side Effects: none +# Return Value: NUM +sub command_context_get_admin_auth +{ + my ($curproc) = @_; + my $pcb = $curproc->pcb(); + + return( $pcb->get("process_command", "admin_auth") || 0 ); +} + + +# Descriptions: store password on memory for later use. +# Arguments: OBJ($curproc) STR($password) +# Side Effects: update pcb. +# Return Value: STR +sub command_context_set_admin_password +{ + my ($curproc, $password) = @_; + my $pcb = $curproc->pcb(); + + $pcb->set("process_command", "admin_password", $password); +} + + +# Descriptions: retrive stored password on memory. +# Arguments: OBJ($curproc) +# Side Effects: none +# Return Value: STR +sub command_context_get_admin_password +{ + my ($curproc) = @_; + my $pcb = $curproc->pcb(); + + return( $pcb->get("process_command", "admin_password") || '' ); +} + + +=head1 CODING STYLE + +See C<http://www.fml.org/software/FNF/> on fml coding style guide. + +=head1 AUTHOR + +Ken'ichi Fukamachi + +=head1 COPYRIGHT + +Copyright (C) 2004 Ken'ichi Fukamachi + +All rights reserved. This program is free software; you can +redistribute it and/or modify it under the same terms as Perl itself. + +=head1 HISTORY + +FML::Process::State appeared in fml8 mailing list driver package. +See C<http://www.fml.org/> for more details. + +=cut + + +1; |
