more journalwatch patterns
[ansible.git] / roles / email / templates / changepw
1 #!/usr/bin/env python3
2
3 import os, cgi, MySQLdb, subprocess, time
4
5 # settings
6 DB_NAME = 'vmail'
7 DB_USER = 'vmail'
8 DB_PW   = '{{postfix.dovecot.mysql_password}}'
9
10 # DB stuff
11 db = MySQLdb.connect(user=DB_USER, passwd=DB_PW, db=DB_NAME, charset='utf8')
12
13 def db_execute(query, args):
14    cursor = db.cursor(MySQLdb.cursors.DictCursor)
15    if len(args) == 1 and isinstance(args[0], dict): args = args[0] # einzelnes dict weiterreichen
16    cursor.execute(query, args)
17    return cursor
18
19 def db_fetch_one_row(query, *args):
20    cursor = db_execute(query, args)
21    result = cursor.fetchone()
22    if cursor.fetchone() is not None:
23       raise Exception("Found more than one result, only one was expected")
24    cursor.close()
25    return result
26
27 def db_run(query, *args):
28    cursor = db_execute(query, args)
29    cursor.close()
30
31 def get_user(name):
32    return db_fetch_one_row('SELECT * FROM users WHERE username=%s', name)
33
34 def change_user_pw(name, hash):
35    db_run('UPDATE users SET password=%s WHERE username=%s', hash, name)
36
37 def change_user_active(name, active):
38    db_run('UPDATE users SET active=%s WHERE username=%s', int(active), name)
39
40 # interaction with dbadm pw
41 def compare_pw(hash, plain):
42    try:
43       subprocess.check_output(["doveadm", "pw", "-t", hash, "-p", plain])
44       return True
45    except subprocess.CalledProcessError:
46       return False
47
48 def gen_hash(plain):
49    return subprocess.check_output(["doveadm", "pw", "-s", "SHA512-CRYPT", "-r", str(64*1024), "-p", plain]).decode('utf-8').strip()
50
51 # the core action
52 def act(user, oldpw, newpw1, newpw2):
53    if user is None and oldpw is None and newpw1 is None and newpw2 is None:
54       return
55    if user is None or oldpw is None or newpw1 is None or newpw2 is None:
56       return "<b>Error</b>: You have to fill all the fields."
57    curdata = get_user(user)
58    if curdata is None:
59       return "<b>Error</b>: User not found."
60    if len(newpw1) < 8:
61       return "<b>Error</b>: Password must be at least 8 characters long."
62    if newpw1 != newpw2:
63       return "<b>Error</b>: New passwords do not match."
64    # slow down brute-force attacks
65    time.sleep(2.5)
66    # now go on
67    if not compare_pw(curdata['password'], oldpw):
68       return "<b>Error</b>: Old PW is wrong."
69    new_hash = gen_hash(newpw1)
70    change_user_pw(user, new_hash)
71    # potentially enable this user
72    if curdata['active'] < 0:
73       change_user_active(user, -curdata['active'])
74    return "Password successfully changed."
75
76 # print headers
77 print("Content-Type: text/html")
78 print()
79
80 # print document header
81 print("<!DOCTYPE html>")
82 print("<html><head>")
83 print("  <meta charset='utf-8'>")
84 print("  <title>User PW change</title>")
85 print("  <style type='text/css'> th { text-align: right; } </style>")
86 print("</head><body>")
87
88 # do stuff
89 form = cgi.FieldStorage()
90 msg = act(form.getfirst('user'), form.getfirst('oldpw'), form.getfirst('newpw1'), form.getfirst('newpw2'))
91 if msg is not None:
92    print("<p>{}</p>".format(msg))
93
94 # print form
95 print("  <form action='changepw' method='post'><table>")
96 print("    <tr><th>Username: </th><td><input type='text' name='user' placeholder='user@domain'></td>")
97 print("    <tr><th>Old Password: </th><td><input type='password' name='oldpw'></td>")
98 print("    <tr><th>New Password: </th><td><input type='password' name='newpw1'></td>")
99 print("    <tr><th>New Password (confirmation): </th><td><input type='password' name='newpw2'></td>")
100 print("    <tr><th></th><td><input type='submit' value='Change Password'></td></tr>")
101 print("  </table></form>")
102 print("</body></html>")