#!/bin/bash /usr/scripts/python.sh # -*- coding: utf-8 -*- """ Script de changement de mots de passe LDAP * Change le mot de passe de l'utilisateur donné en argument. Auteur : Pierre-Elliott Bécue Licence : GPLv3 """ import sys import os if '/usr/scripts' not in sys.path: sys.path.append('/usr/scripts') import gestion.config as config import config.password import getpass import argparse import gestion.affich_tools as affich_tools import lc_ldap.shortcuts import lc_ldap.attributs import lc_ldap.objets import gestion.mail as mail_module encoding = getattr(sys.stdout, 'encoding', "UTF-8") current_user = os.getenv("SUDO_USER") or os.getenv("USER") or os.getenv("LOGNAME") or getpass.getuser() def check_password(password, no_cracklib=False, dialog=False): """ Teste le mot de passe. * Tests custom + cracklib (sauf si no_cracklib) """ problem = False msg = "" try: password.decode('ascii') except UnicodeDecodeError: problem = True msg += u"Le mot de passe ne doit contenir que des caractères ascii.\n" # Nounou mode if no_cracklib: if len(password) >= config.password.root_min_len: return True else: upp = 0 low = 0 oth = 0 cif = 0 # Comptage des caractères for char in password: if char.isdigit(): cif += 1 elif char.isupper(): upp += 1 elif char.islower(): low += 1 else: oth += 1 # Recherche de manque de caractères if cif < config.password.min_cif: msg += u'Le mot de passe doit contenir plus de chiffres.\n' problem = True if upp < config.password.min_upp: msg += u'Le mot de passe doit contenir plus de majuscules.\n' problem = True if low < config.password.min_low: msg += u'Le mot de passe doit contenir plus de minuscules.\n' problem = True if oth < config.password.min_oth: msg += u'Le mot de passe doit contenir plus de caractères qui ne sont ni des chiffres, ni des majuscules, ni des minuscules.\n' problem = True # Scores sur la longueur longueur = config.password.upp_value*upp + config.password.low_value*low + config.password.cif_value*cif + config.password.oth_value*oth if longueur < config.password.min_len: msg += u'Le mot de passe devrait être plus long, ou plus difficile.\n' problem = True if not problem: try: import cracklib except ImportError: affich_tools.cprint("Attention : la librairie python cracklib n'est pas accessible sur ce serveur.", "rouge") if sys.modules.get('cracklib', ''): try: # Le mot vient-il du dico (à améliorer, on voudrait pouvoir préciser # la rigueur du test) ? password = cracklib.VeryFascistCheck(password) if dialog: msg = affich_tools.coul(msg, 'rouge', dialog=dialog) return True, msg except ValueError as e: msg += str(e).decode() if dialog: msg = affich_tools.coul(msg, 'rouge', dialog=dialog) return False, msg else: if dialog: msg = affich_tools.coul(msg, 'rouge', dialog=dialog) return True, msg else: if dialog: msg = affich_tools.coul(msg, 'rouge', dialog=dialog) return False, msg if dialog: msg = affich_tools.coul(msg, 'rouge', dialog=dialog) return False, msg @lc_ldap.shortcuts.with_ldap_conn(retries=2, delay=5, constructor=lc_ldap.shortcuts.lc_ldap_admin) def change_password(ldap, login=None, verbose=False, no_cracklib=False, **args): """ Change le mot de passe en fonction des arguments """ if login is None: login = current_user if type(login) == str: login = login.decode(encoding) login = lc_ldap.crans_utils.escape(login) query = ldap.search(u"(uid=%s)" % login, mode="w") if not query: affich_tools.cprint('Utilisateur introuvable dans la base de données, modification de l\'utilisateur local.', "rouge") sys.exit(2) with query[0] as user: # Test pour vérifier que l'utilisateur courant peut modifier le mdp de user try: user['userPassword'] = [lc_ldap.crans_utils.hash_password("test").decode('ascii')] user.cancel() except EnvironmentError as e: affich_tools.cprint(str(e).decode(encoding), "rouge") # Génération d'un mail From = 'roots@crans.org' To = 'roots@crans.org' mail = """From: Root <%s> To: %s Subject: Tentative de changement de mot de passe ! Tentative de changement du mot de passe de %s par %s. """ % (From, To , login.encode(encoding), current_user) # Envoi mail with mail_module.ServerConnection() as conn: conn.sendmail(From, To , mail ) sys.exit(1) # On peut modifier le MDP if isinstance(user, lc_ldap.objets.club): prenom = "Club" else: prenom = user['prenom'][0] affich_tools.cprint("Changement du mot de passe de %s %s." % (prenom, user['nom'][0]), "vert") # Règles du jeu # (J'ai perdu) if verbose: affich_tools.cprint(u"""Règles : Longueur standard : %s, root : %s, Minimums : chiffres : %s, minuscules : %s, majuscules : %s, autres : %s, Scores de longueur : chiffres : %s, minuscules : %s, majuscules : %s, autres : %s, Cracklib : %s.""" % ( config.password.min_len, config.password.root_min_len, config.password.min_cif, config.password.min_low, config.password.min_upp, config.password.min_oth, config.password.cif_value, config.password.low_value, config.password.upp_value, config.password.oth_value, "Oui" * (not no_cracklib) + "Non" * (no_cracklib) ), 'jaune') else: affich_tools.cprint(u"""Le nouveau mot de passe doit comporter au minimum %s caractères. Il ne doit pas être basé sur un mot du dictionnaire. Il doit contenir au moins %s chiffre(s), %s minuscule(s), %s majuscule(s) et au moins %s autre(s) caractère(s). CTRL+D ou CTRL+C provoquent un abandon.""" % ( config.password.min_len, config.password.min_cif, config.password.min_low, config.password.min_upp, config.password.min_oth ), 'jaune') try: while True: mdp = getpass.getpass("Nouveau mot de passe: ") (ret, msg) = check_password(mdp, no_cracklib) if ret: mdp2 = getpass.getpass("Retaper le mot de passe: ") if mdp != mdp2: affich_tools.cprint(u"Les deux mots de passe diffèrent.", "rouge") else: break else: affich_tools.cprint(msg, 'rouge') except KeyboardInterrupt: affich_tools.cprint(u'\nAbandon', 'rouge') sys.exit(1) except EOFError: # Un Ctrl-D affich_tools.cprint(u'\nAbandon', 'rouge') sys.exit(1) hashedPassword = lc_ldap.crans_utils.hash_password(mdp) user['userPassword'] = [hashedPassword.decode('ascii')] user.save() affich_tools.cprint(u"Mot de passe de %s changé." % (user['uid'][0]), "vert") if __name__ == "__main__": parser = argparse.ArgumentParser( description="Recherche dans la base des adhérents", add_help=False) parser.add_argument('-h', '--help', help="Affiche ce message et quitte.", action="store_true") parser.add_argument('-n', '--no-cracklib', help="Permet de contourner les règles de choix du mot de passe" + "(réservé aux nounous).", action="store_true") parser.add_argument('-v', '--verbose', help="Permet de contourner les règles de choix du mot de passe" + "(réservé aux nounous).", action="store_true") parser.add_argument('login', type=str, nargs="?", help="L'utilisateur dont on veut changer le mot de passe.") args = parser.parse_args() if args.help: parser.print_help() sys.exit(0) if args.no_cracklib: if not lc_ldap.attributs.nounou in ldap.droits: args.no_cracklib = False change_password(**vars(args))