11import sys
22
3- from sqlalchemy import Table
3+ from sqlalchemy import func , Table , select
4+ from sqlalchemy .dialects .sqlite import Insert # used for upsert
45from sqlalchemy .exc import (
56 NoInspectionAvailable ,
67 NoSuchTableError ,
78)
89
910from nxc .database import BaseDB
10-
11+ from nxc . logger import nxc_logger
1112
1213class database (BaseDB ):
1314 def __init__ (self , db_engine ):
14- self .CredentialsTable = None
15+ self .UsersTable = None
1516 self .HostsTable = None
1617
1718 super ().__init__ (db_engine )
1819
1920 @staticmethod
2021 def db_schema (db_conn ):
2122 db_conn .execute (
22- """CREATE TABLE "credentials " (
23+ """CREATE TABLE "users " (
2324 "id" integer PRIMARY KEY,
25+ "domain" text,
2426 "username" text,
25- "password" text
27+ "password" text,
28+ "credtype" text,
29+ "pillaged_from_hostid" integer,
30+ FOREIGN KEY(pillaged_from_hostid) REFERENCES hosts(id)
2631 )"""
2732 )
2833
@@ -31,14 +36,15 @@ def db_schema(db_conn):
3136 "id" integer PRIMARY KEY,
3237 "ip" text,
3338 "hostname" text,
34- "port" integer
39+ "domain" text,
40+ "os" text
3541 )"""
3642 )
3743
3844 def reflect_tables (self ):
3945 with self .db_engine .connect ():
4046 try :
41- self .CredentialsTable = Table ("credentials " , self .metadata , autoload_with = self .db_engine )
47+ self .UsersTable = Table ("users " , self .metadata , autoload_with = self .db_engine )
4248 self .HostsTable = Table ("hosts" , self .metadata , autoload_with = self .db_engine )
4349 except (NoInspectionAvailable , NoSuchTableError ):
4450 print (
@@ -49,3 +55,113 @@ def reflect_tables(self):
4955 [-] Then remove the nxc { self .protocol } DB (`rm -f { self .db_path } `) and run nxc to initialize the new DB"""
5056 )
5157 sys .exit ()
58+
59+ def add_host (
60+ self ,
61+ ip ,
62+ hostname ,
63+ domain ,
64+ os
65+ ):
66+ """Check if this host has already been added to the database, if not, add it in."""
67+ hosts = []
68+ updated_ids = []
69+
70+ q = select (self .HostsTable ).filter (self .HostsTable .c .ip == ip )
71+ results = self .db_execute (q ).all ()
72+
73+ # create new host
74+ if not results :
75+ new_host = {
76+ "ip" : ip ,
77+ "hostname" : hostname ,
78+ "domain" : domain ,
79+ "os" : os
80+ }
81+ hosts = [new_host ]
82+ # update existing hosts data
83+ else :
84+ for host in results :
85+ host_data = host ._asdict ()
86+ # only update column if it is being passed in
87+ if ip is not None :
88+ host_data ["ip" ] = ip
89+ if hostname is not None :
90+ host_data ["hostname" ] = hostname
91+ if domain is not None :
92+ host_data ["domain" ] = domain
93+ # only add host to be updated if it has changed
94+ if host_data not in hosts :
95+ hosts .append (host_data )
96+ updated_ids .append (host_data ["id" ])
97+ nxc_logger .debug (f"Update Hosts: { hosts } " )
98+
99+ # TODO: find a way to abstract this away to a single Upsert call
100+ q = Insert (self .HostsTable ) # .returning(self.HostsTable.c.id)
101+ update_columns = {col .name : col for col in q .excluded if col .name not in "id" }
102+ q = q .on_conflict_do_update (index_elements = self .HostsTable .primary_key , set_ = update_columns )
103+
104+ self .db_execute (q , hosts ) # .scalar()
105+ # we only return updated IDs for now - when RETURNING clause is allowed we can return inserted
106+ if updated_ids :
107+ nxc_logger .debug (f"add_host() - Host IDs Updated: { updated_ids } " )
108+ return updated_ids
109+
110+ def add_credential (self , credtype , domain , username , password , pillaged_from = None ):
111+ """Check if this credential has already been added to the database, if not add it in."""
112+ credentials = []
113+ groups = []
114+
115+ if pillaged_from and not self .is_host_valid (pillaged_from ):
116+ nxc_logger .debug ("Invalid host" )
117+ return
118+
119+ q = select (self .UsersTable ).filter (
120+ func .lower (self .UsersTable .c .domain ) == func .lower (domain ),
121+ func .lower (self .UsersTable .c .username ) == func .lower (username ),
122+ func .lower (self .UsersTable .c .credtype ) == func .lower (credtype ),
123+ )
124+ results = self .db_execute (q ).all ()
125+
126+ # add new credential
127+ if not results :
128+ new_cred = {
129+ "credtype" : credtype ,
130+ "domain" : domain ,
131+ "username" : username ,
132+ "password" : password ,
133+ "pillaged_from" : pillaged_from ,
134+ }
135+ credentials = [new_cred ]
136+ # update existing cred data
137+ else :
138+ for creds in results :
139+ # this will include the id, so we don't touch it
140+ cred_data = creds ._asdict ()
141+ # only update column if it is being passed in
142+ if credtype is not None :
143+ cred_data ["credtype" ] = credtype
144+ if domain is not None :
145+ cred_data ["domain" ] = domain
146+ if username is not None :
147+ cred_data ["username" ] = username
148+ if password is not None :
149+ cred_data ["password" ] = password
150+ if pillaged_from is not None :
151+ cred_data ["pillaged_from" ] = pillaged_from
152+ # only add cred to be updated if it has changed
153+ if cred_data not in credentials :
154+ credentials .append (cred_data )
155+
156+ # TODO: find a way to abstract this away to a single Upsert call
157+ q_users = Insert (self .UsersTable ) # .returning(self.UsersTable.c.id)
158+ update_columns_users = {col .name : col for col in q_users .excluded if col .name not in "id" }
159+ q_users = q_users .on_conflict_do_update (index_elements = self .UsersTable .primary_key , set_ = update_columns_users )
160+ nxc_logger .debug (f"Adding credentials: { credentials } " )
161+
162+ self .db_execute (q_users , credentials ) # .scalar()
163+
164+ if groups :
165+ q_groups = Insert (self .GroupRelationsTable )
166+
167+ self .db_execute (q_groups , groups )
0 commit comments