Compare commits

..

3 commits

Author SHA1 Message Date
Daniel A. Maierhofer 9b74fcabee Remove Message: 2020-04-27 00:35:08 +02:00
Daniel A. Maierhofer 7fafc8f150 Merge branch 'master' of https://github.com/phoerious/vmailmgr-chpw-cgi into german 2020-04-27 00:33:03 +02:00
Daniel A. Maierhofer a27349e736 Add german translation 2017-06-18 13:59:31 +02:00
7 changed files with 66 additions and 103 deletions

View file

@ -1,19 +1,7 @@
# required if using apache2: a2enmod authnz_ldap RewriteEngine on
AuthType Basic RewriteCond %{HTTPS} !=on
AuthName "Login" RewriteCond %{ENV:HTTPS} !=on
AuthBasicProvider ldap RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L,NE]
#AuthLDAPBindAuthoritative off
AuthLDAPBindDN UID=bind,OU=Users,DC=ldap,DC=freiesnetz,DC=at
AuthLDAPBindPassword ldapbindpassword
AuthLDAPURL ldap://localhost/OU=Users,DC=ldap,DC=freiesnetz,DC=at?uid
LDAPReferrals off
Require valid-user
# disabled because of client error: ERR_TOO_MANY_REDIRECTS
#RewriteEngine on
#RewriteCond %{HTTPS} !=on
#RewriteCond %{ENV:HTTPS} !=on
#RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L,NE]
Options +ExecCGI Options +ExecCGI
AddHandler cgi-script .py AddHandler cgi-script .py

View file

