Merge pull request #1310 from termanix/nfs-shares-db

Add Shares and Export DB for NFS
This commit is contained in:
Alex authored and GitHub committed 2026-07-12 15:25:53 +02:00
commit d1fc6d5784
4 files changed
+158 -6

No files matched your search

+1
View File
@@ -271,6 +271,7 @@ class nfs(connection):
read_perm, write_perm, exec_perm = self.get_permissions(file_handle)
self.mount.umnt(self.auth)
self.db.add_share(self.host, (read_perm, write_perm, exec_perm), (convert_size(used_space), "/", convert_size(total_space)), share, network)
self.logger.highlight(f"{self.auth['uid']:<11}{'r' if read_perm else '-'}{'w' if write_perm else '-'}{('x' if exec_perm else '-'):<7}{convert_size(used_space) + '/' + convert_size(total_space):<16} {share:<30} {', '.join(network) if network else 'No network':<15}")
except Exception as e:
self.logger.fail(f"Failed to list share: {share} - {e}")
+51 -4
View File
@@ -1,4 +1,4 @@
from sqlalchemy import Boolean, Column, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, UniqueConstraint, String, select
from sqlalchemy import Boolean, Column, ForeignKeyConstraint, Integer, PrimaryKeyConstraint, UniqueConstraint, String, func, select
from sqlalchemy.dialects.sqlite import Insert # used for upsert
from sqlalchemy.orm import declarative_base
@@ -56,12 +56,17 @@ class database(BaseDB):
class Share(Base):
__tablename__ = "shares"
id = Column(Integer)
lir_id = Column(Integer)
data = Column(String)
host = Column(String)
read_perm = Column(Boolean)
write_perm = Column(Boolean)
exec_perm = Column(Boolean)
storage = Column(String)
share = Column(String)
access = Column(String)
__table_args__ = (
PrimaryKeyConstraint("id"),
ForeignKeyConstraint(["lir_id"], ["loggedin_relations.id"]),
UniqueConstraint("host", "share"),
)
@staticmethod
@@ -132,3 +137,45 @@ class database(BaseDB):
if updated_ids:
nxc_logger.debug(f"add_host() - Host IDs Updated: {updated_ids}")
return updated_ids
def add_share(self, host, permission, storage, share, access):
"""Check if this share is already in the DB, if not add it."""
read_perm, write_perm, exec_perm = permission
used, _, total = storage
storage_str = f"{used} / {total}"
access_str = ", ".join(access) if access else ""
new_share = {
"host": host,
"read_perm": read_perm,
"write_perm": write_perm,
"exec_perm": exec_perm,
"storage": storage_str,
"share": share,
"access": access_str,
}
self.db_execute(
Insert(self.SharesTable).on_conflict_do_nothing(),
new_share,
)
def get_shares(self, filter_term=None):
"""Return shares from the database, optionally filtered by host or share path."""
if self.is_share_valid(filter_term):
q = select(self.SharesTable).filter(self.SharesTable.c.id == filter_term)
elif filter_term:
like_term = func.lower(f"%{filter_term}%")
q = select(self.SharesTable).filter(
self.SharesTable.c.host.like(like_term) | self.SharesTable.c.share.like(like_term)
)
else:
q = select(self.SharesTable)
return self.db_execute(q).all()
def is_share_valid(self, share_id):
"""Check if this share ID is valid."""
q = select(self.SharesTable).filter(self.SharesTable.c.id == share_id)
results = self.db_execute(q).all()
nxc_logger.debug(f"is_share_valid(shareID={share_id}) => {len(results) > 0}")
return len(results) > 0
+105 -1
View File
@@ -1,4 +1,4 @@
from nxc.nxcdb import DatabaseNavigator, print_help, print_table
from nxc.nxcdb import DatabaseNavigator, print_help, print_table, write_csv
class navigator(DatabaseNavigator):
@@ -48,3 +48,107 @@ class navigator(DatabaseNavigator):
data.append([host_id, ip, hostname, port, supported_version, root_escape])
print_table(data, title="Hosts")
def display_shares(self, shares):
"""
shares: list of rows (tuples) -> expects columns:
(id, host, read_perm, write_perm, exec_perm, storage, share, access)
"""
data = [["ID", "Host", "R", "W", "X", "Storage", "Share", "Access"]]
for row in shares:
(
sid, host, r, w, x,
storage_str, share_path, access_str
) = row
data.append([
sid, host,
"True" if r else "False",
"True" if w else "False",
"True" if x else "False",
storage_str,
share_path,
access_str,
])
print_table(data, title="Shares")
def do_shares(self, line):
filter_term = line.strip() or None
shares = self.db.get_shares(filter_term=filter_term)
if shares:
self.display_shares(shares)
else:
print("No shares found.")
def help_shares(self):
help_string = """
shares [host_filter]
Lists all shares. Optionally filters by host substring.
Columns:
ID, Host, Read (True/False), Write, Execute, Storage ("used / total"), Share path, Access list
"""
print_help(help_string)
def do_export(self, line):
if not line:
print("[-] not enough arguments")
return
line = line.split()
command = line[0].lower()
if command == "hosts":
if len(line) < 2:
print("[-] invalid arguments, export hosts <filename>")
return
filename = line[2]
hosts = self.db.get_hosts()
csv_header = ("id", "ip", "hostname", "port", "version", "root_escape")
write_csv(filename, csv_header, hosts)
print("[+] NFS hosts exported")
elif command == "shares":
if len(line) < 3:
print("[-] invalid arguments, export shares <simple|detailed> <filename>")
return
filename = line[2]
mode = line[1].lower()
shares = self.db.get_shares()
if mode == "simple":
csv_header = ("id", "host", "read", "write", "exec", "storage", "share", "access")
write_csv(filename, csv_header, shares)
print("[+] NFS shares exported")
elif mode == "detailed":
csv_header = ("id", "host", "version", "read", "write", "exec", "storage", "share", "access")
# hosts columns: (id, ip, hostname, port, nfs_version, root_escape)
version_by_ip = {host[1]: host[4] for host in self.db.get_hosts()}
formatted = []
for row in shares:
entry = (
row[0], # id
row[1], # host
version_by_ip.get(row[1], ""), # nfs_version
row[2], # read
row[3], # write
row[4], # exec
row[5], # storage
row[6], # share
row[7], # access
)
formatted.append(entry)
write_csv(filename, csv_header, formatted)
print("[+] NFS shares exported")
else:
print(f"[-] No such export option: {line[1]}")
else:
print(f"[-] Export command '{command}' is not supported for NFS. Supported: hosts, shares")
def help_export(self):
help_string = """
export hosts <filename>
exports: id, ip, hostname, port, version, root_escape
export shares <simple|detailed> <filename>
simple - exports: id, host, read, write, exec, storage, share, access
detailed - exports: id, host, version, read, write, exec, storage, share, access
"""
print_help(help_string)
Generated
+1 -1
View File
@@ -2274,7 +2274,7 @@ develop = false
type = "git"
url = "https://github.com/Pennyw0rth/NfsClient"
reference = "HEAD"
resolved_reference = "9b61e0a6385743923ccffa6a80f8dbbf0714121d"
resolved_reference = "a8fea2c163bf665dc6a8e35bb50e7baf81e2c6ed"
[[package]]
name = "pyopenssl"