auth_ds – Datasource based SMTP Authentication

March 26, 2020

With this module, Momentum can provide authenticated SMTP sessions via SMTP AUTH by using any supported datasource as the authentication bridge. When a user connects to Momentum, if authentication is supported, it will attempt to authenticate the user by issuing a query against the configured datasource. If the result of that query is "true", the user was successfully identified.

Since the module uses the datasource layer, it can take advantage of the built-in caching mechanism to avoid putting pressure on your authentication data stores. For more information about the ds_core module and datasource drivers, see “ds_core – Datasource Query Core”.

You may define multiple authentication schemes against different datasources, and configure your listeners to use a different scheme depending on the connecting IP address.

Note

Hosts are not subject to any SMTP authentication that you may have implemented when specified by the relay_hosts option or when open_relay is set to true.

When a user attempts to authenticate, the SMTP listener routes the authentication attempt to the authentication module, which then uses the query that you have configured. If the results of the query are not already known, a query is run asynchronously to obtain them, and the SMTP session is suspended, allowing Momentum to continue to service other sessions. When the results come back, the first column of the first row is inspected; if it is a non-zero value, then the authentication is considered successful.

If you are using a datasource, such as LDAP, where it is not possible to guarantee the ordering of the results returned, you may add a password_column option to the auth_ds module configuration to specify which named column holds the password in the result set.

auth_ds {
  Scheme "ecauth" {
    Authenticate {
      ...
      # this scheme returns a password in the column named "password"
      password_column = "password"
      returns_password = "true"
    }
  }
}

Configuration

This module is loaded automatically as required and does not need to be explicitly included. The following is an example configuration:

auth_ds {
  Scheme "auth" {
    Authenticate {
      query = "SELECT password FROM webconsole.users WHERE username = :user"
      cache = "auth"
      map = [
        user = "%{username}"
      ]
      type = "md5"
      returns_password = "true"
      password_column = "password"
    }
    QueryGroups {
      query = "SELECT groupname FROM webconsole.groupassignments WHERE username = :user"
      cache = "auth"
      group_column = "groupname"
      map = [
        user = "%{username}"
      ]
    }
    EnumGroups {
      query = "SELECT role from webconsole.roles"
      cache = "auth"
      group_column = "role"
    }
  }
}

The following configuration options are available in authentication schemes:

cache

Datasource cache to execute the query against.

EnumGroups

Stanza enumerates all possible groups or roles.

EnumGroups stanzas can be used to plug in to an external authentication system defined in an Authorization stanza. For example, the EnumGroup stanza allows you to interrogate the groups and then present those in a list for assigning permissions via an Authorization stanza. See authorization.

map

Parameter expansion map to use.

query

Query to be executed.

QueryGroups

All the groups associated with the user.

QueryGroups stanzas can be used to plug in to an external authentication system defined in an Authorization stanza. See authorization.

returns_password

Whether or not the query returns a password for validation inside the module or simply a truth value, for validation inside the DB.

type

Encryption type for passwords in the data store. Enabled this by setting the type parameter within the ecauth block. If this is unspecified, you can extract this from the data store as well using the type_column option.

The type option can take the values "ucrypt" and "md5" specifying Unix style and digest md5 style respectively.

type_column

Column name containing the encryption type for a given password. This corresponds with the type_map option.

type_map

Dictionary that maps a column to an encryption type.

Type maps contain key-value pairs where the key is the contents of the column identified by the type_column option and the value is one of the following:

  • clear – Cleartext

  • digest-md5 – Digest-MD5 encryption

  • hmac_md5 – HMAC MD5 encryption

  • md5 – MD5 encryption

  • md5_base64 – MD5 encryption (base64 representation)

  • md5_hex – MD5 encryption (hex representation)

  • rfc2307 – RFC 2307 encryption specifiers (crypt, md5, smd5, sha, ssha)

  • sha1_base64 – SHA1 encryption (base64 representation)

  • sha1_hex – SHA1 encryption (hex representation)

  • smd5 – Seeded MD5 encryption

  • ssha1 – Seeded SHA1 encryption

  • ucrypt – Unix crypt

Parameter expansion maps

Use parameter expansion maps to map authentication parameters into your query. The maps consist of parameter names that map to interpolated value strings based on the authentication parameters listed below. This approach allows Momentum to take advantage of native prepared statement support that may be present in the underlying datasource drivers, while still allowing you a certain amount of freedom for string building.

