#!/usr/bin/python3

import PAM
import subprocess
import sys

config = {
    'db': 'pam',
    'db_user': 'pam',
    'db_password': 'pampasswd',
    'db_table': 'users',
    'user_column': 'username',
    'password_column': 'password',
    'pam_service': 'pamtest',
    'config_file': '/etc/pam-mysql.conf'
}

passwords = {
    'plain'    : { 'crypt':  0, 'hash': 'foopwd' },
    'Y'        : { 'crypt':  1, 'md5': 'false', 'hash': 'xycbw66FMoYGI' }, # mkpasswd -m descrypt foopwd xy
    'Y_MD5'    : { 'crypt':  1, 'md5': 'true' , 'hash': '$1$abcdefgh$cKmmXi05KpTgjDSOXaL4X/' }, # mkpasswd -m md5crypt foopwd abcdefgh
    'mysql'    : { 'crypt':  2, '323': 'false', 'hash': '*1A8A8D8A90F03E8A15D4FFB3FC91A4693F077A84' }, # select PASSWORD('foopwd')
    'mysql_old': { 'crypt':  2, '323': 'true' , 'hash': '2a7c7b955b2d807b' }, # select OLD_PASSWORD('foopwd') (original, pre-4.1 hash)
    'md5'      : { 'crypt':  3, 'hash': '1631f7e7ed3c261d02b309016087f8a9' }, # select MD5('foopwd')
    'sha1'     : { 'crypt':  4, 'hash': '794ed3d18464baff93f8ded1cfd00d9a2d9fe316' }, # select SHA1('foopwd')
    # https://www.useotools.com/drupal-password-hash-generator
    'drupal7'  : { 'crypt':  5, 'hash': '$S$5925NCiI4OFCbPvIIbhLVZagCu/GkASIhxqJHhuMfEP6WRqJLNwe' },
    # https://www.useotools.com/joomla-password-hash-generator
    'joomla15' : { 'crypt':  6, 'hash': 'e6a39083bd9bbc7a4919942146230321:dksVPHnEP2MPyD4CgkSKxSAvTIIuAERa' }
}

mysql = subprocess.Popen('mysql', stdin=subprocess.PIPE, text=True)
mysql.communicate('''\
DROP DATABASE IF EXISTS {db};
CREATE DATABASE {db};
USE {db};
CREATE TABLE {db_table} (
  {user_column} VARCHAR (10) NOT NULL,
  {password_column} VARCHAR (80) NOT NULL
);
INSERT INTO {db_table} ({user_column}, {password_column})
  VALUES {users};
CREATE USER {db_user}@localhost IDENTIFIED BY '{db_password}';
GRANT SELECT ON {db}.{db_table} TO {db_user}@localhost;
'''.format(users=','.join(["('{}','{}')".format(k, v['hash']) for k,v in passwords.items()]), **config))

pam_conf = open('/etc/pam.d/{pam_service}'.format(**config), 'w')
pam_conf.write('''\
auth required pam_mysql.so config_file={config_file}
'''.format(**config))
pam_conf.close()

def write_config_file(conf):
    pam_mysql_conf = open(config['config_file'], 'w')
    pam_mysql_conf.write('''\
users.host 		= localhost
users.database 		= {db}
users.db_user 		= {db_user}
users.db_passwd		= {db_password}
users.table 		= {db_table}
users.user_column 	= {user_column}
users.password_column 	= {password_column}
users.password_crypt 	= {crypt}
'''.format(**config, crypt = conf['crypt']))
    if 'md5' in conf:
        pam_mysql_conf.write('users.use_md5 = {}\n'.format(conf['md5']))
    elif '323' in conf:
        pam_mysql_conf.write('users.use_323_password = {}\n'.format(conf['323']))
    pam_mysql_conf.close()

def pam_conv(auth, query_list, userData):
    if query_list != [('Password:', PAM.PAM_PROMPT_ECHO_OFF)]:
        print('Unexpected query_list', query_list)
        return None
    return [(userData,0)]

def good_auth(user, password):
    p = PAM.pam()
    p.start(config['pam_service'], user, pam_conv)
    p.setUserData(password)
    try:
        p.authenticate()
    except PAM.error as err:
        if err.args == ('Authentication failure', 7):
            return False
        else:
            raise
    else:
        return True

successes = 0
failures = 0
for user, conf in passwords.items():
    write_config_file(conf)
    if good_auth(user, 'foopwd'):
        print('OK: {}: correct password accepted'.format(user))
        successes += 1
    else:
        print('FAIL: {}: correct password refused'.format(user))
        failures += 1
    if good_auth(user, 'wrong_password'):
        print('FAIL: {}: incorrect password accepted'.format(user))
        failures += 1
    else:
        print('OK: {}: incorrect password refused'.format(user))
        successes += 1

print('Summary: {} successes and {} failures'.format(successes, failures))
if (failures == 0):
    sys.exit(0)
else:
    sys.exit(1)
