Updated fdc.in (implemented password distribution server).
authorZoltán Felleg <zoltan.felleg@userrendszerhaz.hu>
Fri, 2 Jun 2023 21:42:51 +0000 (23:42 +0200)
committerZoltán Felleg <zoltan.felleg@userrendszerhaz.hu>
Fri, 2 Jun 2023 21:42:51 +0000 (23:42 +0200)
18 files changed:
sources/fdc.in/c3d/firstboot/scripts/90_setupservices.sh
sources/fdc.in/c3d/mode.txt
sources/fdc.in/c3d/owner.txt
sources/fdc.in/c3d/postinstall/install-data/etc/httpd/conf.d/fdc.80.conf
sources/fdc.in/c3d/postinstall/install-data/etc/pds.conf [new file with mode: 0644]
sources/fdc.in/c3d/postinstall/install-data/etc/rsyncd.conf [new file with mode: 0644]
sources/fdc.in/c3d/postinstall/install-data/etc/systemd/system/pds.service [new file with mode: 0644]
sources/fdc.in/c3d/postinstall/install-data/usr/local/bin/mailpwdexpiration.py
sources/fdc.in/c3d/postinstall/install-data/usr/local/bin/pds [new file with mode: 0755]
sources/fdc.in/c3d/postinstall/install-data/var/www/htdocs.80/index.xhtml
sources/fdc.in/c3d/postinstall/install-data/var/www/htdocs.80/main.css
sources/fdc.in/c3d/postinstall/install-data/var/www/htdocs.80/wsgi/epilogue.xhtml [deleted file]
sources/fdc.in/c3d/postinstall/install-data/var/www/htdocs.80/wsgi/passwordchange.wsgi [deleted file]
sources/fdc.in/c3d/postinstall/install-data/var/www/htdocs.80/wsgi/prologue.xhtml [deleted file]
sources/fdc.in/c3d/postinstall/install-data/var/www/wsgi/passwordchange.wsgi [new file with mode: 0644]
sources/fdc.in/c3d/postinstall/scripts/10_setupservices.sh
sources/fdc.in/c3d/preinstall/scripts/01_rsyncpdsdb.sh [new file with mode: 0755]
sources/fdc.in/envvars

index d22ac19e638f67d19fa816afc8108d614b875777..d8706ee749bff2ba51e80d4f7e2d047b2b4e3765 100755 (executable)
@@ -5,8 +5,12 @@ systemctl enable httpd.service
 systemctl start httpd.service
 systemctl enable oddjobd.service
 systemctl start oddjobd.service
+systemctl enable pds.service
+systemctl start pds.service
 systemctl enable postfix.service
 systemctl start postfix.service
+systemctl enable rsyncd.service
+systemctl start rsyncd.service
 systemctl enable sssd.service
 systemctl start sssd.service
 
