[chgpass] Mise en place d'un nouveau script.
* A priori plus sûr * Utilise lc_ldap
This commit is contained in:
parent
58edc5970a
commit
9e25812e62
3 changed files with 405 additions and 196 deletions
224
archive/gestion/chgpass.py
Executable file
224
archive/gestion/chgpass.py
Executable file
|
@ -0,0 +1,224 @@
|
||||||
|
#! /usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Script de changement de mots de passe LDAP
|
||||||
|
|
||||||
|
Utilisation :
|
||||||
|
* cas 1 : sans arguements par un utlisateur lambda :
|
||||||
|
changement de son propre mdp
|
||||||
|
* cas 2 : avec argument par un utilisateur ayant accès
|
||||||
|
total à la base LDAP (respbats) mais PAS ROOT :
|
||||||
|
changement du mdp de l'adhérent fourni en argument,
|
||||||
|
impossibilité de modifier le mdp d'un compte privilégié
|
||||||
|
* cas 3 : lancé par root : possibilité de modifier
|
||||||
|
tous les mots de passe LDAP
|
||||||
|
|
||||||
|
Copyright (C) Frédéric Pauget
|
||||||
|
Licence : GPLv2
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import getpass, commands, os, sys, base64, syslog
|
||||||
|
from user_tests import getuser, isadm
|
||||||
|
|
||||||
|
from affich_tools import cprint, coul
|
||||||
|
import secrets_new as secrets
|
||||||
|
|
||||||
|
try:
|
||||||
|
ldap_password = secrets.get("ldap_password")
|
||||||
|
ldap_auth_dn = secrets.get("ldap_auth_dn")
|
||||||
|
except:
|
||||||
|
ldap_password = ''
|
||||||
|
ldap_auth_dn = ''
|
||||||
|
|
||||||
|
uri = 'ldap://ldap.adm.crans.org'
|
||||||
|
syslog.openlog('chgpass',syslog.LOG_PID,syslog.LOG_AUTH)
|
||||||
|
|
||||||
|
def decode64(chaine):
|
||||||
|
""" Décode une chaine de caratère utf8/64 et retourne un unicode """
|
||||||
|
try:
|
||||||
|
return base64.decodestring(chaine).decode('utf8','ignore')
|
||||||
|
except:
|
||||||
|
return chaine.decode('utf8','ignore')
|
||||||
|
|
||||||
|
def chgpass(dn, mdp = None) :
|
||||||
|
if mdp == None:
|
||||||
|
mdp = promptpass(dn)
|
||||||
|
|
||||||
|
# Changement mdp
|
||||||
|
if os.system("/usr/bin/ldappasswd -H '%s' -x -D '%s' -w '%s' '%s' -s '%s' > /dev/null" % (uri, ldap_auth_dn, ldap_password, dn, mdp) ):
|
||||||
|
cprint(u'Erreur lors du changement de mot de passe', 'rouge')
|
||||||
|
syslog.syslog("LDAP password changed for dn=%s" % dn)
|
||||||
|
else :
|
||||||
|
cprint(u'Changement effectué avec succès', u'vert')
|
||||||
|
|
||||||
|
def checkpass(mdp, dialog=False, longueur=8):
|
||||||
|
### Test du mdp
|
||||||
|
## 1 - Longueur
|
||||||
|
if len(mdp) < longueur :
|
||||||
|
return False, coul(u'Mot de passe trop court, il doit faire au moins %s caractères de long' % longueur, 'rouge', dialog=dialog)
|
||||||
|
|
||||||
|
## 2 - Empeche les mots de passe non ASCII
|
||||||
|
try:
|
||||||
|
mdp = mdp.encode('ascii')
|
||||||
|
except (UnicodeEncodeError, UnicodeDecodeError):
|
||||||
|
return False, coul(u'Les accents ou caractères bizarres ne sont pas autorisés (mais #!@*&%{}| le sont !)',
|
||||||
|
'rouge', dialog=dialog)
|
||||||
|
|
||||||
|
|
||||||
|
## 2bis - On évite une attaque de type injection de code shell
|
||||||
|
if "'" in mdp:
|
||||||
|
return False, coul(u'Les accents ou caractères bizarres ne sont pas autorisés (mais #!@*&%{}| le sont !)',
|
||||||
|
'rouge', dialog=dialog)
|
||||||
|
|
||||||
|
## 3 - assez de caractères de types différents ?
|
||||||
|
chiffres = 0
|
||||||
|
majuscules = 0
|
||||||
|
minuscules = 0
|
||||||
|
autres = 0
|
||||||
|
for c in mdp[:] :
|
||||||
|
if c.isdigit() :
|
||||||
|
# Un chiffre rapporte 1.5 point avec un maximum de 5
|
||||||
|
if chiffres < 4.5 : chiffres += 1.5
|
||||||
|
elif c.islower() :
|
||||||
|
if minuscules < 3 : minuscules += 1
|
||||||
|
elif c.isupper() :
|
||||||
|
if majuscules < 3 : majuscules += 1
|
||||||
|
else :
|
||||||
|
autres += 4
|
||||||
|
if (not majuscules and not minuscules):
|
||||||
|
return False, coul(u'Mot de passe sans majuscules et sans miniscules. Mélangez des deux ?', dialog=dialog)
|
||||||
|
if len(mdp) < 16 - minuscules - majuscules - chiffres - autres:
|
||||||
|
return False, coul(u'Mot de passe trop simple. Ajoutez des chiffres ou des caractères parmis #!@*&%{}| ou mélangez des majuscules/minuscules ?', 'rouge', dialog=dialog)
|
||||||
|
|
||||||
|
## 4 - Cracklib
|
||||||
|
p = subprocess.Popen(['/usr/sbin/cracklib-check'], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
(test, err) = p.communicate(input=mdp)
|
||||||
|
if test.split(':')[-1].lower().strip() != 'ok' :
|
||||||
|
commentaire = {
|
||||||
|
' it does not contain enough DIFFERENT characters': u'Il y a trop de caractères identiques.' ,
|
||||||
|
' it is based on a dictionary word': u'Le mot de passe est basé sur un mot du dictionnaire' ,
|
||||||
|
' it is too simplistic/systematic': u'Le mot de passe est trop simple/répétitif'
|
||||||
|
}.get(test.split(':')[-1],test.split(':')[-1])
|
||||||
|
return False, coul(commentaire.strip(), 'rouge', dialog=dialog)
|
||||||
|
|
||||||
|
return True, ""
|
||||||
|
|
||||||
|
def promptpass(dn):
|
||||||
|
cprint(u"""Le nouveau mot de passe doit comporter au minimum 6 caractères.
|
||||||
|
Il ne doit pas être basé sur un mot du dictionnaire.""", 'jaune')
|
||||||
|
print u"Il est conseillé d'utiliser une combinaison de minuscules, majuscules,\nde chiffres et d'au moins un caractère spécial."
|
||||||
|
print u"Le mot de passe tapé ne sera pas écrit à l'écran."
|
||||||
|
print u"Taper Ctrl-D pour abandonner"
|
||||||
|
|
||||||
|
try :
|
||||||
|
while 1 :
|
||||||
|
mdp = getpass.getpass('Nouveau mot de passe : ')
|
||||||
|
|
||||||
|
(good, txt) = checkpass(mdp)
|
||||||
|
if not good:
|
||||||
|
print txt
|
||||||
|
continue
|
||||||
|
|
||||||
|
### On redemande le mot de passe
|
||||||
|
mdp1 = getpass.getpass('Retaper mot de passe : ')
|
||||||
|
if mdp != mdp1 :
|
||||||
|
cprint(u'Les deux mots de passe entrés sont différents, réesayer', 'rouge')
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
|
||||||
|
except KeyboardInterrupt :
|
||||||
|
cprint(u'\nAbandon', 'rouge')
|
||||||
|
sys.exit(1)
|
||||||
|
except EOFError :
|
||||||
|
# Un Ctrl-D
|
||||||
|
cprint(u'\nAbandon', 'rouge')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
return mdp
|
||||||
|
|
||||||
|
if __name__ == '__main__' :
|
||||||
|
sys.stdout.write('\r \r') # Pour esthétique lors de l'utilisation par sudo
|
||||||
|
if len(sys.argv) == 1 :
|
||||||
|
# Changement de son mot de passe
|
||||||
|
login = getuser()
|
||||||
|
self_mode = True
|
||||||
|
|
||||||
|
elif '-h' in sys.argv or '--help' in sys.argv or len(sys.argv) != 2 :
|
||||||
|
print u"%s <login>" % sys.argv[0].split('/')[-1].split('.')[0]
|
||||||
|
print u"Changement du mot de passe du compte choisi."
|
||||||
|
sys.exit(255)
|
||||||
|
else :
|
||||||
|
# Changement du mot de passe par un câbleur ou une nounou
|
||||||
|
login = sys.argv[1]
|
||||||
|
self_mode = False
|
||||||
|
for c in login[:] :
|
||||||
|
if not c.isalnum() and not c=='-' :
|
||||||
|
cprint(u'Login incorrect', 'rouge')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if getuser() == login :
|
||||||
|
cprint(u'Utiliser passwd pour changer son propre mot de passe', 'rouge')
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
if self_mode :
|
||||||
|
s = commands.getoutput('sudo -u respbats ldap_whoami')
|
||||||
|
else :
|
||||||
|
s = commands.getoutput("/usr/bin/ldapsearch -D cn=readonly,dc=crans,dc=org -y/etc/ldap/readonly -x -LLL '(&(objectClass=posixAccount)(uid=%s))' dn nom prenom droits | grep -v MultiMachines" % login).strip()
|
||||||
|
if not s :
|
||||||
|
cprint(u'Login non trouvé dans la base LDAP', 'rouge')
|
||||||
|
sys.exit(3)
|
||||||
|
|
||||||
|
# Ca a l'air bon
|
||||||
|
if s.find('\n\n') != -1 :
|
||||||
|
# Plusieurs trouvé : pas normal
|
||||||
|
cprint(u'Erreur lors de la recherche du login : plusieurs occurences !', 'rouge')
|
||||||
|
sys.exit(4)
|
||||||
|
|
||||||
|
s = s.strip().split('\n')
|
||||||
|
dn = s[0].split()[1]
|
||||||
|
try :
|
||||||
|
if len(s) == 2 :
|
||||||
|
cprint(u"Changement du mot de passe du club %s "%decode64(' '.join(s[1].split()[1:])), 'vert')
|
||||||
|
else :
|
||||||
|
cprint(u"Changement du mot de passe de %s %s " % ( s[2].split()[1], s[1].split()[1] ), 'vert')
|
||||||
|
except :
|
||||||
|
cprint(u'Erreur lors de la recherche du login', 'rouge')
|
||||||
|
sys.exit(5)
|
||||||
|
|
||||||
|
if self_mode :
|
||||||
|
# Il faut vérifier l'ancien mot de passe
|
||||||
|
ldap_auth_dn = dn
|
||||||
|
ldap_password = getpass.getpass('Mot de passe actuel : ')
|
||||||
|
s = commands.getoutput("/usr/bin/ldapwhoami -H '%s' -x -D '%s' -w '%s'" % ( uri, ldap_auth_dn, ldap_password ) ).strip()
|
||||||
|
try :
|
||||||
|
resultat = s.split('\n')[0].split(':')[1].strip()
|
||||||
|
except :
|
||||||
|
cprint(u"Erreur lors de l'authentification", 'rouge')
|
||||||
|
sys.exit(7)
|
||||||
|
if resultat != dn :
|
||||||
|
cprint({ 'Invalid credentials (49)': u'Mot de passe invalide' }.get(resultat, resultat), 'rouge')
|
||||||
|
sys.exit(8)
|
||||||
|
|
||||||
|
elif len(s) > 3 and os.getuid()!=0 and not isadm():
|
||||||
|
# Adhérent avec droits et on est pas root
|
||||||
|
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, os.getlogin() )
|
||||||
|
|
||||||
|
# Envoi mail
|
||||||
|
import smtplib
|
||||||
|
conn = smtplib.SMTP('localhost')
|
||||||
|
conn.sendmail(From, To , mail )
|
||||||
|
conn.quit()
|
||||||
|
cprint(u'Impossible de changer le mot de passe de cet adhérent : compte privilégié', 'rouge')
|
||||||
|
sys.exit(6)
|
||||||
|
|
||||||
|
# Finalement !
|
||||||
|
chgpass(dn)
|
|
@ -1,224 +1,177 @@
|
||||||
#! /usr/bin/env python
|
#!/bin/bash /usr/scripts/python.sh
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Script de changement de mots de passe LDAP
|
Script de changement de mots de passe LDAP
|
||||||
|
|
||||||
Utilisation :
|
* Change le mot de passe de l'utilisateur donné en
|
||||||
* cas 1 : sans arguements par un utlisateur lambda :
|
argument.
|
||||||
changement de son propre mdp
|
|
||||||
* cas 2 : avec argument par un utilisateur ayant accès
|
|
||||||
total à la base LDAP (respbats) mais PAS ROOT :
|
|
||||||
changement du mdp de l'adhérent fourni en argument,
|
|
||||||
impossibilité de modifier le mdp d'un compte privilégié
|
|
||||||
* cas 3 : lancé par root : possibilité de modifier
|
|
||||||
tous les mots de passe LDAP
|
|
||||||
|
|
||||||
Copyright (C) Frédéric Pauget
|
|
||||||
Licence : GPLv2
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import subprocess
|
import gestion.config as config
|
||||||
import getpass, commands, os, sys, base64, syslog
|
import config.password
|
||||||
from user_tests import getuser, isadm
|
import getpass
|
||||||
|
import argparse
|
||||||
|
import cracklib
|
||||||
|
import gestion.affich_tools as affich_tools
|
||||||
|
import lc_ldap.shortcuts
|
||||||
|
import lc_ldap.attributs
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import smtplib
|
||||||
|
|
||||||
from affich_tools import cprint, coul
|
encoding = "UTF-8"
|
||||||
import secrets_new as secrets
|
ldap = lc_ldap.shortcuts.lc_ldap_admin()
|
||||||
|
current_user = os.getenv("SUDO_USER") or os.getenv("USER") or os.getenv("LOGNAME") or getpass.getuser()
|
||||||
|
|
||||||
try:
|
def check_password(password, no_cracklib=False):
|
||||||
ldap_password = secrets.get("ldap_password")
|
"""
|
||||||
ldap_auth_dn = secrets.get("ldap_auth_dn")
|
Teste le mot de passe.
|
||||||
except:
|
* Tests custom + cracklib (sauf si no_cracklib)
|
||||||
ldap_password = ''
|
"""
|
||||||
ldap_auth_dn = ''
|
|
||||||
|
|
||||||
uri = 'ldap://ldap.adm.crans.org'
|
|
||||||
syslog.openlog('chgpass',syslog.LOG_PID,syslog.LOG_AUTH)
|
|
||||||
|
|
||||||
def decode64(chaine):
|
|
||||||
""" Décode une chaine de caratère utf8/64 et retourne un unicode """
|
|
||||||
try:
|
try:
|
||||||
return base64.decodestring(chaine).decode('utf8','ignore')
|
password.decode('ascii')
|
||||||
except:
|
except UnicodeDecodeError:
|
||||||
return chaine.decode('utf8','ignore')
|
affich_tools.cprint(u'Le mot de passe ne doit contenir que des caractères ascii.', "rouge")
|
||||||
|
return False
|
||||||
|
|
||||||
def chgpass(dn, mdp = None) :
|
# Nounou mode
|
||||||
if mdp == None:
|
if no_cracklib:
|
||||||
mdp = promptpass(dn)
|
if len(password) >= config.password.root_min_len:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
problem = False
|
||||||
|
upp = 0
|
||||||
|
low = 0
|
||||||
|
oth = 0
|
||||||
|
cif = 0
|
||||||
|
|
||||||
# Changement mdp
|
# Comptage des caractères
|
||||||
if os.system("/usr/bin/ldappasswd -H '%s' -x -D '%s' -w '%s' '%s' -s '%s' > /dev/null" % (uri, ldap_auth_dn, ldap_password, dn, mdp) ):
|
for char in password:
|
||||||
cprint(u'Erreur lors du changement de mot de passe', 'rouge')
|
if char.isdigit():
|
||||||
syslog.syslog("LDAP password changed for dn=%s" % dn)
|
cif += 1
|
||||||
else :
|
elif char.isupper():
|
||||||
cprint(u'Changement effectué avec succès', u'vert')
|
upp += 1
|
||||||
|
elif char.islower():
|
||||||
|
low += 1
|
||||||
|
else:
|
||||||
|
oth += 1
|
||||||
|
|
||||||
def checkpass(mdp, dialog=False, longueur=8):
|
# Recherche de manque de caractères
|
||||||
### Test du mdp
|
if cif < config.password.min_cif:
|
||||||
## 1 - Longueur
|
affich_tools.cprint(u'Le mot de passe doit contenir plus de chiffres.', "rouge")
|
||||||
if len(mdp) < longueur :
|
problem = True
|
||||||
return False, coul(u'Mot de passe trop court, il doit faire au moins %s caractères de long' % longueur, 'rouge', dialog=dialog)
|
if upp < config.password.min_upp:
|
||||||
|
affich_tools.cprint(u'Le mot de passe doit contenir plus de majuscules.', "rouge")
|
||||||
|
problem = True
|
||||||
|
if low < config.password.min_low:
|
||||||
|
affich_tools.cprint(u'Le mot de passe doit contenir plus de minuscules.', "rouge")
|
||||||
|
problem = True
|
||||||
|
if oth < config.password.min_oth:
|
||||||
|
affich_tools.cprint(u'Le mot de passe doit contenir plus de caractères qui ne sont ni des chiffres, ni des majuscules, ni des minuscules.', "rouge")
|
||||||
|
problem = True
|
||||||
|
|
||||||
## 2 - Empeche les mots de passe non ASCII
|
# 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:
|
||||||
|
affich_tools.cprint(u'Le mot de passe devrait être plus long, ou plus difficile.')
|
||||||
|
problem = True
|
||||||
|
|
||||||
|
if not problem:
|
||||||
try:
|
try:
|
||||||
mdp = mdp.encode('ascii')
|
# Le mot vient-il du dico (à améliorer, on voudrait pouvoir préciser
|
||||||
except (UnicodeEncodeError, UnicodeDecodeError):
|
# la rigueur du test) ?
|
||||||
return False, coul(u'Les accents ou caractères bizarres ne sont pas autorisés (mais #!@*&%{}| le sont !)',
|
password = cracklib.VeryFascistCheck(password)
|
||||||
'rouge', dialog=dialog)
|
return True
|
||||||
|
except ValueError as e:
|
||||||
|
affich_tools.cprint(str(e).decode(), "rouge")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
## 2bis - On évite une attaque de type injection de code shell
|
def change_password(arguments):
|
||||||
if "'" in mdp:
|
"""
|
||||||
return False, coul(u'Les accents ou caractères bizarres ne sont pas autorisés (mais #!@*&%{}| le sont !)',
|
Change le mot de passe en fonction des arguments
|
||||||
'rouge', dialog=dialog)
|
"""
|
||||||
|
with ldap.search(u"(uid=%s)" % (arguments.user.decode(encoding),), mode="w")[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")
|
||||||
|
|
||||||
## 3 - assez de caractères de types différents ?
|
# Génération d'un mail
|
||||||
chiffres = 0
|
|
||||||
majuscules = 0
|
|
||||||
minuscules = 0
|
|
||||||
autres = 0
|
|
||||||
for c in mdp[:] :
|
|
||||||
if c.isdigit() :
|
|
||||||
# Un chiffre rapporte 1.5 point avec un maximum de 5
|
|
||||||
if chiffres < 4.5 : chiffres += 1.5
|
|
||||||
elif c.islower() :
|
|
||||||
if minuscules < 3 : minuscules += 1
|
|
||||||
elif c.isupper() :
|
|
||||||
if majuscules < 3 : majuscules += 1
|
|
||||||
else :
|
|
||||||
autres += 4
|
|
||||||
if (not majuscules and not minuscules):
|
|
||||||
return False, coul(u'Mot de passe sans majuscules et sans miniscules. Mélangez des deux ?', dialog=dialog)
|
|
||||||
if len(mdp) < 16 - minuscules - majuscules - chiffres - autres:
|
|
||||||
return False, coul(u'Mot de passe trop simple. Ajoutez des chiffres ou des caractères parmis #!@*&%{}| ou mélangez des majuscules/minuscules ?', 'rouge', dialog=dialog)
|
|
||||||
|
|
||||||
## 4 - Cracklib
|
|
||||||
p = subprocess.Popen(['/usr/sbin/cracklib-check'], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
||||||
(test, err) = p.communicate(input=mdp)
|
|
||||||
if test.split(':')[-1].lower().strip() != 'ok' :
|
|
||||||
commentaire = {
|
|
||||||
' it does not contain enough DIFFERENT characters': u'Il y a trop de caractères identiques.' ,
|
|
||||||
' it is based on a dictionary word': u'Le mot de passe est basé sur un mot du dictionnaire' ,
|
|
||||||
' it is too simplistic/systematic': u'Le mot de passe est trop simple/répétitif'
|
|
||||||
}.get(test.split(':')[-1],test.split(':')[-1])
|
|
||||||
return False, coul(commentaire.strip(), 'rouge', dialog=dialog)
|
|
||||||
|
|
||||||
return True, ""
|
|
||||||
|
|
||||||
def promptpass(dn):
|
|
||||||
cprint(u"""Le nouveau mot de passe doit comporter au minimum 6 caractères.
|
|
||||||
Il ne doit pas être basé sur un mot du dictionnaire.""", 'jaune')
|
|
||||||
print u"Il est conseillé d'utiliser une combinaison de minuscules, majuscules,\nde chiffres et d'au moins un caractère spécial."
|
|
||||||
print u"Le mot de passe tapé ne sera pas écrit à l'écran."
|
|
||||||
print u"Taper Ctrl-D pour abandonner"
|
|
||||||
|
|
||||||
try :
|
|
||||||
while 1 :
|
|
||||||
mdp = getpass.getpass('Nouveau mot de passe : ')
|
|
||||||
|
|
||||||
(good, txt) = checkpass(mdp)
|
|
||||||
if not good:
|
|
||||||
print txt
|
|
||||||
continue
|
|
||||||
|
|
||||||
### On redemande le mot de passe
|
|
||||||
mdp1 = getpass.getpass('Retaper mot de passe : ')
|
|
||||||
if mdp != mdp1 :
|
|
||||||
cprint(u'Les deux mots de passe entrés sont différents, réesayer', 'rouge')
|
|
||||||
continue
|
|
||||||
break
|
|
||||||
|
|
||||||
except KeyboardInterrupt :
|
|
||||||
cprint(u'\nAbandon', 'rouge')
|
|
||||||
sys.exit(1)
|
|
||||||
except EOFError :
|
|
||||||
# Un Ctrl-D
|
|
||||||
cprint(u'\nAbandon', 'rouge')
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
return mdp
|
|
||||||
|
|
||||||
if __name__ == '__main__' :
|
|
||||||
sys.stdout.write('\r \r') # Pour esthétique lors de l'utilisation par sudo
|
|
||||||
if len(sys.argv) == 1 :
|
|
||||||
# Changement de son mot de passe
|
|
||||||
login = getuser()
|
|
||||||
self_mode = True
|
|
||||||
|
|
||||||
elif '-h' in sys.argv or '--help' in sys.argv or len(sys.argv) != 2 :
|
|
||||||
print u"%s <login>" % sys.argv[0].split('/')[-1].split('.')[0]
|
|
||||||
print u"Changement du mot de passe du compte choisi."
|
|
||||||
sys.exit(255)
|
|
||||||
else :
|
|
||||||
# Changement du mot de passe par un câbleur ou une nounou
|
|
||||||
login = sys.argv[1]
|
|
||||||
self_mode = False
|
|
||||||
for c in login[:] :
|
|
||||||
if not c.isalnum() and not c=='-' :
|
|
||||||
cprint(u'Login incorrect', 'rouge')
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if getuser() == login :
|
|
||||||
cprint(u'Utiliser passwd pour changer son propre mot de passe', 'rouge')
|
|
||||||
sys.exit(2)
|
|
||||||
|
|
||||||
if self_mode :
|
|
||||||
s = commands.getoutput('sudo -u respbats ldap_whoami')
|
|
||||||
else :
|
|
||||||
s = commands.getoutput("/usr/bin/ldapsearch -D cn=readonly,dc=crans,dc=org -y/etc/ldap/readonly -x -LLL '(&(objectClass=posixAccount)(uid=%s))' dn nom prenom droits | grep -v MultiMachines" % login).strip()
|
|
||||||
if not s :
|
|
||||||
cprint(u'Login non trouvé dans la base LDAP', 'rouge')
|
|
||||||
sys.exit(3)
|
|
||||||
|
|
||||||
# Ca a l'air bon
|
|
||||||
if s.find('\n\n') != -1 :
|
|
||||||
# Plusieurs trouvé : pas normal
|
|
||||||
cprint(u'Erreur lors de la recherche du login : plusieurs occurences !', 'rouge')
|
|
||||||
sys.exit(4)
|
|
||||||
|
|
||||||
s = s.strip().split('\n')
|
|
||||||
dn = s[0].split()[1]
|
|
||||||
try :
|
|
||||||
if len(s) == 2 :
|
|
||||||
cprint(u"Changement du mot de passe du club %s "%decode64(' '.join(s[1].split()[1:])), 'vert')
|
|
||||||
else :
|
|
||||||
cprint(u"Changement du mot de passe de %s %s " % ( s[2].split()[1], s[1].split()[1] ), 'vert')
|
|
||||||
except :
|
|
||||||
cprint(u'Erreur lors de la recherche du login', 'rouge')
|
|
||||||
sys.exit(5)
|
|
||||||
|
|
||||||
if self_mode :
|
|
||||||
# Il faut vérifier l'ancien mot de passe
|
|
||||||
ldap_auth_dn = dn
|
|
||||||
ldap_password = getpass.getpass('Mot de passe actuel : ')
|
|
||||||
s = commands.getoutput("/usr/bin/ldapwhoami -H '%s' -x -D '%s' -w '%s'" % ( uri, ldap_auth_dn, ldap_password ) ).strip()
|
|
||||||
try :
|
|
||||||
resultat = s.split('\n')[0].split(':')[1].strip()
|
|
||||||
except :
|
|
||||||
cprint(u"Erreur lors de l'authentification", 'rouge')
|
|
||||||
sys.exit(7)
|
|
||||||
if resultat != dn :
|
|
||||||
cprint({ 'Invalid credentials (49)': u'Mot de passe invalide' }.get(resultat, resultat), 'rouge')
|
|
||||||
sys.exit(8)
|
|
||||||
|
|
||||||
elif len(s) > 3 and os.getuid()!=0 and not isadm():
|
|
||||||
# Adhérent avec droits et on est pas root
|
|
||||||
From = 'roots@crans.org'
|
From = 'roots@crans.org'
|
||||||
To = 'roots@crans.org'
|
To = 'roots@crans.org'
|
||||||
mail = """From: Root <%s>
|
mail = """From: Root <%s>
|
||||||
To: %s
|
To: %s
|
||||||
Subject: Tentative de changement de mot de passe !
|
Subject: Tentative de changement de mot de passe !
|
||||||
|
|
||||||
Tentative de changement du mot de passe de %s par %s.
|
Tentative de changement du mot de passe de %s par %s.
|
||||||
""" % ( From, To , login, os.getlogin() )
|
""" % (From, To , sys.argv[1], current_user)
|
||||||
|
|
||||||
# Envoi mail
|
# Envoi mail
|
||||||
import smtplib
|
|
||||||
conn = smtplib.SMTP('localhost')
|
conn = smtplib.SMTP('localhost')
|
||||||
conn.sendmail(From, To , mail )
|
conn.sendmail(From, To , mail )
|
||||||
conn.quit()
|
conn.quit()
|
||||||
cprint(u'Impossible de changer le mot de passe de cet adhérent : compte privilégié', 'rouge')
|
sys.exit(1)
|
||||||
sys.exit(6)
|
|
||||||
|
|
||||||
# Finalement !
|
# On peut modifier le MDP
|
||||||
chgpass(dn)
|
affich_tools.cprint("Changement du mot de passe de %s %s." % (user['prenom'][0], user['nom'][0]), "vert")
|
||||||
|
|
||||||
|
# Règles du jeu
|
||||||
|
if arguments.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 arguments.no_cracklib) + "Non" * (arguments.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 %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: ")
|
||||||
|
if check_password(mdp, arguments.no_cracklib):
|
||||||
|
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
|
||||||
|
|
||||||
|
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('user', 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(args)
|
||||||
|
|
32
gestion/config/password.py
Normal file
32
gestion/config/password.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Longueur minimale quand on appelle le script en étant nounou avec
|
||||||
|
# le bon argument
|
||||||
|
root_min_len = 4
|
||||||
|
|
||||||
|
# Longueur minimale standard
|
||||||
|
min_len = 9
|
||||||
|
|
||||||
|
# Nombre minimal de chiffres requis
|
||||||
|
min_cif = 1
|
||||||
|
|
||||||
|
# Nombre minimal de minuscules requises
|
||||||
|
min_low = 1
|
||||||
|
|
||||||
|
# Nombre minimal de majuscules requises
|
||||||
|
min_upp = 1
|
||||||
|
|
||||||
|
# Nombre minimal d'autres caractères requis
|
||||||
|
min_oth = 1
|
||||||
|
|
||||||
|
# Valeur des majuscules
|
||||||
|
upp_value = 1
|
||||||
|
|
||||||
|
# Valeur des minuscules
|
||||||
|
low_value = 1
|
||||||
|
|
||||||
|
# Valeur des chiffres
|
||||||
|
cif_value = 1
|
||||||
|
|
||||||
|
# Valeur des autres caractères
|
||||||
|
oth_value = 2
|
Loading…
Add table
Add a link
Reference in a new issue