예제 #1
0
def _prompt(finalize, conn, prompt_conn):
    # we match rule again here in case a rule was added while this conn was waiting in the queue
    rule = match_rule(*conn)
    if rule:
        action, _duration, _start = rule
        if action == 'deny':
            finalize('deny', conn)
            return 'deny'
        else:
            return 'allow'
    else:
        formatted = tinysnitch.dns.format(*prompt_conn)
        formatted = formatted.replace('$', '\$').replace('(', '\(').replace(
            ')', '\)').replace('`', '\`')
        output = tinysnitch.lib.check_output(
            f'su {_prompt_user} -c \'DISPLAY=:0 tinysnitch-prompt "{formatted}"\' 2>/tmp/tinysnitch_prompt.log'
        )
        try:
            duration, subdomains, action, ports = output.split()
        except ValueError:
            log(f'output: {output}')
            log(f'FATAL failed to run tinysnitch-prompt\n' +
                tinysnitch.lib.check_output(
                    'cat /tmp/tinysnitch_prompt.log || true'))
            sys.exit(1)
        else:
            action = _process_rule(conn, duration, subdomains, action, ports)
            if action == 'deny':
                finalize('deny', conn)
                return 'deny'
            else:
                return 'allow'
예제 #2
0
def _process_rule(conn, duration, subdomains, action, ports):
    _src, dst, _src_port, dst_port, proto = conn
    if ports == "no":
        dst_port = '-'
    if duration == 'once':
        return action
    else:
        _duration = duration
        if '-minute' in duration:
            minutes = int(duration.split('-')[0])
            duration = 60 * minutes
        elif '-hour' in duration:
            hours = int(duration.split('-')[0])
            duration = 60 * 60 * hours
        elif duration == 'forever':
            duration = None
        if subdomains == 'yes':
            dst = '*.' + '.'.join(dst.split('.')[-2:])
        start = time.monotonic()
        _add_rule(action, duration, start, dst, dst_port, proto)
        if duration is None:
            with open(state.rules_file, 'a') as f:
                f.write(f'{action} {dst} {dst_port} {proto}\n')
            log(f'INFO add permanent rule {action} {dst} {dst_port} {proto}')
        else:
            log(f'INFO add temporary rule {action} {_duration} {dst} {dst_port} {proto}'
                )
        return action
예제 #3
0
def _process_prompt_queue():
    while True:
        finalize, conn = state._prompt_queue.get()
        _src, dst, _src_port, dst_port, _proto = conn
        action = _prompt(finalize, conn, conn)
        finalize(action, conn)
    log('FATAL process-prompt-queue exited prematurely')
    sys.exit(1)
예제 #4
0
def _finalize(nfq, id, data, size, action, conn):
    _src, _dst, _src_port, _dst_port, proto = conn
    if not tinysnitch.dns.is_inbound_dns(
            *conn) and proto in tinysnitch.lib.protos:
        log(f'INFO {action} {tinysnitch.dns.format(*conn)}')
    if action == 'allow':
        lib.nfq_set_verdict(nfq, id, ONE, ZERO, NULL)
    elif action == 'deny':
        lib.nfq_set_verdict2(nfq, id, ONE, MARK, size, data)
    else:
        assert False, f'bad action: {action}'
예제 #5
0
def _gc():
    while True:
        for k, (action, duration, start) in list(state._rules.items()):
            dst, dst_port, proto = k
            if isinstance(duration,
                          int) and time.monotonic() - start > duration:
                log(f'INFO gc expired rule {action} {dst} {dst_port} {proto}')
                del state._rules[k]
        time.sleep(3)
    log('FATAL rules gc exited prematurely')
    sys.exit(1)
예제 #6
0
def update_hosts(packet):
    if UDP in packet and DNS in packet:
        addrs = []
        for name, addr in _parse_dns(packet):
            name = name.lower()
            if addr and name != state._hosts.get(addr):
                state._hosts[addr] = name
                addrs.append(addr)
                state._new_addrs.append(addr)
        if addrs:
            log(f'INFO dns {name} {" ".join(addrs)}')
예제 #7
0
def _persister():
    while True:
        while True:
            with open(_hosts_file, 'a') as f:
                try:
                    addr = state._new_addrs.pop()
                except IndexError:
                    break
                else:
                    f.write(f'{addr} {state._hosts[addr]}\n')
        time.sleep(1)
    log('FATAL dns persister exited prematurely')
    sys.exit(1)
예제 #8
0
def _log_sizes():
    while True:
        states = [
            tinysnitch.dns.state, tinysnitch.rules.state,
            tinysnitch.netfilter.state
        ]
        sizes = [
            f'{state.__module__.split(".")[-1]}.{k}:{len(v)}'
            for state in states for k, v in state.__dict__.items()
            if isinstance(v, dict)
        ]
        log(f"INFO sizes {' '.join(sizes)}")
        time.sleep(5)
    log('FATAL log sizes exited prematurely')
    sys.exit(1)
예제 #9
0
def _load_permanent_rules():
    try:
        with open(state.rules_file) as f:
            lines = reversed(f.read().splitlines()
                             )  # lines at top of file are higher priority
    except FileNotFoundError:
        with open(state.rules_file, 'w') as f:
            lines = []
    i = 0
    lines = [l.split('#')[-1] for l in lines]
    lines = [l for l in lines if l.strip()]
    for i, line in enumerate(lines):
        rule = _parse_rule(line)
        if rule:
            action, dst, dst_port, proto = rule
            duration = start = None
            _add_rule(action, duration, start, dst, dst_port, proto)
            log(f'INFO loaded rule {action} {dst} {dst_port} {proto}')
    if list(lines):
        log(f'INFO loaded {i + 1} rules from {state.rules_file}')
예제 #10
0
#
# Software distributed under the License is distributed
# on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
# express or implied. See the GPL for the specific language
# governing rights and limitations.
#
# You should have received a copy of the GPL along with this
# program. If not, go to http://www.gnu.org/licenses/gpl.html
# or write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

from tinysnitch.lib import log

try:
    from tinysnitch._netfilter import ffi, lib
    log('use existing ffi binaries')
except ModuleNotFoundError:
    log('recompile ffi binaries')
    import tinysnitch.netfilter_build
    import os
    orig = os.getcwd()
    os.chdir(os.path.dirname(os.path.abspath(__file__)))
    print(os.getcwd(), flush=True)
    for path in os.listdir('.'):
        if path.endswith('.c') or path.endswith('.o') or path.endswith('.so'):
            os.remove(path)
    tinysnitch.netfilter_build.ffibuilder.compile(verbose=True)
    os.chdir(orig)
    from tinysnitch._netfilter import ffi, lib

import tinysnitch.dns
예제 #11
0
def _parse_rule(line):
    try:
        action, dst, dst_port, proto = line.split(None, 3)
    except ValueError:
        log(f'ERROR invalid rule, should have been "action dst dst_port proto", was {line}'
            )
        traceback.print_exc()
        return
    try:
        if dst_port != '-':
            dst_port = int(dst_port)
    except ValueError:
        log(f'ERROR invalid rule {line}')
        log(f'ERROR ports should be numbers, was {dst_port}')
        return
    if proto not in tinysnitch.lib.protos:
        log(f'ERROR invalid rule {line}')
        log(f'ERROR bad proto, should be one of {tinysnitch.lib.protos}, was {proto}'
            )
        return
    if action not in _actions:
        log(f'ERROR invalid rule {line}')
        log(f'ERROR bad action, should be one of {_actions}, was {action}')
        return
    return action, dst, dst_port, proto