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
RewriteCond %{HTTPS} !=on
RewriteCond %{ENV:HTTPS} !=on
RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L,NE]
# required if using apache2: a2enmod authnz_ldap
AuthType Basic
AuthName "Login"
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
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
their own mail passwords via a web interface.
This is a Python CGI script that lets ldap users change
their own ldap passwords via a web interface.
This script is specifically tailored to work on hosts at
[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`.
If users with same UID and same oldpassword are found in other OUs,
those passwords are updated too.
## Installation
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
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:
`chcon -t httpd_sys_content_t ~ ~/passwd.cdb`
Configure LDAP settings for your LDAP server in index.py:
```
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
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
for the original source code.

View file

@ -2,12 +2,16 @@
import cgi, cgitb
import re
import sys, os
from subprocess import check_output, Popen, PIPE, STDOUT, CalledProcessError
from os.path import expanduser
import ldap
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):
for varname in formvars:
@ -18,6 +22,7 @@ def check_form(formvars, form):
return None
return True
def read_template_file(filename, **vars):
with open('tpl/' + filename, mode='r', encoding='utf-8') as f:
template = f.read()
@ -25,38 +30,30 @@ def read_template_file(filename, **vars):
template = template.replace('{$' + key + '}', vars[key])
return template
def check_oldpw(accountname, oldpass):
try:
dumpvuserargs = ['dumpvuser', accountname]
userdump = check_output(dumpvuserargs).strip().decode('utf-8')
m = re.search('Encrypted-Password: (\$([^\$]+)\$([^\$]+)\$([^\$\n]+))', userdump)
if None == m:
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:
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, oldpass) == True:
return True
except ldap.INVALID_CREDENTIALS:
conn.unbind()
return False
return False
def generate_headers():
return "Content-Type: text/html; charset=utf-8\n"
def main():
main_content = ''
form = cgi.FieldStorage()
http_host = os.environ.get('HTTP_HOST')
if 'submit' in form.keys():
formvars = ['accountname', 'oldpass', 'newpass', 'newpass2']
form_ok = check_form(formvars, form)
@ -68,16 +65,30 @@ def main():
newpass2 = form['newpass2'].value
if newpass == newpass2:
if check_oldpw(accountname, oldpass):
vpasswdargs = ['vpasswd', accountname]
p = Popen(vpasswdargs, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
p.stdin.write(newpass.encode('utf-8') + b'\n')
p.stdin.write(newpass2.encode('utf-8') + b'\n')
p.stdin.close()
if p.wait() == 0:
conn = ldap.initialize(ldap_proto+ldap_server)
conn.set_option(ldap.OPT_REFERRALS, 0)
conn.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
conn.simple_bind(ldap_bind_attr+'='+accountname+','+ldap_userdn, oldpass)
results = conn.search_s(ldap_basedn, ldap.SCOPE_SUBTREE, '('+ldap_bind_attr+'='+accountname+')', ['dn'])
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
main_content = read_template_file('success.tpl')
conn.unbind()
main_content = read_template_file('success.tpl', http_host=http_host)
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:
main_content = read_template_file('fail.tpl', message='User not found or wrong password entered.')
else:
@ -87,15 +98,16 @@ def main():
else:
main_content = read_template_file('fail.tpl', message='Invalid data type supplied.')
else:
# Submit button not pressed, show form
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
response = generate_headers() + "\n"
response += read_template_file('main.tpl', main_content=main_content)
sys.stdout.buffer.write(response.encode('utf-8'))
if __name__ == "__main__":
main()

View file

@ -1,5 +1,5 @@
<div class="error">
<h2>Oops, something went wrong</h2>
<h2>Oops, something went wrong</h2>
<p>Message: <span class="message">{$message}</span></p>
<a href="" class="back">Try again</a>
</div>

View file

@ -1,13 +1,14 @@
<form action="{$formaction}" method="post">
<div id="PasswordForm">
<p><label for="accountname">Mail Account Name:</label>
<input type="text" name="accountname" id="accountname" placeholder="Your account name, including internal prefix" required></p>
<p><label for="oldpass">Old Mail Password:</label>
<p><label for="accountname">Account Name:</label>
<input type="text" name="accountname" id="accountname" placeholder="Your account name, including internal prefix" required value="{$accountname}"></p>
<p><label for="oldpass">Old Password:</label>
<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>
<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>
<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>
</form>

View file

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

View file

@ -1 +1,2 @@
<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!)