mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 09:49:23 +02:00
Merge remote-tracking branch 'origin/GP-5167_ghidra1_BSimUsername--SQUASHED'
This commit is contained in:
commit
e07aa499ea
34 changed files with 1097 additions and 429 deletions
|
@ -26,7 +26,7 @@ import ghidra.app.script.GhidraScript;
|
|||
import ghidra.features.base.values.GhidraValuesMap;
|
||||
import ghidra.features.bsim.query.*;
|
||||
import ghidra.features.bsim.query.BSimServerInfo.DBType;
|
||||
import ghidra.features.bsim.query.FunctionDatabase.Error;
|
||||
import ghidra.features.bsim.query.FunctionDatabase.BSimError;
|
||||
import ghidra.features.bsim.query.FunctionDatabase.ErrorCategory;
|
||||
import ghidra.features.bsim.query.description.DatabaseInformation;
|
||||
import ghidra.features.bsim.query.description.DescriptionManager;
|
||||
|
@ -71,8 +71,7 @@ public class AddProgramToH2BSimDatabaseScript extends GhidraScript {
|
|||
askValues("Select Database File", null, values);
|
||||
|
||||
File h2DbFile = values.getFile(DATABASE);
|
||||
BSimServerInfo serverInfo =
|
||||
new BSimServerInfo(DBType.file, null, 0, h2DbFile.getAbsolutePath());
|
||||
BSimServerInfo serverInfo = new BSimServerInfo(h2DbFile.getAbsolutePath());
|
||||
|
||||
BSimH2FileDataSource existingBDS =
|
||||
BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo);
|
||||
|
@ -129,7 +128,7 @@ public class AddProgramToH2BSimDatabaseScript extends GhidraScript {
|
|||
InsertRequest insertreq = new InsertRequest();
|
||||
insertreq.manage = manager;
|
||||
if (insertreq.execute(h2Database) == null) {
|
||||
Error lastError = h2Database.getLastError();
|
||||
BSimError lastError = h2Database.getLastError();
|
||||
if ((lastError.category == ErrorCategory.Format) ||
|
||||
(lastError.category == ErrorCategory.Nonfatal)) {
|
||||
Msg.showWarn(this, null, "Skipping Insert",
|
||||
|
|
|
@ -25,7 +25,7 @@ import ghidra.app.script.GhidraScript;
|
|||
import ghidra.features.base.values.GhidraValuesMap;
|
||||
import ghidra.features.bsim.query.*;
|
||||
import ghidra.features.bsim.query.BSimServerInfo.DBType;
|
||||
import ghidra.features.bsim.query.FunctionDatabase.Error;
|
||||
import ghidra.features.bsim.query.FunctionDatabase.BSimError;
|
||||
import ghidra.features.bsim.query.description.DatabaseInformation;
|
||||
import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager;
|
||||
import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource;
|
||||
|
@ -89,8 +89,7 @@ public class CreateH2BSimDatabaseScript extends GhidraScript {
|
|||
List<String> cats = parseCSV(exeCatCSV);
|
||||
|
||||
File dbFile = new File(dbDir, databaseName);
|
||||
BSimServerInfo serverInfo =
|
||||
new BSimServerInfo(DBType.file, null, 0, dbFile.getAbsolutePath());
|
||||
BSimServerInfo serverInfo = new BSimServerInfo(dbFile.getAbsolutePath());
|
||||
|
||||
BSimH2FileDataSource existingBDS =
|
||||
BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo);
|
||||
|
@ -118,7 +117,7 @@ public class CreateH2BSimDatabaseScript extends GhidraScript {
|
|||
req.tag_name = tag;
|
||||
ResponseInfo resp = req.execute(h2Database);
|
||||
if (resp == null) {
|
||||
Error lastError = h2Database.getLastError();
|
||||
BSimError lastError = h2Database.getLastError();
|
||||
throw new LSHException(lastError.message);
|
||||
}
|
||||
}
|
||||
|
@ -128,7 +127,7 @@ public class CreateH2BSimDatabaseScript extends GhidraScript {
|
|||
req.type_name = cat;
|
||||
ResponseInfo resp = req.execute(h2Database);
|
||||
if (resp == null) {
|
||||
Error lastError = h2Database.getLastError();
|
||||
BSimError lastError = h2Database.getLastError();
|
||||
throw new LSHException(lastError.message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
@ -130,7 +130,7 @@ public class QueryWithFiltersScript extends GhidraScript {
|
|||
String dbUrl =
|
||||
askString("", "Enter the URL of the BSim database:", "ghidra://localhost/bsimDb");
|
||||
queryService.initializeDatabase(dbUrl);
|
||||
FunctionDatabase.Error error = queryService.getLastError();
|
||||
FunctionDatabase.BSimError error = queryService.getLastError();
|
||||
if (error != null && error.category == ErrorCategory.Nodatabase) {
|
||||
println("Database [" + dbUrl + "] cannot be found (does it exist?)");
|
||||
return;
|
||||
|
|
|
@ -326,7 +326,12 @@
|
|||
<DD>
|
||||
<P>Creates a new empty repository. A URL and configuration template (<SPAN class=
|
||||
"bold"><STRONG>config_template</STRONG></SPAN>) is required. The new database name
|
||||
is taken from the path element of the URL.</P>
|
||||
is taken from the path element of the URL. See <A class="xref" href=
|
||||
"DatabaseConfiguration.html#CreateDatabase" title="Creating a Database">“Creating a
|
||||
Database”</A> for more details and discussion on configuration template use.
|
||||
See <A class="xref" href="DatabaseConfiguration.html#DatabaseTemplates"
|
||||
title="Creating Database Templates">“Creating Database Templates“</A>
|
||||
if a standard template will not suffice.</P>
|
||||
|
||||
<P>Supported configuration templates (<SPAN class=
|
||||
"bold"><STRONG>config_template</STRONG></SPAN>) are defined within the Ghidra
|
||||
|
@ -792,21 +797,21 @@
|
|||
<TD>PostgreSQL</TD>
|
||||
|
||||
<TD><CODE class=
|
||||
"computeroutput">postgresql://<hostname>[:<port>]/<dbname></CODE></TD>
|
||||
"computeroutput">postgresql://[<username>@]<hostname>[:<port>]/<dbname></CODE></TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD>Elasticsearch</TD>
|
||||
|
||||
<TD><CODE class=
|
||||
"computeroutput">https://<hostname>[:<port>]/<dbname></CODE></TD>
|
||||
"computeroutput">https://[<username>@]<hostname>[:<port>]/<dbname></CODE></TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD>Elasticsearch</TD>
|
||||
|
||||
<TD><CODE class=
|
||||
"computeroutput">elastic://<hostname>[:<port>]/<dbname></CODE></TD>
|
||||
"computeroutput">elastic://[<username>@]<hostname>[:<port>]/<dbname></CODE></TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
|
@ -819,6 +824,24 @@
|
|||
</DIV>
|
||||
|
||||
<P>The use of the <EM>https</EM> and <EM>elastic</EM> is equivalent.</P>
|
||||
|
||||
<P><IMG border="0" src="help/shared/tip.png" alt="Tip: ">The inclusion of a
|
||||
<CODE class="computeroutput"><username></CODE> within a BSim URL
|
||||
supercedes the concurrent use of the <SPAN class="command"><STRONG>--user</STRONG></SPAN>
|
||||
option which can still be used to control login to the Ghidra Server. When a
|
||||
<CODE class="computeroutput"><username></CODE> has been specified within a BSim URL
|
||||
a <CODE class="computeroutput"><password></CODE> may be included if neccessary,
|
||||
albeit highly discouraged (e.g.,
|
||||
<CODE class="computeroutput">postgresql://username:password@hostname/dbname</CODE>).
|
||||
The password is appended to the username with a colon (':') separator and has limitations
|
||||
on the characters which may be used.</P>
|
||||
|
||||
<P><IMG border="0" src="images/warning.help.png" alt="Warning: ">Inclusion of a user
|
||||
<CODE class="computeroutput"><password></CODE> within a BSim URL is highly
|
||||
discouraged and only intended for use within restricted environments since the URL entry will persist
|
||||
within the system process table and possibly within system log files. This may be useful
|
||||
in controlled situations where console password prompts cannot be handled. Handling
|
||||
the password prompt is preferred.</P>
|
||||
|
||||
<P>For local <EM>file</EM> URLs, the absolute path the H2 database <EM>*.mv.db</EM> file
|
||||
must be specified without the <EM>*.mv.db</EM> extension. Only the '/' character should be
|
||||
|
|
|
@ -527,6 +527,34 @@
|
|||
that are different from the standard PostgreSQL defaults. To provide site specific
|
||||
configuration, changes should be made to the normal PostgreSQL configuration files.</P>
|
||||
</DIV>
|
||||
|
||||
<DIV class="sect3">
|
||||
<DIV class="titlepage">
|
||||
<DIV>
|
||||
<DIV>
|
||||
<H4 class="title"><A name="PostgresFirewall"></A>PostgreSQL Firewall Considerations</H4>
|
||||
</DIV>
|
||||
</DIV>
|
||||
</DIV>
|
||||
|
||||
<P>Remote client access to the PostgreSQL server may have network firewalls
|
||||
which must be considered and properly configured to allow network access.
|
||||
Both dedicated network firewalls and OS-level firewalls must be considered.</P>
|
||||
|
||||
<P>Firewall configurations are beyond the scope of this document, however for simple
|
||||
single-node installations on Linux the <CODE class="computeroutput">firewall-cmd</CODE>
|
||||
may be used to allow incoming connections to the appropriate port (TCP port 5432 is the
|
||||
default for PostgreSQL). This does not consider network firewall devices which may
|
||||
also impact connectivity.</P>
|
||||
|
||||
<PRE><CODE class "computeroutput">
|
||||
sudo firewall-cmd --permanent --add-port=5432/tcp && sudo firewall-cmd --reload
|
||||
</CODE></PRE>
|
||||
|
||||
<P>NOTE: The above Linux firewall command assumes the <CODE class="computeroutput">firewalld</CODE>
|
||||
package has been installed on the system.</P>
|
||||
|
||||
</DIV>
|
||||
</DIV>
|
||||
|
||||
<DIV class="sect2">
|
||||
|
@ -562,13 +590,22 @@
|
|||
|
||||
<P>In order to make use of Elasticsearch with BSim, the database administrator must
|
||||
install the <SPAN class="emphasis"><EM>lsh.zip</EM></SPAN> plug-in as part of the
|
||||
Elasticsearch deployment. The plug-in is available in the Ghidra add-on named <SPAN
|
||||
class="emphasis"><EM>BSimElasticPlugin</EM></SPAN>, which unpacks into a standard
|
||||
Ghidra installation. The file <SPAN class="emphasis"><EM>lsh.zip</EM></SPAN> is a
|
||||
standard Elasticsearch plug-in that must be installed on every node of the cluster
|
||||
Elasticsearch deployment. The plug-in is available in the Ghidra extension named <SPAN
|
||||
class="emphasis"><EM>BSimElasticPlugin</EM></SPAN>
|
||||
(<CODE class="computeroutput"><ghidra-install-dir>/Extensions/Ghidra/ghidra_11.2.1_U_20241105_BSimElasticPlugin.zip</CODE>).
|
||||
The extension may be unzipped to a temporary location or use Ghidra's Install Extensions
|
||||
capability which will unpack it into the users platform-specific config directory
|
||||
indicated in the detail view of the Install Extensions window. The extension is not
|
||||
used directly by Ghidra and is only needed for the
|
||||
<SPAN class="emphasis"><EM>lsh.zip</EM></SPAN> elasticsearch plug-in
|
||||
which must be installed on every node of the cluster
|
||||
before a BSim repository can be created. The description below shows how to enable
|
||||
the BSim plug-in for a single node, but this will need to be repeated for any
|
||||
additional nodes.</P>
|
||||
|
||||
<P><IMG border="0" src="help/shared/tip.png" alt="Tip: ">Refer to the <I>README.md</I>
|
||||
file within the unpacked extension for important plugin details. The plugin file
|
||||
stipulates a specific elasticsearch version and may require an adjustment and repack.</P>
|
||||
|
||||
<P>Assuming the add-on has been unpacked, the plug-in can be installed to a single node
|
||||
using the <SPAN class="emphasis"><EM>elasticsearch-plugin</EM></SPAN> command in the
|
||||
|
@ -579,7 +616,7 @@
|
|||
<TABLE border="0" summary="Simple list" class="simplelist">
|
||||
<TR>
|
||||
<TD><CODE class="computeroutput">bin/elasticsearch-plugin install
|
||||
file:///path/to/ghidra/Ghidra/contrib/BSimElasticPlugin/data/lsh.zip</CODE></TD>
|
||||
file:///path/to/ghidra/extension/BSimElasticPlugin/data/lsh.zip</CODE></TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
</DIV>
|
||||
|
@ -610,7 +647,7 @@
|
|||
</DIV>
|
||||
</DIV>
|
||||
|
||||
<P>The open Elasticsearch distribution starts with up with password authentication
|
||||
<P>The open Elasticsearch distribution starts with password authentication
|
||||
enabled by default. When a node is started up for the first time, as described above, an
|
||||
<SPAN class="bold"><STRONG>elastic</STRONG></SPAN> user is created with a randomly
|
||||
generated password that is reported, once, to the console. For a toy deployment, it may
|
||||
|
@ -665,6 +702,34 @@ curl -k -u elastic:XXXXXX -X POST "https://localhost:9200/_security/user/ghidrau
|
|||
class="xref" href="CommandLineReference.html#URLs">“Ghidra and BSim
|
||||
URLs”</A> for additional information about URLs.</P>
|
||||
</DIV>
|
||||
|
||||
<DIV class="sect3">
|
||||
<DIV class="titlepage">
|
||||
<DIV>
|
||||
<DIV>
|
||||
<H4 class="title"><A name="ElasticFirewall"></A>Elasticsearch Firewall Considerations</H4>
|
||||
</DIV>
|
||||
</DIV>
|
||||
</DIV>
|
||||
|
||||
<P>Remote client access to the Elasticsearch server/cluster may have network firewalls
|
||||
which must be considered and properly configured to allow network access.
|
||||
Both dedicated network firewalls and OS-level firewalls must be considered.</P>
|
||||
|
||||
<P>Firewall configurations are beyond the scope of this document, however for simple
|
||||
single-node installations on Linux the <CODE class="computeroutput">firewall-cmd</CODE>
|
||||
may be used to allow incoming connections to the appropriate port (TCP port 9200 is the
|
||||
default for elasticsearch). This does not consider network firewall devices which may
|
||||
also impact connectivity.</P>
|
||||
|
||||
<PRE><CODE class "computeroutput">
|
||||
sudo firewall-cmd --permanent --add-port=9200/tcp && sudo firewall-cmd --reload
|
||||
</CODE></PRE>
|
||||
|
||||
<P>NOTE: The above Linux firewall command assumes the <CODE class="computeroutput">firewalld</CODE>
|
||||
package has been installed on the system.</P>
|
||||
|
||||
</DIV>
|
||||
</DIV>
|
||||
</DIV>
|
||||
|
||||
|
@ -672,8 +737,7 @@ curl -k -u elastic:XXXXXX -X POST "https://localhost:9200/_security/user/ghidrau
|
|||
<DIV class="titlepage">
|
||||
<DIV>
|
||||
<DIV>
|
||||
<H2 class="title" style="clear: both"><A name="CreateDatabase"></A>Creating a
|
||||
Database</H2>
|
||||
<H2 class="title" style="clear: both"><A name="CreateDatabase"></A>Creating a Database</H2>
|
||||
</DIV>
|
||||
</DIV>
|
||||
</DIV>
|
||||
|
|
|
@ -53,9 +53,23 @@
|
|||
<BR>
|
||||
|
||||
|
||||
<P>The dialog displays a table showing all the currently defined BSim databases/servers. Each
|
||||
entry shows a name for the BSim database, its type (postgres, elastic, or file), a host ip
|
||||
and port (if applicable), and finally the number of active connections.</P>
|
||||
<P>The dialog displays a table showing all the currently defined BSim databases/servers.
|
||||
Table columns displayed include:</P>
|
||||
|
||||
<UL>
|
||||
<LI><B>Name</B> - Indicates the name of the BSim database. In the cases of an H2 <I>file</I>
|
||||
database this represents the name of the file (hovering on this will show the full absolute path).</LI>
|
||||
<LI><B>Type</B> - The type of database (<I>postgres</I>, <I>elastic</I> or <I>file</I>).</LI>
|
||||
<LI><B>Host</B> - The network host name or IP address for a <I>postgres</I> or
|
||||
<I>elastic</I> database.</LI>
|
||||
<LI><B>Port</B> - The TCP port for a <I>postgres</I> or <I>elastic</I> database.</LI>
|
||||
<LI><B>User</B> - The non-default user name to be used when connecting to a <I>postgres</I> or
|
||||
<I>elastic</I> database. When actively connected to a <I>postgres</I> database
|
||||
the actual name used for authentication will be shown.</LI>
|
||||
<LI><B>Active/Idle Connections</B> - The active/idle database connections for the
|
||||
connection pool associated with a <I>postgres</I> or <I>file</I> database.
|
||||
This field will be blank for these databases when in a disconnected state.</LI>
|
||||
</UL>
|
||||
|
||||
<P>There are four primary actions for this dialog:</P>
|
||||
<A name="Manage_Servers_Actions"></A>
|
||||
|
@ -68,8 +82,8 @@
|
|||
selected entry will be deleted. This action will force an immediate disconnect for an
|
||||
active/idle connection and should be used with care.</LI>
|
||||
|
||||
<LI><IMG alt="" src="icon.bsim.disconnected"><IMG alt="" src="icon.bsim.connected">
|
||||
Connect or disconnect an inactive database/server connection. This action is not supported
|
||||
<LI><IMG alt="" src="icon.bsim.disconnected"><IMG alt="" src="icon.bsim.connected"> Connect
|
||||
or disconnect an inactive database/server connection which has been selected. This action is not supported
|
||||
by Elastic database servers.</LI>
|
||||
|
||||
<LI><IMG alt="" src="icon.bsim.change.password"> Change password - A change password
|
||||
|
@ -91,9 +105,11 @@
|
|||
|
||||
|
||||
<P>Choose the type of BSim database first as that will affect the type of information that
|
||||
needs to be specified. For postgres and elastic, you need to enter host and port. For file,
|
||||
you see a button for using a filechooser to pick the file that is the local BSim H2
|
||||
database.</P>
|
||||
needs to be specified. For <I>postgres</I> and <I>elastic</I>, you must enter a database name,
|
||||
hostname/address, and port. Specify a host name of "localhost" for a server running on the local system.
|
||||
Invalid or missing entries are shown in red. For a <I>file</I> database,
|
||||
the "..." button is used to launch a filechooser for selecting an existing H2 database file
|
||||
(*.mv.db).</P>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 6.9 KiB |
Binary file not shown.
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 12 KiB |
|
@ -77,10 +77,11 @@ public class BSimServerManager {
|
|||
String dbTypeName = properties.getString("DBType", null);
|
||||
DBType dbType = DBType.valueOf(dbTypeName);
|
||||
String name = properties.getString("Name", null);
|
||||
String user = properties.getString("User", null);
|
||||
String host = properties.getString("Host", null);
|
||||
int port = properties.getInt("Port", 0);
|
||||
if (dbType != null && name != null) {
|
||||
BSimServerInfo info = new BSimServerInfo(dbType, host, port, name);
|
||||
BSimServerInfo info = new BSimServerInfo(dbType, user, host, port, name);
|
||||
return info;
|
||||
}
|
||||
Msg.showError(this, null, "Error reading Bsim Server File",
|
||||
|
@ -97,6 +98,10 @@ public class BSimServerManager {
|
|||
GProperties properties = new GProperties("BSimServerInfo");
|
||||
properties.putString("DBType", info.getDBType().name());
|
||||
properties.putString("Name", info.getDBName());
|
||||
if (!info.hasDefaultLogin()) {
|
||||
// save specified username - but not password
|
||||
properties.putString("User", info.getUserName());
|
||||
}
|
||||
properties.putString("Host", info.getServerName());
|
||||
properties.putInt("Port", info.getPort());
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ import generic.theme.GIcon;
|
|||
import ghidra.features.bsim.gui.BSimServerManager;
|
||||
import ghidra.features.bsim.query.*;
|
||||
import ghidra.features.bsim.query.BSimServerInfo.DBType;
|
||||
import ghidra.features.bsim.query.FunctionDatabase.Error;
|
||||
import ghidra.features.bsim.query.FunctionDatabase.BSimError;
|
||||
import ghidra.features.bsim.query.FunctionDatabase.ErrorCategory;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.*;
|
||||
|
@ -175,7 +175,7 @@ public class BSimServerDialog extends DialogComponentProvider {
|
|||
try (FunctionDatabase db = BSimClientFactory.buildClient(serverInfo, true)) {
|
||||
if (!db.initialize()) {
|
||||
// TODO: Need standardized error handler
|
||||
Error lastError = db.getLastError();
|
||||
BSimError lastError = db.getLastError();
|
||||
if (lastError.category != ErrorCategory.AuthenticationCancelled) {
|
||||
Msg.showError(this, getComponent(), "BSim DB Connection Failed",
|
||||
lastError.message);
|
||||
|
@ -195,7 +195,7 @@ public class BSimServerDialog extends DialogComponentProvider {
|
|||
return; // password dialog entry cancelled by user
|
||||
}
|
||||
|
||||
String resp = db.changePassword(db.getUserName(), pwd);
|
||||
String resp = db.changePassword(pwd);
|
||||
if (resp == null) {
|
||||
Msg.showInfo(this, getComponent(), "Password Changed",
|
||||
"BSim DB password successfully changed");
|
||||
|
|
|
@ -21,11 +21,15 @@ import java.util.*;
|
|||
import javax.swing.Icon;
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import docking.widgets.table.*;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.features.bsim.gui.BSimServerManager;
|
||||
import ghidra.features.bsim.query.BSimServerInfo;
|
||||
import ghidra.features.bsim.query.*;
|
||||
import ghidra.features.bsim.query.BSimPostgresDBConnectionManager.BSimPostgresDataSource;
|
||||
import ghidra.features.bsim.query.BSimServerInfo.DBType;
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.framework.plugintool.ServiceProviderStub;
|
||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||
|
@ -90,6 +94,7 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
|
|||
descriptor.addVisibleColumn(new TypeColumn());
|
||||
descriptor.addVisibleColumn(new HostColumn());
|
||||
descriptor.addVisibleColumn(new PortColumn());
|
||||
descriptor.addVisibleColumn(new UserInfoColumn());
|
||||
descriptor.addVisibleColumn(new ConnectionStatusColumn());
|
||||
return descriptor;
|
||||
}
|
||||
|
@ -165,6 +170,42 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
|
|||
}
|
||||
}
|
||||
|
||||
private static class UserInfoColumn
|
||||
extends AbstractDynamicTableColumn<BSimServerInfo, String, Object> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "User";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(BSimServerInfo serverInfo, Settings settings, Object data,
|
||||
ServiceProvider provider) throws IllegalArgumentException {
|
||||
if (serverInfo.hasDefaultLogin()) {
|
||||
if (serverInfo.getDBType() == DBType.postgres) {
|
||||
BSimPostgresDataSource ds =
|
||||
BSimPostgresDBConnectionManager.getDataSourceIfExists(serverInfo);
|
||||
if (ds != null) {
|
||||
return ds.getUserName();
|
||||
}
|
||||
}
|
||||
// TODO: how can we determine elastic username?
|
||||
return "";
|
||||
}
|
||||
String info = serverInfo.getUserName();
|
||||
boolean hasPassword = serverInfo.hasPassword();
|
||||
if (hasPassword) {
|
||||
info = info + ":****"; // show w/masked password
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
|
||||
private static class HostColumn
|
||||
extends AbstractDynamicTableColumn<BSimServerInfo, String, Object> {
|
||||
|
||||
|
@ -176,7 +217,6 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
|
|||
@Override
|
||||
public String getValue(BSimServerInfo serverInfo, Settings settings, Object data,
|
||||
ServiceProvider provider) throws IllegalArgumentException {
|
||||
|
||||
return serverInfo.getServerName();
|
||||
}
|
||||
|
||||
|
|
|
@ -16,12 +16,17 @@
|
|||
package ghidra.features.bsim.gui.search.dialog;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.*;
|
||||
import java.io.File;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.JFormattedTextField.AbstractFormatterFactory;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.text.DefaultFormatter;
|
||||
import javax.swing.text.DefaultFormatterFactory;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.widgets.OptionDialog;
|
||||
|
@ -29,8 +34,11 @@ import docking.widgets.button.BrowseButton;
|
|||
import docking.widgets.button.GRadioButton;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import docking.widgets.textfield.GFormattedTextField;
|
||||
import docking.widgets.textfield.GFormattedTextField.Status;
|
||||
import ghidra.features.bsim.query.*;
|
||||
import ghidra.features.bsim.query.BSimServerInfo.DBType;
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.filechooser.GhidraFileChooserModel;
|
||||
import ghidra.util.filechooser.GhidraFileFilter;
|
||||
|
@ -45,6 +53,15 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
|
|||
private static final String POSTGRES = "Postgres";
|
||||
private static final String ELASTIC = "Elastic";
|
||||
private static final String FILE_H2 = "File";
|
||||
|
||||
private static final AbstractFormatterFactory FORMATTER_FACTORY =
|
||||
new DefaultFormatterFactory(new DefaultFormatter() {
|
||||
@Override
|
||||
public Object stringToValue(String text) {
|
||||
return text;
|
||||
}
|
||||
});
|
||||
|
||||
private GRadioButton postgresButton;
|
||||
private GRadioButton elasticButton;
|
||||
private GRadioButton fileButton;
|
||||
|
@ -68,16 +85,10 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
|
|||
setHelpLocation(new HelpLocation("BSimSearchPlugin", "Add_Server_Definition_Dialog"));
|
||||
}
|
||||
|
||||
public BSimServerInfo getBsimServerInfo() {
|
||||
BSimServerInfo getBsimServerInfo() {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHelpLocation(HelpLocation helpLocation) {
|
||||
// TODO Auto-generated method stub
|
||||
super.setHelpLocation(helpLocation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void okCallback() {
|
||||
BSimServerInfo serverInfo = activePanel.getServerInfo();
|
||||
|
@ -88,7 +99,7 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean acceptServer(BSimServerInfo serverInfo) {
|
||||
private boolean acceptServer(BSimServerInfo serverInfo) {
|
||||
// FIXME: Use task to correct dialog parenting issue caused by password prompt
|
||||
String errorMessage = null;
|
||||
try (FunctionDatabase database = BSimClientFactory.buildClient(serverInfo, true)) {
|
||||
|
@ -159,6 +170,7 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
|
|||
if (postgresButton.isSelected()) {
|
||||
cardLayout.show(cardPanel, POSTGRES);
|
||||
activePanel = postgresPanel;
|
||||
|
||||
}
|
||||
else if (elasticButton.isSelected()) {
|
||||
cardLayout.show(cardPanel, ELASTIC);
|
||||
|
@ -195,8 +207,9 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
|
|||
}
|
||||
|
||||
private class DbPanel extends ServerPanel {
|
||||
private JTextField nameField;
|
||||
private JTextField hostField;
|
||||
private GFormattedTextField nameField;
|
||||
private GFormattedTextField userField;
|
||||
private GFormattedTextField hostField;
|
||||
private JTextField portField;
|
||||
private DBType type;
|
||||
|
||||
|
@ -204,12 +217,20 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
|
|||
super(new PairLayout(10, 10));
|
||||
this.type = type;
|
||||
|
||||
nameField = new NotifyingTextField();
|
||||
hostField = new NotifyingTextField();
|
||||
portField =
|
||||
new NotifyingTextField(Integer.toString(BSimServerInfo.DEFAULT_POSTGRES_PORT));
|
||||
createDBNameField();
|
||||
createUserField();
|
||||
createHostField();
|
||||
int defaultPort = -1;
|
||||
if (type == BSimServerInfo.DBType.postgres) {
|
||||
defaultPort = BSimServerInfo.DEFAULT_POSTGRES_PORT;
|
||||
}
|
||||
else if (type == BSimServerInfo.DBType.elastic) {
|
||||
defaultPort = BSimServerInfo.DEFAULT_ELASTIC_PORT;
|
||||
}
|
||||
portField = new NotifyingTextField(Integer.toString(defaultPort));
|
||||
|
||||
JLabel nameLabel = new JLabel("DB Name:", SwingConstants.RIGHT);
|
||||
JLabel userLabel = new JLabel("User (optional):", SwingConstants.RIGHT);
|
||||
JLabel hostLabel = new JLabel("Host:", SwingConstants.RIGHT);
|
||||
JLabel portLabel = new JLabel("Port:", SwingConstants.RIGHT);
|
||||
nameLabel.setLabelFor(nameField);
|
||||
|
@ -218,21 +239,197 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
|
|||
|
||||
add(nameLabel);
|
||||
add(nameField);
|
||||
add(userLabel);
|
||||
add(userField);
|
||||
add(hostLabel);
|
||||
add(hostField);
|
||||
add(portLabel);
|
||||
add(portField);
|
||||
}
|
||||
|
||||
private void setStatus(String msg) {
|
||||
CreateBsimServerInfoDialog.this.setStatusText(msg);
|
||||
}
|
||||
|
||||
private void createDBNameField() {
|
||||
|
||||
nameField = new GFormattedTextField(FORMATTER_FACTORY, "");
|
||||
nameField.setName("Name");
|
||||
nameField.setText("");
|
||||
nameField.setDefaultValue("");
|
||||
nameField.setIsError(true);
|
||||
nameField.setEditable(true);
|
||||
|
||||
nameField.setInputVerifier(new InputVerifier() {
|
||||
@Override
|
||||
public boolean verify(JComponent input) {
|
||||
setStatus("");
|
||||
String dbName = nameField.getText().trim();
|
||||
if (dbName.length() == 0) {
|
||||
setStatus("");
|
||||
return false;
|
||||
}
|
||||
// Naming restrictions based upon PostgreSQL and its allowance of unicode chars
|
||||
for (int i = 0; i < dbName.length(); i++) {
|
||||
char c = dbName.charAt(i);
|
||||
if (Character.isLetter(c)) {
|
||||
continue;
|
||||
}
|
||||
if (i == 0 || (!Character.isDigit(c) && c != '_')) {
|
||||
setStatus("Unsupported database name");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldYieldFocus(JComponent source, JComponent target) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
nameField.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
|
||||
e.consume();
|
||||
nameField.setText("");
|
||||
nameField.setDefaultValue("");
|
||||
nameField.setIsError(true);
|
||||
}
|
||||
checkForValidDialog();
|
||||
}
|
||||
});
|
||||
|
||||
nameField.addTextEntryStatusListener(f -> checkForValidDialog());
|
||||
}
|
||||
|
||||
private static final String HOSTNAME_IP_REGEX =
|
||||
"^[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*(\\.[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*)*$";
|
||||
private static final Pattern HOSTNAME_IP_PATTERN = Pattern.compile(HOSTNAME_IP_REGEX);
|
||||
|
||||
private void createHostField() {
|
||||
|
||||
hostField = new GFormattedTextField(FORMATTER_FACTORY, "");
|
||||
hostField.setName("Host");
|
||||
hostField.setText("");
|
||||
hostField.setDefaultValue("");
|
||||
hostField.setIsError(true);
|
||||
hostField.setEditable(true);
|
||||
|
||||
hostField.setInputVerifier(new InputVerifier() {
|
||||
@Override
|
||||
public boolean verify(JComponent input) {
|
||||
setStatus("");
|
||||
String hostname = hostField.getText().trim();
|
||||
if (hostname.length() == 0) {
|
||||
setStatus("");
|
||||
return false;
|
||||
}
|
||||
Matcher hostMatch = HOSTNAME_IP_PATTERN.matcher(hostname);
|
||||
if (!hostMatch.matches()) {
|
||||
setStatus("Unsupported host name or IP address");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldYieldFocus(JComponent source, JComponent target) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
hostField.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
|
||||
e.consume();
|
||||
hostField.setText("");
|
||||
hostField.setDefaultValue("");
|
||||
hostField.setIsError(true);
|
||||
}
|
||||
checkForValidDialog();
|
||||
}
|
||||
});
|
||||
|
||||
hostField.addTextEntryStatusListener(f -> checkForValidDialog());
|
||||
}
|
||||
|
||||
// NOTE: Username pattern based on PostgreSQL restrictions
|
||||
private static final String USERNAME_REGEX = "^[a-zA-Z_][a-zA-Z0-9_$]*$";
|
||||
private static final Pattern USERNAME_PATTERN = Pattern.compile(USERNAME_REGEX);
|
||||
|
||||
private void createUserField() {
|
||||
|
||||
userField = new GFormattedTextField(FORMATTER_FACTORY, "");
|
||||
userField.setName("User");
|
||||
userField.setText("");
|
||||
userField.setDefaultValue("");
|
||||
userField.setEditable(true);
|
||||
|
||||
userField.setInputVerifier(new InputVerifier() {
|
||||
@Override
|
||||
public boolean verify(JComponent input) {
|
||||
setStatus("");
|
||||
String username = userField.getText().trim();
|
||||
if (username.length() == 0) {
|
||||
setStatus("");
|
||||
return true;
|
||||
}
|
||||
Matcher userMatch = USERNAME_PATTERN.matcher(username);
|
||||
if (!userMatch.matches()) {
|
||||
setStatus("Unsupported database user name");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldYieldFocus(JComponent source, JComponent target) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
userField.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
|
||||
e.consume();
|
||||
userField.setText("");
|
||||
userField.setDefaultValue("");
|
||||
userField.setIsError(false);
|
||||
}
|
||||
checkForValidDialog();
|
||||
}
|
||||
});
|
||||
|
||||
userField.addTextEntryStatusListener(f -> checkForValidDialog());
|
||||
}
|
||||
|
||||
@Override
|
||||
BSimServerInfo getServerInfo() {
|
||||
if (nameField.getTextEntryStatus() == Status.INVALID ||
|
||||
userField.getTextEntryStatus() == Status.INVALID ||
|
||||
hostField.getTextEntryStatus() == Status.INVALID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String user = userField.getText().trim();
|
||||
if (ClientUtil.getUserName().equals(user)) {
|
||||
user = null;
|
||||
}
|
||||
|
||||
String name = nameField.getText().trim();
|
||||
String host = hostField.getText().trim();
|
||||
|
||||
int port = getPort(portField.getText().trim());
|
||||
if (name.isBlank() || host.isBlank() || port < 0) {
|
||||
return null;
|
||||
}
|
||||
return new BSimServerInfo(type, host, port, name);
|
||||
|
||||
return new BSimServerInfo(type, user, host, port, name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -291,7 +488,7 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
|
|||
if (file.isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
return new BSimServerInfo(DBType.file, null, -1, path);
|
||||
return new BSimServerInfo(path);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -303,24 +500,24 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
|
|||
public NotifyingTextField(String initialText) {
|
||||
super(20);
|
||||
setText(initialText);
|
||||
getDocument().addDocumentListener(new DocumentListener() {
|
||||
getDocument().addDocumentListener(new MyFieldListener());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
checkForValidDialog();
|
||||
}
|
||||
class MyFieldListener implements DocumentListener {
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
checkForValidDialog();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
checkForValidDialog();
|
||||
}
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
checkForValidDialog();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
checkForValidDialog();
|
||||
}
|
||||
|
||||
});
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
checkForValidDialog();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ import javax.security.auth.callback.NameCallback;
|
|||
import javax.security.auth.callback.PasswordCallback;
|
||||
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.features.bsim.query.BSimServerInfo.DBType;
|
||||
import ghidra.features.bsim.query.FunctionDatabase.ConnectionType;
|
||||
|
@ -283,10 +282,12 @@ public class BSimPostgresDBConnectionManager {
|
|||
*/
|
||||
private Connection connect() throws SQLException, CancelledException {
|
||||
|
||||
String userName = bds.getUsername();
|
||||
bds.setUsername(StringUtils.isBlank(userName) ? ClientUtil.getUserName() : userName);
|
||||
bds.setPassword(null);
|
||||
connectionType = ConnectionType.SSL_No_Authentication;
|
||||
String loginError = null;
|
||||
|
||||
serverInfo.setUserInfo(bds);
|
||||
|
||||
connectionType = serverInfo.hasPassword() ? ConnectionType.SSL_Password_Authentication
|
||||
: ConnectionType.SSL_No_Authentication;
|
||||
try {
|
||||
// Specify SSL connection properties
|
||||
setSSLProperties();
|
||||
|
@ -299,6 +300,10 @@ public class BSimPostgresDBConnectionManager {
|
|||
if (e.getMessage().contains("password-based authentication") ||
|
||||
e.getMessage().contains("SCRAM-based") ||
|
||||
e.getMessage().contains("password authentication failed")) {
|
||||
if (serverInfo.hasPassword()) {
|
||||
loginError = "Access denied: " + serverInfo;
|
||||
Msg.error(this, loginError);
|
||||
}
|
||||
// Use Ghidra's authentication infrastructure
|
||||
connectionType = ConnectionType.SSL_Password_Authentication; // Try again with a password
|
||||
// fallthru to second attempt at getConnection
|
||||
|
@ -319,7 +324,6 @@ public class BSimPostgresDBConnectionManager {
|
|||
" idle=" + bds.getNumIdle());
|
||||
}
|
||||
|
||||
String loginError = null;
|
||||
while (true) {
|
||||
ClientAuthenticator clientAuthenticator = null;
|
||||
if (connectionType == ConnectionType.SSL_Password_Authentication) {
|
||||
|
@ -327,9 +331,11 @@ public class BSimPostgresDBConnectionManager {
|
|||
if (clientAuthenticator == null) { // Make sure authenticator is registered
|
||||
throw new SQLException("No registered authenticator");
|
||||
}
|
||||
NameCallback nameCb = new NameCallback("User ID:");
|
||||
nameCb.setName(bds.getUsername());
|
||||
PasswordCallback passCb = new PasswordCallback("Password:", false);
|
||||
NameCallback nameCb = new NameCallback("User ID:", bds.getUsername());
|
||||
if (!serverInfo.hasDefaultLogin()) {
|
||||
nameCb.setName(bds.getUsername());
|
||||
}
|
||||
PasswordCallback passCb = new PasswordCallback(" ", false); // force use of default prompting
|
||||
try {
|
||||
if (!clientAuthenticator.processPasswordCallbacks(
|
||||
"BSim Database Authentication", "BSim Database Server",
|
||||
|
@ -338,9 +344,8 @@ public class BSimPostgresDBConnectionManager {
|
|||
}
|
||||
bds.setPassword(new String(passCb.getPassword()));
|
||||
// User may have specified new username, or this may return NULL
|
||||
userName = nameCb.getName();
|
||||
if (!StringUtils.isBlank(userName)) {
|
||||
bds.setUsername(userName);
|
||||
if (serverInfo.hasDefaultLogin()) {
|
||||
bds.setUsername(nameCb.getName());
|
||||
}
|
||||
}
|
||||
finally {
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
@ -16,12 +16,15 @@
|
|||
package ghidra.features.bsim.query;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
|
||||
public class BSimServerInfo implements Comparable<BSimServerInfo> {
|
||||
|
||||
/**
|
||||
|
@ -48,6 +51,7 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
|
|||
}
|
||||
|
||||
private final DBType dbType;
|
||||
private final String userinfo; // username[:password]
|
||||
private final String host;
|
||||
private final int port;
|
||||
private final String dbName;
|
||||
|
@ -56,6 +60,62 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
|
|||
|
||||
/**
|
||||
* Construct a new {@link BSimServerInfo} object
|
||||
*
|
||||
* @param dbType BSim DB type
|
||||
* @param userinfo connection user info, {@code username[:password]} (ignored for {@link DBType#file}).
|
||||
* If blank, {@link ClientUtil#getUserName()} is used.
|
||||
* @param host host name (ignored for {@link DBType#file})
|
||||
* @param port port number (ignored for {@link DBType#file})
|
||||
* @param dbName name of database (simple database name except for {@link DBType#file}
|
||||
* which should reflect an absolute file path. On Windows OS the path may start with a
|
||||
* drive letter.
|
||||
* @throws IllegalArgumentException if invalid arguments are specified
|
||||
*/
|
||||
public BSimServerInfo(DBType dbType, String userinfo, String host, int port, String dbName) {
|
||||
Objects.requireNonNull(dbType, "DBType must be specified");
|
||||
this.dbType = dbType;
|
||||
|
||||
if ((dbType == DBType.postgres || dbType == DBType.elastic) && StringUtils.isEmpty(host)) {
|
||||
throw new IllegalArgumentException("host required");
|
||||
}
|
||||
|
||||
dbName = dbName.trim();
|
||||
if (StringUtils.isEmpty(dbName)) {
|
||||
throw new IllegalArgumentException("Non-empty dbName required");
|
||||
}
|
||||
|
||||
if (dbType == DBType.file) {
|
||||
host = null;
|
||||
port = -1;
|
||||
userinfo = null;
|
||||
dbName = cleanupFilename(dbName);
|
||||
}
|
||||
else {
|
||||
if (dbName.contains("/") || dbName.contains("\\")) { // may want additional validation
|
||||
throw new IllegalArgumentException("Invalid " + dbType + " dbName: " + dbName);
|
||||
}
|
||||
userinfo = cleanupUserInfo(userinfo);
|
||||
if (port <= 0) {
|
||||
port = -1;
|
||||
}
|
||||
if (dbType == DBType.postgres && port <= 0) {
|
||||
port = DEFAULT_POSTGRES_PORT;
|
||||
}
|
||||
if (dbType == DBType.elastic && port <= 0) {
|
||||
port = DEFAULT_ELASTIC_PORT;
|
||||
}
|
||||
}
|
||||
|
||||
this.userinfo = userinfo;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.dbName = dbName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new {@link BSimServerInfo} object. For non-file database the user's defaut
|
||||
* username is used (see {@link ClientUtil#getUserName()}).
|
||||
*
|
||||
* @param dbType BSim DB type
|
||||
* @param host host name (ignored for {@link DBType#file})
|
||||
* @param port port number (ignored for {@link DBType#file})
|
||||
|
@ -65,49 +125,34 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
|
|||
* @throws IllegalArgumentException if invalid arguments are specified
|
||||
*/
|
||||
public BSimServerInfo(DBType dbType, String host, int port, String dbName) {
|
||||
Objects.requireNonNull(dbType, "DBType must be specified");
|
||||
this.dbType = dbType;
|
||||
|
||||
if ((dbType == DBType.postgres || dbType == DBType.elastic) && StringUtils.isEmpty(host)) {
|
||||
throw new IllegalArgumentException("host required");
|
||||
}
|
||||
this.host = host;
|
||||
|
||||
if (port <= 0) {
|
||||
port = -1;
|
||||
}
|
||||
if (dbType == DBType.postgres && port <= 0) {
|
||||
port = DEFAULT_POSTGRES_PORT;
|
||||
}
|
||||
if (dbType == DBType.elastic && port <= 0) {
|
||||
port = DEFAULT_ELASTIC_PORT;
|
||||
}
|
||||
this.port = port;
|
||||
this(dbType, null, host, port, dbName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new {@link BSimServerInfo} object for a {@link DBType#file} type database.
|
||||
*
|
||||
* @param dbName name of database which should reflect an absolute file path.
|
||||
* On Windows OS the path may start with a drive letter.
|
||||
* @throws IllegalArgumentException if invalid arguments are specified
|
||||
*/
|
||||
public BSimServerInfo(String dbName) {
|
||||
dbType = DBType.file;
|
||||
userinfo = null;
|
||||
host = null;
|
||||
port = -1;
|
||||
dbName = dbName.trim();
|
||||
if (StringUtils.isEmpty(dbName)) {
|
||||
throw new IllegalArgumentException("Non-empty dbName required");
|
||||
}
|
||||
if (dbType == DBType.file) {
|
||||
// transform dbName into acceptable H2 DB file path
|
||||
dbName = dbName.replace("\\", "/");
|
||||
if ((!dbName.startsWith("/") && !isWindowsFilePath(dbName)) || dbName.endsWith("/")) {
|
||||
throw new IllegalArgumentException("Invalid absolute file path: " + dbName);
|
||||
}
|
||||
if (!dbName.endsWith(H2_FILE_EXTENSION)) {
|
||||
dbName += H2_FILE_EXTENSION;
|
||||
}
|
||||
}
|
||||
else if (dbName.contains("/") || dbName.contains("\\")) { // may want additional validation
|
||||
throw new IllegalArgumentException("Invalid " + dbType + " dbName: " + dbName);
|
||||
}
|
||||
this.dbName = dbName;
|
||||
this.dbName = cleanupFilename(dbName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new {@link BSimServerInfo} object from a suitable database URL
|
||||
* (i.e., {@code postgresql:}, {@code https:}, {@code elastic:}, {@code file:}).
|
||||
* @param url supported BSim database URL
|
||||
*
|
||||
* @param url supported BSim database URL. For non-file URLs, the hostname or
|
||||
* address may be preceeded by a DB username (e.g., postgresql://user@host:port/dbname
|
||||
* @throws IllegalArgumentException if unsupported URL protocol specified
|
||||
*/
|
||||
public BSimServerInfo(URL url) throws IllegalArgumentException {
|
||||
|
@ -118,18 +163,21 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
|
|||
if (protocol.equals("postgresql")) {
|
||||
t = DBType.postgres;
|
||||
host = checkURLField(url.getHost(), "host");
|
||||
userinfo = getURLUserInfo(url);
|
||||
int p = url.getPort();
|
||||
port = p <= 0 ? DEFAULT_POSTGRES_PORT : p;
|
||||
}
|
||||
else if (protocol.equals("https") || protocol.equals("elastic")) {
|
||||
t = DBType.elastic;
|
||||
host = checkURLField(url.getHost(), "host");
|
||||
userinfo = getURLUserInfo(url);
|
||||
int p = url.getPort();
|
||||
port = p <= 0 ? DEFAULT_ELASTIC_PORT : p;
|
||||
}
|
||||
else if (protocol.startsWith("file")) {
|
||||
t = DBType.file;
|
||||
host = null;
|
||||
userinfo = null;
|
||||
port = -1;
|
||||
if (!"".equals(url.getHost())) {
|
||||
throw new IllegalArgumentException("Remote file URL not supported: " + url);
|
||||
|
@ -146,7 +194,7 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
|
|||
}
|
||||
path = path.substring(1).strip();
|
||||
}
|
||||
path = checkURLField(path, "path");
|
||||
path = urlDecode(checkURLField(path, "path"));
|
||||
if (dbType == DBType.file) {
|
||||
if (path.endsWith("/")) {
|
||||
throw new IllegalArgumentException("Missing DB filepath in URL: " + url);
|
||||
|
@ -162,6 +210,53 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
|
|||
dbName = path;
|
||||
}
|
||||
|
||||
private static String getURLUserInfo(URL url) {
|
||||
|
||||
String userinfo = url.getUserInfo();
|
||||
if (userinfo == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int pwSep = userinfo.indexOf(':');
|
||||
String urlUserInfo;
|
||||
if (pwSep >= 0) {
|
||||
urlUserInfo = urlDecode(userinfo.substring(0, pwSep)) + ":" +
|
||||
urlDecode(userinfo.substring(pwSep + 1));
|
||||
}
|
||||
else {
|
||||
urlUserInfo = urlDecode(userinfo);
|
||||
}
|
||||
return cleanupUserInfo(urlUserInfo);
|
||||
}
|
||||
|
||||
private static String cleanupUserInfo(String userinfo) {
|
||||
if (StringUtils.isBlank(userinfo)) {
|
||||
return null;
|
||||
}
|
||||
userinfo = userinfo.trim();
|
||||
int pwdSep = userinfo.indexOf(':');
|
||||
if (pwdSep == 0) {
|
||||
throw new IllegalArgumentException("Invalid userinfo specified");
|
||||
}
|
||||
else if (pwdSep > 0 && (userinfo.length() - pwdSep) == 0) {
|
||||
throw new IllegalArgumentException("Invalid userinfo specified");
|
||||
}
|
||||
return userinfo;
|
||||
}
|
||||
|
||||
private static String cleanupFilename(String name) {
|
||||
// transform dbName into acceptable H2 DB file path
|
||||
String dbName = name.trim();
|
||||
dbName = dbName.replace("\\", "/");
|
||||
if ((!dbName.startsWith("/") && !isWindowsFilePath(dbName)) || dbName.endsWith("/")) {
|
||||
throw new IllegalArgumentException("Invalid absolute file path: " + dbName);
|
||||
}
|
||||
if (!dbName.endsWith(H2_FILE_EXTENSION)) {
|
||||
dbName += H2_FILE_EXTENSION;
|
||||
}
|
||||
return dbName;
|
||||
}
|
||||
|
||||
private static String checkURLField(String val, String name) {
|
||||
if (StringUtils.isEmpty(val)) {
|
||||
throw new IllegalArgumentException("Invalid " + name + " in URL");
|
||||
|
@ -199,29 +294,57 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return BSim server info in URL format
|
||||
* Return BSim server info in URL format.
|
||||
* Warning: If userinfo with password has been specified it will be returned in the URL.
|
||||
* @return BSim server info in URL format
|
||||
*/
|
||||
public String toURLString() {
|
||||
switch (dbType) {
|
||||
case postgres:
|
||||
return "postgresql://" + host + getPortString() + "/" + dbName;
|
||||
return "postgresql://" + formatURLUserInfo() + host + getPortString() + "/" +
|
||||
urlEncode(dbName);
|
||||
|
||||
case elastic:
|
||||
return "https://" + host + getPortString() + "/" + dbName;
|
||||
return "https://" + formatURLUserInfo() + host + getPortString() + "/" +
|
||||
urlEncode(dbName);
|
||||
|
||||
case file: // h2:
|
||||
return "file:" + dbName;
|
||||
return "file:" + urlEncode(dbName);
|
||||
}
|
||||
throw new RuntimeException("Unsupported DBType: " + dbType);
|
||||
}
|
||||
|
||||
private static String urlEncode(String text) {
|
||||
return URLEncoder.encode(text, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private static String urlDecode(String text) {
|
||||
return URLDecoder.decode(text, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private String formatURLUserInfo() {
|
||||
if (userinfo == null) {
|
||||
return "";
|
||||
}
|
||||
int pwSep = userinfo.indexOf(':');
|
||||
String urlUserInfo;
|
||||
if (pwSep >= 0) {
|
||||
urlUserInfo = urlEncode(userinfo.substring(0, pwSep)) + ":" +
|
||||
urlEncode(userinfo.substring(pwSep + 1));
|
||||
}
|
||||
else {
|
||||
urlUserInfo = urlEncode(userinfo);
|
||||
}
|
||||
return urlUserInfo + "@";
|
||||
}
|
||||
|
||||
private String getPortString() {
|
||||
return port > 0 ? (":" + Integer.toString(port)) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return BSim server info in URL
|
||||
* Return BSim server info in URL.
|
||||
* Warning: If userinfo with password has been specified it will be returned in the URL.
|
||||
* @return BSim server info in URL
|
||||
* @throws MalformedURLException if unable to form supported URL
|
||||
*/
|
||||
|
@ -236,6 +359,58 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
|
|||
return dbType;
|
||||
}
|
||||
|
||||
public void setUserInfo(BasicDataSource bds) {
|
||||
bds.setUsername(getUserName());
|
||||
if (hasPassword()) {
|
||||
bds.setPassword(userinfo.substring(userinfo.indexOf(':') + 1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if user information includes password.
|
||||
* NOTE: Use of passwords with this object and URLs is discouraged.
|
||||
* @return true if user information includes password which
|
||||
*/
|
||||
public boolean hasPassword() {
|
||||
return userinfo != null && userinfo.contains(":");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine of user info was stipulated during construction
|
||||
* @return true if user info was stipulated during construction
|
||||
*/
|
||||
public boolean hasDefaultLogin() {
|
||||
return userinfo == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the remote database user name to be used when establishing a connection.
|
||||
* User name obtained from the user information which was provided during instantiation.
|
||||
* @return remote database user information (null for {@link DBType#file}).
|
||||
*/
|
||||
public String getUserName() {
|
||||
if (dbType == DBType.file) {
|
||||
return null;
|
||||
}
|
||||
if (userinfo == null) {
|
||||
return ClientUtil.getUserName();
|
||||
}
|
||||
String username = userinfo;
|
||||
int pwdSep = userinfo.indexOf(':');
|
||||
if (pwdSep > 0) {
|
||||
username = userinfo.substring(0, pwdSep);
|
||||
}
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the remote database user information to be used when establishing a connection.
|
||||
* @return remote database user information (null for {@link DBType#file}).
|
||||
*/
|
||||
public String getUserInfo() {
|
||||
return userinfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the server hostname or IP address as originally specified.
|
||||
* @return hostname or IP address as originally specified
|
||||
|
@ -282,7 +457,14 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
|
|||
@Override
|
||||
public int hashCode() {
|
||||
// use dbType.ordinal; enum hashcodes vary from run to run
|
||||
return Objects.hash(dbName, dbType.ordinal(), host, port);
|
||||
int hashcode = Objects.hash(dbName, dbType.ordinal(), host, port);
|
||||
// Due to the use of hashcode by BSimServerManager for persisting server entries
|
||||
// we cannot change the hashing function above and must only incorporate inclusion
|
||||
// of userinfo if it is specified.
|
||||
if (userinfo != null) {
|
||||
hashcode = 31 * hashcode + userinfo.hashCode();
|
||||
}
|
||||
return hashcode;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -293,7 +475,8 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
|
|||
return false;
|
||||
if (obj instanceof BSimServerInfo other) {
|
||||
return Objects.equals(dbName, other.dbName) && dbType == other.dbType &&
|
||||
Objects.equals(host, other.host) && port == other.port;
|
||||
Objects.equals(userinfo, other.userinfo) && Objects.equals(host, other.host) &&
|
||||
port == other.port;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -33,9 +33,7 @@ import ghidra.features.bsim.query.facade.SFOverviewInfo;
|
|||
import ghidra.features.bsim.query.facade.SFQueryInfo;
|
||||
import ghidra.features.bsim.query.protocol.*;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.program.model.data.DataUtilities;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.StringUtilities;
|
||||
|
||||
public interface FunctionDatabase extends AutoCloseable {
|
||||
|
||||
|
@ -85,11 +83,11 @@ public interface FunctionDatabase extends AutoCloseable {
|
|||
}
|
||||
}
|
||||
|
||||
public static class Error { // Error structure returned by getLastError
|
||||
public static class BSimError { // Error structure returned by getLastError
|
||||
public ErrorCategory category;
|
||||
public String message;
|
||||
|
||||
public Error(ErrorCategory cat, String msg) {
|
||||
public BSimError(ErrorCategory cat, String msg) {
|
||||
category = cat;
|
||||
message = msg;
|
||||
}
|
||||
|
@ -120,12 +118,11 @@ public interface FunctionDatabase extends AutoCloseable {
|
|||
/**
|
||||
* Issue password change request to the server.
|
||||
* The method {@link #isPasswordChangeAllowed()} must be invoked first to ensure that
|
||||
* the user password may be changed.
|
||||
* @param username to change
|
||||
* the user password may be changed.
|
||||
* @param newPassword is password data
|
||||
* @return null if change was successful, or the error message
|
||||
*/
|
||||
public default String changePassword(String username, char[] newPassword) {
|
||||
public default String changePassword(char[] newPassword) {
|
||||
if (getStatus() != Status.Ready) {
|
||||
return "Connection not established";
|
||||
}
|
||||
|
@ -134,7 +131,7 @@ public interface FunctionDatabase extends AutoCloseable {
|
|||
}
|
||||
PasswordChange passwordChange = new PasswordChange();
|
||||
try {
|
||||
passwordChange.username = username;
|
||||
passwordChange.username = getUserName();
|
||||
passwordChange.newPassword = newPassword;
|
||||
ResponsePassword response = passwordChange.execute(this);
|
||||
if (!response.changeSuccessful) {
|
||||
|
@ -162,14 +159,6 @@ public interface FunctionDatabase extends AutoCloseable {
|
|||
*/
|
||||
public String getUserName();
|
||||
|
||||
/**
|
||||
* Set a specific user name for connection. Must be called before connection is initialized.
|
||||
* If this method is not called, connection will use user name of process
|
||||
*
|
||||
* @param userName the user name
|
||||
*/
|
||||
public void setUserName(String userName);
|
||||
|
||||
/**
|
||||
* @return factory the database is using to create LSHVector objects
|
||||
*/
|
||||
|
@ -218,7 +207,7 @@ public interface FunctionDatabase extends AutoCloseable {
|
|||
* If the last query failed to produce a response, use this method to recover the error message
|
||||
* @return a String describing the error
|
||||
*/
|
||||
public Error getLastError();
|
||||
public BSimError getLastError();
|
||||
|
||||
/**
|
||||
* Send a query to the database. The response is returned as a QueryResponseRecord.
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
@ -92,7 +92,7 @@ public abstract class AbstractSQLFunctionDatabase<VF extends LSHVectorFactory>
|
|||
|
||||
protected final VF vectorFactory; // Factory used to generate LSHVector objects
|
||||
|
||||
private Error lasterror;
|
||||
private BSimError lasterror;
|
||||
private Status status;
|
||||
private boolean isinit;
|
||||
|
||||
|
@ -1120,11 +1120,6 @@ public abstract class AbstractSQLFunctionDatabase<VF extends LSHVectorFactory>
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserName(String userName) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
public LSHVectorFactory getLSHVectorFactory() {
|
||||
return vectorFactory;
|
||||
|
@ -1155,7 +1150,7 @@ public abstract class AbstractSQLFunctionDatabase<VF extends LSHVectorFactory>
|
|||
}
|
||||
|
||||
@Override
|
||||
public Error getLastError() {
|
||||
public BSimError getLastError() {
|
||||
return lasterror;
|
||||
}
|
||||
|
||||
|
@ -1188,7 +1183,7 @@ public abstract class AbstractSQLFunctionDatabase<VF extends LSHVectorFactory>
|
|||
}
|
||||
catch (CancelledSQLException e) {
|
||||
status = Status.Error;
|
||||
lasterror = new Error(ErrorCategory.AuthenticationCancelled,
|
||||
lasterror = new BSimError(ErrorCategory.AuthenticationCancelled,
|
||||
"Authentication cancelled by user");
|
||||
return false;
|
||||
}
|
||||
|
@ -1201,19 +1196,19 @@ public abstract class AbstractSQLFunctionDatabase<VF extends LSHVectorFactory>
|
|||
}
|
||||
String msg = cause.getMessage();
|
||||
if (msg.contains("already in use:")) {
|
||||
lasterror = new Error(ErrorCategory.Initialization,
|
||||
lasterror = new BSimError(ErrorCategory.Initialization,
|
||||
"Database already in use by another process");
|
||||
}
|
||||
else if (msg.contains("authentication failed") ||
|
||||
msg.contains("requires a valid client certificate")) {
|
||||
lasterror =
|
||||
new Error(ErrorCategory.Authentication, "Could not authenticate with database");
|
||||
new BSimError(ErrorCategory.Authentication, "Could not authenticate with database");
|
||||
}
|
||||
else if (msg.contains("does not exist") && !msg.contains(" role ")) {
|
||||
lasterror = new Error(ErrorCategory.Nodatabase, cause.getMessage());
|
||||
lasterror = new BSimError(ErrorCategory.Nodatabase, cause.getMessage());
|
||||
}
|
||||
else {
|
||||
lasterror = new Error(ErrorCategory.Initialization,
|
||||
lasterror = new BSimError(ErrorCategory.Initialization,
|
||||
"Database error on initialization: " + cause.getMessage());
|
||||
}
|
||||
return false;
|
||||
|
@ -1628,29 +1623,29 @@ public abstract class AbstractSQLFunctionDatabase<VF extends LSHVectorFactory>
|
|||
lasterror = null;
|
||||
try {
|
||||
if (!(query instanceof CreateDatabase) && !initialize()) {
|
||||
lasterror = new Error(ErrorCategory.Nodatabase, "The database does not exist");
|
||||
lasterror = new BSimError(ErrorCategory.Nodatabase, "The database does not exist");
|
||||
return null;
|
||||
}
|
||||
|
||||
query.buildResponseTemplate();
|
||||
QueryResponseRecord response = doQuery(query, db);
|
||||
if (response == null) {
|
||||
lasterror = new Error(ErrorCategory.Fatal, "Unknown query type");
|
||||
lasterror = new BSimError(ErrorCategory.Fatal, "Unknown query type");
|
||||
query.clearResponse();
|
||||
}
|
||||
}
|
||||
catch (DatabaseNonFatalException err) {
|
||||
lasterror = new Error(ErrorCategory.Nonfatal,
|
||||
lasterror = new BSimError(ErrorCategory.Nonfatal,
|
||||
"Skipping -" + query.getName() + "- : " + err.getMessage());
|
||||
query.clearResponse();
|
||||
}
|
||||
catch (LSHException err) {
|
||||
lasterror = new Error(ErrorCategory.Fatal,
|
||||
lasterror = new BSimError(ErrorCategory.Fatal,
|
||||
"Fatal error during -" + query.getName() + "- : " + err.getMessage());
|
||||
query.clearResponse();
|
||||
}
|
||||
catch (SQLException err) {
|
||||
lasterror = new Error(ErrorCategory.Fatal,
|
||||
lasterror = new BSimError(ErrorCategory.Fatal,
|
||||
"SQL error during -" + query.getName() + "- : " + err.getMessage());
|
||||
query.clearResponse();
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
@ -34,7 +34,7 @@ public class FunctionDatabaseProxy implements FunctionDatabase {
|
|||
private DatabaseInformation info;
|
||||
private LSHVectorFactory vectorFactory;
|
||||
private URL httpURL;
|
||||
private Error lasterror;
|
||||
private BSimError lasterror;
|
||||
private Status status;
|
||||
private boolean isinit;
|
||||
private XmlErrorHandler xmlErrorHandler;
|
||||
|
@ -83,11 +83,6 @@ public class FunctionDatabaseProxy implements FunctionDatabase {
|
|||
return ClientUtil.getUserName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserName(String userName) {
|
||||
// Not currently implemented
|
||||
}
|
||||
|
||||
@Override
|
||||
public LSHVectorFactory getLSHVectorFactory() {
|
||||
return vectorFactory;
|
||||
|
@ -123,7 +118,8 @@ public class FunctionDatabaseProxy implements FunctionDatabase {
|
|||
}
|
||||
if (httpURL == null) {
|
||||
status = Status.Error;
|
||||
lasterror = new FunctionDatabase.Error(ErrorCategory.Initialization, "MalformedURL");
|
||||
lasterror =
|
||||
new FunctionDatabase.BSimError(ErrorCategory.Initialization, "MalformedURL");
|
||||
return false;
|
||||
}
|
||||
QueryInfo queryInfo = new QueryInfo();
|
||||
|
@ -145,7 +141,7 @@ public class FunctionDatabaseProxy implements FunctionDatabase {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Error getLastError() {
|
||||
public BSimError getLastError() {
|
||||
return lasterror;
|
||||
}
|
||||
|
||||
|
@ -168,7 +164,8 @@ public class FunctionDatabaseProxy implements FunctionDatabase {
|
|||
ResponseError respError = new ResponseError();
|
||||
respError.restoreXml(parser, vectorFactory);
|
||||
parser.dispose();
|
||||
lasterror = new FunctionDatabase.Error(ErrorCategory.Fatal, respError.errorMessage);
|
||||
lasterror =
|
||||
new FunctionDatabase.BSimError(ErrorCategory.Fatal, respError.errorMessage);
|
||||
query.clearResponse();
|
||||
return null;
|
||||
}
|
||||
|
@ -184,7 +181,7 @@ public class FunctionDatabaseProxy implements FunctionDatabase {
|
|||
return response;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
lasterror = new FunctionDatabase.Error(ErrorCategory.Connection, ex.getMessage());
|
||||
lasterror = new FunctionDatabase.BSimError(ErrorCategory.Connection, ex.getMessage());
|
||||
status = Status.Error;
|
||||
query.clearResponse();
|
||||
return null;
|
||||
|
|
|
@ -190,8 +190,9 @@ public final class PostgresFunctionDatabase
|
|||
@Override
|
||||
protected void generateRawDatabase() throws SQLException {
|
||||
BSimServerInfo serverInfo = postgresDs.getServerInfo();
|
||||
BSimServerInfo defaultServerInfo = new BSimServerInfo(DBType.postgres,
|
||||
serverInfo.getServerName(), serverInfo.getPort(), DEFAULT_DATABASE_NAME);
|
||||
BSimServerInfo defaultServerInfo =
|
||||
new BSimServerInfo(DBType.postgres, serverInfo.getUserInfo(),
|
||||
serverInfo.getServerName(), serverInfo.getPort(), DEFAULT_DATABASE_NAME);
|
||||
String createdbstring = "CREATE DATABASE \"" + serverInfo.getDBName() + '"';
|
||||
BSimPostgresDataSource defaultDs =
|
||||
BSimPostgresDBConnectionManager.getDataSource(defaultServerInfo);
|
||||
|
@ -502,14 +503,6 @@ public final class PostgresFunctionDatabase
|
|||
return postgresDs.getUserName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserName(String userName) {
|
||||
if (postgresDs.getStatus() == Status.Ready) {
|
||||
throw new IllegalStateException("Connection has already been established");
|
||||
}
|
||||
postgresDs.setPreferredUserName(userName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryResponseRecord doQuery(BSimQuery<?> query, Connection c)
|
||||
throws SQLException, LSHException, DatabaseNonFatalException {
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
@ -31,8 +31,6 @@ public class ElasticConnection {
|
|||
|
||||
protected String hostURL; // http://hostname:port
|
||||
protected String httpURLbase; // Main URL to elasticsearch
|
||||
private HttpURLConnection connection = null;
|
||||
private Writer writer;
|
||||
private int lastResponseCode;
|
||||
|
||||
public ElasticConnection(String url, String repo) {
|
||||
|
@ -41,61 +39,13 @@ public class ElasticConnection {
|
|||
}
|
||||
|
||||
public void close() {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
// nothing to do - http connections do not persist
|
||||
}
|
||||
|
||||
public boolean lastRequestSuccessful() {
|
||||
return (lastResponseCode >= 200) && (lastResponseCode < 300);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new request to the elastic server. This establishes the OutputStream for writing the body of the request
|
||||
* @param command is the type of command
|
||||
* @param path is the overarching index/type/<command> path
|
||||
* @throws IOException for problems with the socket
|
||||
*/
|
||||
public void startHttpRequest(String command, String path) throws IOException {
|
||||
URL httpURL = new URL(httpURLbase + path);
|
||||
connection = (HttpURLConnection) httpURL.openConnection();
|
||||
connection.setRequestMethod(command);
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setDoOutput(true);
|
||||
writer = new OutputStreamWriter(connection.getOutputStream());
|
||||
}
|
||||
|
||||
public void startHttpBulkRequest(String bulkCommand) throws IOException {
|
||||
URL httpURL = new URL(hostURL + bulkCommand);
|
||||
connection = (HttpURLConnection) httpURL.openConnection();
|
||||
connection.setRequestMethod(POST);
|
||||
connection.setRequestProperty("Content-Type", "application/x-ndjson");
|
||||
connection.setDoOutput(true);
|
||||
writer = new OutputStreamWriter(connection.getOutputStream());
|
||||
}
|
||||
|
||||
public void startHttpRawRequest(String command, String path) throws IOException {
|
||||
URL httpURL = new URL(hostURL + path);
|
||||
connection = (HttpURLConnection) httpURL.openConnection();
|
||||
connection.setRequestMethod(command);
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setDoOutput(true);
|
||||
writer = new OutputStreamWriter(connection.getOutputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a request with no input body, URI only
|
||||
* @param command is the command to issue
|
||||
* @param path is the overarching request path: index/...
|
||||
* @throws IOException for problems with the socket
|
||||
*/
|
||||
public void startHttpURICommand(String command, String path) throws IOException {
|
||||
URL httpURL = new URL(httpURLbase + path);
|
||||
connection = (HttpURLConnection) httpURL.openConnection();
|
||||
connection.setRequestMethod(command);
|
||||
connection.setDoOutput(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assuming the writer has been closed and connection.getResponseCode() is called
|
||||
* placing the value in lastResponseCode, read the response and parse into a JSONObject
|
||||
|
@ -103,15 +53,21 @@ public class ElasticConnection {
|
|||
* @throws IOException for problems with the socket
|
||||
* @throws ParseException for JSON parse errors
|
||||
*/
|
||||
private JSONObject grabResponse() throws IOException, ParseException {
|
||||
private JSONObject grabResponse(HttpURLConnection connection)
|
||||
throws IOException, ParseException {
|
||||
JSONParser parser = new JSONParser();
|
||||
Reader reader;
|
||||
InputStream in;
|
||||
if (lastRequestSuccessful()) {
|
||||
reader = new InputStreamReader(connection.getInputStream());
|
||||
in = connection.getInputStream();
|
||||
}
|
||||
else {
|
||||
reader = new InputStreamReader(connection.getErrorStream());
|
||||
in = connection.getErrorStream();
|
||||
}
|
||||
if (in == null) {
|
||||
// Connection error occurred
|
||||
throw new IOException(connection.getResponseMessage());
|
||||
}
|
||||
Reader reader = new InputStreamReader(in);
|
||||
JSONObject jsonObject = (JSONObject) parser.parse(reader);
|
||||
return jsonObject;
|
||||
}
|
||||
|
@ -156,12 +112,18 @@ public class ElasticConnection {
|
|||
*/
|
||||
public JSONObject executeRawStatement(String command, String path, String body)
|
||||
throws ElasticException {
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
startHttpRawRequest(command, path);
|
||||
writer.write(body);
|
||||
writer.close();
|
||||
URL httpURL = new URL(hostURL + path);
|
||||
connection = (HttpURLConnection) httpURL.openConnection();
|
||||
connection.setRequestMethod(command);
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setDoOutput(true);
|
||||
try (Writer writer = new OutputStreamWriter(connection.getOutputStream())) {
|
||||
writer.write(body);
|
||||
}
|
||||
lastResponseCode = connection.getResponseCode();
|
||||
JSONObject resp = grabResponse();
|
||||
JSONObject resp = grabResponse(connection);
|
||||
if (!lastRequestSuccessful()) {
|
||||
throw new ElasticException(parseErrorJSON(resp));
|
||||
}
|
||||
|
@ -173,6 +135,11 @@ public class ElasticConnection {
|
|||
catch (ParseException e) {
|
||||
throw new ElasticException("Error parsing response: " + e.getMessage());
|
||||
}
|
||||
finally {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -185,12 +152,18 @@ public class ElasticConnection {
|
|||
*/
|
||||
public void executeStatementNoResponse(String command, String path, String body)
|
||||
throws ElasticException {
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
startHttpRequest(command, path);
|
||||
writer.write(body);
|
||||
writer.close();
|
||||
URL httpURL = new URL(httpURLbase + path);
|
||||
connection = (HttpURLConnection) httpURL.openConnection();
|
||||
connection.setRequestMethod(command);
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setDoOutput(true);
|
||||
try (Writer writer = new OutputStreamWriter(connection.getOutputStream())) {
|
||||
writer.write(body);
|
||||
}
|
||||
lastResponseCode = connection.getResponseCode();
|
||||
JSONObject resp = grabResponse();
|
||||
JSONObject resp = grabResponse(connection);
|
||||
if (!lastRequestSuccessful()) {
|
||||
throw new ElasticException(parseErrorJSON(resp));
|
||||
}
|
||||
|
@ -201,6 +174,11 @@ public class ElasticConnection {
|
|||
catch (ParseException e) {
|
||||
throw new ElasticException("Error parsing response: " + e.getMessage());
|
||||
}
|
||||
finally {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -213,12 +191,18 @@ public class ElasticConnection {
|
|||
*/
|
||||
public JSONObject executeStatement(String command, String path, String body)
|
||||
throws ElasticException {
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
startHttpRequest(command, path);
|
||||
writer.write(body);
|
||||
writer.close();
|
||||
URL httpURL = new URL(httpURLbase + path);
|
||||
connection = (HttpURLConnection) httpURL.openConnection();
|
||||
connection.setRequestMethod(command);
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setDoOutput(true);
|
||||
try (Writer writer = new OutputStreamWriter(connection.getOutputStream())) {
|
||||
writer.write(body);
|
||||
}
|
||||
lastResponseCode = connection.getResponseCode();
|
||||
JSONObject resp = grabResponse();
|
||||
JSONObject resp = grabResponse(connection);
|
||||
if (!lastRequestSuccessful()) {
|
||||
throw new ElasticException(parseErrorJSON(resp));
|
||||
}
|
||||
|
@ -230,6 +214,11 @@ public class ElasticConnection {
|
|||
catch (ParseException e) {
|
||||
throw new ElasticException("Error parsing response: " + e.getMessage());
|
||||
}
|
||||
finally {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -243,12 +232,18 @@ public class ElasticConnection {
|
|||
*/
|
||||
public JSONObject executeStatementExpectFailure(String command, String path, String body)
|
||||
throws ElasticException {
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
startHttpRequest(command, path);
|
||||
writer.write(body);
|
||||
writer.close();
|
||||
URL httpURL = new URL(httpURLbase + path);
|
||||
connection = (HttpURLConnection) httpURL.openConnection();
|
||||
connection.setRequestMethod(command);
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setDoOutput(true);
|
||||
try (Writer writer = new OutputStreamWriter(connection.getOutputStream())) {
|
||||
writer.write(body);
|
||||
}
|
||||
lastResponseCode = connection.getResponseCode();
|
||||
JSONObject resp = grabResponse();
|
||||
JSONObject resp = grabResponse(connection);
|
||||
return resp;
|
||||
}
|
||||
catch (IOException e) {
|
||||
|
@ -257,6 +252,11 @@ public class ElasticConnection {
|
|||
catch (ParseException e) {
|
||||
throw new ElasticException("Error parsing response: " + e.getMessage());
|
||||
}
|
||||
finally {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -268,12 +268,18 @@ public class ElasticConnection {
|
|||
* @throws ElasticException for any problems with the connection
|
||||
*/
|
||||
public JSONObject executeBulk(String path, String body) throws ElasticException {
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
startHttpBulkRequest(path);
|
||||
writer.write(body);
|
||||
writer.close();
|
||||
URL httpURL = new URL(hostURL + path);
|
||||
connection = (HttpURLConnection) httpURL.openConnection();
|
||||
connection.setRequestMethod(POST);
|
||||
connection.setRequestProperty("Content-Type", "application/x-ndjson");
|
||||
connection.setDoOutput(true);
|
||||
try (Writer writer = new OutputStreamWriter(connection.getOutputStream())) {
|
||||
writer.write(body);
|
||||
}
|
||||
lastResponseCode = connection.getResponseCode();
|
||||
JSONObject resp = grabResponse();
|
||||
JSONObject resp = grabResponse(connection);
|
||||
if (!lastRequestSuccessful()) {
|
||||
throw new ElasticException(parseErrorJSON(resp));
|
||||
}
|
||||
|
@ -285,13 +291,22 @@ public class ElasticConnection {
|
|||
catch (ParseException e) {
|
||||
throw new ElasticException("Error parsing response: " + e.getMessage());
|
||||
}
|
||||
finally {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public JSONObject executeURIOnly(String command, String path) throws ElasticException {
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
startHttpURICommand(command, path);
|
||||
URL httpURL = new URL(httpURLbase + path);
|
||||
connection = (HttpURLConnection) httpURL.openConnection();
|
||||
connection.setRequestMethod(command);
|
||||
connection.setDoOutput(true);
|
||||
lastResponseCode = connection.getResponseCode();
|
||||
JSONObject resp = grabResponse();
|
||||
JSONObject resp = grabResponse(connection);
|
||||
if (!lastRequestSuccessful()) {
|
||||
throw new ElasticException(parseErrorJSON(resp));
|
||||
}
|
||||
|
@ -303,5 +318,10 @@ public class ElasticConnection {
|
|||
catch (ParseException e) {
|
||||
throw new ElasticException("Error parsing response: " + e.getMessage());
|
||||
}
|
||||
finally {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
@ -60,14 +60,13 @@ public class ElasticDatabase implements FunctionDatabase {
|
|||
public static final int MAX_VECTOR_BULK = 200; // Maximum vectors ingested in one bulk request
|
||||
|
||||
private ElasticConnection connection; // Low-level connection to the database
|
||||
private String userName = null; // User name for server authentication
|
||||
private ConnectionType connectionType = ConnectionType.Unencrypted_No_Authentication;
|
||||
private DatabaseInformation info; // Information about the active database
|
||||
private Base64VectorFactory vectorFactory; // factory used to create BSim feature vectors
|
||||
private final BSimServerInfo serverInfo; // NOTE: does not reflect the use of http vs https
|
||||
private final String baseURL; // Base URL for connecting to elasticsearch, i.e. http://hostname:9200
|
||||
private final String repository; // Name of the repository, prefix to all elasticsearch indices
|
||||
private Error lastError; // Info on error caused by last action taken on this interface (null if no error)
|
||||
private BSimError lastError; // Info on error caused by last action taken on this interface (null if no error)
|
||||
private Status status; // status of the connection
|
||||
private boolean initialized; // true if the connection has been successfully initialized
|
||||
|
||||
|
@ -2022,8 +2021,8 @@ public class ElasticDatabase implements FunctionDatabase {
|
|||
throw new MalformedURLException("URL path must indicate the repository only");
|
||||
}
|
||||
repository = path.substring(1);
|
||||
this.serverInfo =
|
||||
new BSimServerInfo(DBType.elastic, baseURL.getHost(), baseURL.getPort(), repository);
|
||||
this.serverInfo = new BSimServerInfo(DBType.elastic, null, baseURL.getHost(),
|
||||
baseURL.getPort(), repository);
|
||||
this.baseURL = fullURL.substring(0, fullURL.length() - path.length());
|
||||
|
||||
lastError = null;
|
||||
|
@ -2398,15 +2397,7 @@ public class ElasticDatabase implements FunctionDatabase {
|
|||
|
||||
@Override
|
||||
public String getUserName() {
|
||||
if (userName != null) {
|
||||
return userName;
|
||||
}
|
||||
return ClientUtil.getUserName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName;
|
||||
return serverInfo.getUserName();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2450,14 +2441,14 @@ public class ElasticDatabase implements FunctionDatabase {
|
|||
vectorFactory.set(config.weightfactory, config.idflookup, config.info.settings);
|
||||
}
|
||||
catch (ElasticException err) {
|
||||
lastError = new Error(ErrorCategory.Initialization,
|
||||
lastError = new BSimError(ErrorCategory.Initialization,
|
||||
"Database error on initialization: " + err.getMessage());
|
||||
status = Status.Error;
|
||||
return false;
|
||||
}
|
||||
catch (NoDatabaseException err) {
|
||||
info = null;
|
||||
lastError = new Error(ErrorCategory.Nodatabase,
|
||||
lastError = new BSimError(ErrorCategory.Nodatabase,
|
||||
"Database has not been created yet: " + err.getMessage());
|
||||
initialized = true;
|
||||
status = Status.Ready;
|
||||
|
@ -2480,14 +2471,14 @@ public class ElasticDatabase implements FunctionDatabase {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Error getLastError() {
|
||||
public BSimError getLastError() {
|
||||
return lastError;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryResponseRecord query(BSimQuery<?> query) {
|
||||
if ((!isInitialized()) && (!(query instanceof CreateDatabase))) {
|
||||
lastError = new Error(ErrorCategory.Nodatabase, "The database does not exist");
|
||||
lastError = new BSimError(ErrorCategory.Nodatabase, "The database does not exist");
|
||||
return null;
|
||||
}
|
||||
lastError = null;
|
||||
|
@ -2554,22 +2545,22 @@ public class ElasticDatabase implements FunctionDatabase {
|
|||
fdbPasswordChange((PasswordChange) query);
|
||||
}
|
||||
else {
|
||||
lastError = new Error(ErrorCategory.Fatal, "Unknown query type");
|
||||
lastError = new BSimError(ErrorCategory.Fatal, "Unknown query type");
|
||||
query.clearResponse();
|
||||
}
|
||||
}
|
||||
catch (DatabaseNonFatalException err) {
|
||||
lastError = new Error(ErrorCategory.Nonfatal,
|
||||
lastError = new BSimError(ErrorCategory.Nonfatal,
|
||||
"Skipping -" + query.getName() + "- : " + err.getMessage());
|
||||
query.clearResponse();
|
||||
}
|
||||
catch (LSHException err) {
|
||||
lastError = new Error(ErrorCategory.Fatal,
|
||||
lastError = new BSimError(ErrorCategory.Fatal,
|
||||
"Fatal error during -" + query.getName() + "- : " + err.getMessage());
|
||||
query.clearResponse();
|
||||
}
|
||||
catch (ElasticException err) {
|
||||
lastError = new Error(ErrorCategory.Fatal,
|
||||
lastError = new BSimError(ErrorCategory.Fatal,
|
||||
"Elastic error during -" + query.getName() + "- : " + err.getMessage());
|
||||
query.clearResponse();
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
@ -384,7 +384,7 @@ public class SimilarFunctionQueryService implements AutoCloseable {
|
|||
return database.getLSHVectorFactory();
|
||||
}
|
||||
|
||||
public FunctionDatabase.Error getLastError() {
|
||||
public FunctionDatabase.BSimError getLastError() {
|
||||
if (database == null) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
@ -17,7 +17,8 @@ package ghidra.features.bsim.query.ingest;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -32,10 +33,8 @@ import ghidra.features.bsim.query.protocol.QueryName;
|
|||
import ghidra.framework.*;
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.framework.client.HeadlessClientAuthenticator;
|
||||
import ghidra.framework.data.DomainObjectAdapter;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.net.SSLContextInitializer;
|
||||
import ghidra.program.database.ProgramDB;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
@ -335,10 +334,6 @@ public class BSimLaunchable implements GhidraLaunchable {
|
|||
optionValueMap.put(option, params[i]);
|
||||
}
|
||||
}
|
||||
String connectingUserName = optionValueMap.get(USER_OPTION);
|
||||
if (connectingUserName == null) {
|
||||
connectingUserName = optionValueMap.put(USER_OPTION, ClientUtil.getUserName());
|
||||
}
|
||||
return subParams;
|
||||
}
|
||||
|
||||
|
@ -981,9 +976,9 @@ public class BSimLaunchable implements GhidraLaunchable {
|
|||
" <config_template> - large_32 | medium_32 | medium_64 | medium_cpool | medium_nosize \n" +
|
||||
"\n" +
|
||||
"BSim URL Forms (bsimURL):\n" +
|
||||
" postgresql://<hostname>[:<port>]/<dbname>\n" +
|
||||
" elastic://<hostname>[:<port>]/<dbname>\n" +
|
||||
" https://<hostname>[:<port>]/<dbname>\n" +
|
||||
" postgresql://[username@]<hostname>[:<port>]/<dbname>\n" +
|
||||
" elastic://[username@]<hostname>[:<port>]/<dbname>\n" +
|
||||
" https://[username@]<hostname>[:<port>]/<dbname>\n" +
|
||||
" file:/[<local-dirpath>/]<dbname>\n" +
|
||||
"\n" +
|
||||
"Ghidra URL Forms (ghidraURL):\n" +
|
||||
|
@ -1010,7 +1005,13 @@ public class BSimLaunchable implements GhidraLaunchable {
|
|||
run(params);
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
Msg.error(this, "Invalid URL specified: " + e.getMessage());
|
||||
String msg = e.getMessage();
|
||||
if (msg == null) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
else {
|
||||
Msg.error(this, "Invalid URL specified: " + msg);
|
||||
}
|
||||
System.exit(22); // EINVAL
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
|
@ -1019,7 +1020,13 @@ public class BSimLaunchable implements GhidraLaunchable {
|
|||
System.exit(22); // EINVAL
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, e.getMessage());
|
||||
String msg = e.getMessage();
|
||||
if (msg == null) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
else {
|
||||
Msg.error(this, msg);
|
||||
}
|
||||
System.exit(1); // Misc Error
|
||||
}
|
||||
}
|
||||
|
@ -1059,7 +1066,7 @@ public class BSimLaunchable implements GhidraLaunchable {
|
|||
|
||||
// Use BSim log config to ensure we get desired console output
|
||||
System.setProperty(LoggingInitialization.LOG4J2_CONFIGURATION_PROPERTY,
|
||||
BSIM_LOGGING_CONFIGURATION_FILE);
|
||||
BSIM_LOGGING_CONFIGURATION_FILE);
|
||||
|
||||
ApplicationConfiguration config;
|
||||
switch (type) {
|
||||
|
@ -1084,6 +1091,10 @@ public class BSimLaunchable implements GhidraLaunchable {
|
|||
ghidra.framework.protocol.ghidra.Handler.registerHandler();
|
||||
ghidra.features.bsim.query.postgresql.Handler.registerHandler();
|
||||
|
||||
if (connectingUserName == null) {
|
||||
// Force default login name
|
||||
connectingUserName = ClientUtil.getUserName();
|
||||
}
|
||||
HeadlessClientAuthenticator.installHeadlessClientAuthenticator(connectingUserName, certPath,
|
||||
true);
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
@ -16,7 +16,6 @@
|
|||
package ghidra.features.bsim.query.ingest;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
||||
|
@ -29,9 +28,8 @@ import org.xml.sax.SAXException;
|
|||
import generic.lsh.vector.LSHVectorFactory;
|
||||
import ghidra.app.decompiler.DecompileException;
|
||||
import ghidra.features.bsim.query.*;
|
||||
import ghidra.features.bsim.query.FunctionDatabase.Error;
|
||||
import ghidra.features.bsim.query.FunctionDatabase.BSimError;
|
||||
import ghidra.features.bsim.query.FunctionDatabase.ErrorCategory;
|
||||
import ghidra.features.bsim.query.FunctionDatabase.Status;
|
||||
import ghidra.features.bsim.query.client.Configuration;
|
||||
import ghidra.features.bsim.query.client.tables.ExeTable.ExeTableOrderColumn;
|
||||
import ghidra.features.bsim.query.description.*;
|
||||
|
@ -52,7 +50,6 @@ public class BulkSignatures implements AutoCloseable {
|
|||
// FIXME: May need to use Msg.showError for popup messages in GUI workbench case
|
||||
|
||||
private final BSimServerInfo bsimServerInfo; // may be null
|
||||
private final String connectingUserName;
|
||||
|
||||
private FunctionDatabase querydb;
|
||||
|
||||
|
@ -61,14 +58,36 @@ public class BulkSignatures implements AutoCloseable {
|
|||
* @param bsimServerInfo the BSim database server info. May be {@code null} if use limited to
|
||||
* signature and update generation only (based upon configuration template).
|
||||
* @param connectingUserName user name to use for BSim server authentication. May be null if
|
||||
* not required or default should be used (see {@link ClientUtil#getUserName()}).
|
||||
* @throws MalformedURLException if the given URL string cannot be parsed
|
||||
* not required or default should be used (see {@link ClientUtil#getUserName()}). If specified
|
||||
* a new {@link BSimServerInfo} instance will be created with the user information set. This
|
||||
* argument is ignored if DB user specified by {@code bsimServerInfo}.
|
||||
*/
|
||||
public BulkSignatures(BSimServerInfo bsimServerInfo, String connectingUserName)
|
||||
throws MalformedURLException {
|
||||
public BulkSignatures(BSimServerInfo bsimServerInfo, String connectingUserName) {
|
||||
if (bsimServerInfo != null && !StringUtils.isBlank(connectingUserName)) {
|
||||
if (!bsimServerInfo.hasDefaultLogin()) {
|
||||
String username = bsimServerInfo.getUserName();
|
||||
if (!username.equals(connectingUserName)) {
|
||||
Msg.warn(this, "BSim DB server info specifies user '" + username +
|
||||
"'. Ignoring user name option: '" + connectingUserName + "'");
|
||||
}
|
||||
}
|
||||
else {
|
||||
bsimServerInfo = new BSimServerInfo(bsimServerInfo.getDBType(), connectingUserName,
|
||||
bsimServerInfo.getServerName(), bsimServerInfo.getPort(),
|
||||
bsimServerInfo.getDBName());
|
||||
}
|
||||
}
|
||||
this.bsimServerInfo = bsimServerInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param bsimServerInfo the BSim database server info. May be {@code null} if use limited to
|
||||
* signature and update generation only (based upon configuration template). If specified,
|
||||
* this object will convey the connecting user name.
|
||||
*/
|
||||
public BulkSignatures(BSimServerInfo bsimServerInfo) {
|
||||
this.bsimServerInfo = bsimServerInfo;
|
||||
this.connectingUserName =
|
||||
connectingUserName != null ? connectingUserName : ClientUtil.getUserName();
|
||||
}
|
||||
|
||||
private void checkBSimServerOperation() {
|
||||
|
@ -93,9 +112,6 @@ public class BulkSignatures implements AutoCloseable {
|
|||
checkBSimServerOperation();
|
||||
|
||||
querydb = BSimClientFactory.buildClient(bsimServerInfo, async);
|
||||
if (querydb.getStatus() == Status.Unconnected) { // may have previously connected
|
||||
querydb.setUserName(connectingUserName);
|
||||
}
|
||||
|
||||
if (!querydb.initialize()) {
|
||||
throw new IOException(querydb.getLastError().message);
|
||||
|
@ -103,7 +119,7 @@ public class BulkSignatures implements AutoCloseable {
|
|||
|
||||
DatabaseInformation info = querydb.getInfo();
|
||||
if (info == null) {
|
||||
Error lastError = querydb.getLastError();
|
||||
BSimError lastError = querydb.getLastError();
|
||||
if (lastError != null && lastError.category == ErrorCategory.Nodatabase) {
|
||||
throw new IOException(lastError.message);
|
||||
}
|
||||
|
@ -190,7 +206,7 @@ public class BulkSignatures implements AutoCloseable {
|
|||
continue;
|
||||
}
|
||||
if (insertreq.execute(querydb) == null) {
|
||||
Error lastError = querydb.getLastError();
|
||||
BSimError lastError = querydb.getLastError();
|
||||
if ((lastError.category == ErrorCategory.Format) ||
|
||||
(lastError.category == ErrorCategory.Nonfatal)) {
|
||||
Msg.warn(this, file.getName() + ": " + lastError.message);
|
||||
|
@ -216,7 +232,7 @@ public class BulkSignatures implements AutoCloseable {
|
|||
loadSignatureXml(file, update.manage);
|
||||
ResponseUpdate respup = update.execute(querydb);
|
||||
if (respup == null) {
|
||||
Error lastError = querydb.getLastError();
|
||||
BSimError lastError = querydb.getLastError();
|
||||
if ((lastError.category == ErrorCategory.Format) ||
|
||||
(lastError.category == ErrorCategory.Nonfatal)) {
|
||||
Msg.warn(this, file.getName() + ": " + lastError.message);
|
||||
|
@ -400,9 +416,6 @@ public class BulkSignatures implements AutoCloseable {
|
|||
checkBSimServerOperation();
|
||||
|
||||
querydb = BSimClientFactory.buildClient(bsimServerInfo, true);
|
||||
if (querydb.getStatus() == Status.Unconnected) { // may have previously connected
|
||||
querydb.setUserName(connectingUserName);
|
||||
}
|
||||
|
||||
// TODO: Should this output differ for command-line vs workbench? debug only?
|
||||
try {
|
||||
|
@ -533,7 +546,7 @@ public class BulkSignatures implements AutoCloseable {
|
|||
establishQueryServerConnection(true);
|
||||
ResponseDelete respdel = query.execute(querydb);
|
||||
if (respdel == null) {
|
||||
Error lastError = querydb.getLastError();
|
||||
BSimError lastError = querydb.getLastError();
|
||||
throw new LSHException("Could not perform delete: " + lastError.message);
|
||||
}
|
||||
|
||||
|
@ -561,7 +574,7 @@ public class BulkSignatures implements AutoCloseable {
|
|||
query.doRebuild = false;
|
||||
ResponseAdjustIndex response = query.execute(querydb);
|
||||
if (response == null) {
|
||||
Error lastError = querydb.getLastError();
|
||||
BSimError lastError = querydb.getLastError();
|
||||
throw new LSHException("Could not drop index: " + lastError.message);
|
||||
}
|
||||
String dbDetail = "for database " + info.databasename + " (" + bsimServerInfo + ")";
|
||||
|
@ -590,7 +603,7 @@ public class BulkSignatures implements AutoCloseable {
|
|||
System.out.println("Starting rebuild ...");
|
||||
ResponseAdjustIndex response = query.execute(querydb);
|
||||
if (response == null) {
|
||||
Error lastError = querydb.getLastError();
|
||||
BSimError lastError = querydb.getLastError();
|
||||
throw new LSHException("Could not rebuild index: " + lastError.message);
|
||||
}
|
||||
String dbDetail = "for database " + info.databasename + " (" + bsimServerInfo + ")";
|
||||
|
@ -617,7 +630,7 @@ public class BulkSignatures implements AutoCloseable {
|
|||
PrewarmRequest request = new PrewarmRequest();
|
||||
ResponsePrewarm response = request.execute(querydb);
|
||||
if (response == null) {
|
||||
Error lastError = querydb.getLastError();
|
||||
BSimError lastError = querydb.getLastError();
|
||||
throw new LSHException("Prewarm failed: " + lastError.message);
|
||||
}
|
||||
String dbDetail = "for database " + info.databasename + " (" + bsimServerInfo + ")";
|
||||
|
@ -662,7 +675,7 @@ public class BulkSignatures implements AutoCloseable {
|
|||
|
||||
ResponseExe response = exeQuery.execute(querydb);
|
||||
if (response == null) {
|
||||
Error lastError = querydb.getLastError();
|
||||
BSimError lastError = querydb.getLastError();
|
||||
throw new LSHException("Could not perform getexeinfo: " + lastError.message);
|
||||
}
|
||||
|
||||
|
@ -735,7 +748,7 @@ public class BulkSignatures implements AutoCloseable {
|
|||
req.description = description;
|
||||
ResponseInfo resp = req.execute(querydb);
|
||||
if (resp == null) {
|
||||
Error lastError = querydb.getLastError();
|
||||
BSimError lastError = querydb.getLastError();
|
||||
throw new LSHException("Could not change metadata: " + lastError.message);
|
||||
}
|
||||
info = resp.info;
|
||||
|
@ -764,7 +777,7 @@ public class BulkSignatures implements AutoCloseable {
|
|||
|
||||
ResponseInfo resp = req.execute(querydb);
|
||||
if (resp == null) {
|
||||
Error lastError = querydb.getLastError();
|
||||
BSimError lastError = querydb.getLastError();
|
||||
throw new LSHException("Could not install new category: " + lastError.message);
|
||||
}
|
||||
info = resp.info;
|
||||
|
@ -798,7 +811,7 @@ public class BulkSignatures implements AutoCloseable {
|
|||
req.tag_name = dequoteString(tagName);
|
||||
ResponseInfo resp = req.execute(querydb);
|
||||
if (resp == null) {
|
||||
Error lastError = querydb.getLastError();
|
||||
BSimError lastError = querydb.getLastError();
|
||||
throw new LSHException(lastError.message);
|
||||
}
|
||||
info = resp.info;
|
||||
|
@ -859,7 +872,7 @@ public class BulkSignatures implements AutoCloseable {
|
|||
while (count != 0) {
|
||||
ResponsePair responsePair = query.execute(querydb);
|
||||
if (responsePair == null) {
|
||||
Error lastError = querydb.getLastError();
|
||||
BSimError lastError = querydb.getLastError();
|
||||
throw new LSHException(lastError.message);
|
||||
}
|
||||
for (PairNote note : responsePair.notes) {
|
||||
|
@ -893,7 +906,7 @@ public class BulkSignatures implements AutoCloseable {
|
|||
establishQueryServerConnection(true);
|
||||
ResponseName resp = query.execute(querydb);
|
||||
if (resp == null) {
|
||||
Error lastError = querydb.getLastError();
|
||||
BSimError lastError = querydb.getLastError();
|
||||
throw new LSHException(lastError.message);
|
||||
}
|
||||
resp.printRaw(outStream, querydb.getLSHVectorFactory(), 0);
|
||||
|
@ -941,7 +954,7 @@ public class BulkSignatures implements AutoCloseable {
|
|||
query.fillinCallgraph = info.trackcallgraph;
|
||||
ResponseName responseName = query.execute(querydb);
|
||||
if (responseName == null) {
|
||||
Error lastError = querydb.getLastError();
|
||||
BSimError lastError = querydb.getLastError();
|
||||
throw new LSHException(lastError.message);
|
||||
}
|
||||
if (!responseName.uniqueexecutable) {
|
||||
|
|
|
@ -20,8 +20,10 @@ import java.util.*;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.DockingWindowManager;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.widgets.textfield.GFormattedTextField;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.features.bsim.gui.*;
|
||||
import ghidra.features.bsim.gui.overview.BSimOverviewProvider;
|
||||
|
@ -90,9 +92,11 @@ public class BSimSearchPluginScreenShots extends GhidraScreenShotGenerator {
|
|||
|
||||
@Test
|
||||
public void testManageServersDialog() {
|
||||
addTestServer(new BSimServerInfo(DBType.postgres, "100.50.123.5", 123, "testDB"));
|
||||
addTestServer(new BSimServerInfo(DBType.postgres, "100.50.123.5", 134, "anotherDB"));
|
||||
addTestServer(new BSimServerInfo(DBType.file, "100.50.123.5", 134, "/bsim/database1"));
|
||||
addTestServer(
|
||||
new BSimServerInfo(DBType.postgres, "mylogin", "100.50.123.5", 123, "testDB"));
|
||||
addTestServer(
|
||||
new BSimServerInfo(DBType.postgres, "mylogin", "100.50.123.5", 134, "anotherDB"));
|
||||
addTestServer(new BSimServerInfo("/bsim/database1"));
|
||||
|
||||
DockingActionIf action = getAction(plugin, "Manage BSim Servers");
|
||||
performAction(action, false);
|
||||
|
@ -106,7 +110,11 @@ public class BSimSearchPluginScreenShots extends GhidraScreenShotGenerator {
|
|||
public void testAddServerDialog() {
|
||||
CreateBsimServerInfoDialog dialog = new CreateBsimServerInfoDialog();
|
||||
runSwingLater(() -> DockingWindowManager.showDialog(dialog));
|
||||
waitForSwing();
|
||||
DialogComponentProvider entryDialog = waitForDialogComponent("Add BSim Server");
|
||||
GFormattedTextField userField =
|
||||
(GFormattedTextField) findComponentByName(entryDialog, "User");
|
||||
userField.setText("mylogin");
|
||||
userField.setDefaultValue("mylogin");
|
||||
captureDialog(dialog);
|
||||
dialog.close();
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
@ -178,7 +178,7 @@ public class BSimSearchPluginTest extends AbstractBSimPluginTest {
|
|||
private FunctionDatabase database;
|
||||
|
||||
public TestBSimServerInfo(FunctionDatabase database) {
|
||||
super(DBType.postgres, "0.0.0.0", 123, "testDB");
|
||||
super(DBType.postgres, null, "0.0.0.0", 123, "testDB");
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ import org.junit.*;
|
|||
|
||||
import ghidra.features.bsim.query.*;
|
||||
import ghidra.features.bsim.query.BSimServerInfo.DBType;
|
||||
import ghidra.features.bsim.query.FunctionDatabase.Error;
|
||||
import ghidra.features.bsim.query.FunctionDatabase.BSimError;
|
||||
import ghidra.features.bsim.query.description.DatabaseInformation;
|
||||
import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource;
|
||||
import ghidra.features.bsim.query.protocol.CreateDatabase;
|
||||
|
@ -68,7 +68,7 @@ public class BSimH2DatabaseManagerTest extends AbstractGhidraHeadedIntegrationTe
|
|||
}
|
||||
|
||||
private BSimServerInfo getBsimServerInfo(String name) {
|
||||
return new BSimServerInfo(DBType.file, null, -1, getDbName(name));
|
||||
return new BSimServerInfo(getDbName(name));
|
||||
}
|
||||
|
||||
private BSimServerInfo createDatabase(String databaseName) {
|
||||
|
@ -103,7 +103,7 @@ public class BSimH2DatabaseManagerTest extends AbstractGhidraHeadedIntegrationTe
|
|||
ResponseInfo response = command.execute(h2Database);
|
||||
if (response == null) {
|
||||
if (expectedError != null) {
|
||||
Error lastError = h2Database.getLastError();
|
||||
BSimError lastError = h2Database.getLastError();
|
||||
assertNotNull(lastError);
|
||||
assertTrue(lastError.message.contains(expectedError));
|
||||
}
|
||||
|
@ -186,7 +186,7 @@ public class BSimH2DatabaseManagerTest extends AbstractGhidraHeadedIntegrationTe
|
|||
BSimServerInfo serverInfo = getBsimServerInfo("test");
|
||||
try (FunctionDatabase fdb = serverInfo.getFunctionDatabase(false)) {
|
||||
assertFalse(fdb.initialize());
|
||||
Error lastError = fdb.getLastError();
|
||||
BSimError lastError = fdb.getLastError();
|
||||
assertNotNull(lastError);
|
||||
assertTrue(lastError.message.startsWith("Database does not exist: "));
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ import ghidra.app.util.headless.HeadlessOptions;
|
|||
import ghidra.features.bsim.gui.filters.ExecutableCategoryBSimFilterType;
|
||||
import ghidra.features.bsim.gui.filters.HasNamedChildBSimFilterType;
|
||||
import ghidra.features.bsim.query.*;
|
||||
import ghidra.features.bsim.query.FunctionDatabase.Error;
|
||||
import ghidra.features.bsim.query.FunctionDatabase.BSimError;
|
||||
import ghidra.features.bsim.query.client.tables.ExeTable.ExeTableOrderColumn;
|
||||
import ghidra.features.bsim.query.description.*;
|
||||
import ghidra.features.bsim.query.ingest.BSimLaunchable;
|
||||
|
@ -264,7 +264,7 @@ public class BSimServerTest {
|
|||
|
||||
private static void testForError(QueryResponseRecord response) throws LSHException {
|
||||
if (response == null) {
|
||||
Error lastError = client.getLastError();
|
||||
BSimError lastError = client.getLastError();
|
||||
if (lastError == null) {
|
||||
throw new LSHException("Unknown error");
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
@ -106,11 +106,6 @@ public class FunctionDatabaseTestDouble implements SQLFunctionDatabase {
|
|||
return ClientUtil.getUserName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserName(String userName) {
|
||||
// Currently not implemented
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getURLString() {
|
||||
return urlString;
|
||||
|
@ -141,8 +136,8 @@ public class FunctionDatabaseTestDouble implements SQLFunctionDatabase {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Error getLastError() {
|
||||
return new Error(ErrorCategory.Unused, errorString);
|
||||
public BSimError getLastError() {
|
||||
return new BSimError(ErrorCategory.Unused, errorString);
|
||||
}
|
||||
|
||||
void setErrorString(String errorString) {
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
@ -23,7 +23,7 @@ public class TestBSimServerInfo extends BSimServerInfo {
|
|||
private FunctionDatabase database;
|
||||
|
||||
public TestBSimServerInfo(FunctionDatabase database) {
|
||||
super(DBType.postgres, "100.50.123.5", 123, "testDB");
|
||||
super(DBType.postgres, null, "100.50.123.5", 123, "testDB");
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
@ -38,7 +38,8 @@ public class PasswordDialog extends DialogComponentProvider {
|
|||
private JPasswordField passwordField;
|
||||
private JComboBox<String> choiceCB;
|
||||
private JCheckBox anonymousAccess;
|
||||
boolean okPressed = false;
|
||||
private boolean okPressed = false;
|
||||
private String defaultUserID;
|
||||
|
||||
/**
|
||||
* Construct a new PasswordDialog.
|
||||
|
@ -47,7 +48,7 @@ public class PasswordDialog extends DialogComponentProvider {
|
|||
* @param serverName name of server or keystore pathname
|
||||
* @param passPrompt password prompt to show in the dialog; may be null, in which case
|
||||
* "Password:" is displayed next to the password field
|
||||
* @param namePrompt name prompt to show in the dialog, if null a name will not be prompted for.
|
||||
* @param userIdPrompt User ID / Name prompt to show in the dialog, if null a name will not be prompted for.
|
||||
* @param defaultUserID default name when prompting for a name
|
||||
* @param choicePrompt namePrompt name prompt to show in the dialog, if null a name will not be prompted for.
|
||||
* @param choices array of choices to present if choicePrompt is not null
|
||||
|
@ -55,9 +56,9 @@ public class PasswordDialog extends DialogComponentProvider {
|
|||
* @param includeAnonymousOption true signals to add a checkbox to request anonymous login
|
||||
*/
|
||||
public PasswordDialog(String title, String serverType, String serverName, String passPrompt,
|
||||
String namePrompt, String defaultUserID, String choicePrompt, String[] choices,
|
||||
String userIdPrompt, String defaultUserID, String choicePrompt, String[] choices,
|
||||
int defaultChoice, boolean includeAnonymousOption) {
|
||||
this(title, serverType, serverName, passPrompt, namePrompt, defaultUserID);
|
||||
this(title, serverType, serverName, passPrompt, userIdPrompt, defaultUserID);
|
||||
if (choicePrompt != null) {
|
||||
workPanel.add(new GLabel(choicePrompt));
|
||||
choiceCB = new GComboBox<>(choices);
|
||||
|
@ -94,12 +95,12 @@ public class PasswordDialog extends DialogComponentProvider {
|
|||
* @param serverName name of server or keystore pathname
|
||||
* @param passPrompt password prompt to show in the dialog; may be null, in which case
|
||||
* "Password:" is displayed next to the password field
|
||||
* @param namePrompt name prompt to show in the dialog, if null a name will not be prompted for.
|
||||
* @param userIdPrompt User ID / Name prompt to show in the dialog, if null a name will not be prompted for.
|
||||
* @param defaultUserID default name when prompting for a name
|
||||
*/
|
||||
public PasswordDialog(String title, String serverType, String serverName, String passPrompt,
|
||||
String namePrompt, String defaultUserID) {
|
||||
this(title, serverType, serverName, passPrompt, namePrompt, defaultUserID, true);
|
||||
String userIdPrompt, String defaultUserID) {
|
||||
this(title, serverType, serverName, passPrompt, userIdPrompt, defaultUserID, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -109,14 +110,17 @@ public class PasswordDialog extends DialogComponentProvider {
|
|||
* @param serverName name of server or keystore pathname
|
||||
* @param passPrompt password prompt to show in the dialog; may be null, in which case
|
||||
* "Password:" is displayed next to the password field
|
||||
* @param namePrompt name prompt to show in the dialog, if null a name will not be prompted for.
|
||||
* @param userIdPrompt User ID / Name prompt to show in the dialog, if null a name will not be prompted for.
|
||||
* @param defaultUserID default name when prompting for a name
|
||||
* @param hasMessages true if the client will set messages on this dialog. If true, the
|
||||
* dialog's minimum size will be increased
|
||||
*/
|
||||
public PasswordDialog(String title, String serverType, String serverName, String passPrompt,
|
||||
String namePrompt, String defaultUserID, boolean hasMessages) {
|
||||
String userIdPrompt, String defaultUserID, boolean hasMessages) {
|
||||
super(title, true);
|
||||
|
||||
this.defaultUserID = defaultUserID;
|
||||
|
||||
setRememberSize(false);
|
||||
setTransient(true);
|
||||
|
||||
|
@ -132,8 +136,8 @@ public class PasswordDialog extends DialogComponentProvider {
|
|||
workPanel.add(new GLabel(serverName));
|
||||
}
|
||||
|
||||
if (namePrompt != null) {
|
||||
workPanel.add(new GLabel(namePrompt));
|
||||
if (userIdPrompt != null) {
|
||||
workPanel.add(new GLabel(userIdPrompt));
|
||||
nameField = new JTextField(defaultUserID, 16);
|
||||
nameField.setName("NAME-ENTRY-COMPONENT");
|
||||
workPanel.add(nameField);
|
||||
|
@ -237,11 +241,11 @@ public class PasswordDialog extends DialogComponentProvider {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return the user ID entered in the password field
|
||||
* @return the user ID entered in the password field
|
||||
* Return the user ID / Name entered in the password field
|
||||
* @return the user ID / Name entered in the password field
|
||||
*/
|
||||
public String getUserID() {
|
||||
return nameField != null ? nameField.getText().trim() : null;
|
||||
return nameField != null ? nameField.getText().trim() : defaultUserID;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
@ -16,11 +16,12 @@
|
|||
package ghidra.framework.client;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.net.Authenticator;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.net.*;
|
||||
|
||||
import javax.security.auth.callback.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import docking.DockingWindowManager;
|
||||
import docking.widgets.*;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
|
@ -35,23 +36,63 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider
|
|||
private Authenticator authenticator = new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
Msg.debug(this, "PasswordAuthentication requested for " + getRequestingURL());
|
||||
NameCallback nameCb = null;
|
||||
if (!"NO_NAME".equals(getRequestingScheme())) {
|
||||
nameCb = new NameCallback("Name: ", ClientUtil.getUserName());
|
||||
|
||||
String serverName = getRequestingHost();
|
||||
URL requestingURL = getRequestingURL();
|
||||
|
||||
String pwd = null;
|
||||
String userName = ClientUtil.getUserName();
|
||||
boolean useDefaultUser = true;
|
||||
|
||||
if (requestingURL != null) {
|
||||
String userInfo = requestingURL.getUserInfo();
|
||||
if (userInfo != null) {
|
||||
// Use user info from URL
|
||||
int pwdSep = userInfo.indexOf(':');
|
||||
if (pwdSep < 0) {
|
||||
userName = userInfo;
|
||||
useDefaultUser = false;
|
||||
}
|
||||
else {
|
||||
pwd = userInfo.substring(pwdSep + 1);
|
||||
if (pwdSep != 0) {
|
||||
userName = userInfo.substring(0, pwdSep);
|
||||
useDefaultUser = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
URL minimalURL = DefaultClientAuthenticator.getMinimalURL(requestingURL);
|
||||
if (minimalURL != null) {
|
||||
serverName = minimalURL.toExternalForm();
|
||||
}
|
||||
}
|
||||
|
||||
Msg.debug(this, "PasswordAuthentication requested for " + serverName);
|
||||
|
||||
if (pwd != null) {
|
||||
// Requesting URL specified password
|
||||
return new PasswordAuthentication(userName, pwd.toCharArray());
|
||||
}
|
||||
|
||||
NameCallback nameCb = new NameCallback("Name: ", userName);
|
||||
if (!useDefaultUser) {
|
||||
// Prevent modification of user name by password prompting
|
||||
nameCb.setName(userName);
|
||||
}
|
||||
|
||||
// Prompt for password
|
||||
String prompt = getRequestingPrompt();
|
||||
if (prompt == null) {
|
||||
prompt = "Password:";
|
||||
if (StringUtils.isBlank(prompt) || "security".equals(prompt)) {
|
||||
prompt = "Password:"; // assume dialog will show user name via nameCb
|
||||
}
|
||||
PasswordCallback passCb = new PasswordCallback(prompt, false);
|
||||
try {
|
||||
ServerPasswordPrompt pp = new ServerPasswordPrompt("Connection Authentication",
|
||||
"Server", getRequestingHost(), nameCb, passCb, null, null, null);
|
||||
"Server", serverName, nameCb, passCb, null, null, null);
|
||||
SystemUtilities.runSwingNow(pp);
|
||||
if (pp.okWasPressed()) {
|
||||
return new PasswordAuthentication(nameCb != null ? nameCb.getName() : null,
|
||||
passCb.getPassword());
|
||||
return new PasswordAuthentication(nameCb.getName(), passCb.getPassword());
|
||||
}
|
||||
}
|
||||
finally {
|
||||
|
@ -61,6 +102,21 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Produce minimal URL (i.e., protocol, host and port)
|
||||
* @param url request URL
|
||||
* @return minimal URL
|
||||
*/
|
||||
public static URL getMinimalURL(URL url) {
|
||||
try {
|
||||
return new URL(url, "/");
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
// ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authenticator getAuthenticator() {
|
||||
return authenticator;
|
||||
|
@ -165,10 +221,25 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider
|
|||
choicePrompt = choiceCb.getPrompt();
|
||||
choices = choiceCb.getChoices();
|
||||
}
|
||||
PasswordDialog pwdDialog =
|
||||
new PasswordDialog(title, serverType, serverName, passCb.getPrompt(),
|
||||
nameCb != null ? nameCb.getPrompt() : null, getDefaultUserName(), choicePrompt,
|
||||
choices, getDefaultChoice(), anonymousCb != null);
|
||||
|
||||
String defaultUserName = null;
|
||||
String namePrompt = null;
|
||||
if (nameCb != null) {
|
||||
defaultUserName = nameCb.getName();
|
||||
if (defaultUserName == null) {
|
||||
// Name entry only permitted with name callback where name has not be pre-set
|
||||
defaultUserName = nameCb.getDefaultName();
|
||||
namePrompt = nameCb.getPrompt();
|
||||
}
|
||||
}
|
||||
if (defaultUserName == null) {
|
||||
defaultUserName = getDefaultUserName();
|
||||
}
|
||||
|
||||
PasswordDialog pwdDialog = new PasswordDialog(title, serverType, serverName,
|
||||
passCb.getPrompt(), namePrompt, defaultUserName, choicePrompt, choices,
|
||||
getDefaultChoice(), anonymousCb != null);
|
||||
|
||||
if (errorMsg != null) {
|
||||
pwdDialog.setErrorText(errorMsg);
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
@ -39,38 +39,58 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
|
|||
private final static char[] BADPASSWORD = "".toCharArray();
|
||||
|
||||
private static Object sshPrivateKey;
|
||||
private static String userID = ClientUtil.getUserName(); // default username
|
||||
private static String defaultUserName = ClientUtil.getUserName();
|
||||
private static boolean passwordPromptAllowed;
|
||||
|
||||
private Authenticator authenticator = new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
Msg.debug(this, "PasswordAuthentication requested for " + getRequestingURL());
|
||||
String usage = null;
|
||||
String prompt = getRequestingPrompt();
|
||||
if ("security".equals(prompt)) {
|
||||
prompt = null; // squash generic "security" prompt
|
||||
|
||||
if (defaultUserName == null) {
|
||||
throw new IllegalStateException("Default user name is unknown");
|
||||
}
|
||||
URL requestingURL = getRequestingURL();
|
||||
|
||||
String serverName = getRequestingHost();
|
||||
URL requestingURL = getRequestingURL(); // may be null
|
||||
|
||||
String pwd = null;
|
||||
String userName = defaultUserName;
|
||||
|
||||
if (requestingURL != null) {
|
||||
URL minimalURL = null;
|
||||
try {
|
||||
minimalURL = new URL(requestingURL, "/");
|
||||
String userInfo = requestingURL.getUserInfo();
|
||||
if (userInfo != null) {
|
||||
// Use user info from URL
|
||||
int pwdSep = userInfo.indexOf(':');
|
||||
if (pwdSep < 0) {
|
||||
userName = userInfo;
|
||||
}
|
||||
else {
|
||||
pwd = userInfo.substring(pwdSep + 1);
|
||||
if (pwdSep != 0) {
|
||||
userName = userInfo.substring(0, pwdSep);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
// ignore
|
||||
|
||||
URL minimalURL = DefaultClientAuthenticator.getMinimalURL(requestingURL);
|
||||
if (minimalURL != null) {
|
||||
serverName = minimalURL.toExternalForm();
|
||||
}
|
||||
usage = "Access password requested for " +
|
||||
(minimalURL != null ? minimalURL.toExternalForm()
|
||||
: requestingURL.getAuthority());
|
||||
prompt = "Password:";
|
||||
}
|
||||
if (prompt == null) {
|
||||
// Assume Ghidra Server access
|
||||
String host = getRequestingHost();
|
||||
prompt = (host != null ? (host + " ") : "") + "(" + userID + ") Password:";
|
||||
|
||||
Msg.debug(this, "PasswordAuthentication requested for " + serverName);
|
||||
|
||||
if (pwd != null) {
|
||||
// Requesting URL specified password
|
||||
return new PasswordAuthentication(userName, pwd.toCharArray());
|
||||
}
|
||||
return new PasswordAuthentication(userID, getPassword(usage, prompt));
|
||||
|
||||
String usage = "Access password requested for " + serverName;
|
||||
String prompt = getRequestingPrompt();
|
||||
if (StringUtils.isBlank(prompt) || "security".equals(prompt)) {
|
||||
prompt = "Password for " + userName +":";
|
||||
}
|
||||
return new PasswordAuthentication(userName, getPassword(usage, prompt));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -85,7 +105,8 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
|
|||
/**
|
||||
* Install headless client authenticator for Ghidra Server
|
||||
* @param username optional username to be used with a Ghidra Server which
|
||||
* allows username to be specified
|
||||
* allows username to be specified. If null, {@link ClientUtil#getUserName()}
|
||||
* will be used.
|
||||
* @param keystorePath optional PKI or SSH keystore path. May also be specified
|
||||
* as resource path for SSH key.
|
||||
* @param allowPasswordPrompt if true the user may be prompted for passwords
|
||||
|
@ -97,7 +118,7 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
|
|||
boolean allowPasswordPrompt) throws IOException {
|
||||
passwordPromptAllowed = allowPasswordPrompt;
|
||||
if (username != null) {
|
||||
userID = username;
|
||||
defaultUserName = username;
|
||||
}
|
||||
|
||||
// clear existing key store settings
|
||||
|
@ -175,7 +196,7 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
|
|||
passwordPrompt += "\n";
|
||||
}
|
||||
|
||||
if (prompt == null) {
|
||||
if (StringUtils.isBlank(prompt)) {
|
||||
prompt = "Password:";
|
||||
}
|
||||
|
||||
|
@ -233,17 +254,39 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
|
|||
anonymousCb.setAnonymousAccessRequested(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (defaultUserName == null) {
|
||||
throw new IllegalStateException("Default user name is unknown");
|
||||
}
|
||||
|
||||
if (choiceCb != null) {
|
||||
choiceCb.setSelectedIndex(1);
|
||||
}
|
||||
if (nameCb != null && userID != null) {
|
||||
nameCb.setName(userID);
|
||||
|
||||
String userName = null;
|
||||
if (nameCb != null) {
|
||||
userName = nameCb.getName();
|
||||
if (userName == null) {
|
||||
userName = nameCb.getDefaultName();
|
||||
}
|
||||
}
|
||||
if (userName == null) {
|
||||
userName = defaultUserName;
|
||||
}
|
||||
|
||||
if (nameCb != null) {
|
||||
nameCb.setName(defaultUserName);
|
||||
}
|
||||
|
||||
String usage = null;
|
||||
if (serverName != null) {
|
||||
usage = serverType + ": " + serverName;
|
||||
}
|
||||
char[] password = getPassword(usage, passCb.getPrompt());
|
||||
|
||||
// Ignore prompt specified by passCb
|
||||
String prompt = "Password for " + userName +":";
|
||||
|
||||
char[] password = getPassword(usage, prompt);
|
||||
passCb.setPassword(password);
|
||||
return password != null;
|
||||
}
|
||||
|
@ -278,7 +321,7 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
|
|||
return false;
|
||||
}
|
||||
if (nameCb != null) {
|
||||
nameCb.setName(userID);
|
||||
nameCb.setName(defaultUserName);
|
||||
}
|
||||
try {
|
||||
sshCb.sign(sshPrivateKey);
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
@ -687,6 +687,10 @@ public class GhidraURL {
|
|||
if (StringUtils.isBlank(host)) {
|
||||
throw new IllegalArgumentException("host required");
|
||||
}
|
||||
// TODO: Need to improve checks and use of URL encoding
|
||||
if (host.indexOf('@') >= 0) { // prevent user info with hostname
|
||||
throw new IllegalArgumentException("invalid host name");
|
||||
}
|
||||
if (StringUtils.isBlank(repositoryName)) {
|
||||
throw new IllegalArgumentException("repository name required");
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
@ -109,6 +109,9 @@ public class GhidraURLConnection extends URLConnection {
|
|||
public GhidraURLConnection(URL url, GhidraProtocolHandler protocolHandler)
|
||||
throws MalformedURLException {
|
||||
super(url);
|
||||
if (url.getUserInfo() != null) {
|
||||
throw new MalformedURLException("User info not supported by Ghidra URLs");
|
||||
}
|
||||
if (protocolHandler == null) {
|
||||
throw new IllegalArgumentException("missing required protocol handler");
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue