#!/bin/bash # Example Dovecot checkpassword script that may be used as both passdb or userdb. # # Originally written by Nikolay Vizovitin, 2013. # 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 # 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" local cached="$4" local ipfile="$5" local ip="$TCPREMOTEIP" #local ip="$TCPLOCALIP" #local ip="192.168.149.100" local timestamp="$(date +%s)" #local defaultTime=300 local defaultTime=10 local expire="" if [ -f "$cached" ]; then expire=`awk -F ':' -v USER="$user" -v IP="$ip" -v PASS="$pass" '($1 == USER && $2 == IP && $3 == PASS) {print $4}' "$cached"` fi if [ ! -z "$expire" ]; then if [ "$timestamp" -gt "$expire" ]; then # Remove from cache sed -i "/$user:$ip:$pass/d" "$cached" #echo "cached is old. fail log in" log_result_basic "cached is old. fail log in" else #echo "cache is current. allow log on" log_result_basic "cache is current. allow log on" echo "true" fi else if python2.7 /etc/dovecot/getTOTP.py -v `awk -F ':' -v USER="$user" '($1 == USER) {print $2}' "$db"` "$pass" | grep 'True'; then #echo "allow log on" log_result_basic "allow log on" if [ -f "$ipfile" ]; then timeforip=`awk -F ':' -v IP="$ip" '($1 == IP) {print $2}' "$ipfile"` if [ -z "$timeforip" ]; then # Use default time cache #echo "Using default time" log_result_basic "Using default time" echo "$user:$ip:$pass:$(($timestamp+$defaultTime))" >> "$cached" else #echo "Using stored time" log_result_basic "Using stored time" echo "$user:$ip:$pass:$(($timestamp+$timeforip))" >> "$cached" fi else # Use default time cache #echo "Using default time" log_result_basic "Using default time" echo "$user:$ip:$pass:$(($timestamp+$defaultTime))" >> "$cached" fi echo "true" fi fi } # Just a simple logging helper. log_result() { echo "$*; Input: $USER:$PASS; Home: $HOME; Reply binary: $CHECKPASSWORD_REPLY_BINARY" >>$LOG } # Just a simpler logging helper. log_result_basic() { echo "$*; Input: $USER:$PASS" >>$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=`python2.7 /etc/dovecot/getHome.py $USER` # CREDENTIALS_LOOKUP=1 environment is set when doing non-plaintext authentication. if [ "$CREDENTIALS_LOOKUP" = 1 ]; then action=credentials_lookup else action=credentials_verify fi # Perform credentials lookup/verification. lookup_result=`$action "/etc/dovecot/users" "$USER" "$PASS" "/etc/dovecot/cached" "/etc/dovecot/ipfile"` || { # 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=vmail export userdb_gid=vmail 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']" 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