exim, perl and snmp! oh my! ian norton shadowcat - agaton

Post on 11-Feb-2022

4 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Exim, Perl and SNMP!Oh my!

Ian NortonShadowcat Systems Ltd.

Exim, Perl and SNMP!Oh my!

Working with Net SNMP Extend.

Ian NortonShadowcat Systems Ltd.

What's this talk about?

● Using Net SNMP extend● Monitoring arbitrary things● Using Exim as an example● Cleverness with OpenNMS

Email systems are evil.

Email systems are evil.

Exim is less evil :)

Exim

● Open source● Mail Transport Agent (MTA)● Mail Delivery Agent (MDA)● Cambridge University

In a previous life....

In a previous life....

postmaster@lancaster.ac.uk

University infrastructure

InternetHub1

Hub1Hub1

Hub1Hub1

Exchange

Unixmail

Dept 1 Dept 2

Standard service monitoring

● Connect TCP port 25● Check banner● How long did it take?

And so it begins...

● Messages delayed● 4xx temporary failures● Badly behaved senders don't retry

Everything is fine!

The monitoring says so!

idn facepalms

Everything is not fine

● One hub has an issue● Spam Assassin is dead● No problem identified● The monitoring is wrong

Service was affected.Messages were delayed.

Service was affected.Messages were delayed.

#fail

So what's the problem?

● Spam Assassin process went away● Exim unable to scan messages● Temporary reject (safe thing to do)

How do we test?

Testing

● Spam Assassin provides spamc● Create a batch SMTP file:

helo testing.example.comMAIL FROM: "Person, A" <norton@exchange.example.com>RCPT TO: idn@unix.example.comDATASubject: Testing viagraTo: idn@unix.example.comFrom: "Person, A" <idn@exchange.example.com>

Test SMTP session.QUIT

Testing

● spamc -c -B < bsmtp-file● Exit status zero or one

Enter Perl

● List of files to test & expected return● Test files and check return

# Files to test along with expected exit valuemy $files = {    "01­spamassassin­normal­message.txt"  => 0,    "02­spamassassin­spammy­message.txt"  => 1,};

my $fail = 0;

# Scan each of the files in the hashforeach my $file (keys(%$files)) {

    # Check the file    system("/usr/bin/spamc ­c ­B  < $file > /dev/null");

    # Bitshift $? to get the exit code    my $exitval = $CHILD_ERROR >> 8;

    # Compare the exit value to that expected    if($exitval != $files­>{$file}) {        $fail = 1;        last;    }}

# Files to test along with expected exit valuemy $files = {    "01­spamassassin­normal­message.txt"  => 0,    "02­spamassassin­spammy­message.txt"  => 1,};

my $fail = 0;

# Scan each of the files in the hashforeach my $file (keys(%$files)) {

    # Check the file    system("/usr/bin/spamc ­c ­B  < $file > /dev/null");

    # Bitshift $? to get the exit code    my $exitval = $CHILD_ERROR >> 8;

    # Compare the exit value to that expected    if($exitval != $files­>{$file}) {        $fail = 1;        last;    }}

Files totest

Expectedexit

value

Write status to a file

open(my $fh, '>', '/tmp/sa­status') or die('Cannot open file');print($fh $fail);close($fh);

So we know #fail.

Now what?!

SNMP

● Add SNMP!

● snmpd.confextend  sa­status  /bin/cat /tmp/sa­status

SNMP

