# Copyright 2026 SigmaSRC, Inc. All Rights Reserved.

"""Web server interface for the Sigma agent."""

# Python imports
import os
import sys
import logging
import tempfile
import xmlrpc.server as SimpleXMLRPCServer

# Sigma imports
from sigma.util import config
from sigma.sched import process

# Global objects
logger = logging.getLogger("sched.webserver")

commands = [ # Label, link, command
    # Commands here must be Python modules residing in AVBIN
    ("", "index", "./homepage.py"),
    ("", "started", "./homepage.py -s"),
    ("General Status", "status", "./status.py"),
    ("Schedule", "schedule", "./sdc.py"),
    ("Matrix", "matrix", "./showmatrix.py"),
    ("Upload Spool Queue", "queue", "./spool.py"),
    ("Certificate", "certificate", "./cert-mgr.py show"),
    ("Gathered Data", "gather", "./gather.py -l"),
    ("Upgrade Status", "upgrade", "./upgrade.py list"),
    ("Message Log", "logtail", "./logtail.py 1000"),
    ("Directives", "policy", "./showdir.py"),
    ]

commanddict = dict([(link, command) for label, link, command in commands])

expertpage = """<h1>Sigma Agent Expert Page</h1>

<p>This page is for SigmaSRC developers and customer service
only.  End users, please see the <a href="/">Sigma Agent Home
Page</a>.

<ul>
%s
</ul>
"""

class MyRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):

    def do_POST(self):
        if self.path == "/RPC2":
            SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.do_POST(self)
        elif self.path == "/doitnow":
            self.doitnow()

    def doitnow(self):
        def isengine(task):
            return task.id.startswith("engine.")
        self.server.handler.schedule.doitnow(isengine)
        text = ('<h1>Compliance Rechecking Started</h1>'
                '<p><a href="/">Back</a></p>').encode("utf-8")
        self.send_response(302) # Most compatible response code
        self.send_header("Location", "/started.html")
        self.send_header("URI", "/started.html") # A little redundancy
        self.send_header("Content-Type", "text/html")
        self.send_header("Content-Length", str(len(text)))
        self.end_headers()
        self.wfile.write(text)

    options = None

    def do_GET(self):
        if self.options is None:
            self.__class__.options = config.Options()
        ctype = "text/plain"
        resp = 200
        # Parse path into command and key
        path = self.path
        if path.startswith("/"):
            path = path[1:]
        qm = path.find("?")
        if qm < 0:
            command = path
            key = ""
        else:
            command = path[:qm]
            key = path[qm+1:]
        if command.endswith(".html"):
            command = command[:-5]
        if not command:
            command = "index"
        # Check various cases
        if command in ("sigma-logo.gif", "checkbox-green-shadow.gif"):
            filename = os.path.join(self.options.agentbin, command)
            try:
                f = open(filename, "rb")
            except IOError as err:
                text = str(err)
                resp = 500
            else:
                try:
                    text = f.read()
                finally:
                    f.close()
                ctype = "image/gif"
        elif (command not in ("index", "started")
              and key != self.options.expert_key):
            resp = 403
            text = "403 error: invalid key"
        elif command == "expert":
            links = ['<li><a href="/%s?%s">%s</li>' % (link, key, label)
                     for label, link, command in commands if label]
            text = expertpage % ("\n".join(links))
            ctype = "text/html"
        elif command in commanddict:
            if command in ("index", "started"):
                ctype = "text/html"
            cmd = commanddict[command]
            # Jump through a few hoops here because os.popen() doesn't
            # like pathnames with spaces in them, and quoting doesn't
            # seem to help (at least not on Windows, where it matters)
            args = cmd.split()
            cmd = args[0]
            if cmd.startswith("./"):
                cmd = cmd[2:]
            if cmd.endswith(".py"):
                cmd = cmd[:-3]
            redirect_py = os.path.join(self.options.agentbin, "redirect.py")
            tfn = tempfile.mktemp()
            try:
                args = [sys.executable, redirect_py, tfn, cmd] + args[1:]
                proc = process.createProcess(args)
                proc.wait()
                f = open(tfn)
                try:
                    text = f.read()
                finally:
                    f.close()
            finally:
                if os.path.exists(tfn):
                    os.remove(tfn)
        else:
            text = "404 error: path %r not found" % self.path
            resp = 404
        self.send_everything(resp, ctype, text)

    def send_everything(self, resp, ctype, text):
        # Python 3: the socket wfile only accepts bytes. Page handlers build
        # 'text' as a str (HTML pages, command output) while images are already
        # bytes; encode the str case. Without this, wfile.write(str) raises
        # AFTER the 200/Content-Length headers are sent, so the client gets a
        # 200 with an empty body -- every web page (homepage, expert, status)
        # rendered blank.
        if isinstance(text, str):
            text = text.encode("utf-8")
        self.send_response(resp)
        self.send_header("Content-Type", ctype)
        self.send_header("Content-Length", str(len(text)))
        self.end_headers()
        self.wfile.write(text)
