#!/usr/bin/env python
# -*- coding: utf-8; -*-
#
# (c) 2004-2007 Linbox / Free&ALter Soft, http://linbox.com
# (c) 2007-2009 Mandriva, http://www.mandriva.com
#
# $Id$
#
# 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
import ConfigParser

CONF = "/etc/mmc-password-helper.ini"

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

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

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

def log(string):
    if verbose:
        sys.stdout.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, True):
                log("Generated password: " + passtry)
                return passtry
        except:
            pass

# Check if this password is safe 
def check_pass(password, check_length=False):
    try:
        password = password.decode('utf-8')
    except UnicodeEncodeError:
        log('The password must be an UTF-8 string')
        return False

    if check_length:
	if len(password) < pass_length:
    	    log("The password length must be %s or longer" % pass_length)
    	    return False

    config = ConfigParser.RawConfigParser()
    if not os.path.exists(CONF):
        error("The configuration file %s doesn't exists" % CONF, True)
        return False
    config.read(CONF)

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

    if config.getboolean('main', 'must_one_number'):
        if not reg_num.match(password):
            log("The password must contain at least one number")
            return False

    if config.getboolean('main', 'must_one_upper_case'):
        if not reg_up.match(password):
            log("The password must contain at least one upper case character")
            return False

    if config.getboolean('main', 'must_one_lower_case'):
        if not reg_down.match(password):
            log("The password must contain at least one lower case character")
            return False

    if config.getboolean('main', 'must_one_special'):
        found_punct = False
        for punct in string.punctuation:
            if password.count(punct):
                found_punct = True

        if not found_punct:
            log("The password must contain at least one special character: #$%&+./:=?@{}")
            return False

    if config.getint('main', 'same_char_max') > 0:
        for char in password:
            if password.count(char) > config.getint('main', 'same_char_max'):
                log("The password must not contain the same character %s times" % config.getint('main', 'same_char_max'))
                return False

    if config.getboolean('main', 'use_cracklib'):
        try:
            import cracklib
        except ImportError:
            # if we do not find python-cracklib, bypass the test
            error("python-cracklib not found. Install it for a better password check")
            return True
        try:
            if password.encode('utf-8') == cracklib.VeryFascistCheck(password.encode('utf-8')):
                return True
            else:
                log("Password not accepted by cracklib")
        except ValueError, reason:
            if verbose:
                log("Password not accepted: %s" % reason)
            return False

    return True

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():
                error("The length must be integer", True)
                sys.exit(1)
            pass_length = int(a)
        #    if pass_length < 6:
        #        error("The password can't be shorter than 6", True)
        #        sys.exit(1)
            if pass_length > 14:
                error("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):
                error("The provided file is invalid", True)
                sys.exit(1)
            file = a
        else:
            assert False, "unhandled option"
    
    if must_check_pass and generate_pass:
        error("You cannot generate a password and check it at the same time", True)
        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()