● snmpwalk$ snmpwalk ­v2c ­c public 127.0.0.1 .1.3.6.1.4.1.8072.1.3.2 NET­SNMP­EXTEND­MIB::nsExtendNumEntries.0 = INTEGER: 1NET­SNMP­EXTEND­MIB::nsExtendCommand."sa­status" = STRING: /bin/catNET­SNMP­EXTEND­MIB::nsExtendArgs."sa­status" = STRING: /tmp/sa­statusNET­SNMP­EXTEND­MIB::nsExtendInput."sa­status" = STRING: NET­SNMP­EXTEND­MIB::nsExtendCacheTime."sa­status" = INTEGER: 5NET­SNMP­EXTEND­MIB::nsExtendExecType."sa­status" = INTEGER: exec(1)NET­SNMP­EXTEND­MIB::nsExtendRunType."sa­status" = INTEGER: run­on­read(1)NET­SNMP­EXTEND­MIB::nsExtendStorage."sa­status" = INTEGER: permanent(4)NET­SNMP­EXTEND­MIB::nsExtendStatus."sa­status" = INTEGER: active(1)NET­SNMP­EXTEND­MIB::nsExtendOutput1Line."sa­status" = STRING: 1NET­SNMP­EXTEND­MIB::nsExtendOutputFull."sa­status" = STRING: 1NET­SNMP­EXTEND­MIB::nsExtendOutNumLines."sa­status" = INTEGER: 1NET­SNMP­EXTEND­MIB::nsExtendResult."sa­status" = INTEGER: 0NET­SNMP­EXTEND­MIB::nsExtendOutLine."sa­status".1 = STRING: 1

SNMP

● snmpwalk$ snmpwalk ­v2c ­c public 127.0.0.1 .1.3.6.1.4.1.8072.1.3.2 NET­SNMP­EXTEND­MIB::nsExtendNumEntries.0 = INTEGER: 1NET­SNMP­EXTEND­MIB::nsExtendCommand."sa­status" = STRING: /bin/catNET­SNMP­EXTEND­MIB::nsExtendArgs."sa­status" = STRING: /tmp/sa­statusNET­SNMP­EXTEND­MIB::nsExtendInput."sa­status" = STRING: NET­SNMP­EXTEND­MIB::nsExtendCacheTime."sa­status" = INTEGER: 5NET­SNMP­EXTEND­MIB::nsExtendExecType."sa­status" = INTEGER: exec(1)NET­SNMP­EXTEND­MIB::nsExtendRunType."sa­status" = INTEGER: run­on­read(1)NET­SNMP­EXTEND­MIB::nsExtendStorage."sa­status" = INTEGER: permanent(4)NET­SNMP­EXTEND­MIB::nsExtendStatus."sa­status" = INTEGER: active(1)NET­SNMP­EXTEND­MIB::nsExtendOutput1Line."sa­status" = STRING: 1NET­SNMP­EXTEND­MIB::nsExtendOutputFull."sa­status" = STRING: 1NET­SNMP­EXTEND­MIB::nsExtendOutNumLines."sa­status" = INTEGER: 1NET­SNMP­EXTEND­MIB::nsExtendResult."sa­status" = INTEGER: 0NET­SNMP­EXTEND­MIB::nsExtendOutLine."sa­status".1 = STRING: 1

Now we can monitor!

OpenNMS

● poller-configuration.xml    <service name="SA­Status" interval="300000" user­defined="false" status="on">      <parameter key="retry" value="2"/>      <parameter key="timeout" value="3000"/>      <parameter key="port" value="161"/>      <parameter key="oid" value=".1.3.6.1.4.1.8072.1.3.2.4.1.2.9.115.97.45.115.116.97.116.117.115.1"/>      <parameter key="operator" value="="/>      <parameter key="operand" value="0"/>    </service>

  <monitor service="SA­Status"    class­name="org.opennms.netmgt.poller.monitors.SnmpMonitor" />

OpenNMS

● poller-configuration.xml    <service name="SA­Status" interval="300000" user­defined="false" status="on">      <parameter key="retry" value="2"/>      <parameter key="timeout" value="3000"/>      <parameter key="port" value="161"/>      <parameter key="oid" value=".1.3.6.1.4.1.8072.1.3.2.4.1.2.9.115.97.45.115.116.97.116.117.115.1"/>      <parameter key="operator" value="="/>      <parameter key="operand" value="0"/>    </service>

  <monitor service="SA­Status"    class­name="org.opennms.netmgt.poller.monitors.SnmpMonitor" />

