Counting emails per hour Exchange Server

Posted on Tuesday, March 3, 2015




I was presented with a new, not so fun, task.   Create a system that can count e-mails per hour from a sender and if they go over a certain number react.   Sensu, for our team, is the obvious choice for the reaction part.  But how do I count emails/hour from an Exchange server without using any Microsoft Products?

Here is my attempt.




Ruby Tools


At the end of the day I want this to be a simple ruby script I can call from Sensu, so I am going to start there and see what tools I can find.

I found viewpoint at https://github.com/WinRb/Viewpoint [1]

Looks like it's in a gem, install it with the following command


    > sudo gem install viewpoint







Poking at it


I just want to poke at it a little and see what I can do.

Let me create a new plugin


    > cd /etc/sensu/plugins
    > sudo touch exchange_email_count.rb
    > sudo chmod u+x exchange_email_count.rb
    > sudo vi exchange_email_count.rb


I stared at the https://github.com/WinRb/Viewpoint site for a long time and came up with the following code, which simply sends out an email from your account.

Before you start down this path make sure you can talk to the SOAP interface for the Exchange server.  First find the URL.

It should in a form like this.



https://example.com/ews/Exchange.asmx


Open that URL and see if you can login



You should get a bunch of XML (I blurred this just in case, but it should be generic wsdl data)


If you confirmed that is working then grab your
   Username
   Password
   And this address

And put them in this bit of code..



#!/usr/bin/ruby

require 'viewpoint'
include Viewpoint::EWS

endpoint = ' https://example.com/ews/Exchange.asmx'
user = 'username'
pass = 'password'

cli = Viewpoint::EWSClient.new endpoint, user, pass
cli.send_message subject: "Test", body: "This is to prove I can email it out!", to_recipients: ['me@example.com']

puts "Email has been sent!"


Change the information for your own.

Now run it


    > ./exchange_email_count.rb


If you are lucky it runs!  If you are like me there is one more thing to take care of.

I get this error back.




