# Copyright 2026 SigmaSRC, Inc. All Rights Reserved.

"""Gather the routing table.
The only useful information here is probably the gateway for the
default route.  To make more use of it, we would need to report the
flags column too, and come up with a way to describe it that is
operating-system independent.  This is a solvable problem.
"""

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

import os
import re
import platform

_ipre = r"\d+\." * 3 + r"\d+"
assert re.match(_ipre,"192.168.1.6"), "_ipre is broken"

# The routing table for Linux comes from "route -n" and looks like:
#
# Kernel IP routing table
# Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
# 10.0.5.0        0.0.0.0         255.255.255.0   U     0      0        0 eth0
# 169.254.0.0     0.0.0.0         255.255.0.0     U     0      0        0 eth0
# 127.0.0.0       0.0.0.0         255.0.0.0       U     0      0        0 lo
# 0.0.0.0         10.0.5.1        0.0.0.0         UG    0      0        0 eth0
# _ipre matches ip addresses.
_linux_routeline = re.compile(r"^(?P<destination>" + _ipre + r")\s+"
                              r"(?P<gateway>" + _ipre + r")\s+"
                              r"(?P<mask>" + _ipre + r")\s+"
                              r"[^\s]+\s+" # Flags
                              r"\d+\s+" # Metric
                              r"\d+\s+" # Ref
                              r"\d+\s+" # Use
                              r"(?P<interface>[^\s]+)\s*$")

assert _linux_routeline.match(\
    "10.0.5.0        0.0.0.0         255.255.255.0   "
    "U     0      0        0 eth0"), "_linux_routeline is broken"

# The routing table on Solaris comes from "netstat -nrv -f inet" and
# looks like:
#
# IRE Table: IPv4
#   Destination             Mask           Gateway          Device Mxfrg  Rtt  Ref Flg  Out  In/Fwd
# -------------------- --------------- -------------------- ------ ----- ----- --- --- ----- ------
# 10.0.5.0             255.255.255.0   10.0.5.201           hme0    1500*    0   1 U     4270     0
# 224.0.0.0            240.0.0.0       10.0.5.201           hme0    1500*    0   1 U        0     0
# default              0.0.0.0         10.0.5.254                   1500*    0   1 UG       0     0
# 127.0.0.1            255.255.255.255 127.0.0.1            lo0     8232*    0   2 UH   93427     0
#
# If I leave out the "v" option, I get a table that's more pleasant
# because it fits in 80 columns, but then it's missing the mask and I
# need the mask.
#
# Hmm, the gateways for the non-gateway routes look like garbage to me
# here.  Oh well, we'll report it anyway.

_solaris_routeline = re.compile(
    r"^(?P<destination>(" + _ipre + r"|default))\s+"
    r"(?P<mask>" + _ipre + r")\s+"
    r"(?P<gateway>" + _ipre + r")\s+"
    r"(?P<interface>[^\s]+)\s")

assert _solaris_routeline.match(\
    "10.0.5.0             255.255.255.0   10.0.5.201           "
    "hme0    1500*    0   1 U     4270     0    "), \
    "_solaris_routeline is broken"

# HP-UX netstat -nrv -f inet output looks like this:
#
# Routing tables
# Dest/Netmask                    Gateway            Flags   Refs Interface  Pmtu
# 127.0.0.1/255.255.255.255       127.0.0.1          UH        0  lo0        4136
# 10.69.19.8/255.255.255.255      10.69.19.8         UH        0  lan0       4136
# 10.69.16.0/255.255.252.0        10.69.19.8         U         2  lan0       1500
# 127.0.0.0/255.0.0.0             127.0.0.1          U         0  lo0           0
# default/0.0.0.0                 10.69.16.1         UG        0  lan0          0

_hpux_routeline = re.compile(
    r"^(?P<destination>(" + _ipre + r"|default))"
    r"/(?P<mask>" + _ipre + r")\s+"
    r"(?P<gateway>" + _ipre + r")\s+"
    r"\S+\s+" # Flags
    r"\S+\s+" # Refs
    r"(?P<interface>\S+)")

