Merge pull request 'fix agent for macos' (#3) from ogagent-macos into py3-win

Reviewed-on: #3
pull/2/head
Natalia Serrano 2024-07-22 15:36:14 +02:00
commit 64089e07af
12 changed files with 945 additions and 46 deletions

5
.gitignore vendored
View File

@ -11,12 +11,17 @@ linux/debian/ogagent.postinst.debhelper
linux/debian/ogagent.postrm.debhelper
linux/configure-stamp
linux/build-stamp
macos/build
windows/vc_redist.x64.exe
ogagent_*_all.deb
ogagent_*_amd64.buildinfo
ogagent_*_amd64.changes
ogagent_*_amd64.build
OGAgentInstaller-*.pkg
OGAgentSetup-*.exe
bin
src/build
src/dist
src/about_dialog_ui.py
src/message_dialog_ui.py
src/dist

View File

@ -1,9 +1,9 @@
#!/bin/bash
# Create macOS installation packages.
# Based on bomutils tutorail: http://bomutils.dyndns.org/tutorial.html
# Based on bomutils tutorial: http://bomutils.dyndns.org/tutorial.html
cd $(dirname $0)
[ -r ../src/VERSION ] && VERSION="$(cat ../src/VERSION)" || VERSION="1.1.0"
[ -r ../src/VERSION ] && VERSION="$(cat ../src/VERSION)" || (echo "Can't get version from ../src/VERSION" 1>&2; exit 1)
AUTHOR="OpenGnsys Project"
# Create empty directories.
@ -12,8 +12,8 @@ mkdir -p build && cd build
mkdir -p flat/base.pkg flat/Resources/en.lproj
mkdir -p root/Applications
# Copy application and script files.
cp -a ../../src root/Applications/OGAgent.app
# Copy application and script files. Exclude 'test_modules'
cp -a ../../src root/Applications/OGAgent.app; rm -rf root/Applications/OGAgent.app/test_modules
cp -a ../scripts .
# Create plist file.
@ -84,4 +84,3 @@ EOT
# Create new Xar application archive.
rm -f ../../../OGAgentInstaller-$VERSION.pkg
( cd flat && xar --compression none -cf "../../../OGAgentInstaller-$VERSION.pkg" * )

View File

@ -2,10 +2,10 @@
<plist version="1.0">
<dict>
<key>Label</key>
<string>es.opengnsys.ogagent</string>
<string>es.opengnsys.agent.system</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/ogagent</string>
<string>/usr/local/bin/ogagent</string>
<string>start</string>
</array>
<key>RunAtLoad</key>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC -//Apple Computer//DTD PLIST 1.0//EN http://www.apple.com/DTDs/PropertyList-1.0.dtd>
<plist version="1.0">
<dict>
<key>Label</key>
<string>es.opengnsys.agent.user</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin</string>
</dict>
<key>WorkingDirectory</key>
<string>/Applications/OGAgent.app</string>
<key>ProgramArguments</key>
<array>
<string>/Applications/OGAgent.app/OGAgentUser.py</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>

832
macos/scripts/ip.py 100755
View File

@ -0,0 +1,832 @@
#!/usr/bin/env python3
# encoding: utf8
"""
This program is taken from https://github.com/brona/iproute2mac
When doing 'brew install iproute2mac', we get an old version (1.4.2) that doesn't support 'ip -json'
The alternative installation method recomended in the project's README file is 'curl; chmod; mv'
Therefore we make the decision of shipping this ip.py (version 1.5.0) along opengnsys, which is pretty much the same as curling it
"""
"""
iproute2mac
CLI wrapper for basic network utilites on Mac OS X.
Homepage: https://github.com/brona/iproute2mac
The MIT License (MIT)
Copyright (c) 2015 Bronislav Robenek <brona@robenek.me>
"""
import ipaddress
import json
import os
import random
import re
import socket
import string
import subprocess
import sys
import types
# Version
VERSION = "1.5.0"
# Utilities
SUDO = "/usr/bin/sudo"
IFCONFIG = "/sbin/ifconfig"
ROUTE = "/sbin/route"
NETSTAT = "/usr/sbin/netstat"
NDP = "/usr/sbin/ndp"
ARP = "/usr/sbin/arp"
NETWORKSETUP = "/usr/sbin/networksetup"
# Helper functions
def perror(*args):
sys.stderr.write(*args)
sys.stderr.write("\n")
def execute_cmd(cmd):
print("Executing: %s" % cmd)
status, output = subprocess.getstatusoutput(cmd)
if status == 0: # unix/linux commands 0 true, 1 false
print(output)
return True
else:
perror(output)
return False
def json_dump(data, pretty):
if pretty:
print(json.dumps(data, indent=4))
else:
print(json.dumps(data, separators=(",", ":")))
return True
# Classful to CIDR conversion with "default" being passed through
def cidr_from_netstat_dst(target):
if target == "default":
return target
dots = target.count(".")
if target.find("/") == -1:
addr = target
netmask = (dots + 1) * 8
else:
[addr, netmask] = target.split("/")
addr = addr + ".0" * (3 - dots)
return addr + "/" + str(netmask)
# Convert hexadecimal netmask in prefix length
def netmask_to_length(mask):
return int(mask, 16).bit_count()
def any_startswith(words, test):
for word in words:
if word.startswith(test):
return True
return False
# Handles passsing return value, error messages and program exit on error
def help_msg(help_func):
def wrapper(func):
def inner(*args, **kwargs):
if not func(*args, **kwargs):
specific = eval(help_func)
if specific:
if isinstance(specific, types.FunctionType):
if args and kwargs:
specific(*args, **kwargs)
else:
specific()
return False
else:
raise Exception("Function expected for: " + help_func)
else:
raise Exception(
"Function variant not defined: " + help_func
)
return True
return inner
return wrapper
# Generate random MAC address with XenSource Inc. OUI
# http://www.linux-kvm.com/sites/default/files/macgen.py
def randomMAC():
mac = [
0x00,
0x16,
0x3E,
random.randint(0x00, 0x7F),
random.randint(0x00, 0xFF),
random.randint(0x00, 0xFF),
]
return ":".join(["%02x" % x for x in mac])
# Decode ifconfig output
def parse_ifconfig(res, af, address):
links = []
count = 1
for r in res.split("\n"):
if re.match(r"^\w+:", r):
if count > 1:
links.append(link)
(ifname, flags, mtu, ifindex) = re.findall(r"^(\w+): flags=\d+<(.*)> mtu (\d+) index (\d+)", r)[0]
flags = flags.split(",")
link = {
"ifindex": int(ifindex),
"ifname": ifname,
"flags": flags,
"mtu": int(mtu),
"operstate": "UNKNOWN",
"link_type": "unknown"
}
if "LOOPBACK" in flags:
link["link_type"] = "loopback"
link["address"] = "00:00:00:00:00:00"
link["broadcast"] = "00:00:00:00:00:00"
elif "POINTOPOINT" in flags:
link["link_type"] = "none"
count = count + 1
else:
if re.match(r"^\s+ether ", r):
link["link_type"] = "ether"
link["address"] = re.findall(r"(\w\w:\w\w:\w\w:\w\w:\w\w:\w\w)", r)[0]
link["broadcast"] = "ff:ff:ff:ff:ff:ff"
elif address and re.match(r"^\s+inet ", r) and af != 6:
(local, netmask) = re.findall(r"inet (\d+\.\d+\.\d+\.\d+) netmask (0x[0-9a-f]+)", r)[0]
addr = {
"family": "inet",
"local": local,
"prefixlen": netmask_to_length(netmask),
}
if re.match(r"^.*broadcast", r):
addr["broadcast"] = re.findall(r"broadcast (\d+\.\d+\.\d+\.\d+)", r)[0]
link["addr_info"] = link.get("addr_info", []) + [addr]
elif address and re.match(r"^\s+inet6 ", r) and af != 4:
(local, prefixlen) = re.findall(r"inet6 ((?:[a-f0-9:]+:+)+[a-f0-9]+)%*\w* +prefixlen (\d+)", r)[0]
link["addr_info"] = link.get("addr_info", []) + [{
"family": "inet6",
"local": local,
"prefixlen": int(prefixlen)
}]
elif re.match(r"^\s+status: ", r):
match re.findall(r"status: (\w+)", r)[0]:
case "active":
link["operstate"] = "UP"
case "inactive":
link["operstate"] = "DOWN"
if count > 1:
links.append(link)
return links
def link_addr_show(argv, af, json_print, pretty_json, address):
if len(argv) > 0 and argv[0] == "dev":
argv.pop(0)
if len(argv) > 0:
param = argv[0]
else:
param = "-a"
status, res = subprocess.getstatusoutput(
IFCONFIG + " -v " + param + " 2>/dev/null"
)
if status: # unix status
if res == "":
perror(param + " not found")
else:
perror(res)
return False
links = parse_ifconfig(res, af, address)
if json_print:
return json_dump(links, pretty_json)
for l in links:
print("%d: %s: <%s> mtu %d status %s" % (
l["ifindex"], l["ifname"], ",".join(l["flags"]), l["mtu"], l["operstate"]
))
print(
" link/" + l["link_type"] +
((" " + l["address"]) if "address" in l else "") +
((" brd " + l["broadcast"]) if "broadcast" in l else "")
)
for a in l.get("addr_info", []):
print(
" %s %s/%d" % (a["family"], a["local"], a["prefixlen"]) +
((" brd " + a["broadcast"]) if "broadcast" in a else "")
)
return True
# Help
def do_help(argv=None, af=None, json_print=None, pretty_json=None):
perror("Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }")
perror("where OBJECT := { link | addr | route | neigh }")
perror(" OPTIONS := { -V[ersion] | -j[son] | -p[retty] |")
perror(" -4 | -6 }")
perror("iproute2mac")
perror("Homepage: https://github.com/brona/iproute2mac")
perror(
"This is CLI wrapper for basic network utilities on Mac OS X"
" inspired with iproute2 on Linux systems."
)
perror(
"Provided functionality is limited and command output is not"
" fully compatible with iproute2."
)
perror(
"For advanced usage use netstat, ifconfig, ndp, arp, route "
" and networksetup directly."
)
exit(255)
def do_help_route():
perror("Usage: ip route list")
perror(" ip route get ADDRESS")
perror(" ip route { add | del | replace } ROUTE")
perror(" ip route flush cache")
perror(" ip route flush table main")
perror("ROUTE := NODE_SPEC [ INFO_SPEC ]")
perror("NODE_SPEC := [ TYPE ] PREFIX")
perror("INFO_SPEC := NH")
perror("TYPE := { blackhole }")
perror("NH := { via ADDRESS | gw ADDRESS | nexthop ADDRESS | dev STRING }")
exit(255)
def do_help_addr():
perror("Usage: ip addr show [ dev STRING ]")
perror(" ip addr { add | del } PREFIX dev STRING")
exit(255)
def do_help_link():
perror("Usage: ip link show [ DEVICE ]")
perror(" ip link set dev DEVICE")
perror(" [ { up | down } ]")
perror(" [ address { LLADDR | factory | random } ]")
perror(" [ mtu MTU ]")
exit(255)
def do_help_neigh():
perror("Usage: ip neighbour show [ [ to ] PREFIX ] [ dev DEV ]")
perror(" ip neighbour flush [ dev DEV ]")
exit(255)
# Route Module
@help_msg("do_help_route")
def do_route(argv, af, json_print, pretty_json):
if not argv or (
any_startswith(["show", "lst", "list"], argv[0]) and len(argv) == 1
):
return do_route_list(af, json_print, pretty_json)
elif "get".startswith(argv[0]) and len(argv) == 2:
argv.pop(0)
return do_route_get(argv, af, json_print, pretty_json)
elif "add".startswith(argv[0]) and len(argv) >= 3:
argv.pop(0)
return do_route_add(argv, af)
elif "delete".startswith(argv[0]) and len(argv) >= 2:
argv.pop(0)
return do_route_del(argv, af)
elif "replace".startswith(argv[0]) and len(argv) >= 3:
argv.pop(0)
return do_route_del(argv, af) and do_route_add(argv, af)
elif "flush".startswith(argv[0]) and len(argv) >= 1:
argv.pop(0)
return do_route_flush(argv, af)
else:
return False
return True
def do_route_list(af, json_print, pretty_json):
# ip route prints IPv6 or IPv4, never both
inet = "inet6" if af == 6 else "inet"
status, res = subprocess.getstatusoutput(
NETSTAT + " -nr -f " + inet + " 2>/dev/null"
)
if status:
perror(res)
return False
res = res.split("\n")
res = res[4:] # Removes first 4 lines
routes = []
for r in res:
ra = r.split()
target = ra[0]
gw = ra[1]
flags = ra[2]
# macOS Mojave and earlier vs Catalina
dev = ra[5] if len(ra) >= 6 else ra[3]
if flags.find("W") != -1:
continue
if af == 6:
target = re.sub(r"%[^ ]+/", "/", target)
else:
target = cidr_from_netstat_dst(target)
if flags.find("B") != -1:
routes.append({"type": "blackhole", "dst": target, "flags": []})
continue
if re.match(r"link.+", gw):
routes.append({"dst": target, "dev": dev, "scope": "link", "flags": []})
else:
routes.append({"dst": target, "gateway": gw, "dev": dev, "flags": []})
if json_print:
return json_dump(routes, pretty_json)
for route in routes:
if "type" in route:
print("%s %s" % (route["type"], route["dst"]))
elif "scope" in route:
print("%s dev %s scope %s" % (route["dst"], route["dev"], route["scope"]))
elif "gateway" in route:
print("%s via %s dev %s" % (route["dst"], route["gateway"], route["dev"]))
return True
def do_route_add(argv, af):
options = ""
if argv[0] == "blackhole":
argv.pop(0)
if len(argv) != 1:
return False
argv.append("via")
argv.append("::1" if ":" in argv[0] or af == 6 else "127.0.0.1")
options = "-blackhole"
if len(argv) not in (3, 5):
return False
if len(argv) == 5:
perror(
"iproute2mac: Ignoring last 2 arguments, not implemented: {} {}".format(
argv[3], argv[4]
)
)
if argv[1] in ["via", "nexthop", "gw"]:
gw = argv[2]
elif argv[1] in ["dev"]:
gw = "-interface " + argv[2]
else:
do_help_route()
prefix = argv[0]
inet = "-inet6 " if ":" in prefix or af == 6 else ""
return execute_cmd(
SUDO + " " + ROUTE + " add " + inet + prefix + " " + gw + " " + options
)
def do_route_del(argv, af):
options = ""
if argv[0] == "blackhole":
argv.pop(0)
if len(argv) != 1:
return False
if ":" in argv[0] or af == 6:
options = " ::1 -blackhole"
else:
options = " 127.0.0.1 -blackhole"
prefix = argv[0]
inet = "-inet6 " if ":" in prefix or af == 6 else ""
return execute_cmd(
SUDO + " " + ROUTE + " delete " + inet + prefix + options
)
def do_route_flush(argv, af):
if not argv:
perror('"ip route flush" requires arguments.')
perror("")
return False
# https://github.com/brona/iproute2mac/issues/38
# http://linux-ip.net/html/tools-ip-route.html
if argv[0] == "cache":
print("iproute2mac: There is no route cache to flush in MacOS,")
print(" returning 0 status code for compatibility.")
return True
elif len(argv) == 2 and argv[0] == "table" and argv[1] == "main":
family = "-inet6" if af == 6 else "-inet"
print("iproute2mac: Flushing all routes")
return execute_cmd(SUDO + " " + ROUTE + " -n flush " + family)
else:
return False
def do_route_get(argv, af, json_print, pretty_json):
target = argv[0]
inet = ""
if ":" in target or af == 6:
inet = "-inet6 "
family = socket.AF_INET6
else:
family = socket.AF_INET
status, res = subprocess.getstatusoutput(
ROUTE + " -n get " + inet + target
)
if status: # unix status or not in table
perror(res)
return False
if res.find("not in table") >= 0:
perror(res)
exit(1)
res = dict(
re.findall(
r"^\W*((?:route to|destination|gateway|interface)): (.+)$",
res,
re.MULTILINE,
)
)
route = {"dst": res["route to"], "dev": res["interface"]}
if "gateway" in res:
route["gateway"] = res["gateway"]
try:
s = socket.socket(family, socket.SOCK_DGRAM)
s.connect((route["dst"], 7))
route["prefsrc"] = src_ip = s.getsockname()[0]
s.close()
except:
pass
route["flags"] = []
route["uid"] = os.getuid()
route["cache"] = []
if json_print:
return json_dump([route], pretty_json)
print(
route["dst"] +
((" via " + route["gateway"]) if "gateway" in route else "") +
" dev " + route["dev"] +
((" src " + route["prefsrc"]) if "prefsrc" in route else "") +
" uid " + str(route["uid"])
)
return True
# Addr Module
@help_msg("do_help_addr")
def do_addr(argv, af, json_print, pretty_json):
if not argv:
argv.append("show")
if any_startswith(["show", "lst", "list"], argv[0]):
argv.pop(0)
return do_addr_show(argv, af, json_print, pretty_json)
elif "add".startswith(argv[0]) and len(argv) >= 3:
argv.pop(0)
return do_addr_add(argv, af)
elif "delete".startswith(argv[0]) and len(argv) >= 3:
argv.pop(0)
return do_addr_del(argv, af)
else:
return False
return True
def do_addr_show(argv, af, json_print, pretty_json):
return link_addr_show(argv, af, json_print, pretty_json, True)
def do_addr_add(argv, af):
if len(argv) < 2:
return False
dst = ""
if argv[1] == "peer":
argv.pop(1)
dst = argv.pop(1)
if argv[1] == "dev":
argv.pop(1)
else:
return False
try:
addr = argv[0]
dev = argv[1]
except IndexError:
perror("dev not found")
exit(1)
inet = ""
if ":" in addr or af == 6:
af = 6
inet = " inet6"
return execute_cmd(
SUDO + " " + IFCONFIG + " " + dev + inet + " add " + addr + " " + dst
)
def do_addr_del(argv, af):
if len(argv) < 2:
return False
if argv[1] == "dev":
argv.pop(1)
try:
addr = argv[0]
dev = argv[1]
except IndexError:
perror("dev not found")
exit(1)
inet = "inet"
if ":" in addr or af == 6:
af = 6
inet = "inet6"
return execute_cmd(
SUDO + " " + IFCONFIG + " " + dev + " " + inet + " " + addr + " remove"
)
# Link module
@help_msg("do_help_link")
def do_link(argv, af, json_print, pretty_json):
if not argv:
argv.append("show")
if any_startswith(["show", "lst", "list"], argv[0]):
argv.pop(0)
return do_link_show(argv, af, json_print, pretty_json)
elif "set".startswith(argv[0]):
argv.pop(0)
return do_link_set(argv, af)
else:
return False
return True
def do_link_show(argv, af, json_print, pretty_json):
return link_addr_show(argv, af, json_print, pretty_json, False)
def do_link_set(argv, af):
if not argv:
return False
elif argv[0] == "dev":
argv.pop(0)
if len(argv) < 2:
return False
dev = argv[0]
IFCONFIG_DEV_CMD = SUDO + " " + IFCONFIG + " " + dev
try:
args = iter(argv)
for arg in args:
if arg == "up":
if not execute_cmd(IFCONFIG_DEV_CMD + " up"):
return False
elif arg == "down":
if not execute_cmd(IFCONFIG_DEV_CMD + " down"):
return False
elif arg in ["address", "addr", "lladdr"]:
addr = next(args)
if addr in ["random", "rand"]:
addr = randomMAC()
elif addr == "factory":
(status, res) = subprocess.getstatusoutput(
NETWORKSETUP + " -listallhardwareports"
)
if status != 0:
return False
details = re.findall(
r"^(?:Device|Ethernet Address): (.+)$",
res,
re.MULTILINE,
)
addr = details[details.index(dev) + 1]
if not execute_cmd(IFCONFIG_DEV_CMD + " lladdr " + addr):
return False
elif arg == "mtu":
mtu = int(next(args))
if not execute_cmd(IFCONFIG_DEV_CMD + " mtu " + str(mtu)):
return False
except Exception:
return False
return True
# Neigh module
@help_msg("do_help_neigh")
def do_neigh(argv, af, json_print, pretty_json):
if not argv:
argv.append("show")
if any_startswith(["show", "list", "lst"], argv[0]) and len(argv) <= 5:
argv.pop(0)
return do_neigh_show(argv, af, json_print, pretty_json)
elif "flush".startswith(argv[0]):
argv.pop(0)
return do_neigh_flush(argv, af)
else:
return False
def do_neigh_show(argv, af, json_print, pretty_json):
prefix = None
dev = None
try:
while argv:
arg = argv.pop(0)
if arg == "to":
prefix = argv.pop(0)
elif arg == "dev":
dev = argv.pop(0)
elif prefix is None:
prefix = arg
else:
return False
if prefix:
prefix = ipaddress.ip_network(prefix, strict=False)
except Exception:
return False
nd_ll_states = {
"R": "REACHABLE",
"S": "STALE",
"D": "DELAY",
"P": "PROBE",
"I": "INCOMPLETE",
"N": "INCOMPLETE",
"W": "INCOMPLETE",
}
neighs = []
if af != 4:
res = subprocess.run(
[NDP, "-an"], capture_output=True, text=True, check=True
)
for row in res.stdout.splitlines()[1:]:
cols = row.split()
entry = {"dst": re.sub(r"%.+$", "", cols[0])}
if cols[1] != "(incomplete)":
entry["lladdr"] = cols[1]
entry["dev"] = cols[2]
if dev and entry["dev"] != dev:
continue
if prefix and ipaddress.ip_address(entry["dst"]) not in prefix:
continue
if cols[1] == "(incomplete)" and cols[4] != "R":
entry["status"] = ["INCOMPLETE"]
else:
entry["status"] = [nd_ll_states[cols[4]]]
entry["router"] = len(cols) >= 6 and cols[5] == "R"
neighs.append(entry)
if af != 6:
args = [ARP, "-anl"]
if dev:
args += ["-i", dev]
res = subprocess.run(args, capture_output=True, text=True, check=True)
for row in res.stdout.splitlines()[1:]:
cols = row.split()
entry = {"dst": cols[0]}
if cols[1] != "(incomplete)":
entry["lladdr"] = cols[1]
entry["dev"] = cols[4]
if dev and entry["dev"] != dev:
continue
if prefix and ipaddress.ip_address(entry["dst"]) not in prefix:
continue
if cols[1] == "(incomplete)":
entry["status"] = ["INCOMPLETE"]
else:
entry["status"] = ["REACHABLE"]
entry["router"] = False
neighs.append(entry)
if json_print:
return json_dump(neighs, pretty_json)
for nb in neighs:
print(
nb["dst"] +
("", " dev " + nb["dev"], "")[dev == None] +
("", " router")[nb["router"]] +
" %s" % (nb["status"][0])
)
return True
def do_neigh_flush(argv, af):
if len(argv) != 2:
perror("Flush requires arguments.")
exit(1)
if argv[0] != "dev":
return False
dev = argv[1]
if af != 4:
print(
"iproute2mac: NDP doesn't support filtering by interface,"
"flushing all IPv6 entries."
)
execute_cmd(SUDO + " " + NDP + " -cn")
if af != 6:
execute_cmd(SUDO + " " + ARP + " -a -d -i " + dev)
return True
# Match iproute2 commands
# https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/ip.c#n86
cmds = [
("address", do_addr),
("route", do_route),
("neighbor", do_neigh),
("neighbour", do_neigh),
("link", do_link),
("help", do_help),
]
@help_msg("do_help")
def main(argv):
af = -1 # default / both
json_print = False
pretty_json = False
while argv and argv[0].startswith("-"):
# Turn --opt into -opt
argv[0] = argv[0][1:] if argv[0][1] == "-" else argv[0]
# Process options
if argv[0] == "-6":
af = 6
argv.pop(0)
elif argv[0] == "-4":
af = 4
argv.pop(0)
elif argv[0].startswith("-color"):
perror("iproute2mac: Color option is not implemented")
argv.pop(0)
elif "-json".startswith(argv[0]):
json_print = True
argv.pop(0)
elif "-pretty".startswith(argv[0]):
pretty_json = True
argv.pop(0)
elif "-Version".startswith(argv[0]):
print("iproute2mac, v" + VERSION)
exit(0)
elif "-help".startswith(argv[0]):
return False
else:
perror('Option "{}" is unknown, try "ip help".'.format(argv[0]))
exit(255)
if not argv:
return False
for cmd, cmd_func in cmds:
if cmd.startswith(argv[0]):
argv.pop(0)
# Functions return true or terminate with exit(255)
# See help_msg and do_help*
return cmd_func(argv, af, json_print, pretty_json)
perror('Object "{}" is unknown, try "ip help".'.format(argv[0]))
exit(1)
if __name__ == "__main__":
main(sys.argv[1:])

View File

@ -3,4 +3,4 @@
FOLDER=/Applications/OGAgent.app
cd $FOLDER
python -m opengnsys.linux.OGAgentService $@
/usr/local/bin/python3 -m opengnsys.linux.OGAgentService $@

View File

@ -1,17 +1,11 @@
#!/usr/bin/env sh
# Directories
SRCDIR=$(dirname "$0")
BINDIR=/usr/bin
INITDIR=/Library/LaunchDaemons
BINDIR=/usr/local/bin
LAUNCH_AGENTS_DIR=/Library/LaunchAgents
LAUNCH_DAEMONS_DIR=/Library/LaunchDaemons
# Check if it needs to install Python dependencies:
if ! which pip &>/dev/null; then
easy_install pip
pip install netifaces requests six
fi
# Copying files.
cp $SRCDIR/ogagent $BINDIR
cp $SRCDIR/es.opengnsys.ogagent.plist $INITDIR
cp $SRCDIR/ip.py $BINDIR/ip ## override 'ip' from iproute2mac-1.4.2 with a more recent one
cp $SRCDIR/es.opengnsys.agent.system.plist $LAUNCH_DAEMONS_DIR
cp $SRCDIR/es.opengnsys.agent.user.plist $LAUNCH_AGENTS_DIR

0
src/OGAgentUser.py 100644 → 100755
View File

View File

@ -1 +1 @@
1.3.1
1.3.3

View File

@ -42,37 +42,54 @@ import subprocess
import struct
import array
import six
import json
from opengnsys import utils
import netifaces
ip_a_s = None
## make sure /usr/local/bin is in PATH
## we need that to run /usr/local/bin/ip, which uses 'env' and pulls from PATH anyway
def check_path():
path = os.getenv ('PATH', '')
usr_local_bin = '/usr/local/bin'
if usr_local_bin not in path.split (os.pathsep):
os.environ['PATH'] = usr_local_bin + os.pathsep + path
def _getMacAddr(ifname):
'''
Returns the mac address of an interface
Mac is returned as unicode utf-8 encoded
'''
if isinstance(ifname, list):
return dict([(name, _getMacAddr(name)) for name in ifname])
if isinstance(ifname, six.text_type):
ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7)
try:
return netifaces.ifaddresses(ifname)[18][0]['addr']
except Exception:
for interface in ip_a_s:
if interface.get ('ifname') != ifname: continue
return interface.get ('address')
return None
def _getIpAddr(ifname):
'''
Returns the IP address of an interface
Returns the first IP address of an interface
IPv4 is preferred over IPv6
IP is returned as unicode utf-8 encoded
'''
if isinstance(ifname, list):
return dict([(name, _getIpAddr(name)) for name in ifname])
if isinstance(ifname, six.text_type):
ifname = ifname.encode('utf-8') # If unicode, convert to bytes (or str in python 2.7)
## loop and return the first IPv4 address found
for interface in ip_a_s:
if interface.get ('ifname') != ifname: continue
for addr_info in interface.get ('addr_info', []):
ip_address = addr_info.get ('local')
try:
return netifaces.ifaddresses(ifname)[2][0]['addr']
except Exception:
ip_address.index ('.')
return ip_address
except: pass
## if nothing found, loop again and return the first IP found, which will be an IPv6
for interface in ip_a_s:
if interface.get ('ifname') != ifname: continue
for addr_info in interface.get ('addr_info', []):
return addr_info.get ('local')
return None
@ -80,7 +97,14 @@ def _getInterfaces():
'''
Returns a list of interfaces names
'''
return netifaces.interfaces()
global ip_a_s
check_path()
result = subprocess.run (['/usr/local/bin/ip', '-json', 'address', 'show'], capture_output=True, text=True)
if result.returncode != 0: raise Exception (f'Command "ip" failed with exit code {result.returncode}')
ip_a_s = json.loads (result.stdout)
return [i.get('ifname') for i in ip_a_s]
def _getIpAndMac(ifname):
@ -152,7 +176,7 @@ def logoff():
import threading
threading._DummyThread._Thread__stop = lambda x: 42
# Exec logout using AppleSctipt
# Exec logout using AppleScript
subprocess.call('/usr/bin/osascript -e \'tell app "System Events" to «event aevtrlgo»\'', shell=True)
@ -244,7 +268,19 @@ def getSessionLanguage():
'''
Returns the user's session language
'''
return locale.getdefaultlocale()[0]
lang = locale.getdefaultlocale()[0]
if lang is None:
return 'C'
else:
return lang
def get_session_type():
"""
Minimal implementation of this required function
:return: string
"""
return 'unknown'
def showPopup(title, message):

