Fun with Klez

Well, someone late friday night managed to send a copy of Klez (or
whatever mutation of the week Lookout! virus/worm is going around now)
with a From address of my postmaster@ address to my abuse@ address,
both of which feed into the abuse queue within RT.

What ensued was an all out war RT had with itself, managing to fill
50Mb worth of autoresponder messages into each of three staff members’
mailboxes. Once the mailboxes were way over any sane limit, messages
started bouncing out the wazoo, and my RT mail server and office mail
server started fighting with each other until I noticed and killed it
off early sunday morning…

These are the scrips I have. The admin cc’s are the three staff
people’s direct email addresses.

OnCreate NotifyAdminCcs with template Transaction
OnCreate AutoreplyToRequestors with template Postmaster/Abuse Autoreply
OnCorrespond NotifyAllWatchers with template Correspondence
OnComment NotifyAdminCcsAsComment with template AdminComment

The curious thing is that after the first autorespond message got sent
back to the queue itself, every subsequent one added one blank line to
the front of that message. Thanks to NotifyAllWatchers and the fact
that the message was from “nobody”, it notified the postmaster@
address, which then fed back into itself. 47,000+ messages later, all
heck broke loose :wink:

Is there some way to protect from this? I see a function called
IsRTAddress, but is is documented as only having any purpose when
$ParseNewMessageForTicketCcs is true. Can I make RT never send email
to any address identified by that function?

Vivek Khera wrote:

Is there some way to protect from this? I see a function called
IsRTAddress, but is is documented as only having any purpose when
$ParseNewMessageForTicketCcs is true. Can I make RT never send email
to any address identified by that function?

You could. Another way might be to use Bruce’s AutoReplySquelch
code from contrib. It Works For Us. :slight_smile:

You might also be being bitten by the loop detection not working
right, due to something (as yet undiagnosed) appending spaces to
some headers. Check

http://lists.fsck.com/pipermail/rt-devel/2002-July/002482.html