index 396fc877a8d2f16426995c995db9a567cc5971e6..4b91e7b39335fe7f30bf922afc60fadab43fa1c1 100644 (file)
@@ -4,6 +4,6 @@
 444 postinstall/install-data/etc/ssh/ssh_host_*_key.pub
 600 postinstall/install-data/etc/ssh/sshd_config.d/*.conf
 600 postinstall/install-data/etc/sssd/sssd.conf
-755 postinstall/install-data/usr/local/bin/*.py
-755 postinstall/install-data/usr/local/bin/*.sh
+755 postinstall/install-data/usr/local/bin/*
 755 postinstall/scripts/*.sh
+755 preinstall/scripts/*.sh
index 1bb5c6eced49745b457fb1a30abf8ab6edc6ce03..49158bf7b8f118b29b240064cbe6a3c48f5f11e5 100644 (file)
@@ -1,2 +1 @@
 # owner file (relative to /c3d)
-root:ssh_keys postinstall/install-data/etc/ssh/ssh_host_*_key
index 53f7397b155cbeb166d9101f17c90dce544766c2..f5b2622a4f1cd7f82ee5fb1426e59083784f76b2 100644 (file)
@@ -1,5 +1,3 @@
-WSGISocketPrefix run/wsgi
-
 <VirtualHost *:80>
     ServerName fdc.in.useribm.hu
     ServerAdmin siteadmin@useribm.hu
@@ -10,7 +8,7 @@ WSGISocketPrefix run/wsgi
         Require all granted
     </Directory>
 
-    WSGIDaemonProcess was.80 processes=1 threads=1 maximum-requests=10000 shutdown-timeout=5
-    WSGIProcessGroup was.80
-    WSGIScriptAlias /passwordchange /var/www/htdocs.80/wsgi/passwordchange.wsgi
+    WSGIDaemonProcess fdc.80
+    WSGIProcessGroup fdc.80
+    WSGIScriptAlias /passwordchange /var/www/wsgi/passwordchange.wsgi
 </VirtualHost>
diff --git a/sources/fdc.in/c3d/postinstall/install-data/etc/pds.conf b/sources/fdc.in/c3d/postinstall/install-data/etc/pds.conf
new file mode 100644 (file)
index 0000000..2c257a2
--- /dev/null
@@ -0,0 +1,23 @@
+[server]
+address =
+port = 1420
+database = /var/lib/pds/db
+
+[targets]
+count = 2
+
+[target.1]
+format = htdigest
+htdigest realm = webdrive
+method = rsync
+rsync host = store.in.useribm.hu
+rsync module = httpdauth
+rsync file = webdrive.digest
+
+[target.2]
+format = htdigest
+htdigest realm = ceges
+method = rsync
+rsync host = store.in.useribm.hu
+rsync module = httpdauth
+rsync file = ceges.digest
diff --git a/sources/fdc.in/c3d/postinstall/install-data/etc/rsyncd.conf b/sources/fdc.in/c3d/postinstall/install-data/etc/rsyncd.conf
new file mode 100644 (file)
index 0000000..a1546d2
--- /dev/null
@@ -0,0 +1,9 @@
+transfer logging = yes
+use chroot = no
+uid = root
+gid = root
+
+[pdsdb]
+    path = /var/lib/pds
+    read only = true
+    hosts allow = 10.228.0.0/16, 2001:1aa1:000a:0424::/64
diff --git a/sources/fdc.in/c3d/postinstall/install-data/etc/systemd/system/pds.service b/sources/fdc.in/c3d/postinstall/install-data/etc/systemd/system/pds.service
new file mode 100644 (file)
index 0000000..4a5c50e
--- /dev/null
@@ -0,0 +1,11 @@
+[Unit]
+Description=Password Distribution Server
+After=NetworkManager.service
+Wants=NetworkManager.service
+
+[Service]
+Type=simple
+ExecStart=/usr/local/bin/pds
+
+[Install]
+WantedBy=multi-user.target
index b8396ac41b1e85a919162cbfe9c15a89c14b474d..e5193ac20f3920f7f7bb9fb8563e8aa5a8c28988 100755 (executable)
@@ -1,14 +1,17 @@
 #!/usr/bin/env python
 
 
+import os
 import ldap
 import time
-import email
+import pickle
 import smtplib
+import email.policy
+import email.message
 
 
-LDAP_URI='ldaps://fds.useribm.hu'
-USERS_BASE='ou=people,dc=user,dc=hu'
+LDAP_URI = 'ldaps://fds.useribm.hu'
+USERS_BASE = 'ou=people,dc=user,dc=hu'
 
 
 PWD_MAX_AGE = 365 * 24 * 60 * 60
@@ -19,38 +22,45 @@ PWD_WARNING_SUBJECT = 'Jelszavad {} nap múlva lejár'
 PWD_WARNING_MESSAGE = '''Tisztelt {}!
 
 A jelszavad a céges címtárban (például a fájlszerver eléréshez) le fog járni {} nap múlva.
-Kérlek adj meg új jelszót (a régi megadása után) a https://www.useribm.hu/passwordchange oldalon
+Kérlek adj meg új jelszót (a régi megadása után) a https://www.useribm.hu/passwordchange oldalon.
 
-Üdvözlettel, Címtár'''
+Üdvözlettel,
+USER Címtár'''
 
 PWD_ERROR_SUBJECT = 'Jelszavad lejárt'
 PWD_ERROR_MESSAGE = '''Tisztelt {}!
 
 A jelszavad a céges címtárban (például a fájlszerver eléréshez) lejárt.
-Kérlek adj meg új jelszót (a régi megadása után) a https://www.useribm.hu/passwordchange oldalon
+Kérlek adj meg új jelszót (a régi megadása után) a https://www.useribm.hu/passwordchange oldalon.
 
-Üdvözlettel, Címtár'''
+Üdvözlettel,
+USER Címtár'''
 
 
 def send_mail(mail_type, expiration_days, uid, email_address):
-    msg = email.message.EmailMessage()
+    msg = email.message.EmailMessage(email.policy.SMTP)
     if mail_type == 'WARNING':
-        msg.set_content(PWD_WARNING_MESSAGE.format(uid, expiration_days))
-        msg['Subject'] = PWD_WARNING_SUBJECT.format(expiration_days)
+        msg.set_content(PWD_WARNING_MESSAGE.format(uid,
+                                                   expiration_days))
+        msg.add_header('Subject',
+                       PWD_WARNING_SUBJECT.format(expiration_days))
     elif mail_type == 'ERROR':
         msg.set_content(PWD_ERROR_MESSAGE.format(uid))
-        msg['Subject'] = PWD_ERROR_SUBJECT
+        msg.add_header('Subject', PWD_ERROR_SUBJECT)
     elif mail_type == 'CRITICAL':
         msg.set_content(PWD_ERROR_MESSAGE.format(uid))
-        msg['Subject'] = PWD_ERROR_SUBJECT
+        msg.add_header('Subject', PWD_ERROR_SUBJECT)
     else:
         msg.set_content('Invalid mail_type value: {}'.format(mail_type))
-        msg['Subject'] = 'Invalid mail_type value: {}'.format(mail_type)
+        msg.add_header('Subject',
+                       'Invalid mail_type value: {}'.format(mail_type))
         email_address = 'zoltan.felleg@userrendszerhaz.hu'
-    msg['From'] = 'dirsrv@useribm.hu'
-    msg['To'] = email_address
+    msg.add_header('From', '"USER Címtár" <dirsrv@useribm.hu>')
+    msg.add_header('To', email_address)
+    msg.add_header('Date', time.asctime(time.gmtime()))
     srv = smtplib.SMTP()
-    srv.connect('mx.in.useribm.hu')
+    srv.connect('mx.in.useribm.hu', 587)
+    srv.ehlo()
     srv.send_message(msg)
     srv.quit()
 
@@ -66,18 +76,28 @@ if __name__ == '__main__':
                                              'pwdUpdateTime'])
     (search_result_type, search_result_data) = ldap_object.result()
 
-    current_timestamp = time.mktime(time.gmtime())
+    current_ts = time.mktime(time.gmtime())
+    valid_users = []
     for item in search_result_data:
         (dn, values) = item
         if ('mail' in values) and ('pwdUpdateTime' in values):
             uid = values['uid'][0].decode('utf-8')
             email_address = values['mail'][0].decode('utf-8')
             pwd_update_time = values['pwdUpdateTime'][0].decode('utf-8')
-            pwd_update_timestamp = time.mktime(time.strptime(pwd_update_time, '%Y%m%d%H%M%S%z'))
-            expiration_seconds = PWD_MAX_AGE - (current_timestamp - pwd_update_timestamp)
+            pwd_update_ts = time.mktime(time.strptime(pwd_update_time,
+                                                      '%Y%m%d%H%M%S%z'))
+            expiration_seconds = PWD_MAX_AGE - (current_ts - pwd_update_ts)
             expiration_days = expiration_seconds / (24 * 60 * 60)
-            rounded_expiration_days = int(expiration_days + 0.5)
+            if expiration_days < 0:
+                rounded_expiration_days = int(expiration_days - 0.5)
+            else:
+                rounded_expiration_days = int(expiration_days + 0.5)
+                valid_users.append(uid)
+            #print(uid, rounded_expiration_days)
+            #continue
             if rounded_expiration_days in PWD_EXPIRED_DAYS:
                 send_mail('ERROR', rounded_expiration_days, uid, email_address)
             elif rounded_expiration_days in PWD_EXPIRING_DAYS:
                 send_mail('WARNING', rounded_expiration_days, uid, email_address)
+            #elif uid == 'zfelleg':
+            #    send_mail('CRITICAL', rounded_expiration_days, uid, email_address)
diff --git a/sources/fdc.in/c3d/postinstall/install-data/usr/local/bin/pds b/sources/fdc.in/c3d/postinstall/install-data/usr/local/bin/pds
new file mode 100755 (executable)
index 0000000..9f8d3d1
--- /dev/null
@@ -0,0 +1,369 @@
+#!/usr/bin/env python
+
+
+import os
+import sys
+import base64
+import pickle
+import select
+import signal
+import socket
+import hashlib
+import subprocess
+import configparser
+
+
+class ossh:
+
+    """Operating System Signal Handler.
+
+    Class attributes:
+        None
+
+    Instance attributes:
+        _cfg (private)
+            The configuration.
+
+    Methods:
+        __init__
+            Create an ossh.
+        _shutdown_server (private)
+            Shut down the server.
+
+    """
+
+    def __init__(self):
+        """Create an ossh.
+
+        Parameters:
+            None
+
+        Return value:
+            An ossh.
+
+        Exceptions:
+            all/any
+
+        """
+        self._cfg = configparser.ConfigParser()
+        self._cfg.read('/etc/pds.conf')
+
+        server_cfg = self._cfg['server']
+        self._server_address = server_cfg.get('address', 'localhost')
+        self._server_port = server_cfg.getint('port')
+
+        signal.signal(signal.SIGINT, self._shutdown_server)
+        signal.signal(signal.SIGTERM, self._shutdown_server)
+
+    def _shutdown_server(self, *args):
+        """Shut down the server.
+
+        Parameters:
+            None
+
+        Return value:
+            None
+
+        Exceptions:
+            all/any
+
+        """
+        request_dict = {'request': 'shutdown'}
+        request_bytes = pickle.dumps(request_dict)
+        co = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        co.connect((self._server_address, self._server_port))
+        co.sendall(request_bytes)
+        co.shutdown(socket.SHUT_RDWR)
+        co.close()
+
+class pds():
+
+    """Password Distribution Server.
+
+    Class attributes:
+        None
+
+    Instance attributes:
+        _cfg (private)
+            The configuration.
+        _co (private)
+            The connection object (socket).
+        _db (private)
+            The password database.
+        _db_file_name (private)
+            The (fully qualified) file name of the password database.
+
+    Methods:
+        __init__
+            Create a pds.
+        _shutdown (private)
+            Shut down the pds.
+        run
+            Run the pds.
+
+    """
+
+    def __init__(self):
+        """Create a pds.
+
+        Parameters:
+            None
+
+        Return value:
+            a pds.
+
+        """
+        self._cfg = configparser.ConfigParser()
+        self._cfg.read('/etc/pds.conf')
+
+        self._db_file_name = self._cfg['server']['database']
+        db_file = open(self._db_file_name, 'rb')
+        self._db = pickle.load(db_file)
+        db_file.close()
+
+        self._co = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self._co.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+
+        self._signal_handler = ossh()
+
+    def _change_password(self, request_dict):
+        """Change the password of a user.
+
+        Parameters:
+            request_dict
+                The request.
+
+        Return value:
+            The response.
+
+        """
+        login_name = request_dict['login name']
+        password_b16 = request_dict['password']
+        password_b64 = base64.b16decode(password_b16)
+        password_bytes = base64.b64decode(password_b64)
+        password_string = password_bytes.decode('utf-8')
+        password_dict = {}
+        targets_cfg = self._cfg['targets']
+        target_count = targets_cfg.getint('count')
+        for target_serial in range(target_count):
+            target_section = 'target.{}'.format(target_serial + 1)
+            target_cfg = self._cfg[target_section]
+            target_format = target_cfg.get('format')
+            if target_format != 'htdigest':
+                reason = 'Invalid format {}.'.format(target_format)
+                return {'success': False, 'reason': reason}
+            target_realm = target_cfg.get('htdigest realm')
+            source_string = '{}:{}:{}'.format(login_name,
+                                              target_realm,
+                                              password_string)
+            source_bytes = source_string.encode('utf-8')
+            htdigest = hashlib.md5(source_bytes).hexdigest()
+            password_dict[(target_format, target_realm)] = htdigest
+            #md5_digest = hashlib.md5(password_bytes).hexdigest()
+            #sha1_digest = hashlib.sha1(password_bytes).hexdigest()
+            #sha224_digest = hashlib.sha224(password_bytes).hexdigest()
+            #sha256_digest = hashlib.sha256(password_bytes).hexdigest()
+            #sha384_digest = hashlib.sha384(password_bytes).hexdigest()
+            #sha512_digest = hashlib.sha512(password_bytes).hexdigest()
+            #result = subprocess.run(['mkpasswd',
+            #                         '--method=md5crypt',
+            #                         password_bytes],
+            #                        capture_output=True)
+            #salted_md5_digest = result.stdout.decode().strip()
+            #result = subprocess.run(['mkpasswd',
+            #                         '--method=sha256crypt',
+            #                         password_bytes],
+            #                        capture_output=True)
+            #salted_sha256_digest = result.stdout.decode().strip()
+            #result = subprocess.run(['mkpasswd',
+            #                         '--method=sha512crypt',
+            #                         password_bytes],
+            #                        capture_output=True)
+            #salted_sha512_digest = result.stdout.decode().strip()
+            #result = subprocess.run(['mkpasswd',
+            #                         '--method=yescrypt',
+            #                         password_bytes],
+            #                        capture_output=True)
+            #yescrypt_digest = result.stdout.decode().strip()
+
+        self._db[login_name] = password_dict
+        success = self._save_db()
+        if not success:
+            reason = 'Could not save password database.'
+            return {'success': False, 'reason': reason}
+        success = self._notifytargets()
+        if not success:
+            reason = 'Could not notify all targets.'
+            return {'success': False, 'reason': reason}
+        return {'success': True}
+
+    def _expire_password(self, request_dict):
+        """Expire the password of a user.
+
+        Parameters:
+            request_dict
+                The request.
+
+        Return value:
+            None
+
+        """
+        login_name = request_dict['login name']
+        if login_name in self._db:
+            del self._db[login_name]
+        success = self._save_db()
+        if not success:
+            reason = 'Could not save password database.'
+            return {'success': False, 'reason': reason}
+        success = self._notifytargets()
+        if not success:
+            reason = 'Could not notify all targets.'
+            return {'success': False, 'reason': reason}
+        return {'success': True}
+
+    def _notifytargets(self):
+        """Notify targets of the changed/expired passwords.
+
+        Parameters:
+            None
+
+        Return value:
+            None
+
+        """
+        success = True
+        targets_cfg = self._cfg['targets']
+        target_count = targets_cfg.getint('count')
+        for target_serial in range(target_count):
+            target_section = 'target.{}'.format(target_serial + 1)
+            target_cfg = self._cfg[target_section]
+            target_format = target_cfg.get('format')
+            target_method = target_cfg.get('method')
+            if (target_format, target_method) != ('htdigest', 'rsync'):
+                success = False
+                continue
+            target_realm = target_cfg.get('htdigest realm')
+            db_key = (target_format, target_realm)
+            target_host = target_cfg.get('rsync host')
+            target_module = target_cfg.get('rsync module')
+            target_file = target_cfg.get('rsync file')
+            local_file_name = os.path.join('/tmp', target_file)
+            local_file = open(local_file_name, 'wt')
+            digest_lines = []
+            for login_name in sorted(self._db.keys()):
+                if db_key not in self._db[login_name]:
+                    success = False
+                    continue
+                htdigest = self._db[login_name][db_key]
+                digest_line = '{}:{}:{}{}'.format(login_name,
+                                                  target_realm,
+                                                  htdigest,
+                                                  os.linesep)
+                digest_lines.append(digest_line)
+            local_file.writelines(digest_lines)
+            local_file.close()
+            rsync_target = '{}::{}'.format(target_host, target_module)
+            result = subprocess.run(['rsync',
+                                     '--archive',
+                                     local_file_name,
+                                     rsync_target])
+            if result.returncode != 0:
+                success = False
+        return success
+
+    def _respond(self, client_co, response_dict):
+        """Respond to a client.
+
+        Parameters:
+            client_co
+                The connection object (socket) of the client.
+            response_dict
+                The response.
+
+        Return value:
+            None
+
+        """
+        response_bytes = pickle.dumps(response_dict)
+        client_co.sendall(response_bytes)
+
+    def _save_db(self):
+        """Save the password database.
+
+        Parameters:
+            None
+
+        Return value:
+            success
+
+        """
+        db_file = open(self._db_file_name, 'wb')
+        pickle.dump(self._db, db_file)
+        db_file.close()
+        return True
+
+    def _shutdown(self):
+        """Shut down the pds.
+
+        Parameters:
+            None
+
+        Return value:
+            None
+
+        """
+        self._co.shutdown(socket.SHUT_RDWR)
+        self._co.close()
+        sys.exit()
+
+    def run(self):
+        """Run the pds.
+
+        Parameters:
+            None
+
+        Return value:
+            None
+
+        """
+        server_cfg = self._cfg['server']
+        server_address = server_cfg.get('address')
+        server_port = server_cfg.getint('port')
+        self._co.bind((server_address, server_port))
+        self._co.listen(5)
+        while True:
+            (rfr, rfw, rfx) = select.select([self._co], [], [], 900)
+            if rfr == []:
+                print('no client')
+                sys.stdout.flush()
+                continue
+
+            (client_co, client_address) = self._co.accept()
+            print('Connected by {}'.format(client_address))
+            sys.stdout.flush()
+            request_bytes = client_co.recv(4096)
+            if not request_bytes:
+                print('Disconnected by {}'.format(client_address))
+                sys.stdout.flush()
+                client_co.close()
+                continue
+            request_dict = pickle.loads(request_bytes)
+            print('Received: {}'.format(request_dict))
+            sys.stdout.flush()
+            request = request_dict['request']
+            if request == 'change password':
+                response_dict = self._change_password(request_dict)
+            elif request == 'expire password':
+                response_dict = self._expire_password(request_dict)
+            elif request == 'shutdown':
+                client_co.shutdown(socket.SHUT_RDWR)
+                client_co.close()
+                self._shutdown()
+            self._respond(client_co, response_dict)
+            client_co.shutdown(socket.SHUT_RDWR)
+            client_co.close()
+
+
+if __name__ == '__main__':
+    server = pds()
+    server.run()
index fde0847623aad8cebcdd4f6177a39a51da70b9e1..ac9eef77dfdc816234440feecf64f0809fb801e0 100644 (file)
@@ -8,16 +8,16 @@
   </head>
   <body>
     <form action="passwordchange" method="post">
-      <p>Login Name:</p>
-      <p><input name="login_name" type="text" /></p>
-      <p>Old Password:</p>
-      <p><input name="old_password" type="password" /></p>
+      <p>Felhaszálónév:</p>
+      <p><input name="login name" type="text" required="true" /></p>
+      <p>Régi jelszó:</p>
+      <p><input name="old password" type="password" required="true" /></p>
       <hr />
-      <p>New Password:</p>
-      <p><input name="new_password" type="password" /></p>
-      <p>New Password Again:</p>
-      <p><input name="new_password_again" type="password" /></p>
-      <p><input type="submit" value="Change" /></p>
+      <p>Új jelszó:</p>
+      <p><input name="new password" type="password" required="true" /></p>
+      <p>Új jelszó mégegyszer:</p>
+      <p><input name="new password again" type="password" required="true" /></p>
+      <p><input type="submit" value="Változtatás" /></p>
     </form>
   </body>
 </html>
index ec079d74678a3c6fbbc7d0655781b5bff7e270a3..e98b637debe23275af054eac7977d4c925be8af5 100644 (file)
@@ -2,3 +2,7 @@ h1, p {
   /* Center horizontally*/
   text-align: center;
 }