View File

@ -149,6 +149,7 @@ class OpenGnSysWorker(ServerWorker):
break
# Raise error after timeout
if not self.interface:
## UnboundLocalError: cannot access local variable 'e' where it is not associated with a value
raise e
# Loop to send initialization message
@ -332,11 +333,23 @@ class OpenGnSysWorker(ServerWorker):
:return: JSON object {"op": "launched"}
"""
logger.debug('Processing script request')
# Decoding script (Windows scripts need a subprocess call per line)
# Decoding script
script = urllib.parse.unquote(base64.b64decode(post_params.get('script')).decode('utf-8'))
logger.debug('received script {}'.format(script))
if operations.os_type == 'Windows':
script = 'import subprocess; {0}'.format(
';'.join(['subprocess.check_output({0},shell=True)'.format(repr(c)) for c in script.split('\n')]))
## for windows, we turn the script into utf16le, then to b64 again, and feed the blob to powershell
u16 = script.encode ('utf-16le') ## utf16
b64 = base64.b64encode (u16).decode ('utf-8') ## b64 (which returns bytes, so we need an additional decode(utf8))
script = """
import os
import tempfile
import subprocess
cp = subprocess.run ("powershell -WindowStyle Hidden -EncodedCommand {}", capture_output=True)
subprocs_log = os.path.join (tempfile.gettempdir(), 'opengnsys-subprocs.log')
with open (subprocs_log, 'ab') as fd: ## TODO improve this logging
fd.write (cp.stdout)
fd.write (cp.stderr)
""".format (b64)
else:
script = 'import subprocess; subprocess.check_output("""{0}""",shell=True)'.format(script)
# Executing script.

View File

@ -1,4 +1,3 @@
#netifaces <--- este es solo para macos...
pywin32
pyqt6
requests