chgpass.py 7.95 KB
Newer Older
bernat's avatar
bernat committed
1
#! /usr/bin/env python
2
# -*- coding: utf-8 -*-
bernat's avatar
bernat committed
3

4 5 6 7
"""
Script de changement de mots de passe LDAP

Utilisation :
8
  * cas 1 : sans arguements par un utlisateur lambda :
bernat's avatar
bernat committed
9
        changement de son propre mdp
10 11 12 13 14
  * 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
15 16
        tous les mots de passe LDAP

17
Copyright (C) Frédéric Pauget
18 19 20
Licence : GPLv2
"""

pauget's avatar
pauget committed
21
import getpass, commands, os, sys, base64, syslog
22
from user_tests import getuser, isadm
bernat's avatar
bernat committed
23

24
from affich_tools import cprint
pauget's avatar
pauget committed
25
try :
26
    sys.path.append('/usr/scripts/gestion/secrets')
pauget's avatar
pauget committed
27 28 29 30
    from secrets import ldap_password, ldap_auth_dn
except :
    ldap_password = ''
    ldap_auth_dn = ''
31

32
uri = 'ldap://ldap.adm.crans.org'
pauget's avatar
pauget committed
33
syslog.openlog('chgpass',syslog.LOG_PID,syslog.LOG_AUTH)
34 35

def decode64(chaine):
36
    """ Décode une chaine de caratère utf8/64 et retourne un unicode """
37 38 39 40
    try:
        return base64.decodestring(chaine).decode('utf8','ignore')
    except:
        return chaine.decode('utf8','ignore')
41

42 43 44 45 46 47 48 49 50 51 52 53 54
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 promptpass(dn):
55 56
    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')
57 58 59
    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"
60

bernat's avatar
bernat committed
61
    try :
bernat's avatar
bernat committed
62 63
        while 1 :
            mdp = getpass.getpass('Nouveau mot de passe : ')
64

bernat's avatar
bernat committed
65 66
            ### Test du mdp
            ## 1 - Longueur
67
            if len(mdp) < 6 :
glondu's avatar
glondu committed
68
                cprint(u'Mot de passe trop court', 'rouge')
bernat's avatar
bernat committed
69
                continue
70 71 72 73

            ## 2 - Empeche les mots de passe non ASCII
            try:
                mdp = mdp.encode('ascii')
74
            except (UnicodeEncodeError, UnicodeDecodeError):
75
                cprint(u'Les accents ou caractères bizarres ne sont pas autorisés (mais #!@*&%{}| le sont !)',
76 77
                       'rouge')
                continue
78

79
            ## 2bis - On évite une attaque de type injection de code shell
80
            if "'" in mdp:
81
                cprint(u'Les accents ou caractères bizarres ne sont pas autorisés (mais #!@*&%{}| le sont !)',
82
                'rouge')
83 84
                continue

85
            ## 3 - assez de caractères de types différents ?
bernat's avatar
bernat committed
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
            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 len(mdp) < 16 - minuscules - majuscules - chiffres - autres or \
               (not majuscules and not minuscules) :
glondu's avatar
glondu committed
102
                cprint(u'Mot de passe trop simple.', 'rouge')
bernat's avatar
bernat committed
103
                continue
104

105
            ## 4 - Cracklib
106
            test = commands.getoutput("echo '%s' | /usr/sbin/cracklib-check" % mdp)
107
            if test.split(':')[-1].lower() != ' ok' :
108
                commentaire = {
109 110 111
                ' 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'
bernat's avatar
bernat committed
112
                }.get(test.split(':')[-1],test.split(':')[-1])
glondu's avatar
glondu committed
113
                cprint(commentaire, 'rouge')
bernat's avatar
bernat committed
114
                continue
115

bernat's avatar
bernat committed
116 117 118
            ### On redemande le mot de passe
            mdp1 = getpass.getpass('Retaper mot de passe : ')
            if mdp != mdp1 :
119
                cprint(u'Les deux mots de passe entrés sont différents, réesayer', 'rouge')
bernat's avatar
bernat committed
120 121
                continue
            break
122

bernat's avatar
bernat committed
123
    except KeyboardInterrupt :
glondu's avatar
glondu committed
124
        cprint(u'\nAbandon', 'rouge')
125
        sys.exit(1)
126
    except EOFError :
bernat's avatar
bernat committed
127
        # Un Ctrl-D
glondu's avatar
glondu committed
128
        cprint(u'\nAbandon', 'rouge')
