#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Univention Net Installer Daemon
#  UVMM handler
#
# Like what you see? Join us!
# https://www.univention.com/about-us/careers/vacancies/
#
# Copyright 2010-2024 Univention GmbH
#
# https://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <https://www.gnu.org/licenses/>.


import os
import signal
import socket
import sys
import threading
from argparse import ArgumentParser, Namespace
from multiprocessing import Queue

import ldap.filter

import univention.debug as ud
import univention.uldap


def createDaemon(options: Namespace) -> None:
    """daemonize via double fork, close all file handles, open /dev/null for stdin/out/err"""
    try:
        pid = os.fork()
    except OSError as e:
        print('Daemon Mode Error: %s' % (e.strerror,), file=sys.stderr)
        sys.exit(1)

    if pid:
        os._exit(0)  # _exit should be used for forking, pylint: disable-msg=W0212

    os.setsid()
    signal.signal(signal.SIGHUP, signal.SIG_IGN)
    try:
        pid = os.fork()
    except OSError as e:
        print('Daemon Mode Error: %s' % (e.strerror,), file=sys.stderr)
        sys.exit(1)
    if pid:
        with open(options.pidfile, 'w+') as fd:
            fd.write(str(pid))
        os._exit(0)  # _exit should be used for forking, pylint: disable-msg=W0212

    os.chdir("/")
    os.umask(0)

    try:
        maxfd = os.sysconf("SC_OPEN_MAX")
    except (AttributeError, ValueError):
        maxfd = 256  # default maximum
    os.closerange(0, maxfd)
    os.open("/dev/null", os.O_RDONLY)
    os.open("/dev/null", os.O_RDWR)
    os.open("/dev/null", os.O_RDWR)


def getLDAPConnection() -> univention.uldap.access:
    # TODO: check for an alternative binddn
    if os.path.exists('/etc/ldap.secret'):
        lo = univention.uldap.getAdminConnection()
    else:
        lo = univention.uldap.getMachineConnection()
    return lo


def processor(options: Namespace, data_queue: Queue) -> None:
    ud.init('/var/log/univention/net-installer-daemon.log', ud.FLUSH, ud.NO_FUNCTION)
    ud.set_level(ud.LDAP, options.debug)
    lo = getLDAPConnection()

    def process(data: str) -> None:
        search_filter = ldap.filter.filter_format('(&(objectClass=univentionHost)(cn=%s)(univentionServerReinstall=1))', (data,))
        result = lo.searchDn(filter=search_filter)
        if not result:
            ud.debug(ud.LDAP, ud.ERROR, 'The computer object %r was not found. Please remove the reinstall flag manually.' % (data,))
        else:
            for dn in result:
                lo.modify(dn, [('univentionServerReinstall', b'1', b'0')])
                ud.debug(ud.LDAP, ud.PROCESS, 'The computer object %r (%r) was successfully modified. ' % (data, dn))

    while True:
        data = data_queue.get()
        process(data.decode('UTF-8'))


def listener(options: Namespace, data_queue: Queue) -> None:
    size = 1024  # max hostname length
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(("0.0.0.0", options.port))  # noqa: S104
    s.listen(1)
    while True:
        (client, _) = s.accept()

        def reader(client: socket.socket) -> None:
            client.settimeout(10)
            try:
                data = client.recv(size)
            except Exception:
                return
            finally:
                client.close()
            if data:
                data = data.strip()
                data_queue.put(data)

        threading.Thread(target=reader, args=(client,)).start()


def parse_args() -> Namespace:
    parser = ArgumentParser()
    parser.add_argument(
        '-p', '--port',
        default="49173",
        type=int,
        help='Port for the daemon [%(default)s]',
    )
    parser.add_argument(
        '-P', '--pidfile',
        default="/run/univention-net-installer-daemon.pid",
        help='Path to the pid-file [%(default)s]',
    )
    parser.add_argument(
        '-D', '--daemon',
        action='store_true',
        help='Fork into background',
    )
    parser.add_argument(
        '-d', '--debug',
        default=1,
        type=int,
        help='Debug level (0 to 4)',
    )
    options = parser.parse_args()
    return options


def main() -> None:
    options = parse_args()
    if options.daemon:
        createDaemon(options)
    data_queue: Queue = Queue()
    subthread = threading.Thread(target=processor, args=(options, data_queue))
    subthread.daemon = True
    subthread.start()
    listener(options, data_queue)


if __name__ == '__main__':
    main()
