# Copyright 2026 SigmaSRC, Inc. All Rights Reserved.

"""Gather last boot time of the agent's system."""

import os
import re
import time
import logging
if os.name == "nt":
    from win32com.client import GetObject
    import pythoncom

if __name__ == "__main__":
    import sys
    try:
        import sigma
    except ImportError:
        sys.path.append(os.path.join(os.pardir, os.pardir))
    del sys

import platform
import string

from sigma.util import ostypes, config, timeskew

options = config.Options()

logger = logging.getLogger("gather.gather_lastboottime")

def getlastboottime(system, skewer, ostype):
    # Return time of last boot, or 0 if unknown
    if system == "Windows":
        return getwinlastboottime(ostype)
    if system == "Linux":
        return getlinuxlastboottime(skewer)
    if system == "Darwin":
        return getmaclastboottime("last reboot")
    if system in ("Solaris", "HP-UX", "AIX"):
        return getsolarislastboottime("uptime")
    # Give up -- return zero meaning "don't know"
    return 0

def getlinuxlastboottime(skewer):
    # Try to read /proc/uptime
    try:
        f = open("/proc/uptime")
    except IOError:
        pass
    else:
        try:
            line = f.readline()
        finally:
            f.close()
        parts = line.split()
        if parts:
            try:
                uptime = float(parts[0])
            except ValueError:
                pass
            else:
                now = time.time() + skewer.get_skew()
                return int(now - uptime)
    # Give up -- return zero meaning "don't know"
    return 0

def getsolarislastboottime(cmd):
    # Parse "uptime" output
    try:
        f = os.popen(cmd, "r")
    except IOError:
        pass
    else:
        try:
            line = f.readline()
        finally:
            f.close()
        # Expect a lines like:
        # 4:32pm  up 85 day(s),  1:25,  1 user,  load average: 0.01, 0.00, 0.00
        # 4:32pm  up 85 days,  1:25,  1 user,  load average: 0.01, 0.00, 0.00
        # 4:12pm  up  4:25,  1 user,  load average: 0.03, 0.02, 0.02
        # 5:05pm  up 11 min(s),  1 user,  load average: 0.05, 0.08, 0.10
        m = re.search(r"up\s+(\d+)\s+day\(s\),\s+(\d+):(\d+),", line)
        if m is not None:
            days = m.group(1)
            hours = m.group(2)
            minutes = m.group(3)
            seconds = int(days) * 24 * 60 * 60 + int(hours) * 60 * 60 + int(minutes) * 60
            now = int(time.time())
            #print time.ctime(now - seconds)
            return now - seconds
        else:
            m = re.search(r"up\s+(\d+)\s+days,\s+(\d+):(\d+),", line)
            if m is not None:
                days = m.group(1)
                hours = m.group(2)
                minutes = m.group(3)
                seconds = int(days) * 24 * 60 * 60 + int(hours) * 60 * 60 + int(minutes) * 60
                now = int(time.time())
                #print time.ctime(now - seconds)
                return now - seconds
            else:
                m = re.search(r"up\s+(\d+):(\d+),", line)
                if m is not None:
                    days = 0
                    hours = m.group(1)
                    minutes = m.group(2)
                    seconds = int(days) * 24 * 60 * 60 + int(hours) * 60 * 60 + int(minutes) * 60
                    now = int(time.time())
                    #print time.ctime(now - seconds)
                    return now - seconds
                else:
                    m = re.search(r"up\s+(\d+)\s+min", line)
                    if m is not None:
                        days = 0
                        hours = 0
                        minutes = m.group(1)
                        seconds = int(days) * 24 * 60 * 60 + int(hours) * 60 * 60 + int(minutes) * 60
                        now = int(time.time())
                        #print time.ctime(now - seconds)
                        return now - seconds

    # Give up -- return zero meaning "don't know"
    return 0