/var/lib/gems/1.9.1/gems/viewpoint-1.0.0/lib/ews/connection.rb:107:in `check_response':  SOAP Error: Message:   Code:  (Viewpoint::EWS::Errors::SoapResponseError)
        from /var/lib/gems/1.9.1/gems/viewpoint-1.0.0/lib/ews/connection.rb:89:in `post'
        from /var/lib/gems/1.9.1/gems/viewpoint-1.0.0/lib/ews/connection.rb:67:in `dispatch'
        from /var/lib/gems/1.9.1/gems/viewpoint-1.0.0/lib/ews/soap/exchange_web_service.rb:212:in `do_soap_request'
        from /var/lib/gems/1.9.1/gems/viewpoint-1.0.0/lib/ews/soap/exchange_data_services.rb:150:in `create_item'
        from /var/lib/gems/1.9.1/gems/viewpoint-1.0.0/lib/ews/message_accessors.rb:65:in `send_message'
        from ./exchange_email_count.rb:16:in `<main>'


The fix for this was to specify the server you are using.




For example if you Exchange server where Version 2010 SP3 you would change your code to this.


#!/usr/bin/ruby

require 'viewpoint'
include Viewpoint::EWS

endpoint = ' https://example.com/ews/Exchange.asmx'
user = 'username'
pass = 'password'

cli = Viewpoint::EWSClient.new endpoint, user, pass,
server_version: SOAP::ExchangeWebService::VERSION_2010_SP3

cli = Viewpoint::EWSClient.new endpoint, user, pass
cli.send_message subject: "Test", body: "This is to prove I can email it out!", to_recipients: ['me@example.com']

puts "Email has been sent!"


Now run it


    > ./exchange_email_count.rb




Now it works!






Counting emails


Let me see if I can just count the number of emails in my inbox.  Here is the code I came up with.


#!/usr/bin/ruby

require 'viewpoint'
include Viewpoint::EWS

endpoint = ' https://example.com/ews/Exchange.asmx'
user = 'username'
pass = 'password'

cli = Viewpoint::EWSClient.new endpoint, user, pass,
server_version: SOAP::ExchangeWebService::VERSION_2010_SP3

folder = cli.get_folder_by_name 'inbox'
dname = folder.display_name
count = folder.total_count

puts "In folder " + dname + " there are " + count.to_s + " emails"




Now run it


    > ./exchange_email_count.rb




I got back 1,000 emails in my inbox.  That number seems suspicious.



To check how many emails I actually have in my inbox I did the following.



Right click on my inbox and select Properties




Select "Show Total Number of items" and click OK.




Oh, there is 1,000 emails in there.


As a test I sent myself 2 emails





Now it's 1002.   Really?  I just happened to check when I had 1,000 emails in my inbox…. That is hard to believe!



Running the script again I get




1,002.  OK I guess just an odd timing on my part J









Counting todays emails


A bit more tweaking,  I figured this out that returns the number of emails in the inbox from today.


#!/usr/bin/ruby

require 'viewpoint'
include Viewpoint::EWS

endpoint = ' https://example.com/ews/Exchange.asmx'
user = 'username'
pass = 'password'

cli = Viewpoint::EWSClient.new endpoint, user, pass,
server_version: SOAP::ExchangeWebService::VERSION_2010_SP3

folder = cli.get_folder_by_name 'inbox'
dname = folder.display_name
count = folder.todays_items.length

puts count.to_s + " emails have been received today"




Now run it


    > ./exchange_email_count.rb





That worked.

Now to see if I can get just the last X minutes of emails listed.






Counting the last X minutes of emails


A bit more tweaking,  I want to get a list of all the emails for over a given time frame.


#!/usr/bin/ruby

require 'viewpoint'
include Viewpoint::EWS

endpoint = ' https://example.com/ews/Exchange.asmx'
user = 'username'
pass = 'password'

cli = Viewpoint::EWSClient.new endpoint, user, pass,
server_version: SOAP::ExchangeWebService::VERSION_2010_SP3

folder = cli.get_folder_by_name 'inbox'
days = 5
end_t = Time.now
start_t = end_t - days*24*60*60

count = (folder.items_between start_t, end_t).length

puts count.to_s + " emails have been received within the last " + days.to_s + " days"





Now run it


    > ./exchange_email_count.rb




That worked.  It's almost what I want.  The only addition I want to add is to filter on a from address.  Let me see if I can do that.







Counting the last X minutes of emails that were send from a single email address.


A bit more tweaking, I came up with this.  Change the email to the one you want to filter on.


#!/usr/bin/ruby

require 'viewpoint'
include Viewpoint::EWS

endpoint = ' https://example.com/ews/Exchange.asmx'
user = 'username'
pass = 'password'

cli = Viewpoint::EWSClient.new endpoint, user, pass,
server_version: SOAP::ExchangeWebService::VERSION_2010_SP3

folder = cli.get_folder_by_name 'inbox'
days = 3
end_t = Time.now
start_t = end_t - days*24*60*60

emails = folder.items_between(start_t, end_t)
sender_name = "test@example.com"
count = 0

for email in emails
  if email.sender.email == sender_name
    count += 1
  end
end

puts "You have received " + count.to_s + " emails from " +
         sender_name + " in the last " + days.to_s + " days"



Now run it


    > ./exchange_email_count.rb





That is basically what I want, now to turn it into a real Sensu plugin.








Change it into a Sensu plugin


  

#!/usr/bin/ruby
#
#
#The MIT License (MIT)
#
#Copyright (c) 2015 Patrick Bailey
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#       The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#       THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#       OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#SOFTWARE.
#
#

require 'sensu-plugin/check/cli'
require 'viewpoint'
include Viewpoint::EWS

class CountEmails< Sensu::Plugin::Check::CLI

 
option :minutes,
        
:description => "How many minutes prior to check emails",
        
:short => '-m MINUTES',
        
:long => '--minutes MINUTES',
        
:proc => proc {|a| a.to_i},
        
:default => 15,
        
:required => true

 
option :from_filter,
        
:description => "Only count emails received from this email",
        
:short => '-f FROM_FILTER',
        
:long => '--from FROM_FILTER',
        
:required => false

 
option :warn_limit,
        
:description => "Threshold, above which a warning is issued",
        
:short => '-w WARN_LIMIT',
        
:long => '--warn WARN_LIMIT',
        
:proc => proc {|a| a.to_i},
        
:required => false

 
option :alarm_limit,
        
:description => "Threshold, above which an alarm is issued",
        
:short => '-a ALARM',
        
:long => '--alarm ALARM',
        
:proc => proc {|a| a.to_i},
        
:required => false



  def
initilize
   
super
  end

  def
run
   
endpoint = ' https://example.com/ews/Exchange.asmx'
   
user = 'username'
   
pass = 'password'

   
cli = Viewpoint::EWSClient.new endpoint, user, pass, server_version: SOAP::ExchangeWebService::VERSION_2010_SP1
   
folder = cli.get_folder_by_name 'inbox'

   
end_t = Time.now
   
start_t = end_t - config[:minutes]*60
   
emails = folder.items_between(start_t, end_t)
   
count = 0
   
filter_note = ""

   
#Filter if a from address is given
   
if !config[:from_filter].nil?
     
filter_note = " filtering on '" + config[:from_filter] + "'"
     
for email in emails
       
if email.sender.email == config[:from_filter]
         
count += 1
       
end
      end
    else
     
#No address to filter on count all email
     
count = emails.length
   
end


    if
!config[:alarm_limit].nil? && count >= config[:alarm_limit]
      critical(message(
count, config[:minutes], filter_note))
   
elsif !config[:warn_limit].nil? && count >= config[:warn_limit]
      warning(message(
count, config[:minutes], filter_note))
   
else
     
ok(message(count, config[:minutes], filter_note))
   
end
  end

  def
message(count, minutes, filter_note)
   
count.to_s + " emails have been received within the last " + minutes.to_s + " minutes" + filter_note
 
end
end



I made a gist of it at https://gist.github.com/patmandenver/7ddafca42c2a555db4d8










Create the actual Check



    > sudo vi /etc/sensu/conf.d/check_email_count.json


Place the following into it.


{
  "checks": {
    "check_email_rate": {
      "subscribers": [
        "check-from-sensu-master"
      ],
      "refresh": 3600,
      "occurrences": 1,
      "interval": 900,
      "command": "/etc/sensu/plugins/exchange_email_count.rb -m 60 -f issue@example.com -w 10 -a 20",
      "handlers": [
        "default"
      ]
    }
  }
}


You will need to tweak these to your Sensu Settings.  Basically I am running this check every 15 minutes.   When it runs it counts the emails sent in the last 60 minutes that were sent by issue@example.com.  If there are more than 20 alarm.  If there are more than 10, but less than 20 issue a warning.


Restart the sensu-server on the client


    > sudo service sensu-client restart


Restart the Sensu Master with the following command.


    > sudo service sensu-server restart && sudo service sensu-api restart



Opening up my Sensu Uchiwa page at

I can see that the check is there.




This check, as is, will not work since there is no example.com.



I tweaked it to actual settings and tested it.  And it works!








That is cool!



References


[1]        Viewpoint (Ruby client for Exchange Web Services)
            https://github.com/WinRb/Viewpoint
            Accessed 1/2015



This post is a part of and epic, the Sensu Epic.

Epic Goal:    My goal is to just really figure out how to use Sensu. 



No comments:

Post a Comment