#!/usr/bin/env python
#
#  Set up postfix to use tumgreyspf

import re, os, string, shutil, sys


#  default values
postfixDir = '/etc/postfix'
tumgreyspf_time_limit = 3600
tumgreyspf_user = 'nobody'
tumgreyspf_path = '/usr/lib/tumgreyspf/tumgreyspf'
G_verbose = 1


#####################################
def addPostfixMultiline(data, value):
	'''Add a value to an existing, possibly multi-line Postfix main.cf block.
	The modified block is returned.'''
	data = data[:]
	if len(data) == 1:
		data[0] = data[0][:-1] + ' ' + value + '\n'
	else:
		#  find prefix
		prefix = '   '
		for line in data[1:]:
			m = re.match(r'^(\s+)\S.*$', line)
			if m:
				prefix = m.group(1)
				break

		#  add value
		data.append(prefix + value + '\n')
	return(data)


########################
#  find path to postconf
postconfPathList = (
      '/usr/sbin/postconf',
      '/sbin/postconf',
      '/usr/bin/postconf',
      )
postconfPath = None
for path in postconfPathList:
	if os.path.exists(path):
		postconfPath = path
		break

##########################
#  check required programs
if not postconfPath:
	sys.stderr.write('Unable to find path to "postconf" program.\n')
	sys.exit(1)

################
#  check version
fp = os.popen('postconf mail_version', 'r')
postfix_version = None
for line in fp.readlines():
	m = re.match(r'^mail_version\s*=\s*([\d.]+)\s*$', line)
	if not m: continue
	postfix_version = m.group(1)
data = fp.read()
fp.close()
if not postfix_version:
	sys.stderr.write('Unable to determine postfix version.\n')
	sys.exit(1)

#  convert version into a list of integers and compare
version_split = map(int, string.split(postfix_version, '.'))
if version_split[0] < 2 or (version_split[0] == 2 and version_split[1] < 1):
	sys.stderr.write('tumgreyspf requires Postfix version 2.1 or higher.\n')
	sys.stderr.write('   Found version %s.\n' % postfix_version)
	sys.exit(1)

#  check existance of postfix directory
if not os.path.exists(postfixDir):
	sys.stderr.write('Postfix does not seem to be in "%s".\n' % postfixDir)
	sys.exit(1)

#######################################
#  make backup of main.cf and master.cf
masterFile = os.path.join(postfixDir, 'master.cf')
masterBackupFile = os.path.join(postfixDir, 'master.cf.tumgreyspf')
mainFile = os.path.join(postfixDir, 'main.cf')
mainBackupFile = os.path.join(postfixDir, 'main.cf.tumgreyspf')
if not os.path.exists(mainFile):
	sys.stderr.write('Could not find main.cf in "%s".\n' % mainFile)
	sys.exit(1)
if not os.path.exists(masterFile):
	sys.stderr.write('Could not find master.cf in "%s".\n' % masterFile)
	sys.exit(1)
if os.path.exists(masterBackupFile):
	sys.stdout.write('master.cf backup file "%s" already exists.\n'
			% masterBackupFile)
	sys.stdout.write('   Not making another backup.\n')
else:
	shutil.copyfile(masterFile, masterBackupFile)
if os.path.exists(mainBackupFile):
	sys.stdout.write('main.cf backup file "%s" already exists.\n'
			% mainBackupFile)
	sys.stdout.write('   Not making another backup.\n')
else:
	shutil.copyfile(mainFile, mainBackupFile)

######################################################
#  rewrite the smtpd_recipient_restrictions in main.cf
fp = open(mainFile, 'r')
mainData = fp.readlines()
fp.close()

#  find smtpd_recipient_restrictions
startLinenum = None
endLinenum = None

#  find start of smtpd_recipient_restrictions
mode = 'start'
smtpd_recipient_restrictions = None
for linenum in xrange(len(mainData)):
	line = mainData[linenum]
	if mode == 'start':
		if re.match(r'^smtpd_recipient_restrictions\s*=', line):
			startLinenum = linenum
			mode = 'end'
	elif mode == 'end':
		if re.match(r'^[^#\s].*', line):
			endLinenum = linenum
			smtpd_recipient_restrictions = mainData[startLinenum:endLinenum]
			break
if startLinenum == None:
	startLinenum = len(mainData)
	endLinenum = len(mainData)
	smtpd_recipient_restrictions = [
			'smtpd_recipient_restrictions = \\n',
			'   reject_unauth_destination\n',
			]
	if G_verbose: print 'No smtpd_recipient_restrictions found, adding.'

#  check for and add reject_unauth_destination
found = 0
for line in smtpd_recipient_restrictions:
	#  remove comments
	m = re.match(r'^(.*)#.*$', line)
	if m: line = m.group(1)

	if re.search(r'\breject_unauth_destination\b', line):
		found = 1
		break
if not found:
	if G_verbose:
		print 'Adding reject_unauth_destination to smtpd_recipient_restrictions'
	smtpd_recipient_restrictions = addPostfixMultiline(
			smtpd_recipient_restrictions, 'reject_unauth_destination')

#  check for and add check_policy_service
found = 0
for line in smtpd_recipient_restrictions:
	#  remove comments
	m = re.match(r'^(.*)#.*$', line)
	if m: line = m.group(1)

	if re.search(r'\bcheck_policy_service unix:private/tumgreyspf\b', line):
		found = 1
		break
if not found:
	if G_verbose:
		print 'Adding tumgreyspf policy to smtpd_recipient_restrictions'
	smtpd_recipient_restrictions = addPostfixMultiline(
			smtpd_recipient_restrictions,
			'check_policy_service unix:private/tumgreyspf')

#  change the policy_time_limit
found = 0
for linenum in xrange(len(mainData)):
	line = mainData[linenum]
	m = re.match(r'^tumgreyspf_time_limit\s*=\s*(\d+).*$', line)
	if m:
		if G_verbose:
			print 'Setting tumgreyspf_time_limit to %s' % tumgreyspf_time_limit
		mainData[linenum] = string.replace(line, m.group(1),
				str(tumgreyspf_time_limit))
		found = 1
if not found:
	if G_verbose:
		print 'Adding tumgreyspf_time_limit line'
	mainData.append('\n')
	mainData.append('#  increase the policy timeout for tumgreyspf\n')
	mainData.append('tumgreyspf_time_limit = %s\n' % tumgreyspf_time_limit)

#  update mainData with new settings
mainData = (mainData[:startLinenum] + smtpd_recipient_restrictions +
		mainData[endLinenum:])

####################
#  write new main.cf
fp = open(mainFile, 'w')
for line in mainData:
	fp.write(line)
fp.close()

#####################################
#  add tumgreyspf policy to master.cf
fp = open(masterFile, 'r')
found = 0
for line in fp.readlines():
	if re.match(r'tumgreyspf\s*', line): found = 1
fp.close()

#  append the data
if not found:
	if G_verbose:
		print 'Adding tumgreyspf policy to master.cf'
	fp = open(masterFile, 'a')
	fp.write('\n')
	fp.write('#  policy for tumgreyspf\n')
	fp.write('tumgreyspf  unix  -       n       n       -       -       spawn\n')
	fp.write('   user=%s argv=%s\n' % ( tumgreyspf_user, tumgreyspf_path ))
	fp.close()

##################
#  restart postfix
if os.path.exists('/etc/init.d/postfix'):
	if G_verbose:
		print 'Restarting postfix.'
	os.system('/etc/init.d/postfix restart')
else:
	sys.stderr.write('Failed to restart postfix.  Please restart manually.\n')
	sys.exit(1)