+
+hr {
+  width: 25%;
+}
diff --git a/sources/fdc.in/c3d/postinstall/install-data/var/www/htdocs.80/wsgi/epilogue.xhtml b/sources/fdc.in/c3d/postinstall/install-data/var/www/htdocs.80/wsgi/epilogue.xhtml
deleted file mode 100644 (file)
index b605728..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-  </body>
-</html>
diff --git a/sources/fdc.in/c3d/postinstall/install-data/var/www/htdocs.80/wsgi/passwordchange.wsgi b/sources/fdc.in/c3d/postinstall/install-data/var/www/htdocs.80/wsgi/passwordchange.wsgi
deleted file mode 100644 (file)
index d8f1047..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-import os
-import cgi
-import sys
-import ldap
-import traceback
-
-
-LDAP_URI='ldaps://fds.useribm.hu'
-USERS_BASE='ou=people,dc=user,dc=hu'
-
-
-def password_change_app(environ, start_response):
-
-    error_occured = False
-
-    field_storage = cgi.FieldStorage(fp=environ['wsgi.input'],
-                                     environ=environ,
-                                     keep_blank_values=True)
-
-    uid = field_storage['login_name'].value
-    old_password = field_storage['old_password'].value
-    new_password = field_storage['new_password'].value
-    new_password_again = field_storage['new_password_again'].value
-
-    if new_password != new_password_again:
-        formatted_traceback = ['NEW_PASSWORD_MISMATCH']
-        error_occured = True
-
-    if not error_occured:
-        try:
-            ldap_object = ldap.initialize(LDAP_URI)
-            #ldap_object.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
-            #ldap_object.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
-        except:
-            (exc_type, exc_value, exc_traceback) = sys.exc_info()
-            formatted_traceback = traceback.format_exception(exc_type,
-                                                             exc_value,
-                                                             exc_traceback)
-            error_occured = True
-
-    if not error_occured:
-        user_dn=','.join(['uid={}'.format(uid), USERS_BASE])
-        try:
-            bind_id = ldap_object.simple_bind(user_dn, old_password)
-            (bind_result_type, bind_result_data) = ldap_object.result()
-        except:
-            (exc_type, exc_value, exc_traceback) = sys.exc_info()
-            formatted_traceback = traceback.format_exception(exc_type,
-                                                             exc_value,
-                                                             exc_traceback)
-            error_occured = True
-
-    if not error_occured:
-        try:
-            password_change_id = ldap_object.passwd(user_dn,
-                                                    old_password,
-                                                    new_password)
-            (pwd_result_type, pwd_result_data) = ldap_object.result()
-        except:
-            (exc_type, exc_value, exc_traceback) = sys.exc_info()
-            formatted_traceback = traceback.format_exception(exc_type,
-                                                             exc_value,
-                                                             exc_traceback)
-            error_occured = True
-
-    prologue_file = open('/var/www/htdocs.80/wsgi/prologue.xhtml', 'r')
-    response_prologue = prologue_file.read()
-    prologue_file.close()
-    response_epilogue = '</body></html>'
-    if not error_occured:
-        success_text = '<h1>Woohoo, you have successfully changed your password!</h1>'
-        response_texts = [response_prologue,
-                          success_text,
-                          response_epilogue]
-    else:
-        response_texts = [response_prologue, '<pre>']
-        response_texts.extend(formatted_traceback)
-        response_texts.append('</pre>')
-        response_texts.append('<p><button onclick="history.back()">Go Back and Try Again</button></p>')
-        response_texts.append(response_epilogue)
-    response_body = bytes(os.linesep.join(response_texts), 'utf-8')
-                                
-    # HTTP response code and message
-    status = '200 OK'
-
-    # HTTP headers expected by the client
-    # They must be wrapped as a list of tupled pairs:
-    # [(Header name, Header value)].
-    response_headers = [
-        ('Content-Type', 'text/html'),
-        ('Content-Length', str(len(response_body)))
-    ]
-
-    # Send them to the server using the supplied function
-    start_response(status, response_headers)
-
-    # Return the response body. Notice it is wrapped
-    # in a list although it could be any iterable.
-    return [response_body]
-
-application = password_change_app
diff --git a/sources/fdc.in/c3d/postinstall/install-data/var/www/htdocs.80/wsgi/prologue.xhtml b/sources/fdc.in/c3d/postinstall/install-data/var/www/htdocs.80/wsgi/prologue.xhtml
deleted file mode 100644 (file)
index b9f6005..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html>
-<html xmlns="http://www.w3.org/1999/xhtml">
-  <head>
-    <title>Password Change</title>
-    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
-    <link rel="stylesheet" href="main.css" type="text/css" />
-  </head>
-  <body>
diff --git a/sources/fdc.in/c3d/postinstall/install-data/var/www/wsgi/passwordchange.wsgi b/sources/fdc.in/c3d/postinstall/install-data/var/www/wsgi/passwordchange.wsgi
new file mode 100644 (file)
index 0000000..3162a52
--- /dev/null
@@ -0,0 +1,156 @@
+import os
+import sys
+import ldap
+import base64
+import pickle
+import socket
+import traceback
+import configparser
+import urllib.parse
+
+
+LDAP_URI='ldaps://fds.useribm.hu'
+USERS_BASE='ou=people,dc=user,dc=hu'
+
+html_prologue = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>Password Change</title>
+    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+    <link rel="stylesheet" href="main.css" type="text/css" />
+  </head>
+  <body>"""
+
+html_epilogue = """  </body>
+</html>"""
+
+error_prologue = """    <p>Az alábbi hiba miatt nem sikerült megváltoztatni a jelszavadat.
+       Ha a hibaüzenet <b>utolsó sora</b> mond neked valamit,
+       próbáld meg újra.</p>
+    <p>Ha nem, keresd meg zfelleget.</p>
+    <p>(Akinek persze az lesz az első kérdése, hogy mi volt a
+        hibaüzenet utolsó sora, úgyhogy azt vagy mentsd el/írd le,
+       vagy hagyd nyitva ezt a böngészőablakot.)</p>
+    <pre>"""
+
+error_epilogue = """    </pre>
+    <p><button onclick="history.back()">Újrapróbálkozás</button></p>"""
+
+success_text = """    <h1>Jelszavadat sikeresen megváltoztattad,
+        az új jelszó azonnal érvényes.</h1>"""
+
+
+def password_change_app(environ, start_response):
+
+    error_occured = False
+
+    form_data_bytes = environ['wsgi.input'].read()
+    form_data_text = form_data_bytes.decode()
+    form_data_dict = urllib.parse.parse_qs(form_data_text)
+    #formatted_traceback = ['{}'.format(form_data_dict)]
+    #error_occured = True
+    uid = form_data_dict['login name'][0]
+    old_password = form_data_dict['old password'][0]
+    new_password = form_data_dict['new password'][0]
+    new_password_again = form_data_dict['new password again'][0]
+
+    if new_password != new_password_again:
+        formatted_traceback = ['The (new) passwords do not match.']
+        error_occured = True
+
+    if not error_occured:
+        try:
+            ldap_object = ldap.initialize(LDAP_URI)
+            #ldap_object.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
+            #ldap_object.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
+        except:
+            (exc_type, exc_value, exc_traceback) = sys.exc_info()
+            formatted_traceback = traceback.format_exception(exc_type,
+                                                             exc_value,
+                                                             exc_traceback)
+            error_occured = True
+
+    if not error_occured:
+        user_dn=','.join(['uid={}'.format(uid), USERS_BASE])
+        try:
+            bind_id = ldap_object.simple_bind(user_dn, old_password)
+            (bind_result_type, bind_result_data) = ldap_object.result()
+        except:
+            (exc_type, exc_value, exc_traceback) = sys.exc_info()
+            formatted_traceback = traceback.format_exception(exc_type,
+                                                             exc_value,
+                                                             exc_traceback)
+            error_occured = True
+
+    if not error_occured:
+        try:
+            password_change_id = ldap_object.passwd(user_dn,
+                                                    old_password,
+                                                    new_password)
+            (pwd_result_type, pwd_result_data) = ldap_object.result()
+        except:
+            (exc_type, exc_value, exc_traceback) = sys.exc_info()
+            formatted_traceback = traceback.format_exception(exc_type,
+                                                             exc_value,
+                                                             exc_traceback)
+            error_occured = True
+
+    if not error_occured:
+        try:
+            password_bytes = bytes(new_password, 'utf-8')
+            password_b64 = base64.b64encode(password_bytes)
+            password_b16 = base64.b16encode(password_b64)
+            cfg = configparser.ConfigParser()
+            cfg.read('/etc/pds.conf')
+            server_cfg = cfg['server']
+            server_address = server_cfg.get('address', 'localhost')
+            server_port = server_cfg.getint('port')
+            request_dict = {'request': 'change password',
+                            'login name': uid,
+                            'password': password_b16}
+            request_bytes = pickle.dumps(request_dict)
+            co = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            co.settimeout(3)
+            co.connect((server_address, server_port))
+            co.sendall(request_bytes)
+            response_bytes = co.recv(4096)
+            co.shutdown(socket.SHUT_RDWR)
+            co.close()
+        except:
+            (exc_type, exc_value, exc_traceback) = sys.exc_info()
+            formatted_traceback = traceback.format_exception(exc_type,
+                                                             exc_value,
+                                                             exc_traceback)
+            error_occured = True
+
+    if not error_occured:
+        response_texts = [html_prologue,
+                          success_text,
+                          html_epilogue]
+    else:
+        response_texts = [html_prologue, error_prologue]
+        response_texts.extend(formatted_traceback)
+        response_texts.append(error_epilogue)
+        response_texts.append(html_epilogue)
+    response_body = bytes(os.linesep.join(response_texts), 'utf-8')
+
+    # HTTP response code and message
+    status = '200 OK'
+
+    # HTTP headers expected by the client
+    # They must be wrapped as a list of tupled pairs:
+    # [(Header name, Header value)].
+    response_headers = [
+        ('Content-Type', 'text/html'),
+        ('Content-Length', str(len(response_body)))
+    ]
+
+    # Send them to the server using the supplied function
+    start_response(status, response_headers)
+
+    # Return the response body. Notice it is wrapped
+    # in a list although it could be any iterable.
+    return [response_body]
+
+application = password_change_app
index 0c374f30a3c14c1a01ad2acab6690d0b439239b5..8c41959adbf485ba8fa7d9b23660df45262835f4 100755 (executable)
@@ -3,7 +3,9 @@
 
 systemctl disable httpd.service
 systemctl disable oddjobd.service
+systemctl disable pds.service
 systemctl disable postfix.service
+systemctl disable rsyncd.service
 systemctl disable sssd.service
 
 systemctl disable NetworkManager-wait-online.service
diff --git a/sources/fdc.in/c3d/preinstall/scripts/01_rsyncpdsdb.sh b/sources/fdc.in/c3d/preinstall/scripts/01_rsyncpdsdb.sh
new file mode 100755 (executable)
index 0000000..40ad8a1
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+
+. $1
+
+
+/usr/bin/rsync \
+    --archive \
+    --delete-after \
+    --info=STATS \
+    --mkpath \
+    fdc.in.useribm.hu::pdsdb \
+    $CONTAINER_BUILDROOT/c3d/postinstall/install-data/var/lib/pds
index 77209a7b5a87293b93a4626f7f607fd58677c427..54292e6f1b41ef64f90388a2026495d284a52ea1 100644 (file)
@@ -3,6 +3,7 @@ DISTRIBUTION_VERSION=38
 SPEC_PACKAGES="authselect \
                cronie \
                httpd \
+               mkpasswd \
                oddjob-mkhomedir \
                openldap-clients \
                openssh-clients \