diff options
Diffstat (limited to 'rt/lib/RT/EmailParser.pm')
| -rw-r--r-- | rt/lib/RT/EmailParser.pm | 784 | 
1 files changed, 0 insertions, 784 deletions
| diff --git a/rt/lib/RT/EmailParser.pm b/rt/lib/RT/EmailParser.pm deleted file mode 100644 index 49f3d5518..000000000 --- a/rt/lib/RT/EmailParser.pm +++ /dev/null @@ -1,784 +0,0 @@ -# BEGIN LICENSE BLOCK -#  -# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> -#  -# (Except where explictly superceded by other copyright notices) -#  -# This work is made available to you under the terms of Version 2 of -# the GNU General Public License. A copy of that license should have -# been provided with this software, but in any event can be snarfed -# from www.gnu.org. -#  -# This work is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU -# General Public License for more details. -#  -# Unless otherwise specified, all modifications, corrections or -# extensions to this work which alter its source code become the -# property of Best Practical Solutions, LLC when submitted for -# inclusion in the work. -#  -#  -# END LICENSE BLOCK -package RT::EmailParser; - - -use base qw/RT::Base/; - -use strict; -use Mail::Address; -use MIME::Entity; -use MIME::Head; -use MIME::Parser; -use File::Temp qw/tempdir/; - -=head1 NAME - -  RT::Interface::CLI - helper functions for creating a commandline RT interface - -=head1 SYNOPSIS - - -=head1 DESCRIPTION - - -=begin testing - -ok(require RT::EmailParser); - -=end testing - - -=head1 METHODS - -=head2 new - - -=cut - -sub new  { -  my $proto = shift; -  my $class = ref($proto) || $proto; -  my $self  = {}; -  bless ($self, $class); -  return $self; -} - - - -# {{{ sub debug - -sub debug { -    my $val = shift; -    my ($debug); -    if ($val) { -        $RT::Logger->debug( $val . "\n" ); -        if ($debug) { -            print STDERR "$val\n"; -        } -    } -    if ($debug) { -        return (1); -    } -} - -# }}} - -# {{{ sub CheckForLoops  - -sub CheckForLoops { -    my $self = shift; - -    my $head = $self->Head; - -    #If this instance of RT sent it our, we don't want to take it in -    my $RTLoop = $head->get("X-RT-Loop-Prevention") || ""; -    chomp($RTLoop);    #remove that newline -    if ( $RTLoop =~ /^$RT::rtname/ ) { -        return (1); -    } - -    # TODO: We might not trap the case where RT instance A sends a mail -    # to RT instance B which sends a mail to ... -    return (undef); -} - -# }}} - -# {{{ sub CheckForSuspiciousSender - -sub CheckForSuspiciousSender { -    my $self = shift; - -    #if it's from a postmaster or mailer daemon, it's likely a bounce. - -    #TODO: better algorithms needed here - there is no standards for -    #bounces, so it's very difficult to separate them from anything -    #else.  At the other hand, the Return-To address is only ment to be -    #used as an error channel, we might want to put up a separate -    #Return-To address which is treated differently. - -    #TODO: search through the whole email and find the right Ticket ID. - -    my ( $From, $junk ) = $self->ParseSenderAddressFromHead(); - -    if ( ( $From =~ /^mailer-daemon/i ) or ( $From =~ /^postmaster/i ) ) { -        return (1); - -    } - -    return (undef); - -} - -# }}} - -# {{{ sub CheckForAutoGenerated -sub CheckForAutoGenerated { -    my $self = shift; -    my $head = $self->Head; - -    my $Precedence = $head->get("Precedence") || ""; -    if ( $Precedence =~ /^(bulk|junk)/i ) { -        return (1); -    } -    else { -        return (undef); -    } -} - -# }}} - -# {{{ sub ParseMIMEEntityFromSTDIN - -sub ParseMIMEEntityFromSTDIN { -    my $self = shift; -    return $self->ParseMIMEEntityFromFileHandle(\*STDIN); -} - -# }}} - - -sub ParseMIMEEntityFromScalar { -    my $self = shift; -    my $message = shift; - -    # Create a new parser object: - -    my $parser = MIME::Parser->new(); -    $self->_SetupMIMEParser($parser); - - -    # TODO: XXX 3.0 we really need to wrap this in an eval { } -    unless ( $self->{'entity'} = $parser->parse_data($message) ) { -        # Try again, this time without extracting nested messages -        $parser->extract_nested_messages(0); -        unless ( $self->{'entity'} = $parser->parse_data($message) ) { -            $RT::Logger->crit("couldn't parse MIME stream"); -            return ( undef); -        } -    } -    $self->_PostProcessNewEntity(); -    return (1); -} - -# {{{ ParseMIMEEntityFromFilehandle *FH - -=head2 ParseMIMEEntityFromFilehandle *FH - -Parses a mime entity from a filehandle passed in as an argument - -=cut - -sub ParseMIMEEntityFromFileHandle { -    my $self = shift; -    my $filehandle = shift; - -    # Create a new parser object: - -    my $parser = MIME::Parser->new(); -    $self->_SetupMIMEParser($parser); - - -    # TODO: XXX 3.0 we really need to wrap this in an eval { } - -    unless ( $self->{'entity'} = $parser->parse($filehandle) ) { - -        # Try again, this time without extracting nested messages -        $parser->extract_nested_messages(0); -        unless ( $self->{'entity'} = $parser->parse($filehandle) ) { -            $RT::Logger->crit("couldn't parse MIME stream"); -            return ( undef); -        } -    } -    $self->_PostProcessNewEntity(); -    return (1); -} - -# }}} - -# {{{ _PostProcessNewEntity  - -=head2 _PostProcessNewEntity - -cleans up and postprocesses a newly parsed MIME Entity - -=cut - -sub _PostProcessNewEntity { -    my $self = shift; - -    #Now we've got a parsed mime object.  - -    # try to convert text parts into utf-8 charset -    RT::I18N::SetMIMEEntityToEncoding($self->{'entity'}, 'utf-8'); - - -    # Unfold headers that are have embedded newlines -    $self->Head->unfold; - - -} - -# }}} - -# {{{ sub ParseTicketId  - -sub ParseTicketId { -    my $self = shift; - -    my $Subject = shift; - -    if ( $Subject =~ s/\[$RT::rtname \#(\d+)\s*\]//i ) { -        my $id = $1; -        $RT::Logger->debug("Found a ticket ID. It's $id"); -        return ($id); -    } -    else { -        return (undef); -    } -} - -# }}} - -# {{{ sub MailError  - -=head2 MailError { } - - -# TODO this doesn't belong here. -# TODO doc this - - -=cut - - -sub MailError { -    my $self = shift; - -    my %args = ( -        To          => $RT::OwnerEmail, -        Bcc         => undef, -        From        => $RT::CorrespondAddress, -        Subject     => 'There has been an error', -        Explanation => 'Unexplained error', -        MIMEObj     => undef, -        LogLevel    => 'crit', -        @_ -    ); - -    $RT::Logger->log( -        level   => $args{'LogLevel'}, -        message => $args{'Explanation'} -    ); -    my $entity = MIME::Entity->build( -        Type                   => "multipart/mixed", -        From                   => $args{'From'}, -        Bcc                    => $args{'Bcc'}, -        To                     => $args{'To'}, -        Subject                => $args{'Subject'}, -        'X-RT-Loop-Prevention' => $RT::rtname, -    ); - -    $entity->attach( Data => $args{'Explanation'} . "\n" ); - -    my $mimeobj = $args{'MIMEObj'}; -    $mimeobj->sync_headers(); -    $entity->add_part($mimeobj); - -    if ( $RT::MailCommand eq 'sendmailpipe' ) { -        open( MAIL, "|$RT::SendmailPath $RT::SendmailArguments" ) || return (0); -        print MAIL $entity->as_string; -        close(MAIL); -    } -    else { -        $entity->send( $RT::MailCommand, $RT::MailParams ); -    } -} - -# }}} - - - -# {{{ sub GetCurrentUser  - -sub GetCurrentUser { -    my $self     = shift; -    my $ErrorsTo = shift; - -    my %UserInfo = (); - -    #Suck the address of the sender out of the header -    my ( $Address, $Name ) = $self->ParseSenderAddressFromHead(); - -    my $tempuser = RT::User->new($RT::SystemUser); - -    #This will apply local address canonicalization rules -    $Address = $tempuser->CanonicalizeEmailAddress($Address); - -    #If desired, synchronize with an external database -    my $UserFoundInExternalDatabase = 0; - -    # Username is the 'Name' attribute of the user that RT uses for things -    # like authentication -    my $Username = undef; -    ( $UserFoundInExternalDatabase, %UserInfo ) = -      $self->LookupExternalUserInfo( $Address, $Name ); - -    $Address  = $UserInfo{'EmailAddress'}; -    $Username = $UserInfo{'Name'}; - -    #Get us a currentuser object to work with.  -    my $CurrentUser = RT::CurrentUser->new(); - -    # First try looking up by a username, if we got one from the external -    # db lookup. Next, try looking up by email address. Failing that, -    # try looking up by users who have this user's email address as their -    # username. - -    if ($Username) { -        $CurrentUser->LoadByName($Username); -    } - -    unless ( $CurrentUser->Id ) { -        $CurrentUser->LoadByEmail($Address); -    } - -    #If we can't get it by email address, try by name.   -    unless ( $CurrentUser->Id ) { -        $CurrentUser->LoadByName($Address); -    } - -    unless ( $CurrentUser->Id ) { - -        #If we couldn't load a user, determine whether to create a user - -        # {{{ If we require an incoming address to be found in the external -        # user database, reject the incoming message appropriately -        if ( $RT::SenderMustExistInExternalDatabase -             && !$UserFoundInExternalDatabase ) { - -            my $Message = -              "Sender's email address was not found in the user database."; - -            # {{{  This code useful only if you've defined an AutoRejectRequest template - -            require RT::Template; -            my $template = new RT::Template($RT::Nobody); -            $template->Load('AutoRejectRequest'); -            $Message = $template->Content || $Message; - -            # }}} - -            MailError( -                 To      => $ErrorsTo, -                 Subject => "Ticket Creation failed: user could not be created", -                 Explanation => $Message, -                 MIMEObj     => $self->Entity, -                 LogLevel    => 'notice' ); - -            return ($CurrentUser); - -        } - -        # }}} - -        else { -            my $NewUser = RT::User->new($RT::SystemUser); - -            my ( $Val, $Message ) = $NewUser->Create( -                                  Name => ( $Username || $Address ), -                                  EmailAddress => $Address, -                                  RealName     => "$Name", -                                  Password     => undef, -                                  Privileged   => 0, -                                  Comments => 'Autocreated on ticket submission' -            ); - -            unless ($Val) { - -                # Deal with the race condition of two account creations at once -                # -                if ($Username) { -                    $NewUser->LoadByName($Username); -                } - -                unless ( $NewUser->Id ) { -                    $NewUser->LoadByEmail($Address); -                } - -                unless ( $NewUser->Id ) { -                    MailError(To          => $ErrorsTo, -                              Subject     => "User could not be created", -                              Explanation => -                                "User creation failed in mailgateway: $Message", -                              MIMEObj  => $self->Entity, -                              LogLevel => 'crit' ); -                } -            } -        } - -        #Load the new user object -        $CurrentUser->LoadByEmail($Address); - -        unless ( $CurrentUser->id ) { -            $RT::Logger->warning( -                               "Couldn't load user '$Address'." . "giving up" ); -            MailError( -                   To          => $ErrorsTo, -                   Subject     => "User could not be loaded", -                   Explanation => -                     "User  '$Address' could not be loaded in the mail gateway", -                   MIMEObj  => $self->Entity, -                   LogLevel => 'crit' ); - -        } -    } - -    return ($CurrentUser); - -} - -# }}} - - -# {{{ ParseCcAddressesFromHead  - -=head2 ParseCcAddressesFromHead HASHREF - -Takes a hashref object containing QueueObj, Head and CurrentUser objects. -Returns a list of all email addresses in the To and Cc  -headers b<except> the current Queue\'s email addresses, the CurrentUser\'s  -email address  and anything that the $RTAddressRegexp matches. - -=cut - -sub ParseCcAddressesFromHead { - -    my $self = shift; - -    my %args = ( -        QueueObj    => undef, -        CurrentUser => undef, -        @_ -    ); - -    my (@Addresses); - -    my @ToObjs = Mail::Address->parse( $self->Head->get('To') ); -    my @CcObjs = Mail::Address->parse( $self->Head->get('Cc') ); - -    foreach my $AddrObj ( @ToObjs, @CcObjs ) { -        my $Address = $AddrObj->address; -        my $user = RT::User->new($RT::SystemUser); -        $Address = $user->CanonicalizeEmailAddress($Address); -        next if ( $args{'CurrentUser'}->EmailAddress   =~ /^$Address$/i ); -        next if ( $args{'QueueObj'}->CorrespondAddress =~ /^$Address$/i ); -        next if ( $args{'QueueObj'}->CommentAddress    =~ /^$Address$/i ); -        next if ( IsRTAddress($Address) ); - -        push ( @Addresses, $Address ); -    } -    return (@Addresses); -} - -# }}} - -# {{{ ParseSenderAdddressFromHead - -=head2 ParseSenderAddressFromHead - -Takes a MIME::Header object. Returns a tuple: (user@host, friendly name)  -of the From (evaluated in order of Reply-To:, From:, Sender) - -=cut - -sub ParseSenderAddressFromHead { -    my $self = shift; - -    #Figure out who's sending this message. -    my $From = $self->Head->get('Reply-To') -      || $self->Head->get('From') -      || $self->Head->get('Sender'); -    return ( $self->ParseAddressFromHeader($From) ); -} - -# }}} - -# {{{ ParseErrorsToAdddressFromHead - -=head2 ParseErrorsToAddressFromHead - -Takes a MIME::Header object. Return a single value : user@host -of the From (evaluated in order of Errors-To:,Reply-To:, From:, Sender) - -=cut - -sub ParseErrorsToAddressFromHead { -    my $self = shift; - -    #Figure out who's sending this message. - -    foreach my $header ( 'Errors-To', 'Reply-To', 'From', 'Sender' ) { - -        # If there's a header of that name -        my $headerobj = $self->Head->get($header); -        if ($headerobj) { -            my ( $addr, $name ) = $self->ParseAddressFromHeader($headerobj); - -            # If it's got actual useful content... -            return ($addr) if ($addr); -        } -    } -} - -# }}} - -# {{{ ParseAddressFromHeader - -=head2 ParseAddressFromHeader ADDRESS - -Takes an address from $self->Head->get('Line') and returns a tuple: user@host, friendly name - -=cut - -sub ParseAddressFromHeader { -    my $self = shift; -    my $Addr = shift; - -    my @Addresses = Mail::Address->parse($Addr); - -    my $AddrObj = $Addresses[0]; - -    unless ( ref($AddrObj) ) { -        return ( undef, undef ); -    } - -    my $Name = ( $AddrObj->phrase || $AddrObj->comment || $AddrObj->address ); - -    #Lets take the from and load a user object. -    my $Address = $AddrObj->address; - -    return ( $Address, $Name ); -} - -# }}} - -# {{{ IsRTAddress - -=item IsRTaddress ADDRESS - -Takes a single parameter, an email address.  -Returns true if that address matches the $RTAddressRegexp.   -Returns false, otherwise. - -=begin testing - -is(RT::EmailParser::IsRTAddress("","rt\@example.com"),1, "Regexp matched rt address" ); -is(RT::EmailParser::IsRTAddress("","frt\@example.com"),undef, "Regexp didn't match non-rt address" ); - -=end testing - -=cut - -sub IsRTAddress { -    my $self = shift; -    my $address = shift; - -    # Example: the following rule would tell RT not to Cc  -    #   "tickets@noc.example.com" -    if ( defined($RT::RTAddressRegexp) && -                       $address =~ /$RT::RTAddressRegexp/ ) { -        return(1); -    } else { -        return (undef); -    } -} - -# }}} - - -# {{{ CullRTAddresses - -=item CullRTAddresses ARRAY - -Takes a single argument, an array of email addresses. -Returns the same array with any IsRTAddress()es weeded out. - -=begin testing - -@before = ("rt\@example.com", "frt\@example.com"); -@after = ("frt\@example.com"); -ok(eq_array(RT::EmailParser::CullRTAddresses("",@before),@after), "CullRTAddresses only culls RT addresses"); - -=end testing - -=cut - -sub CullRTAddresses { -    my $self = shift; -    my @addresses= (@_); -    my @addrlist; - -    foreach my $addr( @addresses ) { -      push (@addrlist, $addr)    unless IsRTAddress("", $addr); -    } -    return (@addrlist); -} - -# }}} - - -# {{{ LookupExternalUserInfo - - -# LookupExternalUserInfo is a site-definable method for synchronizing -# incoming users with an external data source.  -# -# This routine takes a tuple of EmailAddress and FriendlyName -#   EmailAddress is the user's email address, ususally taken from -#       an email message's From: header. -#   FriendlyName is a freeform string, ususally taken from the "comment"  -#       portion of an email message's From: header. -# -# If you define an AutoRejectRequest template, RT will use this    -# template for the rejection message. - - -=item LookupExternalUserInfo - - LookupExternalUserInfo is a site-definable method for synchronizing - incoming users with an external data source.  - - This routine takes a tuple of EmailAddress and FriendlyName -    EmailAddress is the user's email address, ususally taken from -        an email message's From: header. -    FriendlyName is a freeform string, ususally taken from the "comment"  -        portion of an email message's From: header. - - It returns (FoundInExternalDatabase, ParamHash); - -   FoundInExternalDatabase must  be set to 1 before return if the user was -   found in the external database. - -   ParamHash is a Perl parameter hash which can contain at least the following -   fields. These fields are used to populate RT's users database when the user  -   is created - -    EmailAddress is the email address that RT should use for this user.   -    Name is the 'Name' attribute RT should use for this user.  -         'Name' is used for things like access control and user lookups. -    RealName is what RT should display as the user's name when displaying  -         'friendly' names - -=cut - -sub LookupExternalUserInfo { -  my $self = shift; -  my $EmailAddress = shift; -  my $RealName = shift; - -  my $FoundInExternalDatabase = 1; -  my %params; - -  #Name is the RT username you want to use for this user. -  $params{'Name'} = $EmailAddress; -  $params{'EmailAddress'} = $EmailAddress; -  $params{'RealName'} = $RealName; - -  # See RT's contributed code for examples. -  # http://www.fsck.com/pub/rt/contrib/ -  return ($FoundInExternalDatabase, %params); -} - -# }}} - -# {{{ Accessor methods for parsed email messages - -=head2 Head - -Return the parsed head from this message - -=cut - -sub Head { -    my $self = shift; -    return $self->Entity->head; -} - -=head2 Entity  - -Return the parsed Entity from this message - -=cut - -sub Entity { -    my $self = shift; -    return $self->{'entity'}; -} - -# }}} -# {{{ _SetupMIMEParser  - -=head2 _SetupMIMEParser $parser - -A private instance method which sets up a mime parser to do its job - -=cut - - -    ## TODO: Does it make sense storing to disk at all?  After all, we -    ## need to put each msg as an in-core scalar before saving it to -    ## the database, don't we? - -    ## At the same time, we should make sure that we nuke attachments  -    ## Over max size and return them - -sub _SetupMIMEParser { -    my $self = shift; -    my $parser = shift; -    my $AttachmentDir = File::Temp::tempdir( TMPDIR => 1, CLEANUP => 1 ); - -    # Set up output directory for files: -    $parser->output_dir("$AttachmentDir"); - -    #If someone includes a message, don't extract it -    $parser->extract_nested_messages(1); - -    # Set up the prefix for files with auto-generated names: -    $parser->output_prefix("part"); - -    # If content length is <= 50000 bytes, store each msg as in-core scalar; -    # Else, write to a disk file (the default action): - -    $parser->output_to_core(50000); -} -# }}} - -eval "require RT::EmailParser_Vendor"; -die $@ if ($@ && $@ !~ qr{^Can't locate RT/EmailParser_Vendor.pm}); -eval "require RT::EmailParser_Local"; -die $@ if ($@ && $@ !~ qr{^Can't locate RT/EmailParser_Local.pm}); - -1; | 
