#!/usr/bin/env python2
# -*- coding: utf-8 -*-
#
#    LinOTP - the open source solution for two factor authentication
#    Copyright (C) 2010 - 2019 KeyIdentity GmbH
#
#    This file is part of LinOTP server.
#
#    This program is free software: you can redistribute it and/or
#    modify it under the terms of the GNU Affero General Public
#    License, version 3, as published by the Free Software Foundation.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the
#               GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#
#    E-mail: linotp@keyidentity.com
#    Contact: www.linotp.org
#    Support: www.keyidentity.com
#

"""This tool generates passwords for the sql resolver"""

VERSION = '0.1'
import os, sys
from getopt import getopt, GetoptError
import getpass
import crypt
import pprint
import random
import hashlib
import base64

import re

import logging
log = logging.getLogger(__name__)

def usage():
    print  '''

    linotp-create-sqlidresolver-user : create password entry for SQL databases

    Parameter:

    -u user name
    -i user id
    -p password
    -s SHA512         : hash algorithm, either SHA256 or SHA512
    -o output format : format could be SQL or CSV

    '''


def _check_hash_type(password, hash_type, hash_value):
    '''
    Checks, if the password matches the given hash.

    :param password: The user password in clear text
    :type password: string
    :param hash_type: possible values are sha, ssha, sha256, ssha256
    :type hash_type: string
    :param hash_value: The hash value, base64 encoded
    :type hash_value: string
    :return: True or False
    '''
    log.debug("[_check_hash_type] hash type %r, hash_value %r" % (hash_type,
                                                                  hash_value))
    res = False
    if hash_type.lower() == "sha":
        hash_type = "sha1"
    if hash_type.lower() == "ssha":
        hash_type = "ssha1"

    if (hash_type.lower()[0:3] == "sha"):
        log.debug("[_check_hash_type] found a non-salted hash.")
        try:
            H = hashlib.new(hash_type)
            H.update(password)
            hashed_password = base64.b64encode(H.digest())
            res = (hashed_password == hash_value)
        except ValueError:
            log.exception("[_check_hash_type] Unsupported Hash type: %r"
                                                                % hash_type)

    elif (hash_type.lower()[0:4] == "ssha"):
        log.debug("[_check_hash_type] found a salted hash.")
        try:
            new_hash_type = hash_type[1:]
            # binary hash value
            bin_value = base64.b64decode(hash_value)

            H = hashlib.new(new_hash_type)
            hash_len = H.digest_size
            # binary salt
            bin_hash = bin_value[:hash_len]
            bin_salt = bin_value[hash_len:]

            H.update(password + bin_salt)
            bin_hashed_password = H.digest()

            res = (bin_hashed_password == bin_hash)

        except ValueError:
            log.exception("[_check_hash_type] Unsupported Hash type: %r"
                                                                % hash_type)

    return res


def main():

    user = "linotp_user"
    user_id = '1234'
    password = None
    hash_type = 'SHA512'
    output = 'CSV'

    try:
        opts, args = getopt(sys.argv[1:], "u:i:p:s:o:h", ["help"])

    except GetoptError:
        print "There is an error in your parameter syntax:"
        usage()
        sys.exit(1)

    for opt, arg in opts:
        if opt in ('-u'):
            user = arg

        if opt in ('-i'):
            user_id = arg

        if opt in ('-o'):
            output = arg.upper()
            if output not in ['CSV', 'SQL']:
                usage()
                sys.exit(1)

        if opt in ('-s'):
            hash_type = arg.upper()
            if hash_type not in ['SHA256','SHA512']:
                usage()
                sys.exit(1)

        elif opt in ('-p'):
            password = arg

        elif opt in ('-h', '--help'):
            usage()
            sys.exit(1)

    if not password:
        password = getpass.getpass("Please enter a password: ")

    # we use the hashed password storing sheme, which is described in
    # http://wiki2.dovecot.org/Authentication/PasswordSchemes
    # and use a salted hash by default:
    # SSHA564(pass, salt) = SHA256(pass + salt) + salt.

    # the salt characters are from: [./a-zA-Z0-9]
    pool = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    salt = pool[random.randrange(0, len(pool))]

    H = hashlib.new(hash_type)
    hash_len = H.digest_size
    
    for i in range(0,hash_len):
        salt = salt + pool[random.randrange(0, len(pool))]
    salt = salt[0:hash_len]

    H.update('%s%s' % (password,salt))
    hashedPW = H.digest()

    base64PW = base64.b64encode(hashedPW + salt)
    encryptedPW = "{S%s}%s" % (hash_type,base64PW)

    m = re.match("^\{(.*)\}(.*)", encryptedPW)
    hash_type = m.group(1)
    hash_value = m.group(2)

    if _check_hash_type(password, hash_type, hash_value) == False:
        raise Exception("ERROR: Failed to check generated password!")

    if output == 'SQL':
        print ("UPDATE \"UserDB\" \nSET \"password\"= '%s'\n WHERE \"id\" = '%s';"
               % (encryptedPW, user_id))
    else: # for csv import
        print ("%s;%s;%s;" % (user, user_id,encryptedPW))
    return

if __name__ == '__main__':
    main()
