3
#------------------------------------------------------------------------------
4
# This is amavis.pl, a simple demonstrational program functionally much like
5
# the amavis.c helper program, but talks the new AM.PDP protocol with the
6
# amavisd daemon. See README.protocol for the description of AM.PDP protocol.
8
# amavis.pl sender recip1 recip2 ... < message.txt
9
# To be placed in amavisd.conf:
10
# $protocol='AM.PDP'; $unix_socketname='/var/amavis/amavisd.sock';
13
# Author: Mark Martinec <mark.martinec@ijs.si>
14
# Copyright (C) 2004 Mark Martinec, All Rights Reserved.
16
# Redistribution and use in source and binary forms, with or without
17
# modification, are permitted provided that the following conditions are met:
19
# * Redistributions of source code must retain the above copyright notice,
20
# this list of conditions and the following disclaimer.
21
# * Redistributions in binary form must reproduce the above copyright notice,
22
# this list of conditions and the following disclaimer in the documentation
23
# and/or other materials provided with the distribution.
24
# * Neither the name of the author, nor the name of the "Jozef Stefan"
25
# Institute, nor the names of contributors may be used to endorse or
26
# promote products derived from this software without specific prior
29
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
32
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
33
# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
34
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
35
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
36
# OR BUSINESS INTERRUPTION) HOWEVERREADME.protocol CAUSED AND ON ANY THEORY OF LIABILITY,
37
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
38
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
39
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41
#(the license above is the new BSD license, and pertains to this program only)
43
# Patches and problem reports are welcome.
44
# The latest version of this program is available at:
45
# http://www.ijs.si/software/amavisd/
46
#------------------------------------------------------------------------------
54
use vars qw($VERSION); $VERSION = 1.000;
55
use vars qw($socketname);
57
# $socketname = '127.0.0.1:9998';
58
$socketname = '/var/amavis/amavisd.sock';
61
my($str, $keep_eol) = @_;
62
my(%map) = ("\r" => '\\r', "\n" => '\\n', "\f" => '\\f', "\t" => '\\t',
63
"\b" => '\\b', "\e" => '\\e', "\\" => '\\\\');
65
$str =~ s/([^\012\040-\133\135-\176])/ # and \240-\376 ?
66
exists($map{$1}) ? $map{$1} :
67
sprintf(ord($1)>255 ? '\\x{%04x}' : '\\%03o', ord($1))/eg;
69
$str =~ s/([^\040-\133\135-\176])/ # and \240-\376 ?
70
exists($map{$1}) ? $map{$1} :
71
sprintf(ord($1)>255 ? '\\x{%04x}' : '\\%03o', ord($1))/eg;
77
my($level, $errmsg) = @_;
78
print STDERR sanitize_str($errmsg),"\n";
83
$str =~ s/%([0-9a-fA-F]{2})/pack("C",hex($1))/eg;
87
sub proto_encode($@) {
88
my($attribute_name,@strings) = @_; local($1);
89
$attribute_name =~ # encode all but alfanumerics, '_' and '-'
90
s/([^0-9a-zA-Z_-])/sprintf("%%%02x",ord($1))/eg;
91
for (@strings) { # encode % and nonprintables
92
s/([^\041-\044\046-\176])/sprintf("%%%02x",ord($1))/eg;
94
$attribute_name . '=' . join(' ',@strings);
98
my($sock,$query_ref) = @_;
100
map { /^([^=]+)=(.*)\z/s; proto_encode($1,$2) } @$query_ref;
101
do_log(0,'> '.$_) for @encoded_query;
102
$sock->print( map { $_."\015\012" } (@encoded_query,'') )
103
or die "Can't write response to socket: $!";
104
$sock->flush or die "Can't flush on socket: $!";
106
local($/) = "\015\012"; # set line terminator to CRLF
107
# must not use \r and \n, which may not be \015 and \012 on certain platforms
108
do_log(0,"waiting for response");
110
last if /^\015\012\z/; # end of response
111
if (/^ ([^=\000\012]*?) (=|:[ \t]*) ([^\012]*?) \015\012 \z/xsi) {
112
my($attr_name) = proto_decode($1);
113
my($attr_val) = proto_decode($3);
114
if (!exists $attr{$attr_name}) { $attr{$attr_name} = [] }
115
push(@{$attr{$attr_name}}, $attr_val);
118
if (!defined($_) && $! != 0) { die "read from socket failed: $!" }
122
# Main program starts here
124
die "Usage: amavis.pl sender recip1 recip2 ... < message.txt\n" if !@ARGV;
126
my($is_inet) = $socketname=~m{^/} ? 0 : 1; # simpleminded: unix vs. inet sock
127
if ($is_inet) { # inet socket
128
$sock = IO::Socket::INET->new($socketname)
129
or die "Can't connect to INET socket $socketname: $!";
130
} else { # unix socket
131
$sock = IO::Socket::UNIX->new(Type => SOCK_STREAM)
132
or die "Can't create UNIX socket: $!";
133
$sock->connect( pack_sockaddr_un($socketname) )
134
or die "Can't connect to UNIX socket $socketname: $!";
137
# generate some semi-unique directory name; not good enough for production
138
my($ctx) = Digest::MD5->new; # 128 bits (32 hex digits)
139
$ctx->add(sprintf("%s %.9f %s", $$, Time::HiRes::time, join(',',@ARGV)));
140
my($id) = substr($ctx->b64digest,0,16); $id =~ tr{+/}{-.};
142
my($tempdir) = "/var/amavis/amavis-milter-$id";
143
my($fname) = "$tempdir/email.txt";
144
mkdir($tempdir,0750) or die "Can't create directory $tempdir: $!";
146
# copy message from stdin to a file email.txt in the temporary directory
147
open(F,">$fname") or die "Can't create file $fname: $!";
148
while (<STDIN>) { print F $_ or die "Can't write to $fname: $!" }
149
close(F) or die "Can't close $fname: $!";
155
'tempdir_removed_by=server',
156
'sender='.shift @ARGV,
157
map {"recipient=$_"} @ARGV,
158
# 'protocol_name=ESMTP',
159
# 'helo_name=b.example.com',
160
# 'client_address=10.2.3.4',
162
my($attr_ref) = ask_amavisd($sock,\@query);
163
for my $attr_name (keys %$attr_ref) {
164
for my $attr_val (@{$attr_ref->{$attr_name}})
165
{ do_log(0,"< $attr_name=$attr_val") }
167
# may do another query here if needed ...
168
$sock->close or die "Can't close socket: $!";
169
close(STDIN) or die "Can't close STDIN: $!";
170
my($exit_code) = shift @{$attr_ref->{'exit_code'}};
171
$exit_code = 0 if $exit_code==99; # same thing in this case, both is ok