#! /usr/bin/python3 # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import argparse import os import re import sys import apparmor.aa as aa import apparmor.ui as ui import apparmor.common # setup exception handling from apparmor.fail import enable_aa_exception_handler enable_aa_exception_handler() # setup module translations from apparmor.translations import init_translation _ = init_translation() parser = argparse.ArgumentParser(description=_("Lists unconfined processes having tcp or udp ports")) parser.add_argument("--paranoid", action="store_true", help=_("scan all processes from /proc")) args = parser.parse_args() paranoid = args.paranoid aa_mountpoint = aa.check_for_apparmor() if not aa_mountpoint: raise aa.AppArmorException(_("It seems AppArmor was not started. Please enable AppArmor and try again.")) pids = [] if paranoid: pids = list(filter(lambda x: re.search(r"^\d+$", x), aa.get_subdirectories("/proc"))) else: regex_tcp_udp = re.compile(r"^(tcp|udp|raw)6?\s+\d+\s+\d+\s+\S+\:(\d+)\s+\S+\:(\*|\d+)\s+(LISTEN|\d+|\s+)\s+(\d+)\/(\S+)") import subprocess if sys.version_info < (3, 0): output = subprocess.check_output("LANG=C netstat -nlp46", shell=True).split("\n") else: #Python3 needs to translate a stream of bytes to string with specified encoding output = str(subprocess.check_output("LANG=C netstat -nlp46", shell=True), encoding='utf8').split("\n") for line in output: match = regex_tcp_udp.search(line) if match: pids.append(match.groups()[4]) # We can safely remove duplicate pid's? pids = list(map(int, set(pids))) for pid in sorted(pids): try: prog = os.readlink("/proc/%s/exe"%pid) except OSError: continue attr = None if os.path.exists("/proc/%s/attr/current"%pid): with aa.open_file_read("/proc/%s/attr/current"%pid) as current: for line in current: line = line.strip() if line.endswith(' (complain)', 1) or line.endswith(' (enforce)', 1): # enforce at least one char as profile name attr = line cmdline = apparmor.common.cmd(["cat", "/proc/%s/cmdline"%pid])[1] pname = cmdline.split("\0")[0] if '/' in pname and pname != prog: pname = "(%s)"% pname else: pname = "" regex_interpreter = re.compile(r"^(/usr)?/bin/(python|perl|bash|dash|sh)$") if not attr: if regex_interpreter.search(prog): cmdline = re.sub(r"\x00", " ", cmdline) cmdline = re.sub(r"\s+$", "", cmdline).strip() ui.UI_Info(_("%(pid)s %(program)s (%(commandline)s) not confined") % { 'pid': pid, 'program': prog, 'commandline': cmdline }) else: if pname and pname[-1] == ')': pname = ' ' + pname ui.UI_Info(_("%(pid)s %(program)s%(pname)s not confined") % { 'pid': pid, 'program': prog, 'pname': pname }) else: if regex_interpreter.search(prog): cmdline = re.sub(r"\0", " ", cmdline) cmdline = re.sub(r"\s+$", "", cmdline).strip() ui.UI_Info(_("%(pid)s %(program)s (%(commandline)s) confined by '%(attribute)s'") % { 'pid': pid, 'program': prog, 'commandline': cmdline, 'attribute': attr }) else: if pname and pname[-1] == ')': pname = ' ' + pname ui.UI_Info(_("%(pid)s %(program)s%(pname)s confined by '%(attribute)s'") % { 'pid': pid, 'program': prog, 'pname': pname, 'attribute': attr })