2024-02-08 18:39:28 +01:00
#!/usr/bin/env python
2023-01-23 18:17:05 +01:00
# -*- coding: utf-8 -*-
2024-02-08 18:39:28 +01:00
"""
EXCHANGE MAIL PLUGIN
Author : Alejandro Sanchez Carrion
Copyright : Copyright 2024 , PandoraFMS
Maintainer : Operations Department
Status : Production
Version : 1.0
"""
from exchangelib import Credentials , Configuration , Account , DELEGATE , OAUTH2 , IMPERSONATION , Message , Mailbox
from exchangelib import OAuth2Credentials
from exchangelib . version import Version , EXCHANGE_O365
2023-01-23 18:17:05 +01:00
from exchangelib . protocol import BaseProtocol , NoVerifyHTTPAdapter
2024-02-08 18:39:28 +01:00
2023-01-23 18:17:05 +01:00
BaseProtocol . HTTP_ADAPTER_CLS = NoVerifyHTTPAdapter
import urllib3
urllib3 . disable_warnings ( )
2024-02-08 18:39:28 +01:00
import pandoraPlugintools as ppt
2023-01-23 18:17:05 +01:00
import argparse , sys , re , json , os , traceback
2024-02-14 13:53:38 +01:00
from datetime import datetime , timedelta , timezone
2023-01-23 18:17:05 +01:00
__author__ = " Alejandro Sánchez Carrion "
__copyright__ = " Copyright 2022, PandoraFMS "
__maintainer__ = " Operations department "
__status__ = " Production "
__version__ = ' 1.0 '
info = f """
Pandora FMS Exchange Mail
2024-02-08 18:39:28 +01:00
Version = 1.0
2023-01-23 18:17:05 +01:00
Description = This plugin can search for matches in your mail and find the number of matches , as well as list them .
Manual execution
2024-02-14 13:53:38 +01:00
. / exchange_mail \
- - auth < oauth > \
- - server < server > \
- - smtp_address < smtp_address > \
- - client_id < client_id > \
- - tenant_id < tenant_id > \
- - secret < secret > \
[ - - user < user > ] \
[ - - password < password > ] \
[ - - subject < subject > ] \
[ - - sender < sender > ] \
[ - - date_start < date_start > ] \
[ - - date_end < date_end > ] \
[ - - mail_list < mail_list > ] \
[ - - module_prefix < module_prefix > ] \
[ - - agent_prefix < agent_prefix > ] \
[ - - group < group > ] \
[ - - interval < interval > ] \
[ - - temporal < temporal > ] \
[ - - data_dir < data_dir > ] \
[ - - transfer_mode < transfer_mode > ] \
[ - - tentacle_client < tentacle_client > ] \
[ - - tentacle_opts < tentacle_opts > ] \
[ - - tentacle_port < tentacle_port > ] \
[ - - tentacle_address < tentacle_address > ] \
[ - - log_file < log_file > ]
there are three parameters with which to filter the mails
2023-01-23 18:17:05 +01:00
subject
email
date
You can use only one and filter from that or use the following combinations :
subject
subject + sender
subject + sender + date
"""
parser = argparse . ArgumentParser ( description = info , formatter_class = argparse . RawTextHelpFormatter )
2024-02-08 18:39:28 +01:00
parser . add_argument ( ' --server ' , help = " Server name " , default = " outlook.office365.com " , type = str )
parser . add_argument ( ' --smtp_address ' , help = " SMTP address " , required = True , type = str )
parser . add_argument ( ' --user ' , help = " User name " , default = " " , type = str )
parser . add_argument ( ' --password ' , help = " Password " , default = " " , type = str )
parser . add_argument ( ' --client_id ' , help = " Client_id " , default = " " , type = str )
parser . add_argument ( ' --tenant_id ' , help = " Tenant_id " , default = " " , type = str )
parser . add_argument ( ' --secret ' , help = " Secret " , default = " " , type = str )
parser . add_argument ( ' --subject ' , help = " Select match in subjects " , default = None , type = str )
parser . add_argument ( ' --sender ' , help = " Select coincidences from email " , default = None , type = str )
parser . add_argument ( ' --date_start ' , help = " Search for matches from a certain date,Each date must be separated by a hyphen and in quotation marks, with the following format: ' year-month-day-hour-minute ' . example: ' 2021-1-12-0-0 ' " , default = None , type = str )
parser . add_argument ( ' --date_end ' , help = " Search for matches from a certain date,Each date must be separated by a hyphen and in quotation marks, with the following format: ' year-month-day-hour-minute ' . example: ' 2021-6-12-0-0 ' " , default = None , type = str )
parser . add_argument ( ' --mail_list ' , help = ' List mail coincidences ' , default = 0 , type = int )
parser . add_argument ( ' --module_prefix ' , help = ' Prefix for the modules. Example : meraki. ' , default = " " , type = str )
parser . add_argument ( ' --agent_prefix ' , help = ' Prefix for the agents. Example : meraki. ' , default = " " , type = str )
parser . add_argument ( ' --group ' , help = ' PandoraFMS destination group (default exchange) ' , default = ' ' , type = str )
parser . add_argument ( ' --interval ' , help = ' Agent monitoring interval ' , default = 300 , type = int )
parser . add_argument ( ' --temporal ' , help = ' PandoraFMS temporal dir ' , default = ' /tmp ' , type = str )
parser . add_argument ( ' --data_dir ' , help = ' PandoraFMS data dir ' , default = ' /var/spool/pandora/data_in/ ' , type = str )
parser . add_argument ( ' --transfer_mode ' , help = ' Data transfer mode, local or tentacle ' , default = " tentacle " , type = str )
parser . add_argument ( ' --tentacle_client ' , help = ' Tentacle client path, by default tentacle_client ' , default = " tentacle_client " , type = str )
parser . add_argument ( ' --tentacle_opts ' , help = ' Additional tentacle options ' , default = " " , type = str )
parser . add_argument ( ' --tentacle_port ' , help = ' Tentacle port ' , default = 41121 , type = int )
parser . add_argument ( ' --tentacle_address ' , help = ' Tentacle adress ' , default = " 127.0.0.1 " , type = str )
parser . add_argument ( ' --log_file ' , help = ' Log file path ' , default = ' /tmp/exchangemail_logfile.txt ' , type = str )
parser . add_argument ( ' --auth ' , choices = [ ' basic ' , ' oauth ' ] , help = ' Auth type ' , required = True )
2023-01-23 18:17:05 +01:00
args = parser . parse_args ( )
2024-02-08 18:39:28 +01:00
###############
## VARIABLES ##
###############
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
server = args . server
smtp_address = args . smtp_address
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
user = args . user
password = args . password
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
client_id = args . client_id
tenant_id = args . tenant_id
secret = args . secret
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
subject = args . subject
sender = args . sender
date_start = args . date_start
date_end = args . date_end
mail_list = args . mail_list
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
module_prefix = args . module_prefix
agent_prefix = args . agent_prefix
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
temporal = args . temporal
group = args . group
interval = args . interval
data_dir = args . data_dir
transfer_mode = args . transfer_mode
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
tentacle_address = args . tentacle_address
tentacle_port = args . tentacle_port
tentacle_client = args . tentacle_client
tentacle_opts = args . tentacle_opts
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
log_file = args . log_file
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
###############
## FUNCTIONS ##
###############
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
def Oauth_session ( credentials ) :
"""
Creates an OAuth session with an Exchange server using the provided credentials .
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
Args :
credentials ( Credentials ) : Credentials object containing information for OAuth authentication .
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
Returns :
Account : An Account object representing the OAuth session with the Exchange server .
"""
try :
config = Configuration ( server = server , credentials = credentials , auth_type = OAUTH2 , version = Version ( build = EXCHANGE_O365 ) , )
account = Account (
smtp_address ,
credentials = credentials ,
config = config ,
autodiscover = False ,
access_type = IMPERSONATION )
return account
except Exception as e :
print ( 0 )
write_to_log ( f " { type ( e ) . __name__ } : { e } " , log_file )
sys . exit ( )
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
def basic_session ( credentials ) :
"""
Creates a basic session with an Exchange server using the provided credentials .
Args :
credentials ( Credentials ) : Credentials object containing information for authentication .
Returns :
Account : An Account object representing the basic session with the Exchange server .
"""
2023-01-23 18:17:05 +01:00
try :
2024-02-08 18:39:28 +01:00
config = Configuration ( server = server , credentials = credentials )
2023-01-23 18:17:05 +01:00
account = Account (
primary_smtp_address = args . smtp_address ,
autodiscover = False ,
config = config ,
access_type = DELEGATE
)
return account
except Exception as e :
2024-02-08 18:39:28 +01:00
print ( 0 )
write_to_log ( f " { type ( e ) . __name__ } : { e } " , log_file )
sys . exit ( )
def create_module ( name , module_type , description , value , unit = " " ) :
"""
Creates a generic module based on a template .
Args :
module_prefix ( str ) : The prefix for the module name .
name_suffix ( str ) : The suffix to be appended to the module name .
module_type ( str ) : The type of the module .
description ( str ) : The description of the module .
value : The value of the module .
unit ( str , optional ) : The unit of measurement for the module . Defaults to " " .
Returns :
dict : A dictionary representing the generic module .
"""
return {
" name " : f ' { module_prefix } { name } ' ,
" type " : module_type ,
" desc " : description ,
" value " : value ,
" unit " : unit
}
def create_agent ( count , list_mail = None ) :
"""
Creates an agent with specified parameters and transfers it to a target address .
Args :
count ( int ) : Number of mails matching the filter used in the run .
list_mail ( str , optional ) : List of mails matching the filter used in the run . Default is None .
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
Returns :
None
"""
modules = [ ]
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
modules . append ( create_module ( f " { module_prefix } .Coincidences_count " , " generic_data " , " Number of mails matching the filter used in the run " , count ) )
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
if list_mail is not None :
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
modules . append ( create_module ( f " { module_prefix } .Coincidences_list " , " generic_data_string " , " List of mails matching the filter used in the run " , list_mail ) )
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
agent = {
" agent_name " : ppt . generate_md5 ( agent_prefix + smtp_address ) ,
" agent_alias " : agent_prefix + smtp_address ,
" parent_agent_name " : " " ,
" description " : " " ,
" version " : " " ,
" os_name " : " " ,
" os_version " : " " ,
" timestamp " : now ( ) ,
" address " : server ,
" group " : group ,
" interval " : interval ,
" agent_mode " : " 1 "
}
xml_content = ppt . print_agent ( agent , modules )
xml_file = ppt . write_xml ( xml_content , agent [ " agent_name " ] )
ppt . transfer_xml (
xml_file ,
transfer_mode = transfer_mode ,
tentacle_ip = tentacle_address ,
tentacle_port = tentacle_port
)
write_to_log ( " Agent: " + agent_prefix + smtp_address + " getting mail data. " , log_file )
def parse_result ( list_email , sep = " " ) - > list :
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
"""
Parses a list of email elements and converts them into a list of dictionaries .
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
Args :
list_email ( list ) : List of email elements to be parsed .
sep ( str ) : Separator to join elements into a string . Default is an empty string .
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
Returns :
list : A list of dictionaries , where each dictionary has a single key " value " containing the joined string .
"""
result = [ ]
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
for line in list_email :
str_line = sep . join ( str ( elem ) for elem in line )
str_dict = { " value " : str_line }
result . append ( str_dict )
return result
def now (
utimestamp : bool = False
) :
"""
Get the current time in the specified format or as a Unix timestamp .
Args :
utimestamp ( bool ) : Set to True to get the Unix timestamp ( epoch time ) .
print_flag ( bool ) : Set to True to print the time to standard output .
Returns :
str : The current time in the desired format or as a Unix timestamp .
"""
2024-02-14 13:53:38 +01:00
today = datetime . today ( )
2023-01-23 18:17:05 +01:00
2024-02-08 18:39:28 +01:00
if utimestamp :
time = datetime . timestamp ( today )
else :
time = today . strftime ( ' % Y/ % m/ %d % H: % M: % S ' )
return time
def write_to_log ( variable_content , log_file_path ) :
"""
Writes the content of a variable to a log file with timestamp .
Args :
variable_content : Content of the variable to be logged .
log_file_path ( str ) : Path to the log file .
"""
2024-02-14 13:53:38 +01:00
timestamp = datetime . now ( ) . strftime ( " % Y- % m- %d % H: % M: % S " )
2024-02-08 18:39:28 +01:00
log_entry = f " { timestamp } - { variable_content } \n "
try :
with open ( log_file_path , ' a ' ) as log_file :
log_file . write ( log_entry )
except IOError as e :
print ( f " Error writing to log file: { e } " )
if args . auth == ' basic ' :
credentials = Credentials ( username = user , password = password )
account = basic_session ( credentials )
elif args . auth == ' oauth ' :
credentials = OAuth2Credentials ( client_id = client_id , client_secret = secret , tenant_id = tenant_id )
account = Oauth_session ( credentials )
2023-01-23 18:17:05 +01:00
else :
2024-02-08 18:39:28 +01:00
print ( 0 )
write_to_log ( f " { type ( e ) . __name__ } : { e } " , log_file )
sys . exit ( )
try :
## Only one parameter
if subject and sender == None and date_start == None and date_end == None :
filtered_items = account . inbox . filter ( subject__contains = args . subject )
if subject == None and sender and date_start == None and date_end == None :
filtered_items = account . inbox . filter ( sender__icontains = sender )
if subject == None and sender == None and date_start and date_end :
2024-02-14 13:53:38 +01:00
2024-02-08 18:39:28 +01:00
date_start = date_start . split ( " - " )
date_end = date_end . split ( " - " )
filtered_items = account . inbox . filter ( datetime_received__range = ( datetime ( int ( date_start [ 0 ] . strip ( ) ) , int ( date_start [ 1 ] . strip ( ) ) , int ( date_start [ 2 ] . strip ( ) ) , int ( date_start [ 3 ] . strip ( ) ) , int ( date_start [ 4 ] . strip ( ) ) ) . replace ( tzinfo = timezone . utc ) , datetime ( int ( date_end [ 0 ] . strip ( ) ) , int ( date_end [ 1 ] . strip ( ) ) , int ( date_end [ 2 ] . strip ( ) ) , int ( date_end [ 3 ] . strip ( ) ) , int ( date_end [ 4 ] . strip ( ) ) ) . replace ( tzinfo = timezone . utc ) ) )
## Subject + sender
if subject and sender and date_start == None and date_end == None :
filtered_items = account . inbox . filter ( sender__icontains = sender , subject__contains = subject )
## All parameters
if subject and sender and date_start and date_end :
date_start = date_start . split ( " - " )
date_end = date_end . split ( " - " )
filtered_items = account . inbox . filter ( datetime_received__range = ( datetime ( int ( date_start [ 0 ] . strip ( ) ) , int ( date_start [ 1 ] . strip ( ) ) , int ( date_start [ 2 ] . strip ( ) ) , int ( date_start [ 3 ] . strip ( ) ) , int ( date_start [ 4 ] . strip ( ) ) ) . replace ( tzinfo = timezone . utc ) , datetime ( int ( date_end [ 0 ] . strip ( ) ) , int ( date_end [ 1 ] . strip ( ) ) , int ( date_end [ 2 ] . strip ( ) ) , int ( date_end [ 3 ] . strip ( ) ) , int ( date_end [ 4 ] . strip ( ) ) ) . replace ( tzinfo = timezone . utc ) ) , sender__icontains = args . sender , subject__contains = args . subject )
# List Number email coincidences
list_mail = [ ]
# Count number messages coincidences
count = 0
for item in filtered_items :
count = count + 1
if mail_list != 0 :
list_mail . append ( " ( " + str ( item . datetime_received ) + " ) - " + str ( item . subject ) + " - " + str ( item . sender ) )
#print(item.subject, item.sender, item.datetime_received)
if mail_list != 0 :
list_mail = parse_result ( list_mail )
create_agent ( count , list_mail )
else :
create_agent ( count )
except Exception as e :
print ( 0 )
write_to_log ( f " { type ( e ) . __name__ } : { e } " , log_file )
sys . exit ( )
print ( 1 )