# Mac OS X (Darwin) tables look like this, from netstat -r -n -f inet:
# Routing tables
# 
# Internet:
# Destination        Gateway            Flags    Refs      Use  Netif Expire
# default            10.69.32.1         UGSc       11        6    en0
# 10.69.32/22        link#4             UCS         3        0    en0
# 10.69.32.1         0:c:db:69:b3:e0    UHLW       11        0    en0   1199
# 10.69.33.4         127.0.0.1          UHS         0        0    lo0
# 10.69.34.130       0:11:11:46:aa:76   UHLW        0       51    en0    340
# 10.69.35.255       link#4             UHLWb       1       29    en0
# 127                127.0.0.1          UCS         0        0    lo0
# 127.0.0.1          127.0.0.1          UH         13    10936    lo0
# 169.254            link#4             UCS         0        0    en0

_osx_routeline = re.compile(r"""(?x)          # Use verbose re syntax
    ^
    (?P<destination>  default
                    | [\d.]+ (/\d+)? )        # Destination
    \s+
    (?P<gateway> [\d.]+ | link\#\d+ )         # Gateway
    \s+
    \S+                                       # Flags
    \s+
    \d+                                       # Refs
    \s+
    \d+                                       # Use
    \s+
    (?P<interface> \S+ )                      # Netif
""")

def gather(collector, command, regex):
    t = collector.table("route",
                        ["destination", "gateway", "mask", "interface"])
    file = os.popen(command)
    try:
        for line in file:
            m = regex.match(line)
            if m:
                (destination, gateway, mask, interface) = \
                          m.group("destination", "gateway",
                                  "mask", "interface")
                if destination == "default":
                    destination = "0.0.0.0"
                t.add([destination, gateway, mask, interface])
    finally:
        file.close()

def solaris_gather(collector):
    gather(collector, "/usr/bin/netstat -nrv -f inet", _solaris_routeline)

def linux_gather(collector):
    gather(collector, "/sbin/route -n", _linux_routeline)

def hpux_gather(collector):
    gather(collector, "/usr/bin/netstat -nrv -f inet", _hpux_routeline)

def osx_gather(collector):
    t = collector.table("route",
                        ["destination", "gateway", "mask", "interface"])
    file = os.popen("/usr/sbin/netstat -r -n -f inet", "r")
    try:
        default = None
        for line in file:
            m = _osx_routeline.match(line)
            if not m:
                continue
            destination, gateway, interface = \
                m.group("destination", "gateway", "interface")
            if destination == "default":
                destination = "0.0.0.0"
                default = gateway
            if gateway.startswith("link#"):
                # XXX Don't know what these really are...
                gateway = default
            if "/" in destination:
                i = destination.index("/")
                bits = int(destination[i+1:])
                destination = destination[:i]
                destination, mask = makemask(destination, bits)
            else:
                mask = "255"
                while destination.count(".") < 3:
                    destination += ".0"
                    mask += ".0"
                while mask.count(".") < 3:
                    mask = "255." + mask
            t.add([destination, gateway, mask, interface])
    finally:
        file.close()

def makemask(destination, bits):
    while destination.count(".") < 3:
        destination += ".0"
    abcd = 2**32 - 2**(32-bits)
    abc, d = divmod(abcd, 256)
    ab, c = divmod(abc, 256)
    a, b = divmod(ab, 256)
    mask = "%d.%d.%d.%d" % (a, b, c, d)
    return destination, mask

def Gather(collector):
    basicos = platform.uname()[0]
    if basicos == "SunOS":
        solaris_gather(collector)
    elif basicos == "Linux":
        linux_gather(collector)
    elif basicos == "HP-UX":
        hpux_gather(collector)
    elif basicos == "Darwin":
        osx_gather(collector)
    elif basicos == "AIX":
        osx_gather(collector)
    else:
        raise Exception("Can't figure out operating system: %r" % basicos)

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

if __name__ == "__main__":
    _test()
