#!/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 ':' 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' | ./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: