Skip to content

Commit ae121c5

Browse files
authored
Merge pull request #123 from Pennyw0rth/nxcdb-marshall
nxcdb: refactor shared database/workspace setup code & allow for creation/setting of workspaces outside of nxcdb interactive console
2 parents 1645f67 + f76fb16 commit ae121c5

File tree

6 files changed

+154
-98
lines changed

6 files changed

+154
-98
lines changed

nxc/database.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import sys
2+
import configparser
3+
import shutil
4+
from sqlalchemy import create_engine
5+
from sqlite3 import connect
6+
from os import mkdir
7+
from os.path import exists
8+
from os.path import join as path_join
9+
10+
from nxc.loaders.protocolloader import ProtocolLoader
11+
from nxc.paths import WORKSPACE_DIR
12+
13+
14+
def create_db_engine(db_path):
15+
return create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True)
16+
17+
18+
def open_config(config_path):
19+
try:
20+
config = configparser.ConfigParser()
21+
config.read(config_path)
22+
except Exception as e:
23+
print(f"[-] Error reading nxc.conf: {e}")
24+
sys.exit(1)
25+
return config
26+
27+
28+
def get_workspace(config):
29+
return config.get("nxc", "workspace")
30+
31+
32+
def set_workspace(config_path, workspace_name):
33+
config = open_config(config_path)
34+
config.set("nxc", "workspace", workspace_name)
35+
write_configfile(config, config_path)
36+
print(f"[*] Workspace set to {workspace_name}")
37+
38+
39+
def get_db(config):
40+
return config.get("nxc", "last_used_db")
41+
42+
43+
def write_configfile(config, config_path):
44+
with open(config_path, "w") as configfile:
45+
config.write(configfile)
46+
47+
48+
def create_workspace(workspace_name, p_loader=None):
49+
"""
50+
Create a new workspace with the given name.
51+
52+
Args:
53+
----
54+
workspace_name (str): The name of the workspace.
55+
56+
Returns:
57+
-------
58+
None
59+
"""
60+
if exists(path_join(WORKSPACE_DIR, workspace_name)):
61+
print(f"[-] Workspace {workspace_name} already exists")
62+
else:
63+
print(f"[*] Creating {workspace_name} workspace")
64+
mkdir(path_join(WORKSPACE_DIR, workspace_name))
65+
66+
if p_loader is None:
67+
p_loader = ProtocolLoader()
68+
protocols = p_loader.get_protocols()
69+
70+
for protocol in protocols:
71+
protocol_object = p_loader.load_protocol(protocols[protocol]["dbpath"])
72+
proto_db_path = path_join(WORKSPACE_DIR, workspace_name, f"{protocol}.db")
73+
74+
if not exists(proto_db_path):
75+
print(f"[*] Initializing {protocol.upper()} protocol database")
76+
conn = connect(proto_db_path)
77+
c = conn.cursor()
78+
79+
# try to prevent some weird sqlite I/O errors
80+
c.execute("PRAGMA journal_mode = OFF")
81+
c.execute("PRAGMA foreign_keys = 1")
82+
83+
protocol_object.database.db_schema(c)
84+
85+
# commit the changes and close everything off
86+
conn.commit()
87+
conn.close()
88+
89+
90+
def delete_workspace(workspace_name):
91+
shutil.rmtree(path_join(WORKSPACE_DIR, workspace_name))
92+
print(f"[*] Workspace {workspace_name} deleted")
93+
94+
95+
def initialize_db():
96+
if not exists(path_join(WORKSPACE_DIR, "default")):
97+
create_workspace("default")

nxc/first_run.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from os.path import join as path_join
44
import shutil
55
from nxc.paths import NXC_PATH, CONFIG_PATH, TMP_PATH, DATA_PATH
6-
from nxc.nxcdb import initialize_db
6+
from nxc.database import initialize_db
77
from nxc.logger import nxc_logger
88

99

@@ -29,7 +29,7 @@ def first_run_setup(logger=nxc_logger):
2929
logger.display(f"Creating missing folder {folder}")
3030
mkdir(path_join(NXC_PATH, folder))
3131

32-
initialize_db(logger)
32+
initialize_db()
3333

3434
if not exists(CONFIG_PATH):
3535
logger.display("Copying default configuration file")

nxc/netexec.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from nxc.console import nxc_console
1313
from nxc.logger import nxc_logger
1414
from nxc.config import nxc_config, nxc_workspace, config_log, ignore_opsec
15+
from nxc.database import create_db_engine
1516
from concurrent.futures import ThreadPoolExecutor, as_completed
1617
import asyncio
1718
from nxc.helpers import powershell
@@ -21,7 +22,6 @@
2122
from os.path import join as path_join
2223
from sys import exit
2324
import logging
24-
import sqlalchemy
2525
from rich.progress import Progress
2626
import platform
2727

