Dovecot and remote SASL Client via TLS

Gerhard Wiesinger lists at wiesinger.com
Fri Aug 8 06:05:43 UTC 2014


Hello,

Anyone interested, I solved it the following way:
imap server: dovecot <=> /bin/saslcheckpassword <=> local unix domain 
socket for saslauthd <=> socat client
          ^
          = Remote TLS connection
          v
auth server: socat server <=> saslauthd <=> /etc/passwd,/etc/shadow

saslcheckpassword is based on checkpassword.sh and enhanced for SASL, 
modified version attached:
https://bitbucket.org/vizovitin/dovecot-conf-examples/src/tip/checkpassword-shell/checkpassword.sh

Detailed configs below.

imap server:
touch /var/log/dovecot-saslcheckpassword.log
chown dovecot.dovecot /var/log/dovecot-saslcheckpassword.log
chmod 750 /var/log/dovecot-saslcheckpassword.log

Advantage is single authentication entity.

Works well for me for some time now.

If you have some questions feel free to ask.

Ciao,
Gerhard

================================================================================================================================================================
IMAP server, SASL client:
cat /etc/systemd/system/multi-user.target.wants/saslclient.service
[Unit]
Description=SASL remote client for authentication, provides local unix 
domain socket
After=network.target

[Service]
# Must be running under permissions for the dovecot user!
ExecStart=/usr/bin/socat -4 -ly 
UNIX-LISTEN:/var/run/saslauthd/mux,fork,user=dovecot,group=dovecot 
'OPENSSL:myserver:1234,verify=1,method=TLSv1,cipher=DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXP:!LOW:!MD5,cafile=/etc/socat/mycert.crt'

[Install]
WantedBy=multi-user.target
================================================================================================================================================================
Auth server:
cat /etc/systemd/system/multi-user.target.wants/saslserver.service
[Unit]
Description=SASL remote server for authentication
After=network.target

[Service]
ExecStart=/usr/bin/socat -4 -ly 
'OPENSSL-LISTEN:9999,reuseaddr,fork,verify=0,method=TLSv1,cipher=DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXP:!LOW:!MD5,cert=/etc/socat/mycert.pem,cafile=/etc/socat/mycert.crt' 
/var/run/saslauthd/mux

[Install]
WantedBy=multi-user.target
================================================================================================================================================================
Relevant dovecot config:
userdb {
   args = username_format=%n /etc/dovecot/users
   driver = passwd-file
}

passdb {
   args = /bin/saslcheckpassword
   driver = checkpassword
}
================================================================================================================================================================

--
http://www.wiesinger.com/

On 16.07.2014 15:50, Gerhard Wiesinger wrote:
>
> Any comments?
>
> Thank you.
>
> Ciao,
> Gerhard
>
> http://www.wiesinger.com/

-------------- next part --------------
#!/bin/bash

# Example Dovecot checkpassword script that may be used as both passdb or userdb.
#
# Originally written by Nikolay Vizovitin, 2013.
# Enhanced by Gerhard Wiesinger, 2014.

# Assumes authentication DB is in /etc/dovecot/users, each line has '<user>:<password>' format.
# Place this script into /etc/dovecot/checkpassword.sh file and make executable.
# Implementation guidelines at http://wiki2.dovecot.org/AuthDatabase/CheckPassword

# The first and only argument is path to checkpassword-reply binary.
# It should be executed at the end if authentication succeeds.
CHECKPASSWORD_REPLY_BINARY="$1"

# Messages to stderr will end up in mail log (prefixed with "dovecot: auth: Error:")
#LOG=/dev/stderr
# Will be e.g. /tmp/systemd-dovecot.service-XfZAUy5/tmp/saslcheckpassword.log
#LOG=/tmp/saslcheckpassword.log
# touch /var/log/dovecot-saslcheckpassword.log
# chown dovecot.dovecot /var/log/dovecot-saslcheckpassword.log
# chmod 750 /var/log/dovecot-saslcheckpassword.log
LOG=/var/log/dovecot-saslcheckpassword.log

#export default_userdb_uid=popuser
#export default_userdb_gid=popuser
export default_userdb_uid=vmail
export default_userdb_gid=vmail
export SASL=1

# User and password will be supplied on file descriptor 3.
INPUT_FD=3

# Error return codes.
ERR_PERMFAIL=1
ERR_NOUSER=3
ERR_TEMPFAIL=111

# Make testing this script easy. To check it just run:
#   printf '%s\0%s\0' <user> <password> | ./checkpassword.sh test; echo "$?"
if [ "$CHECKPASSWORD_REPLY_BINARY" = "test" ]; then
	CHECKPASSWORD_REPLY_BINARY=/bin/true
	INPUT_FD=0
fi

# Credentials lookup function. Given a user name it should output 'user:password' if such
# account exists or nothing if it does not. Return non-zero code in case of error.
credentials_lookup()
{
	local db="$1"
	local user="$2"

	awk -F ':' -v USER="$user" '($1 == USER) {print}' "$db" 2>>$LOG
}

# Credentials verification function. Given a user name and password it should output non-empty
# string (this implementation outputs 'user:password') in case supplied credentials are valid
# or nothing if they are not. Return non-zero code in case of error.
credentials_verify()
{
	local db="$1"
	local user="$2"
	local pass="$3"

	awk -F ':' -v USER="$user" -v PASS="$pass" '($1 == USER && $2 == PASS) {print}' "$db" 2>>$LOG
}