129 130 131
        sys.exit(1)

    return mdp
bernat's avatar
bernat committed
132 133

if __name__ == '__main__' :
134
    sys.stdout.write('\r                                 \r') # Pour esthétique lors de l'utilisation par sudo
135
    if len(sys.argv) == 1 :
bernat's avatar
bernat committed
136 137 138
        # Changement de son mot de passe
        login = getuser()
        self_mode = True
139

140
    elif '-h' in sys.argv or '--help' in sys.argv or len(sys.argv) != 2 :
141 142
        print u"%s <login>" % sys.argv[0].split('/')[-1].split('.')[0]
        print u"Changement du mot de passe du compte choisi."
bernat's avatar
bernat committed
143
        sys.exit(255)
144
    else :
145
        # Changement du mot de passe par un câbleur ou une nounou
bernat's avatar
bernat committed
146 147 148 149
        login = sys.argv[1]
        self_mode = False
        for c in login[:] :
            if not c.isalnum() and not c=='-' :
glondu's avatar
glondu committed
150
                cprint(u'Login incorrect', 'rouge')
bernat's avatar
bernat committed
151
                sys.exit(1)
152

bernat's avatar
bernat committed
153
        if getuser() == login :
glondu's avatar
glondu committed
154
            cprint(u'Utiliser passwd pour changer son propre mot de passe', 'rouge')
bernat's avatar
bernat committed
155
            sys.exit(2)
156

157
    if self_mode :
bernat's avatar
bernat committed
158
        s = commands.getoutput('sudo -u respbats ldap_whoami')
159
    else :
160
        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()
161
    if not s :
162
        cprint(u'Login non trouvé dans la base LDAP', 'rouge')
bernat's avatar
bernat committed
163
        sys.exit(3)
164

165 166
    # Ca a l'air bon
    if s.find('\n\n') != -1 :
167
        # Plusieurs trouvé : pas normal
glondu's avatar
glondu committed
168
        cprint(u'Erreur lors de la recherche du login : plusieurs occurences !', 'rouge')
bernat's avatar
bernat committed
169
        sys.exit(4)
170

171 172
    s = s.strip().split('\n')
    dn = s[0].split()[1]
173
    try :
174
        if len(s) == 2 :
glondu's avatar
glondu committed
175
            cprint(u"Changement du mot de passe du club %s "%decode64(' '.join(s[1].split()[1:])), 'vert')
bernat's avatar
bernat committed
176
        else :
glondu's avatar
glondu committed
177
            cprint(u"Changement du mot de passe de %s %s " % ( s[2].split()[1], s[1].split()[1] ), 'vert')
178
    except :
glondu's avatar
glondu committed
179
        cprint(u'Erreur lors de la recherche du login', 'rouge')
bernat's avatar
bernat committed
180
        sys.exit(5)
181

182
    if self_mode :
183
        # Il faut vérifier l'ancien mot de passe
bernat's avatar
bernat committed
184 185 186
        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()
187
        try :
188
            resultat = s.split('\n')[0].split(':')[1].strip()
bernat's avatar
bernat committed
189
        except :
glondu's avatar
glondu committed
190
            cprint(u"Erreur lors de l'authentification", 'rouge')
bernat's avatar
bernat committed
191 192
            sys.exit(7)
        if resultat != dn :
glondu's avatar
glondu committed
193
            cprint({ 'Invalid credentials (49)': u'Mot de passe invalide' }.get(resultat, resultat), 'rouge')
bernat's avatar
bernat committed
194
            sys.exit(8)
195

196
    elif len(s) > 3 and os.getuid()!=0 and not isadm():
197
        # Adhérent avec droits et on est pas root
bernat's avatar
bernat committed
198 199 200
        From = 'roots@crans.org'
        To = 'roots@crans.org'
        mail = """From: Root <%s>
201 202 203 204
To: %s
Subject: Tentative de changement de mot de passe !

Tentative de changement du mot de passe de %s par %s.
205
""" % ( From, To , login, os.getlogin() )
206

bernat's avatar
bernat committed
207 208 209 210 211
        # Envoi mail
        import smtplib
        conn = smtplib.SMTP('localhost')
        conn.sendmail(From, To , mail )
        conn.quit()
212
        cprint(u'Impossible de changer le mot de passe de cet adhérent : compte privilégié', 'rouge')
bernat's avatar
bernat committed
213
        sys.exit(6)
214 215 216

    # Finalement !
    chgpass(dn)