summaryrefslogtreecommitdiff
path: root/regress/tinymta
diff options
context:
space:
mode:
authorfukachan <fukachan>2006-06-10 01:05:10 +0000
committerfukachan <fukachan>2006-06-10 01:05:10 +0000
commit896c1b1dcfb3ec2d24591852f51b51f5a040f7e7 (patch)
tree487f6735a7b25e017a84e06971c47551453fc747 /regress/tinymta
parent6b5a07fdbff0b60981764f1e8a35d1d8d6fe6d84 (diff)
downloadfml8-896c1b1dcfb3ec2d24591852f51b51f5a040f7e7.tar.gz
fml8-896c1b1dcfb3ec2d24591852f51b51f5a040f7e7.tar.bz2
fml8-896c1b1dcfb3ec2d24591852f51b51f5a040f7e7.zip
Initial revision
Diffstat (limited to 'regress/tinymta')
-rw-r--r--regress/tinymta/.cvsignore2
-rw-r--r--regress/tinymta/00_README25
-rw-r--r--regress/tinymta/Config.pm35
-rw-r--r--regress/tinymta/Drop.pm172
-rw-r--r--regress/tinymta/Log.pm36
-rw-r--r--regress/tinymta/Makefile.in32
-rw-r--r--regress/tinymta/SMTP.pm317
-rwxr-xr-xregress/tinymta/configure25
-rwxr-xr-xregress/tinymta/loader.in299
-rw-r--r--regress/tinymta/tinymta.cf.in11
10 files changed, 954 insertions, 0 deletions
diff --git a/regress/tinymta/.cvsignore b/regress/tinymta/.cvsignore
new file mode 100644
index 00000000..317e354d
--- /dev/null
+++ b/regress/tinymta/.cvsignore
@@ -0,0 +1,2 @@
+@template
+@template.pm
diff --git a/regress/tinymta/00_README b/regress/tinymta/00_README
new file mode 100644
index 00000000..b9c737aa
--- /dev/null
+++ b/regress/tinymta/00_README
@@ -0,0 +1,25 @@
+
+ tinymta
+
+1. 概要
+
+fml8 のライブラリを使って作るシンプルな MTA です。
+
+半分実益をかねていますが、半分はライブラリのテストみたいなものと考えて
+ください。
+
+2. 構成
+
+ /usr/local/libexec/fml/tmdrop
+ キューへいれる。
+ 入力はメール(RFC822)そのもの
+
+ /usr/local/libexec/fml/tmsmtp
+ キューからメールを一つ選択し SMTP 配送を行なう
+
+ /usr/local/etc/fml/tinymta.cf
+ tinymta 固有の設定ファイル
+
+ /usr/local/etc/fml/main.cf
+ fml8 と共通の設定ファイル
+
diff --git a/regress/tinymta/Config.pm b/regress/tinymta/Config.pm
new file mode 100644
index 00000000..823678db
--- /dev/null
+++ b/regress/tinymta/Config.pm
@@ -0,0 +1,35 @@
+#-*- perl -*-
+#
+# Copyright (C) 2006 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$
+#
+
+package TinyMTA::Config;
+use strict;
+use Carp;
+
+# Descriptions: load configuration from $config_cf_file.
+# Arguments: STR($config_cf_file) OBJ($main_cf)
+# Side Effects: none
+# Return Value: OBJ
+sub load_file
+{
+ my ($config_cf_file, $main_cf) = @_;
+
+ my $opts = {};
+ for my $k (keys %$main_cf) {
+ my $key = sprintf("fml_%s", $k);
+ $opts->{ $key } = $main_cf->{ $k };
+ }
+
+ use FML::Config;
+ my $config = new FML::Config $opts;
+ $config->load_file($config_cf_file);
+ return $config;
+}
+
+
+1;
diff --git a/regress/tinymta/Drop.pm b/regress/tinymta/Drop.pm
new file mode 100644
index 00000000..5e87945d
--- /dev/null
+++ b/regress/tinymta/Drop.pm
@@ -0,0 +1,172 @@
+#-*- perl -*-
+#
+# Copyright (C) 2006 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: @template.pm,v 1.10 2006/01/07 13:16:41 fukachan Exp $
+#
+
+package TinyMTA::Drop;
+use strict;
+use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD $global_counter);
+use Carp;
+
+=head1 NAME
+
+TinyMTA::Drop - mail drop wrapper
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=head2 new()
+
+constructor.
+
+=cut
+
+
+# Descriptions: constructor.
+# Arguments: OBJ($self) OBJ($config)
+# Side Effects:
+# Return Value: none
+sub new
+{
+ my ($self, $config) = @_;
+ my ($type) = ref($self) || $self;
+ my $me = { _config => $config };
+ return bless $me, $type;
+}
+
+
+# Descriptions: main routine.
+# Arguments: OBJ($self)
+# Side Effects: none
+# Return Value: none
+sub run
+{
+ my ($self) = @_;
+ my $config = $self->{ _config };
+
+ # 1. prepare queue directory.
+ my $queue_dir = $config->{ queue_dir };
+ unless (-d $queue_dir) {
+ mkdir $queue_dir, 0720;
+ if (-d $queue_dir) {
+ $self->log("$queue_dir created");
+ }
+ }
+
+ # 2. drop the message taken from STDIN into a new queue file.
+ my ($qid, $qf, $qtmp) = $self->queue_filename();
+ my $wh = new FileHandle "> $qtmp";
+ if (defined $wh) {
+ my $buf;
+
+ $wh->autoflush(1);
+ LINE:
+ while ($buf = <STDIN>) {
+ print $wh $buf;
+ }
+ $wh->close();
+ }
+
+ if (-s $qtmp) {
+ if (rename($qtmp, $qf)) {
+ $self->log("queue-in: $qid");
+ }
+ else {
+ $self->logerror("cannot create $qf");
+ croak("cannot create $qf\n");
+ }
+ }
+}
+
+
+# Descriptions: retrun a new queue file name.
+# Arguments: OBJ($self)
+# Side Effects: none
+# Return Value: ARRAY(STR, STR, STR)
+sub queue_filename
+{
+ my ($self) = @_;
+ my $config = $self->{ _config };
+ my $queue_dir = $config->{ queue_dir };
+
+ use FileHandle;
+ my $qid = sprintf("%d.%d.%d", time, $$, ++$global_counter);
+ my $new = File::Spec->catfile($queue_dir, $qid);
+ my $tmp = File::Spec->catfile($queue_dir, ",$qid");
+
+ return($qid, $new, $tmp);
+}
+
+
+# Descriptions: log as normal level.
+# Arguments: OBJ($self) STR($msg)
+# Side Effects: none
+# Return Value: none
+sub log
+{
+ my ($self, $msg) = @_;
+ &TinyMTA::Log::log($msg);
+}
+
+
+# Descriptions: log as error level.
+# Arguments: OBJ($self) STR($msg)
+# Side Effects: none
+# Return Value: none
+sub logerror
+{
+ my ($self, $msg) = @_;
+ &TinyMTA::Log::log("error: $msg");
+}
+
+
+######################################################################
+#
+# dispatcher
+#
+
+# Descriptions: main dispatcher.
+# Arguments: OBJ($main_cf) STR($config_cf_file)
+# Side Effects: none
+# Return Value: none
+sub main::dispatch
+{
+ my ($main_cf, $config_cf_file) = @_;
+
+ my $config = TinyMTA::Config::load_file($config_cf_file, $main_cf);
+ my $obj = new TinyMTA::Drop $config;
+ $obj->run();
+}
+
+
+=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) 2006 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
+
+TinyMTA::Drop appeared in fml8 mailing list driver package.
+See C<http://www.fml.org/> for more details.
+
+=cut
+
+
+1;
diff --git a/regress/tinymta/Log.pm b/regress/tinymta/Log.pm
new file mode 100644
index 00000000..d3d49404
--- /dev/null
+++ b/regress/tinymta/Log.pm
@@ -0,0 +1,36 @@
+#-*- perl -*-
+#
+# Copyright (C) 2006 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$
+#
+
+package TinyMTA::Log;
+use strict;
+use Carp;
+
+# Descriptions: send log message by syslog(3).
+# Arguments: STR($msg)
+# Side Effects: none
+# Return Value: OBJ
+sub log
+{
+ my ($msg) = @_;
+
+ use File::Basename;
+ my $myname = basename($0);
+ my $ident = "tinymta/$myname";
+ my $logopt = "pid";
+ my $facility = "local0";
+ my $priority = "info";
+
+ use Sys::Syslog;
+ openlog($ident, $logopt, $facility);
+ syslog($priority, $msg);
+ closelog();
+}
+
+
+1;
diff --git a/regress/tinymta/Makefile.in b/regress/tinymta/Makefile.in
new file mode 100644
index 00000000..137b33b5
--- /dev/null
+++ b/regress/tinymta/Makefile.in
@@ -0,0 +1,32 @@
+#
+# $FML$
+#
+
+LIBS = @LIBS@
+
+CC = @CC@
+CFLAGS = @CFLAGS@
+LDFLAGS = @LDFLAGS@
+
+INSTALLCMD = @INSTALL@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+bindir = @bindir@
+mandir = @mandir@
+config_dir = @fmlconfdir@
+libexec_dir = @libexecdir@/fml
+
+
+install:
+ test -f $(config_dir)/tinymta.cf ||\
+ $(INSTALLCMD) -c -m 0644 tinymta.cf $(config_dir)
+ $(INSTALLCMD) -c -m 0755 tmdrop $(libexec_dir)
+ $(INSTALLCMD) -c -m 0755 tmsmtp $(libexec_dir)
+
+clean:
+ rm -f *~
+ rm -f Makefile
+ rm -f loader tmdrop tmsmtp
+ rm -f tinymta.cf
+
diff --git a/regress/tinymta/SMTP.pm b/regress/tinymta/SMTP.pm
new file mode 100644
index 00000000..2abf2470
--- /dev/null
+++ b/regress/tinymta/SMTP.pm
@@ -0,0 +1,317 @@
+#-*- perl -*-
+#
+# Copyright (C) 2006 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: @template.pm,v 1.10 2006/01/07 13:16:41 fukachan Exp $
+#
+
+package TinyMTA::SMTP;
+use strict;
+use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD);
+use Carp;
+
+=head1 NAME
+
+TinyMTA::SMTP - smtp
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head1 METHODS
+
+=head2 new()
+
+constructor.
+
+=cut
+
+
+# Descriptions: constructor.
+# Arguments: OBJ($self) OBJ($config)
+# Side Effects:
+# Return Value: none
+sub new
+{
+ my ($self, $config) = @_;
+ my ($type) = ref($self) || $self;
+ my $me = { _config => $config };
+ return bless $me, $type;
+}
+
+
+# Descriptions: main routine.
+# Arguments: OBJ($self)
+# Side Effects: none
+# Return Value: none
+sub run
+{
+ my ($self) = @_;
+ my $config = $self->{ _config };
+
+ my $q_list = $self->pickup_queue();
+ for my $q (@$q_list) {
+ $self->log("try to send: $q");
+ $self->send($q);
+ }
+}
+
+
+# Descriptions: pick up queue (id's) and return it as ARRAY_REF.
+# Arguments: OBJ($self)
+# Side Effects: none
+# Return Value: ARRAY_REF
+sub pickup_queue
+{
+ my ($self) = @_;
+ my $config = $self->{ _config };
+
+ my (@queue) = ();
+
+ use DirHandle;
+ my $queue_dir = $config->{ queue_dir };
+ my $dh = new DirHandle $queue_dir;
+ if (defined $dh) {
+ my $entry;
+
+ ENTRY:
+ while ($entry = $dh->read()) {
+ next ENTRY if $entry =~ /^\./o;
+ next ENTRY if $entry =~ /^\,/o;
+ next ENTRY if $entry =~ /^\_/o;
+ next ENTRY if $entry !~ /^\d/o;
+
+ push(@queue, $entry);
+ }
+
+ $dh->close();
+ }
+ else {
+ $self->logerror("cannot open $queue_dir");
+ croak("cannot open $queue_dir");
+ }
+
+ return \@queue;
+}
+
+
+# Descriptions: send queue.
+# Arguments: OBJ($self)
+# Side Effects: queue is removed if succeeded.
+# Return Value: none
+sub send
+{
+ my ($self, $q) = @_;
+ my $qf_candidate = $self->queue_file_path($q);
+ my $qf_locked = $self->queue_file_path("_$q");
+
+ if (rename($qf_candidate, $qf_locked)) {
+ $self->_send_file($qf_locked);
+ }
+ else {
+ $self->logerror("cannot lock queue: $q");
+ }
+}
+
+
+# Descriptions: return full path for queue id.
+# Arguments: OBJ($self) STR($qid)
+# Side Effects: none
+# Return Value: STR
+sub queue_file_path
+{
+ my ($self, $qid) = @_;
+ my $config = $self->{ _config };
+ my $queue_dir = $config->{ queue_dir };
+
+ use File::Spec;
+ return File::Spec->catfile($queue_dir, $qid);
+}
+
+
+# Descriptions: send $queue_file by Mail::Delivery.
+# Arguments: OBJ($self) HASH_REF($args)
+# Side Effects: queue removed if suceeded.
+# Return Value: none
+sub _send_file
+{
+ my ($self, $queue_file) = @_;
+ my $config = $self->{ _config };
+ my $queue_dir = $config->{ queue_dir };
+
+ use Mail::Message;
+ my $message = Mail::Message->parse( { file => $queue_file } );
+
+ my ($sender, $rcpt_maps) = $self->_analyze_message($message);
+
+ use Mail::Delivery::Queue;
+ my $queue = new Mail::Delivery::Queue { directory => $queue_dir };
+
+ my $validater = sub {
+ my ($address) = @_;
+ use FML::Restriction::Base;
+ my $restriction = new FML::Restriction::Base;
+ return $restriction->regexp_match( 'address', $address );
+ };
+
+ use Mail::Delivery;
+ my $logfp_normal = sub { $self->log(@_); };
+ my $logfp_error = sub { $self->log(@_); };
+ my $service = new Mail::Delivery {
+ log_info_function => $logfp_normal,
+ log_error_function => $logfp_error,
+ log_debug_function => undef,
+ smtp_log_function => undef,
+ smtp_log_handle => undef,
+ address_validate_function => $validater,
+ };
+ if ($service->error) {
+ # log($service->error);
+ croak("cannot initialize Mail::Delivery object");
+ }
+
+ $service->deliver({
+ 'smtp_servers' => $config->{'smtp_servers'},
+
+ 'smtp_sender' => $sender,
+ 'recipient_array' => $rcpt_maps,
+ 'recipient_limit' => $config->{smtp_recipient_limit},
+
+ 'message' => $message,
+
+ queue => $queue,
+
+ # XXX do not need fallback here ?
+ use_queue_dir => 1,
+ queue_dir => $queue_dir,
+ });
+ if ($service->error) {
+ $self->logerror($service->error);
+ croak($service->error);
+ }
+
+ # delivery not completes.
+ if ($service->get_not_done()) {
+ $self->logerror("delivery not done");
+ croak("delivery not done");
+ }
+
+ # done.
+ unlink $queue_file;
+ unless (-f $queue_file) {
+ use File::Basename;
+ my $qid = basename($queue_file);
+ $qid =~ s/^_//;
+ $self->log("$qid removed");
+ }
+}
+
+
+# Descriptions: analyze message and return sender and recipients info.
+# Arguments: OBJ($self) STR($msg)
+# Side Effects: none
+# Return Value: ARRAY(STR, ARRAY_REF)
+sub _analyze_message
+{
+ my ($self, $msg) = @_;
+ my $header = $msg->whole_message_header();
+
+ # results
+ my ($sender) = '';
+ my ($rcpt_maps) = [];
+
+ {
+ my $from = $header->get('from');
+ use Mail::Address;
+ my (@addrlist) = Mail::Address->parse($from);
+ if (defined $addrlist[0]) {
+ $sender = $addrlist[0]->address;
+ }
+ }
+
+ {
+ my $to = $header->get('to') || '';
+ my $cc = $header->get('cc') || '';
+ my $bcc = $header->get('bcc') || '';
+ use Mail::Address;
+ my (@addrlist) = Mail::Address->parse("$to, $cc, $bcc");
+ if (defined $addrlist[0]) {
+ for my $a (@addrlist) {
+ if ($a->address) {
+ push(@$rcpt_maps, $a->address);
+ }
+ }
+ }
+ }
+
+ return($sender, $rcpt_maps);
+}
+
+
+# Descriptions: log as normal level.
+# Arguments: OBJ($self) STR($msg)
+# Side Effects: none
+# Return Value: none
+sub log
+{
+ my ($self, $msg) = @_;
+ &TinyMTA::Log::log($msg);
+}
+
+
+# Descriptions: log as error level.
+# Arguments: OBJ($self) STR($msg)
+# Side Effects: none
+# Return Value: none
+sub logerror
+{
+ my ($self, $msg) = @_;
+ &TinyMTA::Log::log("error: $msg");
+}
+
+
+######################################################################
+#
+# dispatcher
+#
+
+# Descriptions: main dispatcher.
+# Arguments: OBJ($main_cf) STR($config_cf_file)
+# Side Effects: none
+# Return Value: none
+sub main::dispatch
+{
+ my ($main_cf, $config_cf_file) = @_;
+
+ my $config = TinyMTA::Config::load_file($config_cf_file, $main_cf);
+ my $obj = new TinyMTA::SMTP $config;
+ $obj->run();
+}
+
+
+=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) 2006 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
+
+TinyMTA::SMTP appeared in fml8 mailing list driver package.
+See C<http://www.fml.org/> for more details.
+
+=cut
+
+
+1;
diff --git a/regress/tinymta/configure b/regress/tinymta/configure
new file mode 100755
index 00000000..78470e1e
--- /dev/null
+++ b/regress/tinymta/configure
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# $FML$
+#
+
+
+# assert
+if [ ! -x ../../config.status ];then
+ echo "error: run configure at the top level directory (../..)"
+ echo " before running configure here"
+ exit 1
+fi
+
+env CONFIG_FILES='Makefile loader tinymta.cf' sh ../../config.status
+chmod 755 loader
+
+echo creating tmdrop
+cat loader Config.pm Log.pm Drop.pm > tmdrop
+chmod 755 tmdrop
+
+echo creating tmsmtp
+cat loader Config.pm Log.pm SMTP.pm > tmsmtp
+chmod 755 tmsmtp
+
+exit 0;
diff --git a/regress/tinymta/loader.in b/regress/tinymta/loader.in
new file mode 100755
index 00000000..a2c829f8
--- /dev/null
+++ b/regress/tinymta/loader.in
@@ -0,0 +1,299 @@
+#! @PERL@ -w
+#-*- perl -*-
+#
+# Copyright (C) 2000-2002,2004,2006 Ken'ichi Fukamachi
+# All rights reserved.
+#
+# $FML: loader.in,v 1.13 2004/10/06 13:33:27 fukachan Exp $
+#
+
+eval 'exec @PERL@ -S $0 ${1+"$@"}'
+ if $running_under_some_shell;
+
+use vars qw($running_under_some_shell $hints $ERROR_EXIT_CODE);
+use strict;
+use IO::File;
+
+# reset PATH in the early stage
+$ENV{'PATH'} = '/bin:/usr/bin';
+delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
+
+# XXX irregular global variable permitted to handle emergency cases.
+# default exit code in error.
+$ERROR_EXIT_CODE = 1;
+
+=head1 NAME
+
+loader -- top level wrapper to load and start a real fml program.
+
+=head1 SYNOPSIS
+
+loader C<[-c main.cf]> [program specific options]
+
+=head1 DESCRIPTION
+
+Perl modules C<fml> uses are dependent on fml version.
+C<loader> resolves fml version dependence by
+@sysconfdir@/main.cf,
+set up proper @INC and load C<FML::Process::Switch>.
+
+See C<FML::Process::Switch> for boot strap phase 2 of fml process.
+
+=head1 COMMAND LINE OPTIONS
+
+C<-c main.cf>
+ main.cf alternative
+
+=head1 METHOD
+
+=head2 Bootstrap( main_cf )
+
+top level loader.
+
+=cut
+
+
+# Descriptions: top lebel bootstrap program
+# which load a dispather program (process_switch)
+# for process switch. The flow of execution follows:
+# libexec/loader ->
+# libexec/process_switch ->
+# FML::Process::Something
+# Arguments: none
+# XXX this program sees $0
+# (program name, == argv[0] of C language)
+# Side Effects: switch to the real process
+# Return Value: none
+sub init
+{
+ my ($main_cf_default, $config_cf_file) = @_;
+
+ # 1. main.cf exists and I can open it?
+ unless (-f $main_cf_default) {
+ __CROAK("cannot find $main_cf_default");
+ }
+ my $fh = new IO::File $main_cf_default, "r";
+ unless (defined $fh) {
+ __CROAK("cannot open $main_cf_default");
+ }
+
+ # 1.1 parse command line options (preliminary).
+ # we check @ARGV again after by getopt().
+ my $main_cf_file = $main_cf_default; # main.cf by default
+ for (my $i = 0; $i <= $#ARGV; $i++) {
+ # -c main.cf
+ if ($ARGV[ $i ] =~ /^\-c$/) {
+ $main_cf_file = $ARGV[$i + 1];
+ }
+ }
+
+ # 2.1 o.k. try to load main.cf (1st pass) to resolve @INC
+ my $main_cf = loader_read_main_cf($main_cf_file);
+
+ # 2.1.1 set up @INC to load FML::Process::Switch
+ if (defined $main_cf->{ lib_dir }) {
+ push(@INC, split(/\s+/, $main_cf->{ lib_dir }));
+ }
+ else {
+ __CROAK("\$lib_dir not defined in main.cf");
+ }
+
+ # 2.1.2 inherit some parameters to change behaviour
+ $main_cf->{ _hints } = $hints;
+
+ # arguments to pass off to bootstrap().
+ return ($main_cf, $config_cf_file);
+}
+
+
+# Descriptions: dispatch.
+# Arguments: OBJ($main_cf) STR($config_cf_file)
+# Side Effects: none
+# Return Value: none
+sub bootstrap
+{
+ my ($main_cf, $config_cf_file) = @_;
+
+ # 3. execute
+ eval {
+ main::dispatch($main_cf, $config_cf_file);
+ };
+ if ($@) {
+ my $reason = $@;
+ $reason =~ s/[\n\s]*\s+at\s+.*$//m;
+ __CROAK("cannot load FML::Process::Switch", $reason);
+ }
+}
+
+
+=head2 loader_read_main_cf(cf_file)
+
+load "key = value" style configuration file and build a hash.
+return HASH REFERENCE.
+
+ my $main_cf = loader_read_main_cf($main_cf_file, $params);
+
+where $param is optional.
+
+=cut
+
+
+# Descriptions: load "key = value" style configuration.
+# It is available to use the following style.
+# key = value1 value2
+# value3
+# XXX This file is non-Object Oriented style but
+# XXX this is minimum module used in standalone program.
+# Arguments: $file $params
+# $params is 'key1=value1 key2=value2' syntax.
+# Side Effects: $config (hash reference) is allocated on memory here.
+# Return Value: hash reference to configuration parameters
+sub loader_read_main_cf
+{
+ my ($file) = @_;
+ my $config = {};
+
+ my $fh = new IO::File $file, "r";
+
+ if (defined $fh) {
+ my $curkey = '';
+ while (<$fh>) {
+ next if /^\#/;
+ chomp;
+
+ if (/^([A-Za-z]\w+)\s+=\s*(.*)/) {
+ my ($key, $value) = ($1, $2);
+ $curkey = $key;
+ $config->{$key} = $value;
+ }
+ if (/^\s+(.*)/) {
+ $config->{ $curkey } .= " ". $1;
+ }
+ }
+ $fh->close;
+ }
+ else {
+ __CROAK("Error: cannot open $file");
+ }
+
+ loader_expand_variables( $config );
+ return $config;
+}
+
+
+# Descriptions: expand $var to the value of $var.
+# Arguments: $ref_to_config
+# Side Effects: rewrite the given $config
+# Return Value: none
+sub loader_expand_variables
+{
+ my ($config) = @_;
+ my (@order) = keys %$config;
+
+ # check whether the variable definition is recursive.
+ # For example, definition "var_a = $var_a/b/c" causes a loop.
+ for my $x ( @order ) {
+ if ((defined $x) && defined ($config->{ $x })) {
+ if ($config->{ $x } =~ /\$$x/) {
+ __CROAK("loop1: definition of $x is recursive");
+ }
+ }
+ }
+
+ # main expansion loop
+ my $org = '';
+ my $max = 0;
+ KEY:
+ for my $x ( @order ) {
+ next KEY unless defined($config->{ $x });
+ next KEY if $config->{ $x } !~ /\$/o;
+
+ # we need a loop to expand nested variables, for example,
+ # a = $x/y and b = $a/c/0
+ #
+ $max = 0;
+ EXPANSION_LOOP:
+ while ($max++ < 16) {
+ $org = $config->{ $x };
+
+ if ($config->{ $x } =~ /\{/) { # expand ${prefix}/xxx ...
+ $config->{ $x } =~ s/\$\{([a-z_]+)\}/$config->{$1}/g;
+ }
+ $config->{ $x } =~ s/\$([a-z_]+)/$config->{$1}/g;
+
+ last EXPANSION_LOOP if $config->{ $x } !~ /\$/o;
+ last EXPANSION_LOOP if $org eq $config->{ $x };
+
+ if ($config->{ $x } =~ /\$$x/) {
+ __CROAK("loop2: definition of $x is recursive");
+ }
+ }
+
+ if ($max >= 16) {
+ __CROAK("variable expansion of $x causes infinite loop");
+ }
+ }
+}
+
+
+# Descriptions: print error reason
+# Arguments: STR($reason) STR($detail)
+# Side Effects: print out error reason and exit here
+# Return Value: none
+sub __CROAK
+{
+ my ($reason, $detail) = @_;
+
+ print STDERR "fml loader error: $reason\n";
+ print STDERR " reason(detail): $detail\n" if defined $detail;
+ exit($ERROR_EXIT_CODE);
+}
+
+
+
+#
+# MAIN
+#
+
+# main routine to boot off
+my (@argv) = ();
+BEGIN {
+ my $prefix = "@prefix@";
+ my $exec_prefix = "@exec_prefix@";
+ my $main_cf = "@fmlconfdir@/main.cf";
+ my $config_cf = "@fmlconfdir@/tinymta.cf";
+
+ (@argv) = init($main_cf, $config_cf);
+}
+
+# not use Carp.pm to be quiet if needed
+eval q{ bootstrap(@argv); };
+if ($@) { print STDERR "Error: ", $@, "\n"; exit($ERROR_EXIT_CODE);}
+
+exit(0);
+
+
+=head1 SEE ALSO
+
+L<FML::Process::Switch>
+
+=head1 AUTHOR
+
+Ken'ichi Fukamachi
+
+=head1 COPYRIGHT
+
+Copyright (C) 2000-2002,2004,2006 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
+
+libexec/loader appeared in fml8 mailing list driver package.
+See C<http://www.fml.org/> for more details.
+
+=cut
+
+
+1;
diff --git a/regress/tinymta/tinymta.cf.in b/regress/tinymta/tinymta.cf.in
new file mode 100644
index 00000000..f533b18b
--- /dev/null
+++ b/regress/tinymta/tinymta.cf.in
@@ -0,0 +1,11 @@
+#
+# $FML$
+#
+
+
+queue_dir = /var/tmp/tinymta
+
+smtp_servers = 127.0.0.1:25
+
+smtp_recipient_limit = 1000
+