summaryrefslogtreecommitdiff
path: root/fml/lib/FML
diff options
context:
space:
mode:
authorfukachan <fukachan>2004-03-04 04:30:12 +0000
committerfukachan <fukachan>2004-03-04 04:30:12 +0000
commit55e26f45fb57cb2a6e056af758a24c94ea15bf4a (patch)
treedd022eb101ab7b3ad425e983c0668e34d5df02d4 /fml/lib/FML
parentb74f9240b7cd9c74998b366db4227bb4f83d86cb (diff)
downloadfml8-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.pm109
-rw-r--r--fml/lib/FML/Command/Admin/password.pm76
-rw-r--r--fml/lib/FML/Command/Auth.pm32
-rw-r--r--fml/lib/FML/Command/User/admin.pm186
-rw-r--r--fml/lib/FML/Process/Command.pm795
-rw-r--r--fml/lib/FML/Process/State.pm229
-rw-r--r--fml/lib/FML/Restriction/Command.pm69
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.