#!/usr/bin/python
# -*- coding: utf-8; -*-
#
# (c) 2004-2007 Linbox / Free&ALter Soft, http://linbox.com
# (c) 2007-2009 Mandriva, http://www.mandriva.com
#
# $Id: mmc-password-helper 4899 2009-12-17 17:58:46Z cdelfosse $
#
# This file is part of Mandriva Management Console (MMC).
#
# MMC 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.
#
# MMC 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 MMC.  If not, see <http://www.gnu.org/licenses/>.

"""
mmc-password-helper is a command line tool that checks password complexity, and
generates good passwords.
"""

import base64
import random
import getopt
import sys
import re
import string
import os

verbose = False
pass_length = 8
generate_pass = False
must_check_pass = False
read_from_file = False
file = None
userdn = None

special_characters = "#$%&+./:=?@{}"

def debug(string, force=False):
    if verbose or force:
        sys.stderr.write(string +"\n")

def generate_new_pass():
    while True:
        # Regenerate the password until check_pass() says it is a safe pass
        pass_size = 3+pass_length
        passtry = base64.encodestring(str(random.random())+str(random.random()))[3:pass_size]
        try:
            passtry = passtry.replace( passtry[random.randrange(len(passtry))], special_characters[random.randrange(len(special_characters))])
            if check_pass(passtry):
                debug("generated password: " + passtry)
                return passtry
        except:
            pass

# Check if this password is safe 
def check_pass(password):
    try:
        password = password.decode('utf-8')
    except UnicodeEncodeError:
        sys.stderr.write('Password must be an UTF-8 string')
        return False

    if len(password) < pass_length:
        debug("the password must be %s or longer" % pass_length)
        return False

    reg_num = re.compile(".*[0-9].*")
    reg_up = re.compile(".*[A-Z].*")
    reg_down = re.compile(".*[a-z].*")

    if not reg_num.match(password):
        debug("the password must contain at least one number")
        return False

    if not reg_up.match(password):
        debug("the password must contain at least one upper case character")
        return False

    if not reg_down.match(password):
        debug("the password must contain at least one lower case character")
        return False

    found_punct = False
    for punct in string.punctuation:
        if password.count(punct):
            found_punct = True

    if not found_punct:
        debug("the password must contain at least one special character")
        return False

    for char in password:
        if password.count(char) > 1:
            debug("the password must not contain the same character twice")
            return False
    try:
        import cracklib
    except ImportError:
        # if we do not find python-cracklib, bypass the test
        debug("python-cracklib not found. Install it for a better password check")
        debug("password accepted")
        return True

    try:
        if password.encode('utf-8') == cracklib.VeryFascistCheck(password.encode('utf-8')):
            debug("password accepted")
            return True
        else:
            debug("password NOT accepted")
    except ValueError, reason:
        if verbose:
            debug("password NOT accepted: %s" % reason)
        return False
    return False

def usage():
    print "mmc-password-helper [--help -h|--verbose -v|--newpass -n|--check -c|--length -l|--file -f]"

if __name__ == '__main__':
    try:
        opts, args = getopt.getopt(sys.argv[1:], "hvncl:f:u:", ["help", "verbose","newpass", "check", "length", "file", "user"])
    except getopt.GetoptError, err:
        print str(err)
        sys.exit(1)
    for o, a in opts:
        if o in ("-v","--verbose"):
            verbose = True
        elif o in ("-h", "--help"):
            usage()
            sys.exit(1)
        elif o in ("-l","--length"):
            if not a.isdigit():
                debug("the length must be integer")
                sys.exit(1)
            pass_length = int(a)
            if pass_length < 6:
                debug("the password can be shorter than 6", True)
                sys.exit(1)
            if pass_length > 14:
                debug("the password can't be longer than 14", True)
                sys.exit(1)
        elif o in ("-n", "--newpass"):
            generate_pass = True
        elif o in ("-c", "--check"):
            must_check_pass = True
        elif o in ("-u", "--user"):
            userdn = a
        elif o in ("-f", "--file"):
            read_from_file = True
            if not os.path.isfile(a):
                debug("the provided file is invalid")
                sys.exit(1)
            file = a
        else:
            assert False, "unhandled option"
    
    if must_check_pass and generate_pass:
        debug("you cannot generate pass and check at the same time")
        sys.exit(1)
    
    if must_check_pass:
        if read_from_file:
            password = open(file).readline().strip()
        else:
            password = sys.stdin.readlines()[0].strip()
        if check_pass(password):
            sys.exit(0)
        else:
            sys.exit(1)

    if generate_pass:
        print generate_new_pass()
        sys.exit(0)

    usage()

