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 | |
| 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')
| -rw-r--r-- | fml/lib/FML/Command.pm | 109 | ||||
| -rw-r--r-- | fml/lib/FML/Command/Admin/password.pm | 76 | ||||
| -rw-r--r-- | fml/lib/FML/Command/Auth.pm | 32 | ||||
| -rw-r--r-- | fml/lib/FML/Command/User/admin.pm | 186 | ||||
| -rw-r--r-- | fml/lib/FML/Process/Command.pm | 795 | ||||
| -rw-r--r-- | fml/lib/FML/Process/State.pm | 229 | ||||
| -rw-r--r-- | fml/lib/FML/Restriction/Command.pm | 69 |
7 files changed, 810 insertions, 686 deletions
diff --git a/fml/lib/FML/Command.pm b/fml/lib/FML/Command.pm index 17a287d3..9d7d274c 100644 --- a/fml/lib/FML/Command.pm +++ b/fml/lib/FML/Command.pm @@ -1,10 +1,10 @@ - #-*- perl -*- +#-*- perl -*- # # Copyright (C) 2001,2002,2003,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: Command.pm,v 1.43 2004/01/22 12:35:35 fukachan Exp $ +# $FML: Command.pm,v 1.44.2.1 2004/03/04 04:20:27 fukachan Exp $ # # XXX @@ -108,9 +108,14 @@ sub get_mode { my ($self, $curproc, $command_args) = @_; - # XXX use capital letter for module name used latter. - if ($command_args->{'command_mode'} =~ /admin/i) { - return 'Admin'; + if (defined $command_args->{ command_mode }) { + # XXX use capital letter for module name used latter. + if ($command_args->{'command_mode'} =~ /admin/i) { + return 'Admin'; + } + else { + return 'User'; + } } else { return 'User'; @@ -133,7 +138,7 @@ how to rewrite by rewrite_prompt() method in it. =cut -# Descriptions: rewrite prompt buffer +# Descriptions: rewrite prompt buffer. # Arguments: OBJ($self) # OBJ($curproc) HASH_REF($command_args) STR_REF($rbuf) # Side Effects: none @@ -170,12 +175,12 @@ sub rewrite_prompt return addresses to inform for the command reply. Each module such as C<FML::Command::$MODE::$SOMETING> specifies -recipients by notice_cc_recipient() method in it. +recipients by notice_cc_recipient() method in it if needed. =cut -# Descriptions: return addresses to inform +# Descriptions: return addresses to inform. # Arguments: OBJ($self) OBJ($curproc) HASH_REF($command_args) # Side Effects: none # Return Value: ARRAY_REF @@ -193,6 +198,83 @@ sub notice_cc_recipient $command->notice_cc_recipient($curproc, $command_args); } } + + return []; +} + + +=head2 verify_syntax($curproc, $command_args) + +verify the syntax command string. +return 0 if it looks insecure. + +=cut + + +# Descriptions: verify the syntax command string. +# Arguments: OBJ($self) OBJ($curproc) HASH_REF($command_args) +# Side Effects: none +# Return Value: NUM(1 or 0) +sub verify_syntax +{ + my ($self, $curproc, $command_args) = @_; + my $command = undef; + my $comname = $command_args->{ comname }; + my $mode = $self->get_mode($curproc, $command_args); + my $pkg = "FML::Command::${mode}::${comname}"; + + eval qq{ use $pkg; \$command = new $pkg;}; + unless ($@) { + if ($command->can('verify_syntax')) { + return $command->verify_syntax($curproc, $command_args); + } + } + + return $self->simple_syntax_check($curproc, $command_args); +} + + +# Descriptions: simple syntax checker. +# Arguments: OBJ($self) OBJ($curproc) HASH_REF($command_args) +# Side Effects: none +# Return Value: NUM(1 or 0) +sub simple_syntax_check +{ + my ($self, $curproc, $command_args) = @_; + my $comname = $command_args->{ comname } || ''; + my $comsubname = $command_args->{ comsubname } || ''; + my $options = $command_args->{ options } || []; + + # test pattern + my @test = @$options; + unshift(@test, $comsubname); + unshift(@test, $comname); + $self->safe_regexp_match($curproc, $command_args, \@test); +} + + +# Descriptions: simple syntax of given array by FML::Restriction::Command. +# Arguments: OBJ($self) +# OBJ($curproc) HASH_REF($command_args) ARRAY_REF($testlist) +# Side Effects: none +# Return Value: NUM(1 or 0) +sub safe_regexp_match +{ + my ($self, $curproc, $command_args, $testlist) = @_; + + # simple command syntax check + use FML::Restriction::Command; + my $safe = new FML::Restriction::Command; + if ($safe->command_regexp_match($testlist)) { + return 1; + } + else { + my $command = $command_args->{ masked_original_command }; + $curproc->logerror("insecure command: $command"); + $curproc->reply_message_nl('command.insecure', + "insecure, so ignored."); + return 0; + } } @@ -220,10 +302,7 @@ sub AUTOLOAD # user mode by default # XXX IMPORTANT: user mode if the given mode is invalid. - my $mode = 'User'; - if (defined $command_args->{ command_mode }) { - $mode = $self->get_mode($curproc, $command_args); - } + my $mode = $self->get_mode($curproc, $command_args); my $comname = $AUTOLOAD; $comname =~ s/.*:://; @@ -237,11 +316,7 @@ sub AUTOLOAD my $need_lock = 0; # no lock by default. my $lock_channel = $default_lock_channel; - # we need to authenticate this ? - if ($command->can('auth')) { - $command->auth($curproc, $command_args); - } - + # resource limit check. if ($command->can('check_limit')) { my $n = $command->check_limit($curproc, $command_args); if ($n) { croak("exceed limit");} diff --git a/fml/lib/FML/Command/Admin/password.pm b/fml/lib/FML/Command/Admin/password.pm index 949c08c9..1649a660 100644 --- a/fml/lib/FML/Command/Admin/password.pm +++ b/fml/lib/FML/Command/Admin/password.pm @@ -4,7 +4,7 @@ # All rights reserved. This program is free software; you can # redistribute it and/or modify it under the same terms as Perl itself. # -# $FML: password.pm,v 1.12 2004/01/01 08:41:34 fukachan Exp $ +# $FML: password.pm,v 1.13.2.1 2004/03/04 04:00:20 fukachan Exp $ # package FML::Command::Admin::password; @@ -59,6 +59,59 @@ sub need_lock { 1;} sub lock_channel { return 'command_serialize';} +# Descriptions: rewrite buffer to hide the password phrase in $rbuf +# Arguments: OBJ($self) OBJ($curproc) HASH_REF($command_args) STR_REF($rbuf) +# Side Effects: none +# Return Value: none +sub rewrite_prompt +{ + my ($self, $curproc, $command_args, $rbuf) = @_; + + if (defined $rbuf) { + $$rbuf =~ s/^(.*(password|pass)\s+).*/$1 ********/; + } +} + + +=head2 verify_syntax($curproc, $command_args) + +verify the syntax command string. +return 0 if it looks insecure. + +=cut + + +# Descriptions: verify the syntax command string. +# Arguments: OBJ($self) OBJ($curproc) HASH_REF($command_args) +# Side Effects: none +# Return Value: NUM(1 or 0) +sub verify_syntax +{ + my ($self, $curproc, $command_args) = @_; + my $comname = $command_args->{ comname } || ''; + my $comsubname = $command_args->{ comsubname } || ''; + my $options = $command_args->{ options } || []; + my @test = ($comname); + + # XXX Let original_command be "admin password PASSWORD". + # XXX options = [ 'password', PASSWORD ] (not shifted yet here). + my $i = 0; + DATA: + for my $x (@$options) { + if ($i++ == 1) { + push(@test, "PASSWORD"); + } + else { + push(@test, $x); + } + } + + use FML::Command; + my $dispatch = new FML::Command; + return $dispatch->safe_regexp_match($curproc, $command_args, \@test); +} + + # Descriptions: dummy in the case of command mail. # # [Case 1: command mail] @@ -74,23 +127,12 @@ sub lock_channel { return 'command_serialize';} sub process { my ($self, $curproc, $command_args) = @_; + my $option = $command_args->{ options } || []; - # dummy. see descriptions above. - return 1; -} - - -# Descriptions: rewrite buffer to hide the password phrase in $rbuf -# Arguments: OBJ($self) OBJ($curproc) HASH_REF($command_args) STR_REF($rbuf) -# Side Effects: none -# Return Value: none -sub rewrite_prompt -{ - my ($self, $curproc, $command_args, $rbuf) = @_; - - if (defined $rbuf) { - $$rbuf =~ s/^(.*(password|pass)\s+).*/$1 ********/; - } + # XXX Let original_command be "admin password PASSWORD". + # XXX $option is shifted before this method called, so [ PASSWORD ] now. + my $p = $option->[ 0 ]; + $curproc->command_context_set_admin_password($p); } diff --git a/fml/lib/FML/Command/Auth.pm b/fml/lib/FML/Command/Auth.pm index 9b88d9c0..5fc76037 100644 --- a/fml/lib/FML/Command/Auth.pm +++ b/fml/lib/FML/Command/Auth.pm @@ -4,7 +4,7 @@ # All rights reserved. This program is free software; you can # redistribute it and/or modify it under the same terms as Perl itself. # -# $FML: Auth.pm,v 1.32 2004/01/02 14:50:30 fukachan Exp $ +# $FML: Auth.pm,v 1.33.2.1 2004/03/04 04:21:28 fukachan Exp $ # package FML::Command::Auth; @@ -132,6 +132,7 @@ sub check_admin_member_password my ($self, $curproc, $optargs) = @_; my $function = "check_admin_member_password"; my $cred = $curproc->{ credential }; + my $sender = $cred->sender(); my $config = $curproc->config(); my $status = 0; @@ -142,8 +143,8 @@ sub check_admin_member_password return 0 unless $optargs->{ password }; # get a set of address and password - my $address = $optargs->{ address }; - my $password = $optargs->{ password }; + my $address = $optargs->{ address } || $sender; + my $password = $optargs->{ password } || ''; unless ($address && $password) { # XXX-TODO: please return error message. return 0; @@ -162,6 +163,10 @@ sub check_admin_member_password # o.k. start ... $curproc->lock($lock_channel); + # error details. + my $user_entry_found = 0; + my $password_match = 0; + # search $user in password database map, which has a hash of # { $user => $encrypted_passwrod }. my $maplist = $config->get_as_array_ref('admin_member_password_maps'); @@ -179,15 +184,18 @@ sub check_admin_member_password PASSWORD_ENTRY: for my $r (@$pwent) { my ($u, $p_infile) = split(/\s+/, $r); - my $p_input = $crypt->unix_crypt($password, $p_infile); # 1.1 user match ? ($address syntax is checked above.) if ($cred->is_same_address($u, $address)) { + $user_entry_found = 1; + # 1.2 password match ? + my $p_input = $crypt->unix_crypt($password, $p_infile); if ($p_infile eq $p_input) { if ($debug) { $curproc->log("$function: password match"); } + $password_match = 1; $status = 1; last PASSWORD_ENTRY; } @@ -198,7 +206,18 @@ sub check_admin_member_password $curproc->unlock($lock_channel); - $curproc->logerror("$function: password mismatch") unless $status; + unless ($status) { + # 1. user not found. + unless ($user_entry_found) { + $curproc->logerror("$function: no such user"); + } + # 2. user is found but password is wrong. + else { + unless ($password_match) { + $curproc->logerror("$function: password mismatch"); + } + } + } return $status; } @@ -249,6 +268,9 @@ sub change_password $curproc->log("delete $address from=$map"); } } + else { + $curproc->logerror("$address not found"); + } # add $obj->add( $address, [ $cp, "UNIX_CRYPT" ] ) && $status++; diff --git a/fml/lib/FML/Command/User/admin.pm b/fml/lib/FML/Command/User/admin.pm index d4a2b5ee..a6769fbe 100644 --- a/fml/lib/FML/Command/User/admin.pm +++ b/fml/lib/FML/Command/User/admin.pm @@ -1,10 +1,10 @@ #-*- perl -*- # -# Copyright (C) 2003 Ken'ichi Fukamachi +# Copyright (C) 2003,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: admin.pm,v 1.2 2003/02/09 12:31:42 fukachan Exp $ +# $FML: admin.pm,v 1.3.2.1 2004/03/04 04:02:56 fukachan Exp $ # package FML::Command::User::admin; @@ -13,6 +13,19 @@ use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD); use Carp; +=head1 NAME + +FML::Command::User::admin - entrance for priviledged command world. + +=head1 SYNOPSIS + +See C<FML::Command> for more details. + +=head1 DESCRIPTION + +=cut + + # Descriptions: constructor. # Arguments: OBJ($self) # Side Effects: none @@ -42,22 +55,171 @@ sub rewrite_prompt } -# XXX-TODO: process() is not implementd. correct ? +# Descriptions: need lock or not +# Arguments: none +# Side Effects: none +# Return Value: NUM( 1 or 0) +sub need_lock { 0;} -=head1 NAME +=head2 verify_syntax($curproc, $command_args) -FML::Command::User::admin - dummy. +verify the syntax command string. +return 0 if it looks insecure. -=head1 SYNOPSIS +=cut -See C<FML::Command> for more details. -=head1 DESCRIPTION +# Descriptions: verify the syntax command string. +# Arguments: OBJ($self) OBJ($curproc) HASH_REF($command_args) +# Side Effects: none +# Return Value: NUM(1 or 0) +sub verify_syntax +{ + my ($self, $curproc, $command_args) = @_; + my $command = undef; + my $comname = $command_args->{ comsubname }; + my $pkg = "FML::Command::Admin::${comname}"; + + eval qq{ use $pkg; \$command = new $pkg;}; + unless ($@) { + if ($command->can('verify_syntax')) { + return $command->verify_syntax($curproc, $command_args); + } + } + + use FML::Command; + my $dispatch = new FML::Command; + return $dispatch->simple_syntax_check($curproc, $command_args); +} + + +# Descriptions: interface for priviledged command world. +# Arguments: OBJ($self) OBJ($curproc) HASH_REF($command_args) +# Side Effects: update authentication state if needed. +# Return Value: none +sub process +{ + my ($self, $curproc, $command_args) = @_; + my $command = $command_args->{ original_command }; + + # 1. check already authenticated. if not, try auth. + # authentication rules are defined as $admin_command_restrictions. + # XXX _try_admin_auth() needs to handle several types: + # 1. password auth (one line). + # 2. pgp auth (one file). + unless ($curproc->command_context_get_admin_auth()) { + my $status = $self->_try_admin_auth($curproc, $command_args); + if ($status) { + $curproc->reply_message_nl("command.admin_auth_ok", + "authenticated."); + $curproc->command_context_set_admin_auth(); + } + } + + # 2. run admin command if already authenticated. + # if not, fatal error. + if ($curproc->command_context_get_admin_auth()) { + my $class = $command_args->{ comsubname } || ''; + $self->_execute_admin_command($curproc, $command_args, $class); + } + else { + $curproc->logerror("admin: not auth, cannot run \"$command\""); + $curproc->reply_message_nl("command.admin_auth_fail", + "not authenticated."); + $curproc->command_context_set_stop_process(); + croak("admin authentication failed."); + } +} + + +# Descriptions: authenticate the currrent process sender as an admin. +# Arguments: OBJ($self) OBJ($curproc) HASH_REF($command_args) +# Side Effects: none +# Return Value: NUM(1 or 0) +sub _try_admin_auth +{ + my ($self, $curproc, $command_args) = @_; + my $config = $curproc->config(); + my $rules = $config->get_as_array_ref('admin_command_restrictions'); + my $cred = $curproc->{ credential }; + my $sender = $cred->sender(); + my $optargs = { address => $sender }; + my $class = $command_args->{ comsubname } || ''; + my $is_auth = 0; + + # XXX-TODO: configurable. + # prepare() for later use. + if ($class eq 'pass' || $class eq 'password') { + $self->_execute_admin_command($curproc, $command_args, $class); + my $p = $curproc->command_context_get_admin_password(); + $optargs->{ password } = $p || ''; + } + + use FML::Command::Auth; + my $auth = new FML::Command::Auth; + for my $rule (@$rules) { + $is_auth = $auth->$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"); + } + } + + # deny transition to admin mode by default + return 0; +} + + +# Descriptions: execute admin command. +# Arguments: OBJ($self) OBJ($curproc) HASH_REF($command_args) STR($class) +# Side Effects: none +# Return Value: none +sub _execute_admin_command +{ + my ($self, $curproc, $command_args, $class) = @_; + my $args = $self->_prepare_command_args($curproc, $command_args); + + use FML::Command; + my $dispatch = new FML::Command; + $dispatch->$class($curproc, $args); +} + + +# Descriptions: adjust $command_args for admin mode. +# Arguments: OBJ($self) OBJ($curproc) HASH_REF($command_args) +# Side Effects: none +# Return Value: none +sub _prepare_command_args +{ + my ($self, $curproc, $command_args) = @_; + + # duplicate $command_args HASH_REF. + my $args = {}; + for my $k (keys %$command_args) { + $args->{ $k } = $command_args->{ $k }; + } + $args->{ command_mode } = 'Admin'; + + # we need to shift "options" by one column in admin command. + # e.g. for "admin add some thing", + # options = [ add, some, thing ] => [ some, thing ] + my @options = @{ $command_args->{ options } }; + shift @options; + $args->{ options } = \@options; + + return $args; +} -In fact, this module is dummy but provide utility for the case auth -fail. It rewrites buffer to hide the password phrase in command -prompt. =head1 CODING STYLE @@ -69,7 +231,7 @@ Ken'ichi Fukamachi =head1 COPYRIGHT -Copyright (C) 2003 Ken'ichi Fukamachi +Copyright (C) 2003,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. 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; diff --git a/fml/lib/FML/Restriction/Command.pm b/fml/lib/FML/Restriction/Command.pm index 86ff7200..2d73d9f3 100644 --- a/fml/lib/FML/Restriction/Command.pm +++ b/fml/lib/FML/Restriction/Command.pm @@ -1,10 +1,10 @@ #-*- perl -*- # -# Copyright (C) 2002,2003 Ken'ichi Fukamachi +# Copyright (C) 2002,2003,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: Command.pm,v 1.8 2002/12/15 15:17:18 fukachan Exp $ +# $FML: Command.pm,v 1.9.4.1 2004/03/04 04:07:54 fukachan Exp $ # package FML::Restriction::Command; @@ -24,22 +24,30 @@ collection of utility functions used in command routines. =head1 METHODS -=head2 is_secure_command_string($str) +=cut -check if $str string looks secure ? -return 1 if secure. -=cut +# Descriptions: constructor. +# Arguments: OBJ($self) OBJ($curproc) +# Side Effects: none +# Return Value: OBJ +sub new +{ + my ($self, $curproc) = @_; + my ($type) = ref($self) || $self; + my $me = { _curproc => $curproc }; + return bless $me, $type; +} -# Descriptions: $s looks secure ? -# Arguments: STR($s) +# Descriptions: $s looks secure as a command ? +# Arguments: OBJ($self) STR($s) # Side Effects: none # History: fml 4.0's SecureP() # Return Value: NUM(1 or 0) -sub is_secure_command_string +sub _is_secure_command_string { - my ($s) = @_; + my ($self, $s) = @_; # 0. clean up $s =~ s/^\s*\#\s*//o; # remove ^# @@ -69,6 +77,45 @@ sub is_secure_command_string } +# Descriptions: incremental regexp match for the given data. +# Arguments: OBJ($self) VAR_ARGS($data) +# Side Effects: none +# Return Value: NUM(>0 or 0) +sub command_regexp_match +{ + my ($self, $data) = @_; + my $r = 0; + + use FML::Restriction::Base; + my $safe = new FML::Restriction::Base; + + if (ref($data)) { + if (ref($data) eq 'ARRAY') { + DATA: + for my $x (@$data) { + next DATA unless $x; + + unless ($safe->regexp_match('command', $x)) { + $r = 0; + last DATA; + } + else { + $r++; + } + } + } + else { + croak("FML::Restriction::Command: wrong data"); + } + } + else { + $r = $safe->regexp_match('command', $data); + } + + return $r; +} + + =head1 CODING STYLE See C<http://www.fml.org/software/FNF/> on fml coding style guide. @@ -79,7 +126,7 @@ Ken'ichi Fukamachi =head1 COPYRIGHT -Copyright (C) 2002,2003 Ken'ichi Fukamachi +Copyright (C) 2002,2003,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. |