Commandoutput

OpenNMS

● poller-configuration.xml    <service name="SA­Status" interval="300000" user­defined="false" status="on">      <parameter key="retry" value="2"/>      <parameter key="timeout" value="3000"/>      <parameter key="port" value="161"/>      <parameter key="oid" value=".1.3.6.1.4.1.8072.1.3.2.4.1.2.9.115.97.45.115.116.97.116.117.115.1"/>      <parameter key="operator" value="="/>      <parameter key="operand" value="0"/>    </service>

  <monitor service="SA­Status"    class­name="org.opennms.netmgt.poller.monitors.SnmpMonitor" />

Externalcommandnumber

Erk.

#fail.

+++OUT OF CHEESE+++

OpenNMS

● poller-configuration.xml    <service name="SA­Status" interval="300000" user­defined="false" status="on">      <parameter key="retry" value="2"/>      <parameter key="timeout" value="3000"/>      <parameter key="port" value="161"/>      <parameter key="oid" value=".1.3.6.1.4.1.8072.1.3.2.4.1.2.9.115.97.45.115.116.97.116.117.115.1"/>      <parameter key="operator" value="="/>      <parameter key="operand" value="0"/>    </service>

  <monitor service="SA­Status"    class­name="org.opennms.netmgt.poller.monitors.SnmpMonitor" />

Length ofidentifier

“sa-status”Thanks to roskens on

#opennms for pointing outthat this was wrong.

Cheers! :)

OpenNMS

● poller-configuration.xml    <service name="SA­Status" interval="300000" user­defined="false" status="on">      <parameter key="retry" value="2"/>      <parameter key="timeout" value="3000"/>      <parameter key="port" value="161"/>      <parameter key="oid" value=".1.3.6.1.4.1.8072.1.3.2.4.1.2.9.115.97.45.115.116.97.116.117.115.1"/>      <parameter key="operator" value="="/>      <parameter key="operand" value="0"/>    </service>

  <monitor service="SA­Status"    class­name="org.opennms.netmgt.poller.monitors.SnmpMonitor" />

“sa-status”

OpenNMS

● poller-configuration.xml    <service name="SA­Status" interval="300000" user­defined="false" status="on">      <parameter key="retry" value="2"/>      <parameter key="timeout" value="3000"/>      <parameter key="port" value="161"/>      <parameter key="oid" value=".1.3.6.1.4.1.8072.1.3.2.4.1.2.9.115.97.45.115.116.97.116.117.115.1"/>      <parameter key="operator" value="="/>      <parameter key="operand" value="0"/>    </service>

  <monitor service="SA­Status"    class­name="org.opennms.netmgt.poller.monitors.SnmpMonitor" />

Linenumber ofcommand

output

OpenNMS

● poller-configuration.xml    <service name="SA­Status" interval="300000" user­defined="false" status="on">      <parameter key="retry" value="2"/>      <parameter key="timeout" value="3000"/>      <parameter key="port" value="161"/>      <parameter key="oid" value=".1.3.6.1.4.1.8072.1.3.2.4.1.2.9.115.97.45.115.116.97.116.117.115.1"/>      <parameter key="operator" value="="/>      <parameter key="operand" value="0"/>    </service>

  <monitor service="SA­Status"    class­name="org.opennms.netmgt.poller.monitors.SnmpMonitor" />

Commandoutput

“sa-status”

Linenumber ofcommand

output

Length ofidentifier

“sa-status”

Provisioning

● Foreign source

● Node

That works!

That works!

But it sucks!

Issues

● Stale file?● Not SNMP tables - no way to instance map● Works for simple data● Can have multiple lines● Assumes ordering with multiple lines

Issues

● Want to add a service● Want to remove a service● Want to re-order my file● …...

Issues

● Want to add a service● Want to remove a service● Want to re-order my file (OCD attack)● …...

Mail queue size