@@ -38,11 +38,6 @@
3838
resource.setrlimit(resource.RLIMIT_NOFILE, file_limit)
3939

4040

41-
42-
def create_db_engine(db_path):
43-
return sqlalchemy.create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True)
44-
45-
4641
async def start_run(protocol_obj, args, db, targets):
4742
nxc_logger.debug("Creating ThreadPoolExecutor")
4843
if args.no_progress or len(targets) == 1:

nxc/nxcdb.py

Lines changed: 51 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,25 @@
11
import cmd
2-
import configparser
32
import csv
3+
import sys
44
import os
5+
import argparse
56
from os import listdir
67
from os.path import exists
78
from os.path import join as path_join
8-
import shutil
9-
from sqlite3 import connect
10-
import sys
119
from textwrap import dedent
12-
1310
from requests import get, post, ConnectionError
14-
from sqlalchemy import create_engine
1511
from terminaltables import AsciiTable
12+
from termcolor import colored
1613

1714
from nxc.loaders.protocolloader import ProtocolLoader
18-
from nxc.paths import CONFIG_PATH, WS_PATH, WORKSPACE_DIR
15+
from nxc.paths import CONFIG_PATH, WORKSPACE_DIR
16+
from nxc.database import create_db_engine, open_config, get_workspace, get_db, write_configfile, create_workspace, set_workspace
1917

2018

2119
class UserExitedProto(Exception):
2220
pass
2321

2422

25-
def create_db_engine(db_path):
26-
return create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True)
27-
28-
2923
def print_table(data, title=None):
3024
print()
3125
table = AsciiTable(data)
@@ -446,29 +440,15 @@ class NXCDBMenu(cmd.Cmd):
446440
def __init__(self, config_path):
447441
cmd.Cmd.__init__(self)
448442
self.config_path = config_path
449-
450-
try:
451-
self.config = configparser.ConfigParser()
452-
self.config.read(self.config_path)
453-
except Exception as e:
454-
print(f"[-] Error reading nxc.conf: {e}")
455-
sys.exit(1)
456-
457443
self.conn = None
458444
self.p_loader = ProtocolLoader()
459445
self.protocols = self.p_loader.get_protocols()
460446

461-
self.workspace = self.config.get("nxc", "workspace")
447+
self.config = open_config(self.config_path)
448+
self.workspace = get_workspace(self.config)
449+
self.db = get_db(self.config)
462450
self.do_workspace(self.workspace)
463451

464-
self.db = self.config.get("nxc", "last_used_db")
465-
if self.db:
466-
self.do_proto(self.db)
467-
468-
def write_configfile(self):
469-
with open(self.config_path, "w") as configfile:
470-
self.config.write(configfile)
471-
472452
def do_proto(self, proto):
473453
if not proto:
474454
return
@@ -479,7 +459,7 @@ def do_proto(self, proto):
479459
db_nav_object = self.p_loader.load_protocol(self.protocols[proto]["nvpath"])
480460
db_object = self.p_loader.load_protocol(self.protocols[proto]["dbpath"])
481461
self.config.set("nxc", "last_used_db", proto)
482-
self.write_configfile()
462+
write_configfile(self.config, self.config_path)
483463
try:
484464
proto_menu = db_nav_object.navigator(self, db_object.database(self.conn), proto)
485465
proto_menu.cmdloop()
@@ -506,18 +486,18 @@ def do_workspace(self, line):
506486
if subcommand == "create":
507487
new_workspace = line.split()[1].strip()
508488
print(f"[*] Creating workspace '{new_workspace}'")
509-
self.create_workspace(new_workspace, self.p_loader, self.protocols)
489+
create_workspace(new_workspace, self.p_loader)
510490
self.do_workspace(new_workspace)
511491
elif subcommand == "list":
512492
print("[*] Enumerating Workspaces")
513493
for workspace in listdir(path_join(WORKSPACE_DIR)):
514494
if workspace == self.workspace:
515-
print("==> " + workspace)
495+
print(f" * {colored(workspace, 'green')}")
516496
else:
517-
print(workspace)
497+
print(f" {workspace}")
518498
elif exists(path_join(WORKSPACE_DIR, line)):
519499
self.config.set("nxc", "workspace", line)
520-
self.write_configfile()
500+
write_configfile(self.config, self.config_path)
521501
self.workspace = line
522502
self.prompt = f"nxcdb ({line}) > "
523503

