mirror of
https://github.com/infinition/Bjorn.git
synced 2026-03-15 17:01:58 +00:00
Add Loki and Sentinel utility classes for web API endpoints
- Implemented LokiUtils class with GET and POST endpoints for managing scripts, jobs, and payloads. - Added SentinelUtils class with GET and POST endpoints for managing events, rules, devices, and notifications. - Both classes include error handling and JSON response formatting.
This commit is contained in:
103
bifrost/bettercap.py
Normal file
103
bifrost/bettercap.py
Normal file
@@ -0,0 +1,103 @@
|
||||
"""
|
||||
Bifrost — Bettercap REST API client.
|
||||
Ported from pwnagotchi/bettercap.py using urllib (no requests dependency).
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import base64
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
from logger import Logger
|
||||
|
||||
logger = Logger(name="bifrost.bettercap", level=logging.DEBUG)
|
||||
|
||||
|
||||
class BettercapClient:
|
||||
"""Synchronous REST client for the bettercap API."""
|
||||
|
||||
def __init__(self, hostname='127.0.0.1', scheme='http', port=8081,
|
||||
username='user', password='pass'):
|
||||
self.hostname = hostname
|
||||
self.scheme = scheme
|
||||
self.port = port
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.url = "%s://%s:%d/api" % (scheme, hostname, port)
|
||||
self.websocket = "ws://%s:%s@%s:%d/api" % (username, password, hostname, port)
|
||||
self._auth_header = 'Basic ' + base64.b64encode(
|
||||
('%s:%s' % (username, password)).encode()
|
||||
).decode()
|
||||
|
||||
def _request(self, method, path, data=None, verbose_errors=True):
|
||||
"""Make an HTTP request to bettercap API."""
|
||||
url = "%s%s" % (self.url, path)
|
||||
body = json.dumps(data).encode() if data else None
|
||||
req = urllib.request.Request(url, data=body, method=method)
|
||||
req.add_header('Authorization', self._auth_header)
|
||||
if body:
|
||||
req.add_header('Content-Type', 'application/json')
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
raw = resp.read().decode('utf-8')
|
||||
try:
|
||||
return json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
return raw
|
||||
except urllib.error.HTTPError as e:
|
||||
err = "error %d: %s" % (e.code, e.read().decode('utf-8', errors='replace').strip())
|
||||
if verbose_errors:
|
||||
logger.info(err)
|
||||
raise Exception(err)
|
||||
except urllib.error.URLError as e:
|
||||
raise Exception("bettercap unreachable: %s" % e.reason)
|
||||
|
||||
def session(self):
|
||||
"""GET /api/session — current bettercap state."""
|
||||
return self._request('GET', '/session')
|
||||
|
||||
def run(self, command, verbose_errors=True):
|
||||
"""POST /api/session — execute a bettercap command."""
|
||||
return self._request('POST', '/session', {'cmd': command},
|
||||
verbose_errors=verbose_errors)
|
||||
|
||||
def events(self):
|
||||
"""GET /api/events — poll recent events (REST fallback)."""
|
||||
try:
|
||||
result = self._request('GET', '/events', verbose_errors=False)
|
||||
# Clear after reading so we don't reprocess
|
||||
try:
|
||||
self.run('events.clear', verbose_errors=False)
|
||||
except Exception:
|
||||
pass
|
||||
return result if isinstance(result, list) else []
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
async def start_websocket(self, consumer, stop_event=None):
|
||||
"""Connect to bettercap websocket event stream.
|
||||
|
||||
Args:
|
||||
consumer: async callable that receives each message string.
|
||||
stop_event: optional threading.Event — exit when set.
|
||||
"""
|
||||
import websockets
|
||||
import asyncio
|
||||
ws_url = "%s/events" % self.websocket
|
||||
while not (stop_event and stop_event.is_set()):
|
||||
try:
|
||||
async with websockets.connect(ws_url, ping_interval=60,
|
||||
ping_timeout=90) as ws:
|
||||
async for msg in ws:
|
||||
if stop_event and stop_event.is_set():
|
||||
return
|
||||
try:
|
||||
await consumer(msg)
|
||||
except Exception as ex:
|
||||
logger.debug("Error parsing event: %s", ex)
|
||||
except Exception as ex:
|
||||
if stop_event and stop_event.is_set():
|
||||
return
|
||||
logger.debug("Websocket error: %s — reconnecting...", ex)
|
||||
await asyncio.sleep(2)
|
||||
Reference in New Issue
Block a user