The right hand side of the expansion maps consists of a string value. The string can contain %{parametername}, where parametername corresponds to one of the parameters listed below. When the query is executed, the parameter name is expanded to its value before being passed on to the data source. You may interpolate multiple parameters in a single string in this way, if desired.

crypt

Describes the level of crypto in use. Valid values are clear (for LOGIN), md5, hmac_md5 (for CRAM-MD5) or digest-md5 (for DIGEST-MD5).

nonce

nonce for MD5 based authenticators

password

The password may be cleartext, or it may have some level of crypto applied to it, according to the crypt parameter.

realm , digest-uri

Parameters for DIGEST-MD5 based auth

uri

uri specified in the listener configuration

username

Username

Authenticating against MySQL

The following configuration excerpt demonstrates how to configure Momentum to authenticate against MySQL:

# Define a cache named authdb
# that talks to a mysql server
Datasource "authdb" {
  uri = ( "mysql:host=localhost;port=3306;dbname=ecauth;user=root;password=password" )
}

# Load the authentication module
auth_ds {
  # register "mysqlauth" as an authentication scheme
  # this is referenced by the ESMTP_Listener below.
  Scheme "mysqlauth" {
    Authenticate {
      # This query will return a row containing the value 1 if
      # the username and password match up
        query = "SELECT 1 AS ALLOW FROM users WHERE user_name = :user and
          password = encrypt(:pass, PASSWORD)"
      # The query will be presented against the MySQL cache
      cache = "authdb"
      # This map is used to resolve the placeholders in the query by substituting
      # the username and password supplied by the user.

      map = [
        user = "%{username}"
        pass = "%{password}"
      ]
    }
  }
}

ESMTP_Listener {
  Listen ":25" {
    SMTP_Extensions = (
      "ENHANCEDSTATUSCODES"
      "STARTTLS"
      "AUTH LOGIN"
    )
    AuthLoginParameters = [uri = "mysqlauth://"]
    SMTP_Extensions = ( "ENHANCEDSTATUSCODES" "AUTH LOGIN" )
  }
}

Note

For licensing reasons, the MySQL module does not ship with Momentum and must be downloaded separately. For instructions on downloading and installing this module, see 8, “MySQL”.

Authentication against LDAP

The precise configuration used to implement LDAP based authentication varies depending on your LDAP schema. If you are migrating from our older LDAP auth module, the authentication concept was that the username and password from the SMTP session would be used as the bind name and password used to establish a connection to the LDAP server. If the bind attempt was successful, the user was deemed to have been authenticated. With this module, we also need to obtain results from an LDAP query.

The suggested approach for implementing LDAP authentication using the datasource layer is to craft an LDAP query that returns the common name of the user you are authenticating.

In the case of an LDAP directory like the Openwave directory, you will need to be able to specify a type_map so that the server can correctly encrypt the provided password for comparison. The following example shows integration against an openwave directory:

auth_ds {
  Scheme "myscheme_openwave" {
    Authenticate {
      query = "ldap:///?mailpassword,mailpasswordtype?sub? »
              (|(maillogin=$user)(mail=$user)(mailalternateaddress=$user))"
      cache = "openwave_directory"
      returns_password = "true"
      password_column = "mailpassword"
      type_column = "mailpasswordtype"
      map = [
        user = "%{username}"
      ]
      type_map = [
        C = "clear"
        U = "ucrypt"
        H = "sha1_hex"
        S = "ssha1"
        M = "smd5"
      ]
    }
  }
}

The LDAP driver supports a bind-only mode that allows connection and authentication without execution of a query. If it succeeds, it returns a "bind" column with a value of 1. This is useful in cases where it is desirable to pass the credentials through from SMTP authentication to the LDAP directory, for example, to eliminate the need to change the LDAP query if authentication settings change.

The following example shows how to use the bind only feature.

auth_ds {
  Scheme "ldapauthscheme" {
    Authenticate {
      query = "ldap://ldaphostname/????bindname=uid=$user%2Cou=people »
              %2Cdc=example%2Cdc=com,x-bindpw=$pass"
      cache = "ldapauth"
      map =  [
        user = "%{username}"
        pass = "%{password}"
      ]
    }
  }
}