Compare commits

...

20 commits

Author SHA1 Message Date
Christoph Loesch 49ea64c75d
Update .htaccess 2021-04-12 03:08:00 +02:00
Christoph Loesch 094b515ef1
Update README.md 2021-04-12 03:07:37 +02:00
Christoph Loesch a48f4f7434
Update README.md 2021-04-12 03:05:06 +02:00
Christoph Loesch ed2b5cc275
Update index.py 2021-04-12 03:04:33 +02:00
Christoph Loesch f8bd9411f4
Update README.md 2021-04-12 02:45:40 +02:00
Christoph Loesch 49b356c855
Update README.md 2021-04-12 02:45:00 +02:00
Christoph Loesch 9754430012
Update README.md 2021-04-12 02:42:42 +02:00
Christoph Loesch 6848278380
Update README.md 2021-04-12 02:42:24 +02:00
Christoph Loesch 103516548e
Update README.md 2021-04-12 02:40:52 +02:00
Christoph Loesch 104da4fadb
Update README.md 2021-04-12 02:40:17 +02:00
Christoph Loesch e99539b53f
Update main.tpl 2021-04-12 02:36:13 +02:00
Christoph Loesch 560607330c
Update form.tpl 2021-04-12 02:35:35 +02:00
Christoph Loesch 9fbb6c21b6
Update success.tpl 2021-04-12 02:35:16 +02:00
Christoph Loesch a61d81cc36
complete rewrite to ldap 2021-04-12 02:34:38 +02:00
Christoph Loesch 9f559c7c33
Update main.tpl 2021-04-11 05:27:03 +02:00
Christoph Loesch 0db426f137
Update form.tpl 2021-04-11 05:26:21 +02:00
Christoph Loesch 27750c4cbd
Update fail.tpl 2021-04-11 05:25:57 +02:00
Christoph Loesch e123051d74
add basic-auth via ldap 2021-04-11 05:24:52 +02:00
Christoph Loesch 3f2b4554d7
Update README.md 2021-04-11 05:20:27 +02:00
Christoph Loesch 42279bbcfa
rewrite index.py to work with ldap 2021-04-11 05:16:42 +02:00
7 changed files with 95 additions and 58 deletions

View file

@ -1,7 +1,19 @@
RewriteEngine on # required if using apache2: a2enmod authnz_ldap
RewriteCond %{HTTPS} !=on AuthType Basic
RewriteCond %{ENV:HTTPS} !=on AuthName "Login"
RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L,NE] AuthBasicProvider ldap
#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,22 +1,34 @@
# VMailMgr chpw CGI # LDAP chpw CGI
This is a Python CGI script that lets virtual `VMailMgr`/`qmail` users change This is a Python CGI script that lets ldap users change
their own mail passwords via a web interface. their own ldap passwords via a web interface.
This script is specifically tailored to work on hosts at If users with same UID and same oldpassword are found in other OUs,
[uberspace.de](https://uberspace.de). But it may also work on other qmail-based those passwords are updated too.
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. location is reachable via HTTPS. If used with Apache2, this module is required: `a2enmod authnz_ldap`
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: Configure LDAP settings for your LDAP server in index.py:
`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 refined version of a script originally developed by Dirk Boye. This is a majorly for ldap-support rewritten 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,12 +2,16 @@
import cgi, cgitb import cgi, cgitb
import re import re
import sys, os import sys, os
from subprocess import check_output, Popen, PIPE, STDOUT, CalledProcessError import ldap
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:
@ -18,6 +22,7 @@ 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()
@ -25,38 +30,30 @@ 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:
dumpvuserargs = ['dumpvuser', accountname] conn = ldap.initialize(ldap_proto+ldap_server)
userdump = check_output(dumpvuserargs).strip().decode('utf-8') conn.set_option(ldap.OPT_REFERRALS, 0)
m = re.search('Encrypted-Password: (\$([^\$]+)\$([^\$]+)\$([^\$\n]+))', userdump) conn.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
if None == m: if conn.simple_bind(ldap_bind_attr+'='+accountname+','+ldap_userdn, oldpass) == True:
return False
oldhash = m.group(1)
hashtype = m.group(2)
salt = m.group(3)
except CalledProcessError:
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 True
except ldap.INVALID_CREDENTIALS:
conn.unbind()
return False
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)
@ -68,16 +65,30 @@ 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):
vpasswdargs = ['vpasswd', accountname] conn = ldap.initialize(ldap_proto+ldap_server)
p = Popen(vpasswdargs, stdin=PIPE, stdout=PIPE, stderr=STDOUT) conn.set_option(ldap.OPT_REFERRALS, 0)
p.stdin.write(newpass.encode('utf-8') + b'\n') conn.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
p.stdin.write(newpass2.encode('utf-8') + b'\n') conn.simple_bind(ldap_bind_attr+'='+accountname+','+ldap_userdn, oldpass)
p.stdin.close() results = conn.search_s(ldap_basedn, ldap.SCOPE_SUBTREE, '('+ldap_bind_attr+'='+accountname+')', ['dn'])
if p.wait() == 0: conn.unbind()
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
main_content = read_template_file('success.tpl') conn.unbind()
main_content = read_template_file('success.tpl', http_host=http_host)
else: else:
main_content = read_template_file('fail.tpl', message=cgi.escape(p.stdout.read())) conn.unbind()
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='User not found or wrong password entered.')
else: else:
@ -87,15 +98,16 @@ def main():
else: else:
main_content = read_template_file('fail.tpl', message='Invalid data type supplied.') main_content = read_template_file('fail.tpl', message='Invalid data type supplied.')
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"])
form = read_template_file('form.tpl', formaction=formaction) #accountname = os.environ.get('REMOTE_USER')
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>Oops, something went wrong</h2>
<p>Message: <span class="message">{$message}</span></p> <p>Message: <span class="message">{$message}</span></p>
<a href="" class="back">Try again</a> <a href="" class="back">Try again</a>
</div> </div>

View file

@ -1,13 +1,14 @@
<form action="{$formaction}" method="post"> <form action="{$formaction}" method="post">
<div id="PasswordForm"> <div id="PasswordForm">
<p><label for="accountname">Mail Account Name:</label> <p><label for="accountname">Account Name:</label>
<input type="text" name="accountname" id="accountname" placeholder="Your account name, including internal prefix" required></p> <input type="text" name="accountname" id="accountname" placeholder="Your account name, including internal prefix" required value="{$accountname}"></p>
<p><label for="oldpass">Old Mail Password:</label> <p><label for="oldpass">Old Password:</label>
<input type="password" name="oldpass" id="oldpass" required></p> <input type="password" name="oldpass" id="oldpass" required></p>
<p><label for="newpass">New Mail Password:</label> <p><label for="newpass">New Password:</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 Mail Password:</label> <p><label for="newpass2">Repeat New Password:</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="Change Password"></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>Change Mailbox Password</title> <title>LDAP Change Password</title>
<meta charset="utf-8"> <meta charset="utf-8">
<style> <style>
html { html {
@ -49,7 +49,6 @@
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;
@ -75,7 +74,7 @@
</style> </style>
<body> <body>
<div id="Main"> <div id="Main">
<h1>Change Mailbox Password</h1> <h1>Change LDAP Password</h1>
{$main_content} {$main_content}
</div> </div>
</body> </body>

View file

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