def getmaclastboottime(cmd):
    # Parse "who -b" output
    try:
        f = os.popen(cmd, "r")
    except IOError:
        pass
    else:
        try:
            line = f.readline()
        finally:
            f.close()
        # Expect a line ending in a timestamp like "Aug 17 16:19"
        m = re.search(r"\s(\w+\s+\d+\s+\d+:\d+)\s*$", line)
        if m is not None:
            datetime = m.group(1)
            try:
                tt = time.strptime(datetime, "%b %d %H:%M")
            except ValueError:
                pass
            else:
                month, day, hh, mm, ss = tt[1:6]
                now = time.localtime()[:6]
                year = now[0]
                if (year, month, day, hh, mm, ss) > now:
                    year = year-1
                tt = year, month, day, hh, mm, ss, -1, -1, -1
                return int(time.mktime(tt))
    # Give up -- return zero meaning "don't know"
    return 0

def geteventlogtime():
    # Windows specific code to get the last boot time
    timegenerated = ""
    try:
        objItems = GetObject("winmgmts:{impersonationLevel=impersonate, \
            (Security, Audit)}\\\\.\\root\\cimv2").ExecQuery("Select * from \
            Win32_NTLogEvent where logfile = 'System' \
            and (EventCode = 6005)")
        for item in objItems:
            if timegenerated < item.TimeGenerated:
                timegenerated = item.TimeGenerated
        if timegenerated == "":
            return 0
        return fixwintime(timegenerated)
    except:
        logger.exception("osinfo gather: getting last boot time from event log failed")
        return 0


def getwinlastboottime(ostype):
    # Windows specific code to get the last boot time
    """
    try:
        objItem = GetObject("WinMgMts://./root/cimv2").InstancesOf("Win32_PerfRawData_PerfOS_System")
        for item in objItem:
            intPerfTimeStamp = int(item.Properties_("Timestamp_Object").Value)
            intPerfTimeFreq = int(item.Properties_("Frequency_Object").Value)
            intCounter = int(item.Properties_("SystemUpTime").Value)
            # // keeps integer-seconds semantics (Py2 long division floored;
            # Py3 / would yield a float and change the serialized value).
            iUptimeInSec = (intPerfTimeStamp - intCounter) // intPerfTimeFreq
            return iUptimeInSec
    except pythoncom.com_error:
        return 0
    """
    eventlogtime = 0
    if ostype != "win2ks" and ostype != "win2kws":
        try:
            objItem = GetObject("WinMgMts://./root/cimv2").InstancesOf("Win32_OperatingSystem")
            for item in objItem:
                t = item.Properties_("LastBootUpTime").Value
                eventlogtime = fixwintime(t)
        except:
            logger.exception("osinfo gather: getting LastBootUpTime from Win32_OperatingSystem failed")
    if eventlogtime > 0:
        return eventlogtime
    else:
        return geteventlogtime()
            
def fixwintime(s):
    # Input is of the form YYYYMMDDhhmmss.uuuuuu-zzz
    # Output is an integral timestamps counting seconds since 1/1/1970
    # We ignore the microseconds (uuuuuu) and timezone (zzz)
    assert len(s) == 25
    YYYY, MM, DD, hh, mm, ss = map(int, 
        [s[0:4], s[4:6], s[6:8], s[8:10], s[10:12], s[12:14]])
    t = (YYYY, MM, DD, hh, mm, ss, -1, -1, -1) # Assume it's local time
    #print time.ctime(time.mktime(t))
    return int(time.mktime(t))

def Gather(collector):
    system, node, release, version, machine, processor = platform.uname()
    if system in [ "Microsoft", "Microsoft Windows" ]:
        system = "Windows"
    system, release, version = platform.system_alias(system, release, version)
    skewer = timeskew.TimeSkew(options)
    ostype = ostypes.guess_ostype()
    lastboottime = getlastboottime(system, skewer, ostype)
    t = collector.table("lastboottime",
                        ["lastboottime"])
    t.add([lastboottime])

def _test():
    from sigma.gather import gathering
    gathering.test(Gather)

if __name__ == "__main__":
    _test()
