401 lines
16 KiB
Python
401 lines
16 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
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
|
|
from exchangelib.protocol import BaseProtocol, NoVerifyHTTPAdapter
|
|
|
|
BaseProtocol.HTTP_ADAPTER_CLS = NoVerifyHTTPAdapter
|
|
|
|
import urllib3
|
|
urllib3.disable_warnings()
|
|
|
|
import pandoraPlugintools as ppt
|
|
|
|
import argparse,sys,re,json,os,traceback
|
|
from datetime import datetime,timedelta,timezone
|
|
|
|
|
|
|
|
__author__ = "Alejandro Sánchez Carrion"
|
|
__copyright__ = "Copyright 2022, PandoraFMS"
|
|
__maintainer__ = "Operations department"
|
|
__status__ = "Production"
|
|
__version__= '1.0'
|
|
|
|
info = f"""
|
|
Pandora FMS Exchange Mail
|
|
Version = 1.0
|
|
Description = This plugin can search for matches in your mail and find the number of matches, as well as list them.
|
|
|
|
Manual execution
|
|
|
|
./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
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
args = parser.parse_args()
|
|
|
|
###############
|
|
## VARIABLES ##
|
|
###############
|
|
|
|
server = args.server
|
|
smtp_address = args.smtp_address
|
|
|
|
user = args.user
|
|
password = args.password
|
|
|
|
client_id = args.client_id
|
|
tenant_id = args.tenant_id
|
|
secret = args.secret
|
|
|
|
subject = args.subject
|
|
sender = args.sender
|
|
date_start = args.date_start
|
|
date_end = args.date_end
|
|
mail_list = args.mail_list
|
|
|
|
module_prefix = args.module_prefix
|
|
agent_prefix = args.agent_prefix
|
|
|
|
temporal = args.temporal
|
|
group = args.group
|
|
interval = args.interval
|
|
data_dir = args.data_dir
|
|
transfer_mode = args.transfer_mode
|
|
|
|
tentacle_address = args.tentacle_address
|
|
tentacle_port = args.tentacle_port
|
|
tentacle_client = args.tentacle_client
|
|
tentacle_opts = args.tentacle_opts
|
|
|
|
log_file = args.log_file
|
|
|
|
###############
|
|
## FUNCTIONS ##
|
|
###############
|
|
|
|
def Oauth_session(credentials):
|
|
"""
|
|
Creates an OAuth session with an Exchange server using the provided credentials.
|
|
|
|
Args:
|
|
credentials (Credentials): Credentials object containing information for OAuth authentication.
|
|
|
|
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()
|
|
|
|
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.
|
|
"""
|
|
try:
|
|
config = Configuration(server=server, credentials=credentials)
|
|
|
|
account = Account(
|
|
primary_smtp_address=args.smtp_address,
|
|
autodiscover=False,
|
|
config=config,
|
|
access_type=DELEGATE
|
|
)
|
|
return account
|
|
except Exception as e:
|
|
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.
|
|
|
|
Returns:
|
|
None
|
|
"""
|
|
|
|
modules = []
|
|
|
|
modules.append(create_module(f"{module_prefix}.Coincidences_count", "generic_data", "Number of mails matching the filter used in the run", count))
|
|
|
|
if list_mail is not None :
|
|
|
|
modules.append(create_module(f"{module_prefix}.Coincidences_list", "generic_data_string", "List of mails matching the filter used in the run", list_mail))
|
|
|
|
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:
|
|
|
|
"""
|
|
Parses a list of email elements and converts them into a list of dictionaries.
|
|
|
|
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.
|
|
|
|
Returns:
|
|
list: A list of dictionaries, where each dictionary has a single key "value" containing the joined string.
|
|
"""
|
|
|
|
result=[]
|
|
|
|
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.
|
|
"""
|
|
|
|
today = datetime.today()
|
|
|
|
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.
|
|
"""
|
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
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)
|
|
else:
|
|
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 :
|
|
|
|
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)
|