@@ -538,65 +518,49 @@ def help_exit():
538518
Exits
539519
"""
540520
print_help(help_string)
541-
542-
@staticmethod
543-
def create_workspace(workspace_name, p_loader, protocols):
544-
os.mkdir(path_join(WORKSPACE_DIR, workspace_name))
545-
546-
for protocol in protocols:
547-
protocol_object = p_loader.load_protocol(protocols[protocol]["dbpath"])
548-
proto_db_path = path_join(WORKSPACE_DIR, workspace_name, f"{protocol}.db")
549-
550-
if not exists(proto_db_path):
551-
print(f"[*] Initializing {protocol.upper()} protocol database")
552-
conn = connect(proto_db_path)
553-
c = conn.cursor()
554-
555-
# try to prevent some weird sqlite I/O errors
556-
c.execute("PRAGMA journal_mode = OFF")
557-
c.execute("PRAGMA foreign_keys = 1")
558-
559-
protocol_object.database.db_schema(c)
560-
561-
# commit the changes and close everything off
562-
conn.commit()
563-
conn.close()
564-
565-
566-
def delete_workspace(workspace_name):
567-
shutil.rmtree(path_join(WORKSPACE_DIR, workspace_name))
568-
569-
570-
def initialize_db(logger):
571-
if not exists(path_join(WS_PATH, "default")):
572-
logger.debug("Creating default workspace")
573-
os.mkdir(path_join(WS_PATH, "default"))
574-
575-
p_loader = ProtocolLoader()
576-
protocols = p_loader.get_protocols()
577-
for protocol in protocols:
578-
protocol_object = p_loader.load_protocol(protocols[protocol]["dbpath"])
579-
proto_db_path = path_join(WS_PATH, "default", f"{protocol}.db")
580-
581-
if not exists(proto_db_path):
582-
logger.debug(f"Initializing {protocol.upper()} protocol database")
583-
conn = connect(proto_db_path)
584-
c = conn.cursor()
585-
# try to prevent some weird sqlite I/O errors
586-
c.execute("PRAGMA journal_mode = OFF") # could try setting to PERSIST if DB corruption starts occurring
587-
c.execute("PRAGMA foreign_keys = 1")
588-
# set a small timeout (5s) so if another thread is writing to the database, the entire program doesn't crash
589-
c.execute("PRAGMA busy_timeout = 5000")
590-
protocol_object.database.db_schema(c)
591-
# commit the changes and close everything off
592-
conn.commit()
593-
conn.close()
594-
521+
595522

596523
def main():
597524
if not exists(CONFIG_PATH):
598525
print("[-] Unable to find config file")
599526
sys.exit(1)
527+
528+
parser = argparse.ArgumentParser(
529+
description="NXCDB is a database navigator for NXC",
530+
)
531+
parser.add_argument(
532+
"-gw",
533+
"--get-workspace",
534+
action="store_true",
535+
help="get the current workspace",
536+
)
537+
parser.add_argument(
538+
"-cw",
539+
"--create-workspace",
540+
help="create a new workspace",
541+
)
542+
parser.add_argument(
543+
"-sw",
544+
"--set-workspace",
545+
help="set the current workspace",
546+
)
547+
args = parser.parse_args()
548+
549+
if args.create_workspace:
550+
create_workspace(args.create_workspace)
551+
sys.exit()
552+
if args.set_workspace:
553+
set_workspace(CONFIG_PATH, args.set_workspace)
554+
sys.exit()
555+
if args.get_workspace:
556+
current_workspace = get_workspace(open_config(CONFIG_PATH))
557+
for workspace in listdir(path_join(WORKSPACE_DIR)):
558+
if workspace == current_workspace:
559+
print(f" * {colored(workspace, 'green')}")
560+
else:
561+
print(f" {workspace}")
562+
sys.exit()
563+
600564
try:
601565
nxcdbnav = NXCDBMenu(CONFIG_PATH)
602566
nxcdbnav.cmdloop()

nxc/paths.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
TMP_PATH = os.getenv("LOCALAPPDATA") + "\\Temp\\nxc_hosted"
99
if hasattr(sys, "getandroidapilevel"):
1010
TMP_PATH = os.path.join("/data", "data", "com.termux", "files", "usr", "tmp", "nxc_hosted")
11-
WS_PATH = os.path.join(NXC_PATH, "workspaces")
11+
1212
CERT_PATH = os.path.join(NXC_PATH, "nxc.pem")
1313
CONFIG_PATH = os.path.join(NXC_PATH, "nxc.conf")
1414
WORKSPACE_DIR = os.path.join(NXC_PATH, "workspaces")

tests/test_smb_database.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
from nxc.first_run import first_run_setup
88
from nxc.loaders.protocolloader import ProtocolLoader
99
from nxc.logger import NXCAdapter
10-
from nxc.paths import WS_PATH
10+
from nxc.paths import WORKSPACE_DIR
1111
from sqlalchemy.dialects.sqlite import Insert
1212

1313

1414
@pytest.fixture(scope="session")
1515
def db_engine():
16-
db_path = os.path.join(WS_PATH, "test/smb.db")
16+
db_path = os.path.join(WORKSPACE_DIR, "test/smb.db")
1717
db_engine = create_engine(f"sqlite:///{db_path}", isolation_level="AUTOCOMMIT", future=True)
1818
yield db_engine
1919
db_engine.dispose()

0 commit comments

Comments
 (0)