Skip to content

Instantly share code, notes, and snippets.

@mss
Forked from umrysh/checkpassword.sh
Created September 15, 2021 07:22
Show Gist options
  • Save mss/0d045bc1d308de310addbf05672856e4 to your computer and use it in GitHub Desktop.
Save mss/0d045bc1d308de310addbf05672856e4 to your computer and use it in GitHub Desktop.

Revisions

  1. @umrysh umrysh revised this gist Nov 17, 2014. 2 changed files with 212 additions and 0 deletions.
    186 changes: 186 additions & 0 deletions checkpassword.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,186 @@
    #!/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 '<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

    # 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"
    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
    26 changes: 26 additions & 0 deletions getHome.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,26 @@
    # Built for python 2.7
    import MySQLdb as mdb
    import sys

    host = "127.0.0.1"
    database = "vmail"
    username = "vmail"
    password = "VeNUlA6xKjpfceGsN3Ull8hLmAVjc3"

    def main():
    if len(sys.argv) == 2:
    con = mdb.connect(host=host, port=3306,user=username, passwd=password, db=database)
    cur = con.cursor()
    cur.execute("SELECT CONCAT(mailbox.storagebasedirectory, '/', mailbox.storagenode, '/', mailbox.maildir) AS home FROM mailbox where username = '%s'" % con.escape_string(sys.argv[1]))
    row = cur.fetchone()
    if row is not None:
    sys.stdout.write("%s" % row[0])
    else:
    sys.stdout.write("")
    else:
    sys.stdout.write("")
    sys.stdout.flush()
    sys.exit(0)

    if __name__ == "__main__":
    main()
  2. @umrysh umrysh created this gist Nov 17, 2014.
    23 changes: 23 additions & 0 deletions getTOTP.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,23 @@
    # Built for python 2.7
    import onetimepass as otp
    import sys,base64,os

    def main():
    if len(sys.argv) >= 2:
    if sys.argv[1] == "-v":
    sys.stdout.write("%s" % otp.valid_totp(token=sys.argv[3], secret=sys.argv[2]))
    elif sys.argv[1] == "-c":
    try:
    sys.stdout.write("%s" % otp.get_totp(sys.argv[2]))
    except:
    sys.stdout.write("")
    elif sys.argv[1] == "-g":
    sys.stdout.write("%s" % base64.b32encode(os.urandom(10)))
    else:
    sys.stdout.write("")

    sys.stdout.flush()
    sys.exit(0)

    if __name__ == "__main__":
    main()