Server : LiteSpeed System : Linux in-mum-web1949.main-hosting.eu 5.14.0-503.40.1.el9_5.x86_64 #1 SMP PREEMPT_DYNAMIC Mon May 5 06:06:04 EDT 2025 x86_64 User : u595547767 ( 595547767) PHP Version : 7.4.33 Disable Function : NONE Directory : /opt/alt/python27/lib/python2.7/site-packages/postomaat/ |
# -*- coding: utf-8 -*-
# Copyright 2012-2018 Oli Schacher
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
try:
import ConfigParser
except ImportError:
import configparser as ConfigParser
import hashlib
import logging
import logging.config
import multiprocessing
import optparse
import os
import signal
import sys
import postomaat.funkyconsole
from postomaat import __version__
from postomaat.addrcheck import Addrcheck
from postomaat.checkLogfileConfig import checkLogfileConfig
from postomaat.core import MainController
from postomaat.daemon import DaemonStuff
controller = None
theconfigfile = '/etc/postomaat/postomaat.conf'
dconfdir = '/etc/postomaat/conf.d'
theloggingfile = '/etc/postomaat/logging.conf'
thepidfile = '/var/run/postomaat.pid'
def reloadconfig():
"""reload configuration file"""
logger = logging.getLogger('postomaat')
if controller:
if controller.threadpool is None and controller.procpool is None:
logger.error("""No process/threadpool -> This is not the main process!
\nNo changes will be applied...!\nSend SIGHUP to the main process!
\nCurrent controller object : %s, %s""" % (id(controller),controller.__repr__()))
return
else:
logger.error("No controller -> This is not the main process!\n"
"No changes will be applied...!\nSend SIGHUP to the main process!")
return
if controller.logProcessFacQueue is None:
logger.error("No log process in controller -> This is not the main process!\n"
"No changes will be applied...!\nSend SIGHUP to the main process!")
return
assert controller.logConfigFileUpdates is not None
assert controller.configFileUpdates is not None
configFileUpdates = getConfigFileUpdatesDict(theconfigfile,dconfdir)
logConfigFileUpdates = getLogConfigFileUpdatesDict(theloggingfile)
logger.info('Number of messages in logging queue: %u'%controller.logQueue.qsize())
logger.info("Log config has changes: %s" % str(logConfigFileUpdates != controller.logConfigFileUpdates))
logger.info("Main config has changes: %s" % str(configFileUpdates != controller.configFileUpdates))
if logConfigFileUpdates != controller.logConfigFileUpdates:
# save back log config file dict for later use
controller.logConfigFileUpdates = logConfigFileUpdates
logger.info("Create new log process with new configuration")
logProcessFacQueue = controller.logProcessFacQueue
newLogConfigure = postomaat.logtools.logConfig(logConfigFile=theloggingfile)
logProcessFacQueue.put(newLogConfigure)
if configFileUpdates != controller.configFileUpdates:
logger.info('Reloading configuration')
# save back config file dict for later use
controller.configFileUpdates = configFileUpdates
newconfig = ConfigParser.RawConfigParser()
with open(theconfigfile) as fp:
newconfig.readfp(fp)
# Setup address compliance checker
try:
address_check = newconfig.get('main','address_compliance_checker')
except Exception as e:
# might happen for some tests which do not propagate defaults
address_check = "Default"
Addrcheck().set(address_check)
identifier = "no identifier given"
if newconfig.has_option('main', 'identifier'):
identifier = newconfig.get('main', 'identifier')
# load conf.d
if os.path.isdir(dconfdir):
filelist = os.listdir(dconfdir)
configfiles = [
dconfdir + '/' + c for c in filelist if c.endswith('.conf')]
logger.debug('Conffiles in %s: %s' % (dconfdir, configfiles))
readfiles = newconfig.read(configfiles)
logger.debug('Read additional files: %s' % (readfiles))
logger.info('Reload config complete. Current configuration:%s' %
identifier)
controller.config = newconfig
controller.propagate_core_defaults()
logger.info('Reloading plugins...')
ok = controller.load_plugins()
if ok:
logger.info('Plugin reload completed')
else:
logger.error('Plugin reload failed')
controller.propagate_plugin_defaults()
controller.reload()
def sighup(signum, frame):
"""handle sighup to reload config"""
reloadconfig()
def getConfigFileUpdatesDict(configfilename,dconfigFileDir):
configfiles = [configfilename]
# load conf.d
if dconfigFileDir and os.path.isdir(dconfigFileDir):
filelist = os.listdir(dconfigFileDir)
configfiles.extend([dconfigFileDir + '/' + c for c in filelist if c.endswith('.conf')])
hashlist = createMD5(configfiles)
configFileUpdates = dict(zip(configfiles, hashlist))
return configFileUpdates
def getLogConfigFileUpdatesDict(logConfFile):
logConfigFileUpdates = {}
logConfigFileUpdates[logConfFile] = createMD5([logConfFile])[0]
return logConfigFileUpdates
def hash_bytestr_iter(bytesiter, hasher, ashexstr=False):
for block in bytesiter:
hasher.update(block)
return (hasher.hexdigest() if ashexstr else hasher.digest())
def file_as_blockiter(afile, blocksize=65536):
with afile:
block = afile.read(blocksize)
while len(block) > 0:
yield block
block = afile.read(blocksize)
def createMD5(fnamelst):
return [(fname, hash_bytestr_iter(file_as_blockiter(open(fname, 'rb')), hashlib.md5()))
for fname in fnamelst]
def main():
lint = False
debugmsg = False
console = False
global controller
global theconfigfile
global dconfdir
global theloggingfile
global thepidfile
defaultattrs={
#Postfix version 2.1 and later:
'request':'smtpd_access_policy',
'protocol_state':'RCPT',
'protocol_name':'SMTP',
'helo_name':'smtp.example.com',
'queue_id':'8045F2AB23',
'sender':'sender@example.com',
'recipient':'recipient@example.org',
'recipient_count':'1',
'client_address':'1.2.3.4',
'client_name':'host.example.net',
'reverse_client_name':'host.example.net',
'instance':'123.456.7',
#Postfix version 2.2 and later:
'sasl_method':'',
'sasl_username':'',
'sasl_sender':'',
'size':'12345',
'ccert_subject':'',
'ccert_issuer':'',
'ccert_fingerprint':'',
#Postfix version 2.3 and later:
'encryption_protocol':'TLSv1/SSLv3',
'encryption_cipher':'DHE-RSA-AES256-SHA',
'encryption_keysize':'256',
'etrn_domain':'',
#Postfix version 2.5 and later:
'stress':'',
}
parser = optparse.OptionParser(version=__version__)
parser.add_option("--lint", action="store_true", dest="lint",
default=False, help="Check configuration and exit")
parser.add_option("--console", action="store_true", dest="console",
default=False, help="start an interactive console after postomaat startup")
parser.add_option("--attrs", action="store_true", dest="attrs",
default=False, help="print available message attributes and their defaults and exit")
parser.add_option("-f", "--foreground", action="store_true", dest="foreground", default=False,
help="start postomaat in the foreground, even if daemonize is enabled in the config")
parser.add_option("--pidfile", action="store", dest="pidfile",
help="use a different pidfile than /var/run/postomaat.pid")
parser.add_option("-c", "--config", action="store", dest="configfile",
help="use a different config file and disable reading from /etc/postomaat/conf.d, logging configuration file is supposed to be in the same directory")
parser.add_option("--debugmsg", action="store_true", dest="debugmsg",
default=False, help="simulate a message and print the result. use the debugmsg arguments listed below to override defaults")
parser.add_option("--debugmsgport", action="store", dest="debugmsgport",help="test a specific port configuration with debugmsg. if not specified, all plugins are run")
parser.add_option("--debug", action="store_true", dest="debug",
default=False, help="enable verbose/debug output for lint/debugmsg")
for k,v in iter(defaultattrs.items()):
parser.add_option("--%s"%k,action="store",dest=k,default=None,help="debugmsg argument, default=%s"%v)
(opts, args) = parser.parse_args()
if len(args) > 0:
print("Unknown option(s): %s" % args)
print("")
parser.print_help()
sys.exit(1)
lint = opts.lint
console = opts.console
debugmsg = opts.debugmsg
if opts.pidfile:
thepidfile = opts.pidfile
if opts.configfile:
theconfigfile = opts.configfile
theloggingfile = os.path.join(
os.path.split(theconfigfile)[0], os.path.split(theloggingfile)[1])
dconfdir = None
if opts.attrs:
print("Supported attributes and their default:")
print("See http://www.postfix.org/SMTPD_POLICY_README.html#protocol for a full description")
print("")
for k,v in iter(defaultattrs.items()):
print("%s : %s"%(k,v))
sys.exit(0)
config=ConfigParser.ConfigParser()
if not os.path.exists(theconfigfile):
print("""Configfile (%s) not found. Please create it by renaming the .dist file and modifying it to your needs""" % theconfigfile)
sys.exit(1)
with open(theconfigfile) as fp:
readconfig = config.readfp(fp)
# load conf.d
if dconfdir and os.path.isdir(dconfdir):
filelist = os.listdir(dconfdir)
configfiles = [dconfdir + '/' + c for c in filelist if c.endswith('.conf')]
readfiles = config.read(configfiles)
daemon = DaemonStuff(thepidfile)
#we could have an empty config file
if not (lint or debugmsg or console or opts.foreground):
if config.has_option('main', 'daemonize'):
if config.getboolean('main', 'daemonize'):
daemon.createDaemon()
else: # option not specified -> default to run daemon
daemon.createDaemon()
# drop privileges
try:
running_user=config.get('main','user')
running_group=config.get('main','group')
except Exception:
running_user='nobody'
running_group='nobody'
priv_drop_ok = False
try:
daemon.drop_privs(running_user, running_group)
priv_drop_ok = True
except:
err = sys.exc_info()[1]
print("Could not drop privileges to %s/%s : %s" %
(running_user, running_group, str(err)))
# all threads/processes write to the logQueue which
# will be handled by a separate process
logQueue = multiprocessing.Queue(-1)
logFactoryQueue = multiprocessing.Queue(-1)
if lint or debugmsg:
fc=postomaat.funkyconsole.FunkyConsole()
print(fc.strcolor("postomaat", "yellow"))
print(fc.strcolor(__version__, "green"))
# check if directory used by logfile exists
if not checkLogfileConfig(theloggingfile):
sys.exit(1)
level=logging.INFO
if opts.debug:
level=logging.DEBUG
logConfigure = postomaat.logtools.logConfig(lint=True,lintlevel=level)
else:
logConfigure = postomaat.logtools.logConfig(logConfigFile=theloggingfile)
#--
# start process handling logging queue
#--
logProcessFactory = multiprocessing.Process(target=postomaat.logtools.logFactoryProcess,
args=(logFactoryQueue,logQueue))
logProcessFactory.start()
# now create a log - listener process
logFactoryQueue.put(logConfigure)
# setup this main thread to send messages to the log queue
# (which is handled by the logger process created by the logProcessFactory)
postomaat.logtools.client_configurer(logQueue)
#=== ===#
#= Now logging is available =#
#=== ===#
baselogger = logging.getLogger()
baselogger.info("postomaat version %s starting up" % __version__)
#--
# start try-except-finally statement to make sure logging the logging
# process is stopped correctly if an exception happens
#--
try:
# Setup address compliance checker
try:
address_check = config.get('main','address_compliance_checker')
except Exception as e:
# might happen for some tests which do not propagate defaults
address_check = "Default"
Addrcheck().set(address_check)
# instantiate the MainController and load default configuration
controller = MainController(config,logQueue,logFactoryQueue)
controller.configFileUpdates = getConfigFileUpdatesDict(theconfigfile,dconfdir)
controller.logConfigFileUpdates = getLogConfigFileUpdatesDict(theloggingfile)
controller.propagate_core_defaults()
if lint:
controller.lint()
elif debugmsg:
attrs=defaultattrs.copy()
for arg in defaultattrs.keys():
if getattr(opts,arg) is not None:
attrs[arg]=getattr(opts,arg)
print(attrs)
action,arg=controller.test(attrs,port=opts.debugmsgport)
if arg is None:
arg=""
print("Result: %s %s"%(action.upper(),arg))
else:
signal.signal(signal.SIGHUP, sighup)
if console:
controller.debugconsole = True
controller.startup()
except Exception as e:
baselogger.error("Exception catched: %s"%(str(e)))
#baselogger.exception(e)
finally:
#---
# stop logger factory & process
#---
baselogger.info("Stop logging framework -> Goodbye")
try:
baselogger.debug("Send Poison pill to logFactoryQueue")
logFactoryQueue.put_nowait(None)
logProcessFactory.join(120)
except Exception as e:
logProcessFactory.terminate()