# -*- cperl -*- use strict; use warnings; sub register { my ($self, $qp, @args) = @_; if ( @args ) { if ( grep { ! /\d{1-3}\.\d{1-3}\.\d{1-3}\.\d{1-3}/ } @args ) { $self->log(LOGERROR, "Bad parameters for the xforward plugin."); die "Bad parameters for the xforward plugin."; } } else { @args = ( '127.0.0.1' ); } $self->{_auth_ips} = \@args; $self->log(LOGINFO, "Allowing XFORWARD from @args"); } sub hook_ehlo { my ($self, $transaction) = @_; # Only offer XFORWARD to authorized hosts (well, IP addresses) - # it's not a good idea to allow any host to inject suspect audit # information: my $client_ip = $self->qp->connection->remote_ip(); my @auth_ips = @{ $self->{_auth_ips} }; if ( grep { $_ eq $client_ip } @auth_ips ) { # Ick! Why no mutator for the capabilities? my $cap = $transaction->notes('capabilities'); $cap ||= []; push @$cap, 'XFORWARD NAME ADDR PROTO HELO SOURCE'; $transaction->notes('capabilities', $cap); $self->qp->connection->notes('xforward_enabled', 1); } return DECLINED; } sub hook_unrecognized_command { my ($self, $transaction, $cmd, @args) = @_; return DECLINED unless $cmd eq 'xforward'; return (DENY, "Insufficient authorization") unless $self->qp->connection->notes('xforward_enabled'); unless ( @args ) { # Should really return 501 return (DENY, "Bad command parameter syntax") unless @args; } if ( $transaction->sender ) { # MAIL FROM has been issued: should really return 503 $self->log(LOGINFO, "MAIL transaction already in progress"); return (DENY, "MAIL transaction in process"); } else { # Main processing. foreach my $arg ( @args ) { my ($param, $value); # Validate/untaint arguments. if ( $arg =~ /(NAME|ADDR|HELO)=([\041-\176]{1,255})/i || $arg =~ /(PROTO)=([\041-\176]{1,64})/i || $arg =~ /(SOURCE)=(LOCAL|REMOTE)/i ) { $param = uc $1; $value = $2; # Replace restricted characters from arguments with '?', as # per NEUTER_CHARACTERS (" <>()\\\";@") in # postfix/src/smtpd/smtpd.c # Note that I'm not excepting whitespace as a valid component # of the value above $value =~ s/[<>()\\";@]/?/g; # Don't store UNAVAILABLE - pretty pointless. undef is easier to test on. $value = $value eq '[UNAVAILABLE]' ? undef : $value; $self->log(LOGDEBUG, "found parameter $param, argument $value"); my %p = %{ $self->qp->connection->notes('xforward') || {} }; $p{ $param } = $value; $self->qp->connection->notes('xforward', \%p); } else { # Return DECLINED, so something like # count_unrecognised_commands can still count bad XFORWARD # commands. $self->log(LOGINFO, "bad syntax for $arg"); return (DECLINED, "Bad command parameter syntax"); } } $self->log(LOGDEBUG, "command processed."); $self->qp->respond(250, "Ok"); return DONE; } } =pod =head1 NAME xforward - plugin to support XFORWARD commands, a Postfix-centric SMTP extension. =head1 SYNOPSIS # in config/plugins # XFORWARD only offered to localhost xforward # ...or to permit from IP addresses other than localhost, list trusted IP addresses: xforward 127.0.0.1 192.168.0.1 1.2.3.4 =head1 DESCRIPTION This plugin allows qpsmtpd to accept XFORWARD commands, as described at http://www.postfix.org/XFORWARD_README.html By default, this plugin will only advertise and accept XFORWARD commands in connections from localhost. If this is not the desired behaviour, specify all IP addresses to permit XFORWARD commands from as arguments in config/plugins. =head1 ISSUES At the moment the code processing XFORWARD commands is split into two parts - this plugin accepts the commands from the upstream server and the xforward-smtp-forward plugin sends XFORWARD commands on to a downstream server. This may well change in future versions. Due to the way that SMTP status codes are processed within qpsmtpd, the values returned when there is a problem with the XFORWARD command differs slightly from those defined in http://www.postfix.org/XFORWARD_README.html: Specification Actual code Meaning Code 250 250 success 501 500 bad command parameter syntax 503 500 mail transaction in progress 421 FIXME unable to proceed, disconnecting =head1 AUTHOR Barrie Bremner L. =head1 LICENSE Copyright (c) 2006 Barrie Bremner This plugin is licensed under the same terms as the qpsmtpd package itself. Please see the LICENSE file included with qpsmtpd for details. =cut