emailrelay-ldap-verify.pyΒΆ

#!/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)