# -*- coding: utf-8 -*-
###############################################################################
# ldap_passwd.py : manipulation des mots de passes LDAP
# $Id: ldap_passwd.py,v 1.7 2006-05-04 17:46:58 chove Exp $
###############################################################################
# The authors of this code are
#   Bjorn Ove Grotan <bgrotan@grotan.com>
#   Etienne Chové <etienne.chove@crans.org>
#
# Copyright (C) 2005 Bjorn Ove Grotan
# Copyright (C) 2006 Etienne Chové
# All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
###############################################################################

# For extra strength passwords, we wanted SSHA in our LDAP-environment
# as the standard python-module 'sha' does not support ssha, but this can
# easily be implemented with a few extra functions.
#
# SSHA can be described as:
#     the SHA1-digest of a password with a sequence of "salt" bytes, where
#     the bytes are randomly chosen - followed by the same salt bytes
# For LDAP-use, the SHA1 and SSHA-digest has to be base64-encoded.
#
# Example-LDIF:
#     {SSHA}oaEG3PJ10sHxGcSxsDRRooTifL55/2NOdN3nU1VEV+NFzc9Q
#
# This package should now support passwords compatible with [1] Samba using the [2]
# smbpasswd module for [3] Python. The samba compability is added for use with Samba
# as PDC with storing user and host-information in LDAP.
#
# [1] http://www.samba.org
# [2] http://barryp.org/software/py-smbpasswd/
# [3] http://www.python.org

import string,base64
import random,sys
import exceptions
import crypt
import hashlib

try:
    import smbpasswd
    smb = True
except:
    smb = False

algos={}
algos = {
    'ssha':'Seeded SHA',
    'sha':'Secure Hash Algorithm',
    'md5':'MD5',
    'smd5':'Seeded MD5',
    'crypt':'standard unix crypt',
    'cleartext':'clear text'
}
if smb:
    algos['lmpassword'] = 'lan man hash'
    algos['ntpassword'] = 'nt hash'


def getsalt(chars=string.letters+string.digits, length=16):
    ''' Generate a random salt. Default length is 16 '''
    salt = ''
    for i in range(int(length)):
        salt += random.choice(chars)
    return salt

def mkpasswd(pwd, sambaver=3, algo='SSHA', salt=None):
    ''' Make a given password cryptated, possibly with different
        crypt-algorihtms. This module was written for use with
        LDAP - so default is seeded sha
    '''

    if salt == None:
        salt = getsalt()

    algo = algo.lower()
    if algo not in algos.keys():
        raise TypeError, 'Algorithm <%s> not supported in this version.' % algo

    if algo == 'ssha':
        h = hashlib.sha1()
        h.update(str(pwd) + salt)
        pwdhash = "{SSHA}" + base64.encodestring(h.digest() + salt)
    elif algo =='sha':
        h = hashlib.sha1()
        h.update(str(pwd))
        pwdhash = "{SHA}" + base64.encodestring(h.digest())
    elif algo =='md5':
        h = hashlib.md5()
        h.update(str(pwd))
        pwdhash = "{MD5}" + base64.encodestring(h.digest())
    elif algo == 'smd5':
        h = hashlib.md5()
        h.update(str(pwd) + salt)
        pwdhash = "{SMD5}" + base64.encodestring(h.digest() + salt)
    elif algo =='crypt':
        pwdhash = "{CRYPT}" + crypt.crypt(str(pwd),getsalt(length=2)) # crypt only uses a salt of length 2
    elif algo == 'lmpassword':
        if sambaver==3:
            pwdhash = "{sambaLMPassword}" + smbpasswd.lmhash(pwd)
        elif sambaver==2:
            pwdhash = "{lmPassword}" + smbpasswd.lmhash(pwd)
    elif algo == 'ntpassword':
        if sambaver == 3:
            pwdhash = "{sambaNTPassword}" + smbpasswd.lmhash(pwd)
        elif sambaver == 2:
            pwdhash = "{NTPassword}" + smbpasswd.lmhash(pwd)
    elif algo == 'cleartext':
        pwdhash = "{CLEARTEXT}" + pwd
    return pwdhash.strip()

def checkpwd(pwd, pwdhash):
    ''' Check if the password matches the hash '''

    algo = pwdhash[1:].split('}')[0]
    algo = algo.lower()

    if algo.startswith('samba'):
        sambaver = 3
        algo = algo[5:]
    else:
        sambaver = 2

    if not algo in algos.keys():
        raise TypeError, 'Algorithm <%s> not supported in this version.' % algo

    if algos[algo].startswith('Seeded '):
        salt = base64.decodestring(pwdhash.split('}')[1])[20:]
    else:
        salt = None

    return mkpasswd(pwd, sambaver=sambaver, algo=algo, salt=salt) == pwdhash
