#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#    LinOTP - the open source solution for two factor authentication
#    Copyright (C) 2010 - 2014 LSE Leading Security Experts 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@lsexperts.de
#    Contact: www.linotp.org
#    Support: www.lsexperts.de
#
"""This tool reads the HMAC keys from a LinOTP 1.0
                Installation and writes it to LinOTP 2

  Dependencies: clientutils,
"""


VERSION = '0.1'
import os
from getopt import getopt, GetoptError
import getpass
from linotputils.clientutils import *
import pprint


import ldap
import sys
from hashlib import sha1
import hmac, struct, binascii


if sys.version_info[0:2] >= (2, 6):
    from json import loads
else:
    from simplejson import loads
import logging

def usage():
    print  '''
    Parameter:
    --ldap=ldapuri of LinOTP 1.0
    --binddn=binddn to the LDAP of LinOTP 1.0
    --filter=something like: cn=*,ou=users,dc=,dc=... of LinOTP 1.0
        BindPW is asked

    --linotp2=linotp2url
    --admin=linotp2 admin
        Password is asked
    --loginattr="The LDAP attribute that holds the login name"

    Note: The LinOTP2 useridresolver needs to be set up correctly
    and needs to be configured in the default realm.
'''


def main():
    linotp_url = ""
    linotp_protocol = ""
    ldap_url = ""
    ldap_protocol = ""
    admin = ""
    adminpw = ""
    binddn = ""
    bindpw = ""
    filter = ""
    base = ""
    loginattr = ""

    try:
        opts, args = getopt(sys.argv[1:], "",
                ["help", "ldap=", "binddn=", "filter=", "linotp2=", "admin=", "loginattr="])

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


    for opt, arg in opts:
        if opt in ('--help'):
            usage()
            sys.exit(0)
        elif opt in ('--linotp2'):
            if arg.startswith('https://'):
                linotp_protocol = "https"
                linotp_url = arg[8:]
            elif arg.startswith('http://'):
                linotp_protocol = "http"
                linotp_url = arg[7:]
            else:
                print "Malformed url format. You need to start with http or https [" + arg + "]"
                sys.exit(1)
        elif opt in ('--ldap'):
            if arg.startswith('ldaps://'):
                ldap_protocol = "ldaps"
                ldap_url = arg[8:]
            elif arg.startswith('ldap://'):
                ldap_protocol = "ldap"
                ldap_url = arg[7:]
            else:
                print "Malformed url format. You need to start with ldap or ldaps [" + arg + "]"
                sys.exit(1)
        elif opt in ('--admin'):
            admin = arg
            adminpw = getpass.getpass(prompt="Please enter password for '%s':" % admin)
        elif opt in ('--binddn'):
            binddn = arg
            bindpw = getpass.getpass(prompt="Please enter password for '%s':" % binddn)
        elif opt in ('--filter'):
            (filter, sep, base) = arg.partition(',')
        elif opt in ('--loginattr'):
            loginattr = arg

    print("Connecting to LDAP: ldap_url=%s, bind=%s, filter=%s, base=%s." % (ldap_url, binddn, filter, base))
    l = ldap.initialize(ldap_protocol + '://' + ldap_url, trace_level=0)
    l.simple_bind_s(binddn, bindpw)
    id = l.search(base, ldap.SCOPE_ONELEVEL, '(' + filter + ')')
    resultList = l.result(id, all=1)
    #print resultList
    l.unbind_s()

    # Create the linotpclient instance
    print "LinOTP 2 protocol: %s " % linotp_protocol
    print "LinOTP 2 server  : %s " % linotp_url
    lotpc = linotpclient(linotp_protocol, linotp_url, admin=admin, adminpw=adminpw)

    serial = 0
    for key in resultList[1]:
        serial += 1
        print ("Found new user: %s" % key[0])
        user_data = key[1]
        if user_data.has_key('LinOtpKeyEnc'):
            submit_linotp11(key[0], user_data)
        elif user_data.has_key('LinOtpKey'):
            submit_linotp10(key[0], user_data, serial, loginattr, lotpc)
        else:
            print "    This user has no LinOTP attributes"

def submit_linotp11(dn, user_data):
    print "WARNING: Your users got the LinOtpKeyEnc attribute. Obviously you are running LinOTP 1.1 or higher"
    print "         This is not supported at the moment"
    sys.exit(2)

def submit_linotp10(dn, user_data, serial, loginattr, lotpc):
    # examle
    # {'LinOtpKey': ['1a9782105af443def89d07d5ea3eb323'],
    #  'cn': ['user1'],
    #  'objectClass': ['top', 'person', 'inetOrgPerson', 'LinOtpAccount'],
    #  'LinOtpIsactive': ['TRUE'],
    #  'LinOtpPin': ['test'],
    #  'sn': ['heinz'],
    #  'LinOtpCount': ['0'],
    #  'mail': ['test@az.local'],
    #  'LinOtpFailcount': ['0'],
    #  'LinOtpMaxfail': ['50']}
    hmac = user_data['LinOtpKey'][0]
    if 32 == len(hmac):
        print "  ....converting HMAC key to hex string"
        # this is stored as a binary string with 32 bytes.
        # so we convert it to a hex string of length 64
        hmac = binascii.hexlify(hmac)

    if user_data.has_key(loginattr):
        loginname = user_data[loginattr][0]
    else:
        print("    WARNING: no loginattr <<%s>> found for user %s!" % (loginattr, dn))
        return
    serialnumber = "linotp1.0_%03d" % serial

    pin = ""
    if user_data.has_key('LinOtpPin'):
        pin = user_data['LinOtpPin']

    print "    Importing Token %s for user %s...." % (serialnumber, loginname)
    r1 = lotpc.inittoken({ 'serial' : serialnumber,
        'user' : loginname,
        'otpkey' : hmac,
        'pin' : pin })

    print "    ", r1
    #print "    setting pin %s" % pin
    print "    setting pin: %s, %s" % (serialnumber, pin)
    r1 = lotpc.set({'serial' : serialnumber, 'pin' : pin })

    print "    ", r1
    #  disable token?
    if user_data.has_key('LinOtpIsactive'):
        if user_data['LinOtpIsactive'][0] != "TRUE":
            print "    Disabling token"
            r1 = lotpc.disabletoken({ 'serial' : serialnumber })

    # set OTP counter
    if user_data.has_key('LinOtpCount'):
        counter = int(user_data['LinOtpCount'][0])
        otp1 = generate(hmac, counter - 1)
        print "    OTP1: %s" % otp1
        otp2 = generate(hmac, counter)
        print "    OTP2: %s" % otp2
        print "    Resyncing OTP counter %d" % counter
        r1 = lotpc.resynctoken({ 'serial':serialnumber , 'otp1':otp1, 'otp2': otp2 })
        print "    ", r1

def generate(key, counter):
    digits = 6
    def truncate(digest):
        offset = ord(digest[-1:]) & 0x0f

        binary = (ord(digest[offset + 0]) & 0x7f) << 24
        binary |= (ord(digest[offset + 1]) & 0xff) << 16
        binary |= (ord(digest[offset + 2]) & 0xff) << 8
        binary |= (ord(digest[offset + 3]) & 0xff)

        return binary % (10 ** digits)

    bkey = binascii.unhexlify(key)

    digest = hmac.new(bkey, struct.pack(">Q", counter), sha1)
    dig = str(digest.digest())
    otp = str (truncate(dig))
    """  fill in the leading zeros  """
    sotp = (digits - len(otp)) * "0" + otp
    return sotp



if __name__ == '__main__':
    main()