● Want to track destination domains● Which are queueing● Which are local, remote, etc

Our single file approachdoes not scale.

Our single file approachdoes not scale.

Add cloud more files!

Output to two files

● Keys● Values

● The data can change● OpenNMS will map together for us● Add a timestamp

Output to two files

# Open the files to work withmy $file       = $path . $counter . '_';my $keys_file  = IO::File­>new( $file . 'keys', 'w' ) or croak($OS_ERROR);my $stats_file = IO::File­>new( $file . 'stats', 'w' ) or croak($OS_ERROR);

my $timestamp = time();

# Output the current timestamp to both files$keys_file­>print("$timestamp\n");$stats_file­>print("$timestamp\n");

# Output the keys and stats to the relevant files.foreach my $key ( sort( keys( %{$stats} ) ) ) {    $keys_file­>print("$key\n");    $stats_file­>print( $stats­>{$key} . "\n" );}

# Close the File::IO objects$keys_file­>close();$stats_file­>close();

Generate the data

● Exim has exipick● --flatq option is designed for parsing● Options for extra data

Generate the data# Set the exipick binary & CLI optionsmy $exipick_cmd = "/usr/sbin/exipick";my $exipick_opt = "­­flatq ­­show­vars message_size,deliver_freeze";

# Initialise interesting domain counters and the two catch­all domains.my %domains = ('exchange.example.com'   => 0,               'dept1.example.com'      => 0,               '*.example.com'          => 0,   # Catch­all for other example.com               'internal'               => 0,   # Counter for all internal               'external'               => 0,   # Catch­all for everything else);

# Run the exipick command and process the output.open(my $exipick, '­|', "$exipick_cmd $exipick_opt");

# Loop through the command outputwhile(<$exipick>) {    chomp;

    my $line = $_;

    # Split the show­vars with spaces rather than semi­colons    $line =~ s/;/ /g;

    # Send the exipick line to be processed.    process_line($line);}

close($exipick);

