#!perl -w

=head1 NAME

smtp-forward

=head1 DESCRIPTION

This plugin forwards the mail via SMTP to a specified server, rather than
delivering the email locally.

It also supports the Postfix XCLIENT extension.

=head1 CONFIG

It takes one required parameter, the IP address or hostname to forward to. 

  queue/smtp-forward 10.2.2.2

Optionally you can also add a port:

  queue/smtp-forward 10.2.2.2 9025

And a flag:

  queue/smtp-forward 10.2.2.2 9025 xclient

=cut

use Net::SMTP;
use Net::Cmd qw//;

sub init {
    my ($self, $qp, @args) = @_;

    if (@args <= 0) {
        die "No SMTP server specified in smtp-forward config";
    };

    if ($args[0] =~ /^([\.\w_-]+)$/) {
        $self->{_smtp_server} = $1;
    }
    else {
        die "Bad data in smtp server: $args[0]";
    }

    $self->{_smtp_port} = 25;
    if (@args > 1 and $args[1] =~ /^(\d+)$/) {
        $self->{_smtp_port} = $1;
    }

    for (my $i = 2; $i < @args; $i++) {
        if ($args[$i] !~ /^(\w+)$/) {
            $self->log(LOGWARN, "WARNING: Rejecting invalid flag");
            next;
        }
        my $flag = lc($1);
        $self->log(LOGWARN, "WARNING: Unknown flag $flag") unless $flag eq 'xclient';
        $self->{_flags}{$flag} = 1; 
    }
}

sub hook_queue {
    my ($self, $transaction) = @_;

    $self->log(LOGINFO,
               "forwarding to $self->{_smtp_server}:$self->{_smtp_port}");
    my $smtp = Net::SMTP->new(
                              $self->{_smtp_server},
                              Port    => $self->{_smtp_port},
                              Timeout => 60,
                              Hello   => $self->qp->config("me"),
                             ) || die $!;


    my $xcret = $self->xclient($smtp);
    return(DECLINED, $xcret) if defined $xcret;

    $smtp->mail($transaction->sender->address || "")
      or return (DECLINED, "Unable to queue message ($!)");
    for ($transaction->recipients) {
        $smtp->to($_->address)
          or return (DECLINED, "Unable to queue message ($!)");
    }
    $smtp->data() or return (DECLINED, "Unable to queue message ($!)");
    $smtp->datasend($transaction->header->as_string)
      or return (DECLINED, "Unable to queue message ($!)");
    $transaction->body_resetpos;
    while (my $line = $transaction->body_getline) {
        $smtp->datasend($line)
          or return (DECLINED, "Unable to queue message ($!)");
    }
    $smtp->dataend() or return (DECLINED, "Unable to queue message ($!)");
    my $qid = $smtp->message();
    my @list = split(' ', $qid);
    $qid = pop(@list);

    $smtp->quit()    or return (DECLINED, "Unable to queue message ($!)");
    $self->log(LOGINFO, "finished queueing");
    return (OK, "queued as $qid");
}

sub xclient {
    my ($self, $smtp) = @_;

    return unless $self->{_flags}{xclient};

    my $parts = $smtp->supports('XCLIENT');
    if (!defined($parts)) {      # what parts do they want?
        return "Unable to queue message (Server does not advertise XCLIENT support)";
    };

    my %haveparts;
    for my $part (split(/\s+/, $parts)) {
        next unless $part =~ /^(\w+)$/;
        $haveparts{uc($part)} = 1;
    }

    my $conn = $self->qp->connection;
    my @rparts;

    if ($haveparts{NAME}) {
        my $name = $conn->remote_host || '[UNAVAILABLE]';
        $name = '[UNAVAILABLE]' if ($name eq 'Unknown');
        push(@rparts, "NAME=$name");
    }

    if ($haveparts{ADDR}) {
        my $ip = $conn->remote_ip;
        push(@rparts, "ADDR=$ip");
    }

    if ($haveparts{PORT}) {
        my $port = $conn->remote_port;
        push(@rparts, "PORT=$port");
    }

    my $hello_name = $self->connection->hello_host;
    $hello_name ||= '[UNAVAILABLE]';
    if ($haveparts{HELO}) {
        push(@rparts, "HELO=$hello_name");
    }

    my $hello = $conn->hello;
    if ($haveparts{PROTO} && defined($hello)) {
        my $proto = (uc($hello) eq 'EHLO') ? 'ESMTP' : 'SMTP';
        push(@rparts, "PROTO=$proto");
    }

    while (scalar(@rparts)) {
        my @items;
        my $cursz = 0;
        while (defined(my $item = $rparts[0])) {
            my $len = length($item);
            last if ($cursz + $len > 500); 
            $cursz += $len;
            push(@items, shift @rparts); 
        }

        last unless @items;
        if ($smtp->command('XCLIENT', @items)->response() != Net::Cmd::CMD_OK) {
            return "Unable to queue message (XCLIENT failed)";
        }
    }

    $smtp->hello($hello_name) or return "Unable to queue message (HELLO after XCLIENT failed)";
    return;
}