summaryrefslogtreecommitdiff
path: root/rt/bin/rt-mailgate
diff options
context:
space:
mode:
Diffstat (limited to 'rt/bin/rt-mailgate')
-rwxr-xr-xrt/bin/rt-mailgate325
1 files changed, 225 insertions, 100 deletions
diff --git a/rt/bin/rt-mailgate b/rt/bin/rt-mailgate
index de0529d..98da587 100755
--- a/rt/bin/rt-mailgate
+++ b/rt/bin/rt-mailgate
@@ -1,9 +1,9 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl
# BEGIN BPS TAGGED BLOCK {{{
#
# COPYRIGHT:
#
-# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
# <sales@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
@@ -48,7 +48,7 @@
# END BPS TAGGED BLOCK }}}
=head1 NAME
-rt-mailgate - Mail interface to RT3.
+rt-mailgate - Mail interface to RT.
=cut
@@ -56,85 +56,186 @@ use strict;
use warnings;
use Getopt::Long;
+
+my $opts = { };
+GetOptions( $opts, "queue=s", "action=s", "url=s",
+ "jar=s", "help", "debug", "extension=s",
+ "timeout=i", "verify-ssl!", "ca-file=s",
+ );
+
+my $gateway = RT::Client::MailGateway->new();
+
+$gateway->run($opts);
+
+package RT::Client::MailGateway;
+
use LWP::UserAgent;
use HTTP::Request::Common qw($DYNAMIC_FILE_UPLOAD);
+use File::Temp qw(tempfile tempdir);
$DYNAMIC_FILE_UPLOAD = 1;
use constant EX_TEMPFAIL => 75;
use constant BUFFER_SIZE => 8192;
-my %opts;
-GetOptions( \%opts, "queue=s", "action=s", "url=s", "jar=s", "help", "debug", "extension=s", "timeout=i" );
+sub new {
+ my $class = shift;
+ my $self = bless {}, $class;
+ return $self;
+}
+
+sub run {
+ my $self = shift;
+ my $opts = shift;
+
+ if ( $opts->{running_in_test_harness} ) {
+ $self->{running_in_test_harness} = 1;
+ }
+
+ $self->validate_cli_flags($opts);
+
+ my $ua = $self->get_useragent($opts);
+ my $post_params = $self->setup_session($opts);
+ $self->upload_message( $ua => $post_params );
+ $self->exit_with_success();
+}
+
+sub exit_with_success {
+ my $self = shift;
+ if ( $self->{running_in_test_harness} ) {
+ return 1;
+ } else {
+ exit 0;
+ }
+}
+
+sub tempfail {
+ my $self = shift;
+ if ( $self->{running_in_test_harness} ) {
+ die "tempfail";
+ } else {
-if ( $opts{'help'} ) {
- require Pod::Usage;
- import Pod::Usage;
- pod2usage("RT Mail Gateway\n");
- exit 1; # Don't want to succeed if this is really an email!
+ exit EX_TEMPFAIL;
+ }
}
-unless ( $opts{'url'} ) {
- print STDERR "$0 invoked improperly\n\nNo 'url' provided to mail gateway!\n";
- exit 1;
+sub permfail {
+ my $self = shift;
+ if ( $self->{running_in_test_harness} ) {
+ die "permfail";
+ } else {
+
+ exit 1;
+ }
}
-my $ua = new LWP::UserAgent;
-$ua->cookie_jar( { file => $opts{'jar'} } ) if $opts{'jar'};
-
-my %args = (
- SessionType => 'REST', # Surpress login box
-);
-foreach ( qw(queue action) ) {
- $args{$_} = $opts{$_} if defined $opts{$_};
-};
-
-if ( ($opts{'extension'} || '') =~ /^(?:action|queue|ticket)$/i ) {
- $args{ lc $opts{'extension'} } = $ENV{'EXTENSION'} || $opts{$opts{'extension'}};
-} elsif ( $opts{'extension'} && $ENV{'EXTENSION'} ) {
- print STDERR "Value of the --extension argument is not action, queue or ticket"
- .", but environment variable EXTENSION is also defined. The former is ignored.\n";
+sub validate_cli_flags {
+ my $self = shift;
+ my $opts = shift;
+ if ( $opts->{'help'} ) {
+ require Pod::Usage;
+ Pod::Usage::pod2usage( { verbose => 2 } );
+ return $self->permfail()
+ ; # Don't want to succeed if this is really an email!
+ }
+
+ unless ( $opts->{'url'} ) {
+ print STDERR
+ "$0 invoked improperly\n\nNo 'url' provided to mail gateway!\n";
+ return $self->permfail();
+ }
+
+ if (($opts->{'ca-file'} or $opts->{"verify-ssl"})
+ and not LWP::UserAgent->can("ssl_opts")) {
+ print STDERR "Verifying SSL certificates requires LWP::UserAgent 6.0 or higher.\n";
+ return $self->tempfail();
+ }
+
+ $opts->{"verify-ssl"} = 1 unless defined $opts->{"verify-ssl"};
}
-# add ENV{'EXTENSION'} as X-RT-MailExtension to the message header
-if ( my $value = ( $ENV{'EXTENSION'} || $opts{'extension'} ) ) {
- # prepare value to avoid MIME format breakage
- # strip trailing newline symbols
- $value =~ s/(\r*\n)+$//;
- # make a correct multiline header field,
- # with tabs in the beginning of each line
- $value =~ s/(\r*\n)/$1\t/g;
- $opts{'headers'} .= "X-RT-Mail-Extension: $value\n";
+sub get_useragent {
+ my $self = shift;
+ my $opts = shift;
+ my $ua = LWP::UserAgent->new();
+ $ua->cookie_jar( { file => $opts->{'jar'} } ) if $opts->{'jar'};
+
+ if ( $ua->can("ssl_opts") ) {
+ $ua->ssl_opts( verify_hostname => $opts->{'verify-ssl'} );
+ $ua->ssl_opts( SSL_ca_file => $opts->{'ca-file'} )
+ if $opts->{'ca-file'};
+ }
+
+ return $ua;
}
-# Read the message in from STDIN
-my %message = write_down_message();
-unless( $message{'filename'} ) {
- $args{'message'} = [
- undef, '',
- 'Content-Type' => 'application/octet-stream',
- Content => ${ $message{'content'} },
- ];
-} else {
- $args{'message'} = [
- $message{'filename'}, '',
- 'Content-Type' => 'application/octet-stream',
- ];
+sub setup_session {
+ my $self = shift;
+ my $opts = shift;
+ my %post_params;
+ $post_params{SessionType} = 'REST'; # Surpress login box
+ foreach (qw(queue action)) {
+ $post_params{$_} = $opts->{$_} if defined $opts->{$_};
+ }
+
+ if ( ( $opts->{'extension'} || '' ) =~ /^(?:action|queue|ticket)$/i ) {
+ $post_params{ lc $opts->{'extension'} } = $ENV{'EXTENSION'}
+ || $opts->{ $opts->{'extension'} };
+ } elsif ( $opts->{'extension'} && $ENV{'EXTENSION'} ) {
+ print STDERR
+ "Value of the --extension argument is not action, queue or ticket"
+ . ", but environment variable EXTENSION is also defined. The former is ignored.\n";
+ }
+
+ # add ENV{'EXTENSION'} as X-RT-MailExtension to the message header
+ if ( my $value = ( $ENV{'EXTENSION'} || $opts->{'extension'} ) ) {
+
+ # prepare value to avoid MIME format breakage
+ # strip trailing newline symbols
+ $value =~ s/(\r*\n)+$//;
+
+ # make a correct multiline header field,
+ # with tabs in the beginning of each line
+ $value =~ s/(\r*\n)/$1\t/g;
+ $opts->{'headers'} .= "X-RT-Mail-Extension: $value\n";
+ }
+
+ # Read the message in from STDIN
+ # _raw_message is used for testing
+ my $message = $opts->{'_raw_message'} || $self->slurp_message();
+ unless ( $message->{'filename'} ) {
+ $post_params{'message'} = [
+ undef, '',
+ 'Content-Type' => 'application/octet-stream',
+ Content => ${ $message->{'content'} },
+ ];
+ } else {
+ $post_params{'message'} = [
+ $message->{'filename'}, '',
+ 'Content-Type' => 'application/octet-stream',
+ ];
+ }
+
+ return \%post_params;
}
-my $full_url = $opts{'url'}. "/REST/1.0/NoAuth/mail-gateway";
-print STDERR "$0: connecting to $full_url\n" if $opts{'debug'};
+sub upload_message {
+ my $self = shift;
+ my $ua = shift;
+ my $post_params = shift;
+ my $full_url = $opts->{'url'} . "/REST/1.0/NoAuth/mail-gateway";
+ print STDERR "$0: connecting to $full_url\n" if $opts->{'debug'};
-$ua->timeout( exists( $opts{'timeout'} )? $opts{'timeout'}: 180 );
-my $r = $ua->post( $full_url, \%args, Content_Type => 'form-data' );
-check_failure($r);
+ $ua->timeout( exists( $opts->{'timeout'} ) ? $opts->{'timeout'} : 180 );
+ my $r = $ua->post( $full_url, $post_params, Content_Type => 'form-data' );
+ $self->check_failure($r);
-my $content = $r->content;
-print STDERR $content ."\n" if $opts{'debug'};
+ my $content = $r->content;
+ print STDERR $content . "\n" if $opts->{'debug'};
-if ( $content !~ /^(ok|not ok)/ ) {
+ return if ( $content =~ /^(ok|not ok)/ );
- # It's not the server's fault if the mail is bogus. We just want to know that
- # *something* came out of the server.
+ # It's not the server's fault if the mail is bogus. We just want to know that
+ # *something* came out of the server.
print STDERR <<EOF;
RT server error.
@@ -144,18 +245,12 @@ said:
$content
EOF
- exit EX_TEMPFAIL;
-}
-
-exit;
-
-END {
- unlink $message{'filename'} if $message{'filename'};
+ return $self->tempfail();
}
-
sub check_failure {
- my $r = shift;
+ my $self = shift;
+ my $r = shift;
return if $r->is_success;
# This ordinarily oughtn't to be able to happen, suggests a bug in RT.
@@ -164,65 +259,67 @@ sub check_failure {
require HTML::FormatText;
my $error = $r->error_as_HTML;
- my $tree = HTML::TreeBuilder->new->parse( $error );
+ my $tree = HTML::TreeBuilder->new->parse($error);
$tree->eof;
# It'll be a cold day in hell before RT sends out bounces in HTML
- my $formatter = HTML::FormatText->new(
- leftmargin => 0,
- rightmargin => 50,
- );
- print STDERR $formatter->format( $tree );
- print STDERR "\n$0: undefined server error\n" if $opts{'debug'};
- exit EX_TEMPFAIL;
+ my $formatter =
+ HTML::FormatText->new( leftmargin => 0,
+ rightmargin => 50, );
+ print STDERR $formatter->format($tree);
+ print STDERR "\n$0: undefined server error\n" if $opts->{'debug'};
+ return $self->tempfail();
}
-sub write_down_message {
- use File::Temp qw(tempfile);
+sub slurp_message {
+ my $self = shift;
local $@;
- my ($fh, $filename) = eval { tempfile() };
+
+ my %message;
+ my ( $fh, $filename )
+ = eval { tempfile( DIR => tempdir( CLEANUP => 1 ) ) };
if ( !$fh || $@ ) {
print STDERR "$0: Couldn't create temp file, using memory\n";
print STDERR "error: $@\n" if $@;
- my $message = \do { local (@ARGV, $/); <STDIN> };
+ my $message = \do { local ( @ARGV, $/ ); <STDIN> };
unless ( $$message =~ /\S/ ) {
print STDERR "$0: no message passed on STDIN\n";
- exit 0;
+ $self->exit_with_success;
}
- $$message = $opts{'headers'} . $$message if $opts{'headers'};
- return ( content => $message );
+ $$message = $opts->{'headers'} . $$message if $opts->{'headers'};
+ return ( { content => $message } );
}
binmode $fh;
binmode \*STDIN;
-
- print $fh $opts{'headers'} if $opts{'headers'};
- my $buf; my $empty = 1;
- while(1) {
+ print $fh $opts->{'headers'} if $opts->{'headers'};
+
+ my $buf;
+ my $empty = 1;
+ while (1) {
my $status = read \*STDIN, $buf, BUFFER_SIZE;
unless ( defined $status ) {
print STDERR "$0: couldn't read message: $!\n";
- exit EX_TEMPFAIL;
+ return $self->tempfail();
} elsif ( !$status ) {
last;
}
$empty = 0 if $buf =~ /\S/;
print $fh $buf;
- };
+ }
close $fh;
- if ( $empty ) {
+ if ($empty) {
print STDERR "$0: no message passed on STDIN\n";
- exit 0;
+ $self->exit_with_success;
}
- print STDERR "$0: temp file is '$filename'\n" if $opts{'debug'};
- return (filename => $filename);
+ print STDERR "$0: temp file is '$filename'\n" if $opts->{'debug'};
+ return ( { filename => $filename } );
}
-
=head1 SYNOPSIS
rt-mailgate --help : this text
@@ -267,8 +364,34 @@ is found.
=item C<--url>
This flag tells the mail gateway where it can find your RT server. You should
-probably use the same URL that users use to log into RT.
+probably use the same URL that users use to log into RT.
+If your RT server uses SSL, you will need to install additional Perl
+libraries. RT will detect and install these dependencies if you pass the
+C<--enable-ssl-mailgate> flag to configure as documented in RT's README.
+
+If you have a self-signed SSL certificate, you may also need to pass
+C<--ca-file> or C<--no-verify-ssl>, below.
+
+=item C<--ca-file> I<path>
+
+Specifies the path to the public SSL certificate for the certificate
+authority that should be used to verify the website's SSL certificate.
+If your webserver uses a self-signed certificate, you should
+preferentially use this option over C<--no-verify-ssl>, as it will
+ensure that the self-signed certificate that the mailgate is seeing the
+I<right> self-signed certificate.
+
+=item C<--no-verify-ssl>
+
+This flag tells the mail gateway to trust all SSL certificates,
+regardless of if their hostname matches the certificate, and regardless
+of CA. This is required if you have a self-signed certificate, or some
+other certificate which is not traceable back to an certificate your
+system ultimitely trusts.
+
+Verifying SSL certificates requires L<LWP::UserAgent> version 6.0 or
+higher; explicitly passing C<--verify-ssl> on prior versions will error.
=item C<--extension> OPTIONAL
@@ -290,6 +413,8 @@ Print debugging output to standard error
Configure the timeout for posting the message to the web server. The
default timeout is 3 minutes (180 seconds).
+=back
+
=head1 DESCRIPTION
@@ -312,10 +437,10 @@ Next, you need to route mail to C<rt-mailgate> for the queues you're
monitoring. For instance, if you're using F</etc/aliases> and you have a
"bugs" queue, you will want something like this:
- bugs: "|/opt/rt3/bin/rt-mailgate --queue bugs --action correspond
+ bugs: "|/opt/rt4/bin/rt-mailgate --queue bugs --action correspond
--url http://rt.mycorp.com/"
- bugs-comment: "|/opt/rt3/bin/rt-mailgate --queue bugs --action comment
+ bugs-comment: "|/opt/rt4/bin/rt-mailgate --queue bugs --action comment
--url http://rt.mycorp.com/"
Note that you don't have to run your RT server on your mail server, as
@@ -379,7 +504,7 @@ If we don't already have a ticket id, we need to know which queue we're talking
The action being performed. At the moment, it's one of "comment" or "correspond"
-=back 4
+=back
It returns two values, the new C<RT::CurrentUser> object, and the new
authentication level. The authentication level can be zero, not allowed
@@ -403,7 +528,7 @@ See also C<--extension> option. Note that value of the environment variable is
always added to the message header when it's not empty even if C<--extension>
option is not provided.
-=back 4
+=back
=cut