for a patch if this is affecting you. (See also: [fsck.com #1589])
Phil Homewood, Systems Janitor, www.SnapGear.com
pdh@snapgear.com Ph: +61 7 3435 2810 Fx: +61 7 3891 3630
SnapGear - Custom Embedded Solutions and Security Appliances

Vivek Khera wrote:

Is there some way to protect from this? I see a function called
IsRTAddress, but is is documented as only having any purpose when
$ParseNewMessageForTicketCcs is true. Can I make RT never send email
to any address identified by that function?

You might also be being bitten by the loop detection not working
right, due to something (as yet undiagnosed) appending spaces to
some headers. Check

And to get around that, I’d suggest changing one line in
RT::Interface::Email->CheckForLoops from:

    if ($RTLoop eq "$RT::rtname") {

to

    if ($RTLoop =~ /^\s*$RT::rtname\s*$/ ) {

Also note the TODO comment in that function. Personally I’d suggest
adding the following after that:

    # If X-RT-Loop-Prevention contains _anything_, treat it as a
    # loop.
    if ($RTLoop =~ /\S+/ ) {
        return( 2 );
    }

RT::Action::AnythingSendingEmail should also be preserving X-Loop headers
as-is from correspondence (which to my shame my queues do not do).

Regards,

                         Bruce Campbell                            RIPE
               Systems/Network Engineer                             NCC
             www.ripe.net - PGP562C8B1B             Operations/Security

“PH” == Phil Homewood pdh@snapgear.com writes:

PH> You might also be being bitten by the loop detection not working
PH> right, due to something (as yet undiagnosed) appending spaces to
PH> some headers. Check

PH> http://lists.fsck.com/pipermail/rt-devel/2002-July/002482.html

PH> for a patch if this is affecting you. (See also: [fsck.com #1589])

Yep… that was the cause. Apparently all RT-appended headers
contain a trialing space, so I’ll blame Mail::Header for doing it :wink:

Thanks.

“BC” == Bruce Campbell bruce_campbell@ripe.net writes:

BC> RT::Interface::Email->CheckForLoops from:

BC> if ($RTLoop eq “$RT::rtname”) {

BC> to

BC> if ($RTLoop =~ /^\s*$RT::rtname\s*$/ ) {

Great. I like this approach the best. I’m using it but modified a
bit for efficiency:

if ($RTLoop =~ m/\s?$RT::rtname\s?/) {

since the * is greedy. I suspect that the \s? could just be \b but
I’m happy with it this way.

Thanks for the patch/post!

Vivek Khera wrote:

“BC” == Bruce Campbell bruce_campbell@ripe.net writes:

BC> if ($RTLoop =~ /^\s*$RT::rtname\s*$/ ) {

I’m using it but modified a bit for efficiency:

if ($RTLoop =~ m/\s?$RT::rtname\s?/) {

since the * is greedy.

What’s wrong with that? I was always under the impression that greedy
modifiers are more efficient that non-greedy versions because the greedy
versions don’t involve backtracking.

Also, expressions that are anchored are much more efficient than those
that aren’t. In Bruce’s regexp Perl just has to start looking at the
start of the string. It goes along one character at a time until it
finds something that isn’t whitespace. Then it looks to see if that
character is the first character of $RT::rtname, and so on. If it finds
a non-matching character the whole match must have failed – there is
no other ‘start of string’ for it to look – so it fails quickly.

A non-greedy version could look like this:

if ($RTLoop =~ m/\s*?$RT::rtname\s*?/) {

Perl would first have to iterate through the string till it finds a
whitespace character. Then it looks if $RT::rtname matches. If it
doesn’t, it has to go back to the whitespace character it’s already
found and see if the following character is also whitespace and possibly
try $RT::rtname again. Once it’s exhausted all lengths of whitespace
starting from the first stretch of whitespace it tries again starting at
the second stretch of whitespace. That’s considerably more effort.

However what you wrote isn’t non-greedy – it just uses ? instead of *,
replacing one greedy quantifier with another. So you’re greedily
matching either one or zero spaces before and after $RT::rtname. Since
the expression isn’t anchored, that’s completely pointless; these two
statements are equivalent:

1 Does the string contain “ham” somewhere in its length?

2 Does the string contain “ham” somewhere in its length, with that
“ham” preceeded by either at least one character of whitespace or no
whitespace at all?

\s? would only match one whitespace character, but because the string
isn’t bounded there isn’t anything to stop the (non-matching) character
just before the matching space also being a space.

Note in particular that both of the above would match “sham” and
“shamed” as well as “ham” and " ham ", so your regexp matches many more
things than Bruce’s.

I suspect that \Q is needed in there somewhere though, in case
$RT::rtname contains any special characters (such as a . in a domain
name):

if ($RTLoop =~ /^\s*\Q$RT::rtname\E\s*$/ ) {

Smylers
GBdirect

“s” == smylers smylers@gbdirect.co.uk writes:

s> I suspect that \Q is needed in there somewhere though, in case
s> $RT::rtname contains any special characters (such as a . in a domain
s> name):

s> if ($RTLoop =~ /^\s*\Q$RT::rtname\E\s*$/ ) {

My bleary eyes didn’t notice the anchor at the front… you are indeed
correct, and I’m putting this version into place. Thanks.

Vivek Khera wrote:

“BC” == Bruce Campbell bruce_campbell@ripe.net writes:

BC> if ($RTLoop =~ /^\s*$RT::rtname\s*$/ ) {

I’m using it but modified a bit for efficiency:

if ($RTLoop =~ m/\s?$RT::rtname\s?/) {

since the * is greedy.

What’s wrong with that? I was always under the impression that greedy
modifiers are more efficient that non-greedy versions because the greedy
versions don’t involve backtracking.

Here’s a potentially helpful benchmark:

$ perl -MBenchmark -Mstrict
my $c = 0;
my $d = 0;
my $f = “hello world”;
my $pat = “hello”;
timethese( 1_000_000, {
‘anchored_greedy’ => sub { if ($f =~ /^\s*\Q$pat\E\s*$/) { $c++ } },
‘unanchored_nongreedy’ => sub { if ($f =~ /\s?\Q$pat\E\s?/) { $d++ } },
});

Benchmark: timing 1000000 iterations of anchored_greedy, unanchored_nongreedy…
anchored_greedy: 2 wallclock secs ( 3.36 usr + 0.00 sys = 3.36 CPU) @ 297619.05/s (n=1000000)
unanchored_nongreedy: 4 wallclock secs ( 3.03 usr + 0.00 sys = 3.03 CPU) @ 330033.00/s (n=1000000)

(darren)

Hell is other people.
– Jean-Paul Sartre