#!/usr/bin/env python3
#
# SPDX-FileCopyrightText: 2020-2026 <richardwvm@users.sourceforge.net>
# SPDX-License-Identifier: FSFAP
#
# Copyright (c) 2020-2026 <richardwvm@users.sourceforge.net>
#
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
# notice and this notice are preserved. This file is offered as-is,
# without any warranty.
# ===
#
# emailrelay-ldap-verify.py
#
# Example E-MailRelay address verifier script using LDAP.
#
# See also: https://www.python-ldap.org/en/python-ldap-3.3.0
#
import sys
import ldap
# configuration -- edit as required
if sys.platform == "win32":
Server = "ldaps://ldaps-server.example.com:636"
Timeout = 3
Username = "EXAMPLE\\user"
Password = "secret"
UserSearchPath = "CN=Users,DC=example,DC=com"
UserFilter = "(&(objectCategory=person)(objectclass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(proxyAddresses=SMTP:%s))"
FolderSearchPath = "CN=Microsoft Exchange System Objects,DC=example,DC=com"
FolderFilter = "(&(objectclass=publicFolder)(proxyAddresses=SMTP:%s))"
else:
Server = "ldap://"
Timeout = 3
Username = "cn=admin,dc=example,dc=com"
Password = "secret"
UserSearchPath = "dc=example,dc=com"
UserFilter = "(mail=%s)"
FolderSearchPath = None
FolderFilter = None
# parse the command-line
try:
ArgAddress = sys.argv[1]
ArgDomain = sys.argv[4]
except:
print("error")
print("Usage: emailrelay-ldap-verify.py <email-address> <ignored> <ignored> <domain> ...")
sys.exit(3)
# check for basic "user-part@domain-part" syntax
AtPos = ArgAddress.find("@")
if AtPos <= 0:
print("invalid mailbox")
print("malformed address")
sys.exit(2)
# optionally check the domain-part matches the emailrelay
# "--domain" to avoid unnecessary ldap lookups
LocalDomain = "@" + ArgDomain
if ArgAddress[AtPos:].lower() != LocalDomain.lower():
print("invalid mailbox")
print("invalid domain")
sys.exit(2)
def first(attrname,ldapresult):
try:
return ldapresult[0][1][attrname][0].decode('utf-8')
except:
pass
return None
# ldap query, optionally in two passes
try:
ldapobject = ldap.initialize(Server)
scope = ldap.SCOPE_SUBTREE
ldapobject.set_option(ldap.OPT_NETWORK_TIMEOUT, Timeout)
ldapobject.set_option(ldap.OPT_REFERRALS, 0)
ldapobject.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
ldapobject.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
ldapobject.simple_bind_s(Username, Password)
result = ldapobject.search_s( UserSearchPath, scope,
UserFilter % ArgAddress, ["mail"] )
mail = first("mail",result)
if mail is not None:
print()
print(mail)
sys.exit(1)
if FolderSearchPath is None:
print("invalid mailbox")
print("invalid mailbox: %s" % ArgAddress)
sys.exit(2)
result = ldapobject.search_s( FolderSearchPath, scope,
FolderFilter % ArgAddress, ["mail"] )
mail = first("mail",result)
if mail is not None:
print()
print(mail)
sys.exit(1)
print("invalid mailbox")
print("invalid mailbox: %s" % ArgAddress)
sys.exit(2)
except ldap.LDAPError as e:
print("temporary error")
print("ldap error: %s" % str(e))
sys.exit(3)
except Exception as e:
print("error")
print("exception: %s" % str(e))
sys.exit(3)