
Ce script appel ldappasswd avec os.system en passant les mots de passes ldap et mot de passe de l'utilisateur sur le ligne de commande, donc visible sur zamok en faisant juste un ps uaxf. Ça serait bien de changer ça…
223 lines
8.2 KiB
Python
Executable file
223 lines
8.2 KiB
Python
Executable file
#! /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 ?')
|
|
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, 'rouge')
|
|
|
|
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)
|