Generate the datasub process_line {    my $line = shift;

    # Sample exipick output line with email address changed to anonymise:    #  4d message_size='9265' deliver_freeze='' 1E9hWO­0001AH­4A <> sn@als.org

    # Split the output line using a regexp    if($line =~ m/.* message_size='(\d*)' deliver_freeze='(.*)' (.*­.*­.*) <(.*)> (.*)/){        my $msg_size      = $1;        my $msg_frozen    = $2;        my $msg_id        = $3;        my $msg_sender    = $4;        my $msg_recipient = $5;

        # Send the recipient email address to process domain for a breakdown.        process_domain($msg_recipient);    }}

Generate the datasub process_domain {    my $email = shift;

    # Get the domain from the email address.    my $domain = $email;    $domain =~ s/^.*@//;

    # Is this an internal domain?    if($domain =~ m/example.com$/) {        $domains{'internal'}++;    }

    # If this domain exists, increment the counter for that domain.    if(defined($domains{$domain})) {        $domains{$domain}++;    }

    # Increment the catch­all counter for *.example.com addresses    elsif ($domain =~ m/example.com$/) {        $domains{'*.example.com'}++;    }

    # Increment the external counter for everything else    else {        $domains{'external'}++;    }}

Generate the data

$VAR1 = {          'dept1.example.com' => 34,          'exchange.example.com' => 26,          'external' => 43,          'internal' => 110,          '*.example.com' => 50        };

● Generates this data:

● Which writes our two files

/tmp/mailq_keys

/tmp/mailq_stats

SNMP

● snmpd.conf

extend  mailq­keys  /bin/cat /tmp/mailq_keysextend  mailq­stats /bin/cat /tmp/mailq_stats

SNMP

● snmpwalk$ snmpwalk ­v2c ­c public 127.0.0.1 NET­SNMP­EXTEND­MIB::nsExtendOutLineNET­SNMP­EXTEND­MIB::nsExtendOutLine."mailq­keys".1 = STRING: 1534221421134720NET­SNMP­EXTEND­MIB::nsExtendOutLine."mailq­keys".2 = STRING: *.example.comNET­SNMP­EXTEND­MIB::nsExtendOutLine."mailq­keys".3 = STRING: exchange.example.comNET­SNMP­EXTEND­MIB::nsExtendOutLine."mailq­keys".4 = STRING: dept1.example.comNET­SNMP­EXTEND­MIB::nsExtendOutLine."mailq­keys".5 = STRING: internalNET­SNMP­EXTEND­MIB::nsExtendOutLine."mailq­keys".6 = STRING: externalNET­SNMP­EXTEND­MIB::nsExtendOutLine."mailq­stats".1 = STRING: 1534221421134720NET­SNMP­EXTEND­MIB::nsExtendOutLine."mailq­stats".2 = STRING: 50NET­SNMP­EXTEND­MIB::nsExtendOutLine."mailq­stats".3 = STRING: 26NET­SNMP­EXTEND­MIB::nsExtendOutLine."mailq­stats".4 = STRING: 34NET­SNMP­EXTEND­MIB::nsExtendOutLine."mailq­stats".5 = STRING: 110NET­SNMP­EXTEND­MIB::nsExtendOutLine."mailq­stats".6 = STRING: 43

Now we can monitor!

OpenNMS

  <resourceType name="eximMailQueueInst" label="Exim mail queue">    <persistenceSelectorStrategy class="org.opennms.netmgt.collectd.PersistRegexSelectorStrategy">      <parameter key="match­expression"                  value="not(#eximMailQueueKey matches '^\d+$')"      />    </persistenceSelectorStrategy>

    <storageStrategy class="org.opennms.netmgt.dao.support.SiblingColumnStorageStrategy">      <parameter key="sibling­column­name" value="eximMailQueueKey" />      <parameter key="replace­all" value="s/\s/_/" />    </storageStrategy>  </resourceType>

OpenNMS

  <group name="exim­mailq" ifType="ignore">    <mibObj oid=".1.3.6.1.4.1.8072.1.3.2.4.1.2.10.109.97.105.108.113.45.107.101.121.115"            instance="eximMailQueueInst"            alias="eximMailQueueKey"            type="string"     />    <mibObj oid=".1.3.6.1.4.1.8072.1.3.2.4.1.2.11.109.97.105.108.113.45.115.116.97.116.115"            instance="eximMailQueueInst"            alias="eximMailQueueStat"            type="gauge"    />  </group>

  <systemDef name="Exim monitoring">    <sysoidMask>.1.3.6.1.4.1.8072.3.2.10</sysoidMask>      <collect>        <includeGroup>exim­mailq</includeGroup>      </collect>  </systemDef>

Graphs!

# Reports list for Eximreports=exim.eximMailQueueKey, exim.eximMailQueueStat

report.exim.eximMailQueueStat.name=Messagesreport.exim.eximMailQueueStat.columns=eximMailQueueStatreport.exim.eximMailQueueStat.propertiesValues=eximMailQueueKeyreport.exim.eximMailQueueStat.type=eximMailQueueInstreport.exim.eximMailQueueStat.command=­­title="{eximMailQueueKey}" \ DEF:messages={rrd1}:eximMailQueueStat:AVERAGE \ LINE2:messages#0000ff:"Messages" \ GPRINT:messages:AVERAGE:" Avg \\: %2.0lf %s" \ GPRINT:messages:MIN:" Min  \\: %2.0lf %s" \ GPRINT:messages:MAX:" Max  \\: %2.0lf %s\\n"

Graphs!

OpenNMS

● Now we get thresholding● And all the fun of the NMS!

Further expansion

● Antivirus● Total number of messages● Log file parsing● Message transit times● RBL monitoring

Questions?

Thanks!

Slides available at:http://agaton.scsys.co.uk/~iann/talks/

Ian Nortoni.norton@shadowcat.co.uk

idn on irc.perl.org and irc.freenode.net

top related