credentials_verify_sasl()
{
	local db="$1"
	local user="$2"
	local pass="$3"

	mail_name="`echo \"$user\" | awk -F '@' '{ print $1 }'`"
	domain_name="`echo \"$user\" | awk -F '@' '{ print $2 }'`"
	#export HOME="/var/qmail/mailnames/$domain_name/$mail_name/"
	export HOME="/home/vmail/${user}/"

	if [ ! -z "${domain_name}" ]; then
		# Don't log to stdout (otherwise authenticated=yes)!!
		/usr/sbin/testsaslauthd -s smtp -r "${domain_name}" -u "${mail_name}" -p "${pass}" > /dev/null 2>&1
	else
		# Don't log to stdout (otherwise authenticated=yes)!!
		/usr/sbin/testsaslauthd -s smtp -u "${mail_name}" -p "${pass}" > /dev/null 2>&1
	fi
	if [ "$?" = "0" ]; then
		# Success, echo something
		echo "USER=\"$user\" PASS=\"**********\""
	fi
}

# Just a simple logging helper.
log_result()
{
#	echo "$*; Input: $USER:$PASS Home: $HOME; Reply binary: $CHECKPASSWORD_REPLY_BINARY" >>$LOG
	echo "`date "+%Y.%m.%d %H:%M:%S"` $*; Input: $USER Home: $HOME; Reply binary: $CHECKPASSWORD_REPLY_BINARY" >>$LOG
}

# Read input data. It is available from $INPUT_FD as "${USER}\0${PASS}\0".
# Password may be empty if not available (i.e. if doing credentials lookup).
read -d $'\0' -r -u $INPUT_FD USER
read -d $'\0' -r -u $INPUT_FD PASS

# Both mailbox and domain directories should be in lowercase on file system.
# So let's convert login user name to lowercase and tell Dovecot that 'user' and 'home' 
# (which overrides 'mail_home' global parameter) values should be updated.
# Of course, conversion to lowercase may be done in Dovecot configuration as well.
export USER="`echo \"$USER\" | tr 'A-Z' 'a-z'`"
mail_name="`echo \"$USER\" | awk -F '@' '{ print $1 }'`"
domain_name="`echo \"$USER\" | awk -F '@' '{ print $2 }'`"
#export HOME="/var/qmail/mailnames/$domain_name/$mail_name/"
export HOME="/home/vmail/${USER}/"

# Script will be called under dovecot/dovecot user/group
#id >>$LOG
# CREDENTIALS_LOOKUP is not set in our use case!

# CREDENTIALS_LOOKUP=1 environment is set when doing non-plaintext authentication.
if [ "$CREDENTIALS_LOOKUP" = 1 ]; then
	action=credentials_lookup
else
	if [ ! -z "${SASL}" ]; then
		action=credentials_verify_sasl
	else
		action=credentials_verify
	fi
fi

# Perform credentials lookup/verification.
lookup_result=`$action "/etc/dovecot/users" "$USER" "$PASS"` || {
	# If it failed, consider it an internal temporary error.
	# This usually happens due to permission problems.
	log_result "internal error (ran as `id`)"
	exit $ERR_TEMPFAIL
}

if [ -n "$lookup_result" ]; then
	# Dovecot calls the script with AUTHORIZED=1 environment set when performing a userdb lookup.
	# The script must acknowledge this by changing the environment to AUTHORIZED=2,
	# otherwise the lookup fails.
	[ "$AUTHORIZED" != 1 ] || export AUTHORIZED=2

	# And here's how to return extra fields from userdb/passdb lookup, e.g. 'uid' and 'gid'.
	# All virtual mail users in Plesk actually run under 'popuser'.
	# See also:
	#   http://wiki2.dovecot.org/PasswordDatabase/ExtraFields
	#   http://wiki2.dovecot.org/UserDatabase/ExtraFields
	#   http://wiki2.dovecot.org/VirtualUsers
	export userdb_uid=${default_userdb_uid}
	export userdb_gid=${default_userdb_gid}
	export EXTRA="userdb_uid userdb_gid $EXTRA"

	if [ "$CREDENTIALS_LOOKUP" = 1 ]; then
		# If this is a credentials lookup, return password together with its scheme.
		# The password scheme that Dovecot wants is available in SCHEME environment variable
		# (e.g. SCHEME=CRAM-MD5), however 'PLAIN' scheme can be converted to anything internally
		# by Dovecot, so we'll just return 'PLAIN' password.
		found_password="`echo \"$lookup_result\" | awk -F ':' '{ print $2 }'`"
		export password="{PLAIN}$found_password"
		export EXTRA="password $EXTRA"
#		log_result "credentials lookup result: '$password' [SCHEME='$SCHEME', EXTRA='$EXTRA']"
		log_result "credentials lookup result: '**********' [SCHEME='$SCHEME', EXTRA='$EXTRA']"
	else
		log_result "lookup result: '$lookup_result'"
	fi

	# At the end of successful authentication execute checkpassword-reply binary.
	exec $CHECKPASSWORD_REPLY_BINARY
else
	# If matching credentials were not found, return proper error code depending on lookup mode.
	if [ "$AUTHORIZED" = 1 -a "$CREDENTIALS_LOOKUP" = 1 ]; then
		log_result "lookup failed (user not found)"
		exit $ERR_NOUSER
	else
		log_result "lookup failed (credentials are invalid)"
		exit $ERR_PERMFAIL
	fi
fi

# vim:set ts=4 sts=4 sw=4 ai:


More information about the dovecot mailing list