@ -1,34 +1,22 @@
# LDAP chpw CGI # VMailMgr chpw CGI
This is a Python CGI script that lets ldap users change This is a Python CGI script that lets virtual `VMailMgr`/`qmail` users change
their own ldap passwords via a web interface. their own mail passwords via a web interface.
If users with same UID and same oldpassword are found in other OUs, This script is specifically tailored to work on hosts at
those passwords are updated too. [uberspace.de](https://uberspace.de). But it may also work on other qmail-based
systems as long as virtual mail users are managed via `VMailMgr`.
## Installation ## Installation
To install the script, simply extract all the repository contents into a folder To install the script, simply extract all the repository contents into a folder
under your document root. No paths need to be configured. Only make sure that the under your document root. No paths need to be configured. Only make sure that the
location is reachable via HTTPS. If used with Apache2, this module is required: `a2enmod authnz_ldap` location is reachable via HTTPS.
Configure LDAP settings for your LDAP server in index.py: In case you are using a U7 uberspace, you have to add a SELinux permission to allow apache to access both your home directory and the VMailMgr user database:
``` `chcon -t httpd_sys_content_t ~ ~/passwd.cdb`
ldap_proto = 'ldap://'
ldap_server = 'localhost'
ldap_basedn = 'dc=ldap,dc=freiesnetz,dc=at'
ldap_userdn = 'ou=Users' +','+ ldap_basedn
ldap_bind_attr = 'uid'
```
Configure LDAP settings for your LDAP server in .htaccess:
```
AuthLDAPBindDN UID=bind,OU=Users,DC=ldap,DC=freiesnetz,DC=at
AuthLDAPBindPassword ldapbindpassword
AuthLDAPURL ldap://localhost/OU=Users,DC=ldap,DC=freiesnetz,DC=at?uid
```
## Acknowledgements ## Acknowledgements
This is a majorly for ldap-support rewritten version of a script originally developed by Dirk Boye. This is a majorly refined version of a script originally developed by Dirk Boye.
See [dirkboye/mailpw_change](https://github.com/dirkboye/mailpw_change) at GitHub See [dirkboye/mailpw_change](https://github.com/dirkboye/mailpw_change) at GitHub
for the original source code. for the original source code.

View file

@ -2,16 +2,12 @@
import cgi, cgitb import cgi, cgitb
import re import re
import sys, os import sys, os
import ldap from subprocess import check_output, Popen, PIPE, STDOUT, CalledProcessError
from os.path import expanduser
ldap_proto = 'ldap://'
ldap_server = 'localhost'
ldap_basedn = 'dc=ldap,dc=freiesnetz,dc=at'
ldap_userdn = 'ou=Users' +','+ ldap_basedn
ldap_bind_attr = 'uid'
cgitb.enable(display=0, logdir='logs/')
cgitb.enable()
home_dir = expanduser("~")
os.environ['HOME'] = home_dir
def check_form(formvars, form): def check_form(formvars, form):
for varname in formvars: for varname in formvars:
@ -22,7 +18,6 @@ def check_form(formvars, form):
return None return None
return True return True
def read_template_file(filename, **vars): def read_template_file(filename, **vars):
with open('tpl/' + filename, mode='r', encoding='utf-8') as f: with open('tpl/' + filename, mode='r', encoding='utf-8') as f:
template = f.read() template = f.read()
@ -30,30 +25,38 @@ def read_template_file(filename, **vars):
template = template.replace('{$' + key + '}', vars[key]) template = template.replace('{$' + key + '}', vars[key])
return template return template
def check_oldpw(accountname, oldpass): def check_oldpw(accountname, oldpass):
try: try:
conn = ldap.initialize(ldap_proto+ldap_server) dumpvuserargs = ['dumpvuser', accountname]
conn.set_option(ldap.OPT_REFERRALS, 0) userdump = check_output(dumpvuserargs).strip().decode('utf-8')
conn.set_option(ldap.OPT_PROTOCOL_VERSION, 3) m = re.search('Encrypted-Password: (\$([^\$]+)\$([^\$]+)\$([^\$\n]+))', userdump)
if conn.simple_bind(ldap_bind_attr+'='+accountname+','+ldap_userdn, oldpass) == True: if None == m:
return True return False
except ldap.INVALID_CREDENTIALS: oldhash = m.group(1)
conn.unbind() hashtype = m.group(2)
salt = m.group(3)
except CalledProcessError:
return False return False
opensslargs = ['openssl', 'passwd', '-' + hashtype, '-salt', salt, '-stdin']
p = Popen(opensslargs, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
p.stdin.write(oldpass.encode('utf-8') + b'\n')
p.stdin.close()
if p.wait() == 0:
newhash = p.stdout.readline().strip().decode('utf-8');
if newhash == oldhash:
return True
return False return False
def generate_headers(): def generate_headers():
return "Content-Type: text/html; charset=utf-8\n" return "Content-Type: text/html; charset=utf-8\n"
def main(): def main():
main_content = '' main_content = ''
form = cgi.FieldStorage() form = cgi.FieldStorage()
http_host = os.environ.get('HTTP_HOST')
if 'submit' in form.keys(): if 'submit' in form.keys():
formvars = ['accountname', 'oldpass', 'newpass', 'newpass2'] formvars = ['accountname', 'oldpass', 'newpass', 'newpass2']
form_ok = check_form(formvars, form) form_ok = check_form(formvars, form)
@ -65,49 +68,34 @@ def main():
newpass2 = form['newpass2'].value newpass2 = form['newpass2'].value
if newpass == newpass2: if newpass == newpass2:
if check_oldpw(accountname, oldpass): if check_oldpw(accountname, oldpass):
conn = ldap.initialize(ldap_proto+ldap_server) vpasswdargs = ['vpasswd', accountname]
conn.set_option(ldap.OPT_REFERRALS, 0) p = Popen(vpasswdargs, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
conn.set_option(ldap.OPT_PROTOCOL_VERSION, 3) p.stdin.write(newpass.encode('utf-8') + b'\n')
conn.simple_bind(ldap_bind_attr+'='+accountname+','+ldap_userdn, oldpass) p.stdin.write(newpass2.encode('utf-8') + b'\n')
results = conn.search_s(ldap_basedn, ldap.SCOPE_SUBTREE, '('+ldap_bind_attr+'='+accountname+')', ['dn']) p.stdin.close()
conn.unbind() if p.wait() == 0:
for dn in results:
conn = ldap.initialize(ldap_proto+ldap_server)
conn.set_option(ldap.OPT_REFERRALS, 0)
conn.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
# do a synchronous ldap bind
conn.simple_bind_s(dn[0], oldpass)
conn.passwd_s(dn[0], oldpass, newpass)
conn.unbind_s()
conn = ldap.initialize(ldap_proto+ldap_server)
conn.set_option(ldap.OPT_REFERRALS, 0)
conn.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
if conn.simple_bind(ldap_bind_attr+'='+accountname+','+ldap_userdn, newpass) == True:
# We did it # We did it
conn.unbind() main_content = read_template_file('success.tpl')
main_content = read_template_file('success.tpl', http_host=http_host)
else: else:
conn.unbind() main_content = read_template_file('fail.tpl', message=cgi.escape(p.stdout.read()))
main_content = read_template_file('fail.tpl', message=cgi.escape(ldap.LDAPError))
else: else:
main_content = read_template_file('fail.tpl', message='User not found or wrong password entered.') main_content = read_template_file('fail.tpl', message='Benutzer nicht gefunden oder Passwort falsch.')
else: else:
main_content = read_template_file('fail.tpl', message='Passwords do not match.') main_content = read_template_file('fail.tpl', message='Passwörter stimmen nicht überein.')
elif form_ok == False: elif form_ok == False:
main_content = read_template_file('fail.tpl', message='All fields are required.') main_content = read_template_file('fail.tpl', message='Alle Felder müssen ausgefüllt werden.')
else: else:
main_content = read_template_file('fail.tpl', message='Invalid data type supplied.') main_content = read_template_file('fail.tpl', message='Ungültiger Datentyp.')
else: else:
# Submit button not pressed, show form
formaction = cgi.escape("https://" + os.environ["HTTP_HOST"] + os.environ["REQUEST_URI"]) formaction = cgi.escape("https://" + os.environ["HTTP_HOST"] + os.environ["REQUEST_URI"])
#accountname = os.environ.get('REMOTE_USER') form = read_template_file('form.tpl', formaction=formaction)
accountname = os.environ.get('AUTHENTICATE_UID')
form = read_template_file('form.tpl', formaction=formaction, accountname=accountname, http_host=http_host)
main_content = form main_content = form
response = generate_headers() + "\n" response = generate_headers() + "\n"
response += read_template_file('main.tpl', main_content=main_content) response += read_template_file('main.tpl', main_content=main_content)
sys.stdout.buffer.write(response.encode('utf-8')) sys.stdout.buffer.write(response.encode('utf-8'))
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View file

@ -1,5 +1,5 @@
<div class="error"> <div class="error">
<h2>Oops, something went wrong</h2> <h2>Euje, da ist etwas schief gelaufen…</h2>
<p>Message: <span class="message">{$message}</span></p> <p><span class="message">{$message}</span></p>
<a href="" class="back">Try again</a> <a href="" class="back">Nochmal probieren</a>
</div> </div>

View file

@ -1,14 +1,13 @@
<form action="{$formaction}" method="post"> <form action="{$formaction}" method="post">
<div id="PasswordForm"> <div id="PasswordForm">
<p><label for="accountname">Account Name:</label> <p><label for="accountname">E-Mail-Adresse:</label>
<input type="text" name="accountname" id="accountname" placeholder="Your account name, including internal prefix" required value="{$accountname}"></p> <input type="text" name="accountname" id="accountname" required></p>
<p><label for="oldpass">Old Password:</label> <p><label for="oldpass">Altes Passwort:</label>
<input type="password" name="oldpass" id="oldpass" required></p> <input type="password" name="oldpass" id="oldpass" required></p>
<p><label for="newpass">New Password:</label> <p><label for="newpass">Neues Passwort:</label>
<input type="password" name="newpass" id="newpass" required></p> <input type="password" name="newpass" id="newpass" required></p>
<p><label for="newpass2">Repeat New Password:</label> <p><label for="newpass2">Neues Passwort nochmals:</label>
<input type="password" name="newpass2" id="newpass2" required></p> <input type="password" name="newpass2" id="newpass2" required></p>
<p><input type="submit" name="submit" value="Change Password"></p> <p><input type="submit" name="submit" value="Passwort ändern"></p>
<p><a href="https://log:out@{$http_host}/">Logout</a> (after logout click cancel and close browser/tab!)
</div> </div>
</form> </form>

View file

@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>LDAP Change Password</title> <title>Mailbox Passwort</title>
<meta charset="utf-8"> <meta charset="utf-8">
<style> <style>
html { html {
@ -49,6 +49,7 @@
a:hover, a:focus { a:hover, a:focus {
text-decoration: none; text-decoration: none;
} }
#Main { #Main {
margin: 3rem auto; margin: 3rem auto;
padding: 1rem; padding: 1rem;
@ -74,7 +75,7 @@
</style> </style>
<body> <body>
<div id="Main"> <div id="Main">
<h1>Change LDAP Password</h1> <h1>Mailbox Passwort ändern</h1>
{$main_content} {$main_content}
</div> </div>
</body> </body>

View file

@ -1,2 +1 @@
<p class="success">Your password was changed successfully.</p> <p class="success">Passwort erfolgreich geändert.</p>
<p><a href="https://log:out@{$http_host}/">Logout</a> (after logout click cancel and close browser/tab!)