# Copyright (c) 2015 Michel Oosterhof <michel@oosterhof.net>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. The names of the author(s) may not be used to endorse or promote
#    products derived from this software without specific prior written
#    permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.


from __future__ import annotations
from binascii import crc32
import configparser
import importlib.resources
from random import randint, seed
import sys
from typing import Any

from twisted.python import log

from cowrie.core.config import CowrieConfig
from cowrie import data


class Passwd:
    """
    This class contains code to handle the users and their properties in
    /etc/passwd. Note that contrary to the name, it does not handle any
    passwords.
    """

    try:
        with open(
            "{}/etc/passwd".format(CowrieConfig.get("honeypot", "contents_path")),
            encoding="ascii",
        ) as f:
            passwd_contents = f.read().split("\n")
    except configparser.Error:
        log.msg("Loading default /etc/passwd file from pickle file")
        resources_path = importlib.resources.files(data)
        config_file_path = (
            resources_path.joinpath("honeyfs").joinpath("etc").joinpath("passwd")
        )
        passwd_contents = config_file_path.read_text(encoding="utf-8").split("\n")
    except Exception as e:
        log.err(e, "ERROR: Failed to load /etc/passwd")
        sys.exit(2)

    passwd: list[dict[str, Any]]

    def __init__(self) -> None:
        self.load()

    def load(self) -> None:
        """
        Load /etc/passwd
        """
        self.passwd = []
        for rawline in self.passwd_contents:
            line = rawline.strip()
            if not line:
                continue

            if line.startswith("#"):
                continue

            if len(line.split(":")) != 7:
                log.msg("Error parsing line `" + line + "` in <honeyfs>/etc/passwd")
                continue

            (
                pw_name,
                pw_passwd,
                pw_uid,
                pw_gid,
                pw_gecos,
                pw_dir,
                pw_shell,
            ) = line.split(":")

            e: dict[str, str | int] = {}
            e["pw_name"] = pw_name
            e["pw_passwd"] = pw_passwd
            e["pw_gecos"] = pw_gecos
            e["pw_dir"] = pw_dir
            e["pw_shell"] = pw_shell
            try:
                e["pw_uid"] = int(pw_uid)
            except ValueError:
                e["pw_uid"] = 1001
            try:
                e["pw_gid"] = int(pw_gid)
            except ValueError:
                e["pw_gid"] = 1001

            self.passwd.append(e)

    def save(self):
        """
        Save the user db
        Note: this is subject to races between cowrie instances, but hey ...
        """
        #        with open(self.passwd_file, 'w') as f:
        #            for (login, uid, passwd) in self.userdb:
        #                f.write('%s:%d:%s\n' % (login, uid, passwd))
        raise NotImplementedError

    def getpwnam(self, name: str) -> dict[str, Any]:
        """
        Get passwd entry for username
        """
        for e in self.passwd:
            if e["pw_name"] == name:
                return e
        raise KeyError("getpwnam(): name not found in passwd file: " + name)

    def getpwuid(self, uid: int) -> dict[str, Any]:
        """
        Get passwd entry for uid
        """
        for e in self.passwd:
            if uid == e["pw_uid"]:
                return e
        raise KeyError("getpwuid(): uid not found in passwd file: " + str(uid))

    def setpwentry(self, name: str) -> dict[str, Any]:
        """
        If the user is not in /etc/passwd, creates a new user entry for the session
        """

        # ensure consistent uid and gid
        seed_id = crc32(name.encode("utf-8"))
        seed(seed_id)

        e: dict[str, Any] = {}
        e["pw_name"] = name
        e["pw_passwd"] = "x"
        e["pw_gecos"] = 0
        e["pw_dir"] = "/home/" + name
        e["pw_shell"] = "/bin/bash"
        e["pw_uid"] = randint(1500, 10000)
        e["pw_gid"] = e["pw_uid"]
        self.passwd.append(e)
        return e


class Group:
    """
    This class contains code to handle the groups and their properties in
    /etc/group.
    """

    group_file = "{}/etc/group".format(
        CowrieConfig.get("honeypot", "contents_path", fallback="honeyfs")
    )
    group: list[dict[str, Any]]

    def __init__(self):
        self.load()

    def load(self) -> None:
        """
        Load /etc/group
        """
        self.group = []
        with open(self.group_file, encoding="ascii") as f:
            while True:
                rawline = f.readline()
                if not rawline:
                    break

                line = rawline.strip()
                if not line:
                    continue

                if line.startswith("#"):
                    continue

                (gr_name, _, gr_gid, gr_mem) = line.split(":")

                e: dict[str, str | int] = {}
                e["gr_name"] = gr_name
                try:
                    e["gr_gid"] = int(gr_gid)
                except ValueError:
                    e["gr_gid"] = 1001
                e["gr_mem"] = gr_mem

                self.group.append(e)

    def save(self) -> None:
        """
        Save the group db
        Note: this is subject to races between cowrie instances, but hey ...
        """
        #        with open(self.group_file, 'w') as f:
        #            for (login, uid, passwd) in self.userdb:
        #                f.write('%s:%d:%s\n' % (login, uid, passwd))
        raise NotImplementedError

    def getgrnam(self, name: str) -> dict[str, Any]:
        """
        Get group entry for groupname
        """
        for e in self.group:
            if name == e["gr_name"]:
                return e
        raise KeyError("getgrnam(): name not found in group file: " + name)

    def getgrgid(self, uid: int) -> dict[str, Any]:
        """
        Get group entry for gid
        """
        for e in self.group:
            if uid == e["gr_gid"]:
                return e
        raise KeyError("getgruid(): uid not found in group file: " + str(uid))
