Merge remote-tracking branch 'origin/GP-5167_ghidra1_BSimUsername--SQUASHED'

This commit is contained in:
ghidra1 2024-12-12 14:17:53 -05:00
commit e07aa499ea
34 changed files with 1097 additions and 429 deletions

View file

@ -26,7 +26,7 @@ import ghidra.app.script.GhidraScript;
import ghidra.features.base.values.GhidraValuesMap; import ghidra.features.base.values.GhidraValuesMap;
import ghidra.features.bsim.query.*; import ghidra.features.bsim.query.*;
import ghidra.features.bsim.query.BSimServerInfo.DBType; 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.FunctionDatabase.ErrorCategory;
import ghidra.features.bsim.query.description.DatabaseInformation; import ghidra.features.bsim.query.description.DatabaseInformation;
import ghidra.features.bsim.query.description.DescriptionManager; import ghidra.features.bsim.query.description.DescriptionManager;
@ -71,8 +71,7 @@ public class AddProgramToH2BSimDatabaseScript extends GhidraScript {
askValues("Select Database File", null, values); askValues("Select Database File", null, values);
File h2DbFile = values.getFile(DATABASE); File h2DbFile = values.getFile(DATABASE);
BSimServerInfo serverInfo = BSimServerInfo serverInfo = new BSimServerInfo(h2DbFile.getAbsolutePath());
new BSimServerInfo(DBType.file, null, 0, h2DbFile.getAbsolutePath());
BSimH2FileDataSource existingBDS = BSimH2FileDataSource existingBDS =
BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo); BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo);
@ -129,7 +128,7 @@ public class AddProgramToH2BSimDatabaseScript extends GhidraScript {
InsertRequest insertreq = new InsertRequest(); InsertRequest insertreq = new InsertRequest();
insertreq.manage = manager; insertreq.manage = manager;
if (insertreq.execute(h2Database) == null) { if (insertreq.execute(h2Database) == null) {
Error lastError = h2Database.getLastError(); BSimError lastError = h2Database.getLastError();
if ((lastError.category == ErrorCategory.Format) || if ((lastError.category == ErrorCategory.Format) ||
(lastError.category == ErrorCategory.Nonfatal)) { (lastError.category == ErrorCategory.Nonfatal)) {
Msg.showWarn(this, null, "Skipping Insert", Msg.showWarn(this, null, "Skipping Insert",

View file

@ -25,7 +25,7 @@ import ghidra.app.script.GhidraScript;
import ghidra.features.base.values.GhidraValuesMap; import ghidra.features.base.values.GhidraValuesMap;
import ghidra.features.bsim.query.*; import ghidra.features.bsim.query.*;
import ghidra.features.bsim.query.BSimServerInfo.DBType; 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.description.DatabaseInformation;
import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager; import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager;
import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource; import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource;
@ -89,8 +89,7 @@ public class CreateH2BSimDatabaseScript extends GhidraScript {
List<String> cats = parseCSV(exeCatCSV); List<String> cats = parseCSV(exeCatCSV);
File dbFile = new File(dbDir, databaseName); File dbFile = new File(dbDir, databaseName);
BSimServerInfo serverInfo = BSimServerInfo serverInfo = new BSimServerInfo(dbFile.getAbsolutePath());
new BSimServerInfo(DBType.file, null, 0, dbFile.getAbsolutePath());
BSimH2FileDataSource existingBDS = BSimH2FileDataSource existingBDS =
BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo); BSimH2FileDBConnectionManager.getDataSourceIfExists(serverInfo);
@ -118,7 +117,7 @@ public class CreateH2BSimDatabaseScript extends GhidraScript {
req.tag_name = tag; req.tag_name = tag;
ResponseInfo resp = req.execute(h2Database); ResponseInfo resp = req.execute(h2Database);
if (resp == null) { if (resp == null) {
Error lastError = h2Database.getLastError(); BSimError lastError = h2Database.getLastError();
throw new LSHException(lastError.message); throw new LSHException(lastError.message);
} }
} }
@ -128,7 +127,7 @@ public class CreateH2BSimDatabaseScript extends GhidraScript {
req.type_name = cat; req.type_name = cat;
ResponseInfo resp = req.execute(h2Database); ResponseInfo resp = req.execute(h2Database);
if (resp == null) { if (resp == null) {
Error lastError = h2Database.getLastError(); BSimError lastError = h2Database.getLastError();
throw new LSHException(lastError.message); throw new LSHException(lastError.message);
} }
} }

View file

@ -130,7 +130,7 @@ public class QueryWithFiltersScript extends GhidraScript {
String dbUrl = String dbUrl =
askString("", "Enter the URL of the BSim database:", "ghidra://localhost/bsimDb"); askString("", "Enter the URL of the BSim database:", "ghidra://localhost/bsimDb");
queryService.initializeDatabase(dbUrl); queryService.initializeDatabase(dbUrl);
FunctionDatabase.Error error = queryService.getLastError(); FunctionDatabase.BSimError error = queryService.getLastError();
if (error != null && error.category == ErrorCategory.Nodatabase) { if (error != null && error.category == ErrorCategory.Nodatabase) {
println("Database [" + dbUrl + "] cannot be found (does it exist?)"); println("Database [" + dbUrl + "] cannot be found (does it exist?)");
return; return;

View file

@ -326,7 +326,12 @@
<DD> <DD>
<P>Creates a new empty repository. A URL and configuration template (<SPAN class= <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 "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">&ldquo;Creating a
Database&rdquo;</A> for more details and discussion on configuration template use.
See <A class="xref" href="DatabaseConfiguration.html#DatabaseTemplates"
title="Creating Database Templates">&ldquo;Creating Database Templates&ldquo;</A>
if a standard template will not suffice.</P>
<P>Supported configuration templates (<SPAN class= <P>Supported configuration templates (<SPAN class=
"bold"><STRONG>config_template</STRONG></SPAN>) are defined within the Ghidra "bold"><STRONG>config_template</STRONG></SPAN>) are defined within the Ghidra
@ -792,21 +797,21 @@
<TD>PostgreSQL</TD> <TD>PostgreSQL</TD>
<TD><CODE class= <TD><CODE class=
"computeroutput">postgresql://&lt;hostname&gt;[:&lt;port&gt;]/&lt;dbname&gt;</CODE></TD> "computeroutput">postgresql://[&lt;username&gt;@]&lt;hostname&gt;[:&lt;port&gt;]/&lt;dbname&gt;</CODE></TD>
</TR> </TR>
<TR> <TR>
<TD>Elasticsearch</TD> <TD>Elasticsearch</TD>
<TD><CODE class= <TD><CODE class=
"computeroutput">https://&lt;hostname&gt;[:&lt;port&gt;]/&lt;dbname&gt;</CODE></TD> "computeroutput">https://[&lt;username&gt;@]&lt;hostname&gt;[:&lt;port&gt;]/&lt;dbname&gt;</CODE></TD>
</TR> </TR>
<TR> <TR>
<TD>Elasticsearch</TD> <TD>Elasticsearch</TD>
<TD><CODE class= <TD><CODE class=
"computeroutput">elastic://&lt;hostname&gt;[:&lt;port&gt;]/&lt;dbname&gt;</CODE></TD> "computeroutput">elastic://[&lt;username&gt;@]&lt;hostname&gt;[:&lt;port&gt;]/&lt;dbname&gt;</CODE></TD>
</TR> </TR>
<TR> <TR>
@ -820,6 +825,24 @@
<P>The use of the <EM>https</EM> and <EM>elastic</EM> is equivalent.</P> <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">&lt;username&gt;</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">&lt;username&gt;</CODE> has been specified within a BSim URL
a <CODE class="computeroutput">&lt;password&gt;</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">&lt;password&gt;</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 <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 must be specified without the <EM>*.mv.db</EM> extension. Only the '/' character should be
used as a directory separator. In addition, when running on Windows, the directory path used as a directory separator. In addition, when running on Windows, the directory path

View file

@ -527,6 +527,34 @@
that are different from the standard PostgreSQL defaults. To provide site specific that are different from the standard PostgreSQL defaults. To provide site specific
configuration, changes should be made to the normal PostgreSQL configuration files.</P> configuration, changes should be made to the normal PostgreSQL configuration files.</P>
</DIV> </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&nbsp;"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>
<DIV class="sect2"> <DIV class="sect2">
@ -562,14 +590,23 @@
<P>In order to make use of Elasticsearch with BSim, the database administrator must <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 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 Elasticsearch deployment. The plug-in is available in the Ghidra extension named <SPAN
class="emphasis"><EM>BSimElasticPlugin</EM></SPAN>, which unpacks into a standard class="emphasis"><EM>BSimElasticPlugin</EM></SPAN>
Ghidra installation. The file <SPAN class="emphasis"><EM>lsh.zip</EM></SPAN> is a (<CODE class="computeroutput">&lt;ghidra-install-dir&gt;/Extensions/Ghidra/ghidra_11.2.1_U_20241105_BSimElasticPlugin.zip</CODE>).
standard Elasticsearch plug-in that must be installed on every node of the cluster 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 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 the BSim plug-in for a single node, but this will need to be repeated for any
additional nodes.</P> 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 <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 using the <SPAN class="emphasis"><EM>elasticsearch-plugin</EM></SPAN> command in the
<SPAN class="emphasis"><EM>bin</EM></SPAN> directory of the node's Elasticsearch <SPAN class="emphasis"><EM>bin</EM></SPAN> directory of the node's Elasticsearch
@ -579,7 +616,7 @@
<TABLE border="0" summary="Simple list" class="simplelist"> <TABLE border="0" summary="Simple list" class="simplelist">
<TR> <TR>
<TD><CODE class="computeroutput">bin/elasticsearch-plugin install <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> </TR>
</TABLE> </TABLE>
</DIV> </DIV>
@ -610,7 +647,7 @@
</DIV> </DIV>
</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 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 <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 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">&ldquo;Ghidra and BSim class="xref" href="CommandLineReference.html#URLs">&ldquo;Ghidra and BSim
URLs&rdquo;</A> for additional information about URLs.</P> URLs&rdquo;</A> for additional information about URLs.</P>
</DIV> </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&nbsp;"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>
</DIV> </DIV>
@ -672,8 +737,7 @@ curl -k -u elastic:XXXXXX -X POST "https://localhost:9200/_security/user/ghidrau
<DIV class="titlepage"> <DIV class="titlepage">
<DIV> <DIV>
<DIV> <DIV>
<H2 class="title" style="clear: both"><A name="CreateDatabase"></A>Creating a <H2 class="title" style="clear: both"><A name="CreateDatabase"></A>Creating a Database</H2>
Database</H2>
</DIV> </DIV>
</DIV> </DIV>
</DIV> </DIV>

View file

@ -53,9 +53,23 @@
<BR> <BR>
<P>The dialog displays a table showing all the currently defined BSim databases/servers. Each <P>The dialog displays a table showing all the currently defined BSim databases/servers.
entry shows a name for the BSim database, its type (postgres, elastic, or file), a host ip Table columns displayed include:</P>
and port (if applicable), and finally the number of active connections.</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> <P>There are four primary actions for this dialog:</P>
<A name="Manage_Servers_Actions"></A> <A name="Manage_Servers_Actions"></A>
@ -68,8 +82,8 @@
selected entry will be deleted. This action will force an immediate disconnect for an selected entry will be deleted. This action will force an immediate disconnect for an
active/idle connection and should be used with care.</LI> active/idle connection and should be used with care.</LI>
<LI><IMG alt="" src="icon.bsim.disconnected"><IMG alt="" src="icon.bsim.connected">&nbsp; <LI><IMG alt="" src="icon.bsim.disconnected"><IMG alt="" src="icon.bsim.connected">&nbsp;Connect
Connect or disconnect an inactive database/server connection. This action is not supported or disconnect an inactive database/server connection which has been selected. This action is not supported
by Elastic database servers.</LI> by Elastic database servers.</LI>
<LI><IMG alt="" src="icon.bsim.change.password">&nbsp;Change password - A change password <LI><IMG alt="" src="icon.bsim.change.password">&nbsp;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 <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, needs to be specified. For <I>postgres</I> and <I>elastic</I>, you must enter a database name,
you see a button for using a filechooser to pick the file that is the local BSim H2 hostname/address, and port. Specify a host name of "localhost" for a server running on the local system.
database.</P> 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>
</BLOCKQUOTE> </BLOCKQUOTE>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

View file

@ -77,10 +77,11 @@ public class BSimServerManager {
String dbTypeName = properties.getString("DBType", null); String dbTypeName = properties.getString("DBType", null);
DBType dbType = DBType.valueOf(dbTypeName); DBType dbType = DBType.valueOf(dbTypeName);
String name = properties.getString("Name", null); String name = properties.getString("Name", null);
String user = properties.getString("User", null);
String host = properties.getString("Host", null); String host = properties.getString("Host", null);
int port = properties.getInt("Port", 0); int port = properties.getInt("Port", 0);
if (dbType != null && name != null) { if (dbType != null && name != null) {
BSimServerInfo info = new BSimServerInfo(dbType, host, port, name); BSimServerInfo info = new BSimServerInfo(dbType, user, host, port, name);
return info; return info;
} }
Msg.showError(this, null, "Error reading Bsim Server File", Msg.showError(this, null, "Error reading Bsim Server File",
@ -97,6 +98,10 @@ public class BSimServerManager {
GProperties properties = new GProperties("BSimServerInfo"); GProperties properties = new GProperties("BSimServerInfo");
properties.putString("DBType", info.getDBType().name()); properties.putString("DBType", info.getDBType().name());
properties.putString("Name", info.getDBName()); 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.putString("Host", info.getServerName());
properties.putInt("Port", info.getPort()); properties.putInt("Port", info.getPort());

View file

@ -35,7 +35,7 @@ import generic.theme.GIcon;
import ghidra.features.bsim.gui.BSimServerManager; import ghidra.features.bsim.gui.BSimServerManager;
import ghidra.features.bsim.query.*; import ghidra.features.bsim.query.*;
import ghidra.features.bsim.query.BSimServerInfo.DBType; 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.FunctionDatabase.ErrorCategory;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.util.*; import ghidra.util.*;
@ -175,7 +175,7 @@ public class BSimServerDialog extends DialogComponentProvider {
try (FunctionDatabase db = BSimClientFactory.buildClient(serverInfo, true)) { try (FunctionDatabase db = BSimClientFactory.buildClient(serverInfo, true)) {
if (!db.initialize()) { if (!db.initialize()) {
// TODO: Need standardized error handler // TODO: Need standardized error handler
Error lastError = db.getLastError(); BSimError lastError = db.getLastError();
if (lastError.category != ErrorCategory.AuthenticationCancelled) { if (lastError.category != ErrorCategory.AuthenticationCancelled) {
Msg.showError(this, getComponent(), "BSim DB Connection Failed", Msg.showError(this, getComponent(), "BSim DB Connection Failed",
lastError.message); lastError.message);
@ -195,7 +195,7 @@ public class BSimServerDialog extends DialogComponentProvider {
return; // password dialog entry cancelled by user return; // password dialog entry cancelled by user
} }
String resp = db.changePassword(db.getUserName(), pwd); String resp = db.changePassword(pwd);
if (resp == null) { if (resp == null) {
Msg.showInfo(this, getComponent(), "Password Changed", Msg.showInfo(this, getComponent(), "Password Changed",
"BSim DB password successfully changed"); "BSim DB password successfully changed");

View file

@ -21,11 +21,15 @@ import java.util.*;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.JLabel; import javax.swing.JLabel;
import org.apache.commons.lang3.StringUtils;
import docking.widgets.table.*; import docking.widgets.table.*;
import ghidra.docking.settings.Settings; import ghidra.docking.settings.Settings;
import ghidra.features.bsim.gui.BSimServerManager; 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.features.bsim.query.BSimServerInfo.DBType;
import ghidra.framework.client.ClientUtil;
import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProvider;
import ghidra.framework.plugintool.ServiceProviderStub; import ghidra.framework.plugintool.ServiceProviderStub;
import ghidra.util.table.column.AbstractGColumnRenderer; import ghidra.util.table.column.AbstractGColumnRenderer;
@ -90,6 +94,7 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
descriptor.addVisibleColumn(new TypeColumn()); descriptor.addVisibleColumn(new TypeColumn());
descriptor.addVisibleColumn(new HostColumn()); descriptor.addVisibleColumn(new HostColumn());
descriptor.addVisibleColumn(new PortColumn()); descriptor.addVisibleColumn(new PortColumn());
descriptor.addVisibleColumn(new UserInfoColumn());
descriptor.addVisibleColumn(new ConnectionStatusColumn()); descriptor.addVisibleColumn(new ConnectionStatusColumn());
return descriptor; 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 private static class HostColumn
extends AbstractDynamicTableColumn<BSimServerInfo, String, Object> { extends AbstractDynamicTableColumn<BSimServerInfo, String, Object> {
@ -176,7 +217,6 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
@Override @Override
public String getValue(BSimServerInfo serverInfo, Settings settings, Object data, public String getValue(BSimServerInfo serverInfo, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException { ServiceProvider provider) throws IllegalArgumentException {
return serverInfo.getServerName(); return serverInfo.getServerName();
} }

View file

@ -16,12 +16,17 @@
package ghidra.features.bsim.gui.search.dialog; package ghidra.features.bsim.gui.search.dialog;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionListener; import java.awt.event.*;
import java.io.File; import java.io.File;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.*; import javax.swing.*;
import javax.swing.JFormattedTextField.AbstractFormatterFactory;
import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener; import javax.swing.event.DocumentListener;
import javax.swing.text.DefaultFormatter;
import javax.swing.text.DefaultFormatterFactory;
import docking.DialogComponentProvider; import docking.DialogComponentProvider;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
@ -29,8 +34,11 @@ import docking.widgets.button.BrowseButton;
import docking.widgets.button.GRadioButton; import docking.widgets.button.GRadioButton;
import docking.widgets.filechooser.GhidraFileChooser; import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.filechooser.GhidraFileChooserMode; 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.*;
import ghidra.features.bsim.query.BSimServerInfo.DBType; import ghidra.features.bsim.query.BSimServerInfo.DBType;
import ghidra.framework.client.ClientUtil;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.filechooser.GhidraFileChooserModel; import ghidra.util.filechooser.GhidraFileChooserModel;
import ghidra.util.filechooser.GhidraFileFilter; import ghidra.util.filechooser.GhidraFileFilter;
@ -45,6 +53,15 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
private static final String POSTGRES = "Postgres"; private static final String POSTGRES = "Postgres";
private static final String ELASTIC = "Elastic"; private static final String ELASTIC = "Elastic";
private static final String FILE_H2 = "File"; 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 postgresButton;
private GRadioButton elasticButton; private GRadioButton elasticButton;
private GRadioButton fileButton; private GRadioButton fileButton;
@ -68,16 +85,10 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
setHelpLocation(new HelpLocation("BSimSearchPlugin", "Add_Server_Definition_Dialog")); setHelpLocation(new HelpLocation("BSimSearchPlugin", "Add_Server_Definition_Dialog"));
} }
public BSimServerInfo getBsimServerInfo() { BSimServerInfo getBsimServerInfo() {
return result; return result;
} }
@Override
public void setHelpLocation(HelpLocation helpLocation) {
// TODO Auto-generated method stub
super.setHelpLocation(helpLocation);
}
@Override @Override
protected void okCallback() { protected void okCallback() {
BSimServerInfo serverInfo = activePanel.getServerInfo(); 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 // FIXME: Use task to correct dialog parenting issue caused by password prompt
String errorMessage = null; String errorMessage = null;
try (FunctionDatabase database = BSimClientFactory.buildClient(serverInfo, true)) { try (FunctionDatabase database = BSimClientFactory.buildClient(serverInfo, true)) {
@ -159,6 +170,7 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
if (postgresButton.isSelected()) { if (postgresButton.isSelected()) {
cardLayout.show(cardPanel, POSTGRES); cardLayout.show(cardPanel, POSTGRES);
activePanel = postgresPanel; activePanel = postgresPanel;
} }
else if (elasticButton.isSelected()) { else if (elasticButton.isSelected()) {
cardLayout.show(cardPanel, ELASTIC); cardLayout.show(cardPanel, ELASTIC);
@ -195,8 +207,9 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
} }
private class DbPanel extends ServerPanel { private class DbPanel extends ServerPanel {
private JTextField nameField; private GFormattedTextField nameField;
private JTextField hostField; private GFormattedTextField userField;
private GFormattedTextField hostField;
private JTextField portField; private JTextField portField;
private DBType type; private DBType type;
@ -204,12 +217,20 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
super(new PairLayout(10, 10)); super(new PairLayout(10, 10));
this.type = type; this.type = type;
nameField = new NotifyingTextField(); createDBNameField();
hostField = new NotifyingTextField(); createUserField();
portField = createHostField();
new NotifyingTextField(Integer.toString(BSimServerInfo.DEFAULT_POSTGRES_PORT)); 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 nameLabel = new JLabel("DB Name:", SwingConstants.RIGHT);
JLabel userLabel = new JLabel("User (optional):", SwingConstants.RIGHT);
JLabel hostLabel = new JLabel("Host:", SwingConstants.RIGHT); JLabel hostLabel = new JLabel("Host:", SwingConstants.RIGHT);
JLabel portLabel = new JLabel("Port:", SwingConstants.RIGHT); JLabel portLabel = new JLabel("Port:", SwingConstants.RIGHT);
nameLabel.setLabelFor(nameField); nameLabel.setLabelFor(nameField);
@ -218,21 +239,197 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
add(nameLabel); add(nameLabel);
add(nameField); add(nameField);
add(userLabel);
add(userField);
add(hostLabel); add(hostLabel);
add(hostField); add(hostField);
add(portLabel); add(portLabel);
add(portField); 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 @Override
BSimServerInfo getServerInfo() { 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 name = nameField.getText().trim();
String host = hostField.getText().trim(); String host = hostField.getText().trim();
int port = getPort(portField.getText().trim()); int port = getPort(portField.getText().trim());
if (name.isBlank() || host.isBlank() || port < 0) { if (name.isBlank() || host.isBlank() || port < 0) {
return null; 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()) { if (file.isDirectory()) {
return null; 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) { public NotifyingTextField(String initialText) {
super(20); super(20);
setText(initialText); setText(initialText);
getDocument().addDocumentListener(new DocumentListener() { getDocument().addDocumentListener(new MyFieldListener());
}
}
@Override class MyFieldListener implements DocumentListener {
public void insertUpdate(DocumentEvent e) { @Override
checkForValidDialog(); public void insertUpdate(DocumentEvent e) {
} checkForValidDialog();
}
@Override @Override
public void removeUpdate(DocumentEvent e) { public void removeUpdate(DocumentEvent e) {
checkForValidDialog(); checkForValidDialog();
} }
@Override @Override
public void changedUpdate(DocumentEvent e) { public void changedUpdate(DocumentEvent e) {
checkForValidDialog(); checkForValidDialog();
}
});
} }
} }

View file

@ -25,7 +25,6 @@ import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.PasswordCallback;
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.StringUtils;
import ghidra.features.bsim.query.BSimServerInfo.DBType; import ghidra.features.bsim.query.BSimServerInfo.DBType;
import ghidra.features.bsim.query.FunctionDatabase.ConnectionType; import ghidra.features.bsim.query.FunctionDatabase.ConnectionType;
@ -283,10 +282,12 @@ public class BSimPostgresDBConnectionManager {
*/ */
private Connection connect() throws SQLException, CancelledException { private Connection connect() throws SQLException, CancelledException {
String userName = bds.getUsername(); String loginError = null;
bds.setUsername(StringUtils.isBlank(userName) ? ClientUtil.getUserName() : userName);
bds.setPassword(null); serverInfo.setUserInfo(bds);
connectionType = ConnectionType.SSL_No_Authentication;
connectionType = serverInfo.hasPassword() ? ConnectionType.SSL_Password_Authentication
: ConnectionType.SSL_No_Authentication;
try { try {
// Specify SSL connection properties // Specify SSL connection properties
setSSLProperties(); setSSLProperties();
@ -299,6 +300,10 @@ public class BSimPostgresDBConnectionManager {
if (e.getMessage().contains("password-based authentication") || if (e.getMessage().contains("password-based authentication") ||
e.getMessage().contains("SCRAM-based") || e.getMessage().contains("SCRAM-based") ||
e.getMessage().contains("password authentication failed")) { e.getMessage().contains("password authentication failed")) {
if (serverInfo.hasPassword()) {
loginError = "Access denied: " + serverInfo;
Msg.error(this, loginError);
}
// Use Ghidra's authentication infrastructure // Use Ghidra's authentication infrastructure
connectionType = ConnectionType.SSL_Password_Authentication; // Try again with a password connectionType = ConnectionType.SSL_Password_Authentication; // Try again with a password
// fallthru to second attempt at getConnection // fallthru to second attempt at getConnection
@ -319,7 +324,6 @@ public class BSimPostgresDBConnectionManager {
" idle=" + bds.getNumIdle()); " idle=" + bds.getNumIdle());
} }
String loginError = null;
while (true) { while (true) {
ClientAuthenticator clientAuthenticator = null; ClientAuthenticator clientAuthenticator = null;
if (connectionType == ConnectionType.SSL_Password_Authentication) { if (connectionType == ConnectionType.SSL_Password_Authentication) {
@ -327,9 +331,11 @@ public class BSimPostgresDBConnectionManager {
if (clientAuthenticator == null) { // Make sure authenticator is registered if (clientAuthenticator == null) { // Make sure authenticator is registered
throw new SQLException("No registered authenticator"); throw new SQLException("No registered authenticator");
} }
NameCallback nameCb = new NameCallback("User ID:"); NameCallback nameCb = new NameCallback("User ID:", bds.getUsername());
nameCb.setName(bds.getUsername()); if (!serverInfo.hasDefaultLogin()) {
PasswordCallback passCb = new PasswordCallback("Password:", false); nameCb.setName(bds.getUsername());
}
PasswordCallback passCb = new PasswordCallback(" ", false); // force use of default prompting
try { try {
if (!clientAuthenticator.processPasswordCallbacks( if (!clientAuthenticator.processPasswordCallbacks(
"BSim Database Authentication", "BSim Database Server", "BSim Database Authentication", "BSim Database Server",
@ -338,9 +344,8 @@ public class BSimPostgresDBConnectionManager {
} }
bds.setPassword(new String(passCb.getPassword())); bds.setPassword(new String(passCb.getPassword()));
// User may have specified new username, or this may return NULL // User may have specified new username, or this may return NULL
userName = nameCb.getName(); if (serverInfo.hasDefaultLogin()) {
if (!StringUtils.isBlank(userName)) { bds.setUsername(nameCb.getName());
bds.setUsername(userName);
} }
} }
finally { finally {

View file

@ -16,12 +16,15 @@
package ghidra.features.bsim.query; package ghidra.features.bsim.query;
import java.io.Closeable; import java.io.Closeable;
import java.net.MalformedURLException; import java.net.*;
import java.net.URL; import java.nio.charset.StandardCharsets;
import java.util.Objects; import java.util.Objects;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import ghidra.framework.client.ClientUtil;
public class BSimServerInfo implements Comparable<BSimServerInfo> { public class BSimServerInfo implements Comparable<BSimServerInfo> {
/** /**
@ -48,6 +51,7 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
} }
private final DBType dbType; private final DBType dbType;
private final String userinfo; // username[:password]
private final String host; private final String host;
private final int port; private final int port;
private final String dbName; private final String dbName;
@ -56,6 +60,62 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
/** /**
* Construct a new {@link BSimServerInfo} object * 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 dbType BSim DB type
* @param host host name (ignored for {@link DBType#file}) * @param host host name (ignored for {@link DBType#file})
* @param port port number (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 * @throws IllegalArgumentException if invalid arguments are specified
*/ */
public BSimServerInfo(DBType dbType, String host, int port, String dbName) { public BSimServerInfo(DBType dbType, String host, int port, String dbName) {
Objects.requireNonNull(dbType, "DBType must be specified"); this(dbType, null, host, port, dbName);
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;
/**
* 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(); dbName = dbName.trim();
if (StringUtils.isEmpty(dbName)) { if (StringUtils.isEmpty(dbName)) {
throw new IllegalArgumentException("Non-empty dbName required"); throw new IllegalArgumentException("Non-empty dbName required");
} }
if (dbType == DBType.file) { this.dbName = cleanupFilename(dbName);
// 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;
} }
/** /**
* Construct a new {@link BSimServerInfo} object from a suitable database URL * Construct a new {@link BSimServerInfo} object from a suitable database URL
* (i.e., {@code postgresql:}, {@code https:}, {@code elastic:}, {@code file:}). * (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 * @throws IllegalArgumentException if unsupported URL protocol specified
*/ */
public BSimServerInfo(URL url) throws IllegalArgumentException { public BSimServerInfo(URL url) throws IllegalArgumentException {
@ -118,18 +163,21 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
if (protocol.equals("postgresql")) { if (protocol.equals("postgresql")) {
t = DBType.postgres; t = DBType.postgres;
host = checkURLField(url.getHost(), "host"); host = checkURLField(url.getHost(), "host");
userinfo = getURLUserInfo(url);
int p = url.getPort(); int p = url.getPort();
port = p <= 0 ? DEFAULT_POSTGRES_PORT : p; port = p <= 0 ? DEFAULT_POSTGRES_PORT : p;
} }
else if (protocol.equals("https") || protocol.equals("elastic")) { else if (protocol.equals("https") || protocol.equals("elastic")) {
t = DBType.elastic; t = DBType.elastic;
host = checkURLField(url.getHost(), "host"); host = checkURLField(url.getHost(), "host");
userinfo = getURLUserInfo(url);
int p = url.getPort(); int p = url.getPort();
port = p <= 0 ? DEFAULT_ELASTIC_PORT : p; port = p <= 0 ? DEFAULT_ELASTIC_PORT : p;
} }
else if (protocol.startsWith("file")) { else if (protocol.startsWith("file")) {
t = DBType.file; t = DBType.file;
host = null; host = null;
userinfo = null;
port = -1; port = -1;
if (!"".equals(url.getHost())) { if (!"".equals(url.getHost())) {
throw new IllegalArgumentException("Remote file URL not supported: " + url); 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 = path.substring(1).strip();
} }
path = checkURLField(path, "path"); path = urlDecode(checkURLField(path, "path"));
if (dbType == DBType.file) { if (dbType == DBType.file) {
if (path.endsWith("/")) { if (path.endsWith("/")) {
throw new IllegalArgumentException("Missing DB filepath in URL: " + url); throw new IllegalArgumentException("Missing DB filepath in URL: " + url);
@ -162,6 +210,53 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
dbName = path; 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) { private static String checkURLField(String val, String name) {
if (StringUtils.isEmpty(val)) { if (StringUtils.isEmpty(val)) {
throw new IllegalArgumentException("Invalid " + name + " in URL"); 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 * @return BSim server info in URL format
*/ */
public String toURLString() { public String toURLString() {
switch (dbType) { switch (dbType) {
case postgres: case postgres:
return "postgresql://" + host + getPortString() + "/" + dbName; return "postgresql://" + formatURLUserInfo() + host + getPortString() + "/" +
urlEncode(dbName);
case elastic: case elastic:
return "https://" + host + getPortString() + "/" + dbName; return "https://" + formatURLUserInfo() + host + getPortString() + "/" +
urlEncode(dbName);
case file: // h2: case file: // h2:
return "file:" + dbName; return "file:" + urlEncode(dbName);
} }
throw new RuntimeException("Unsupported DBType: " + dbType); 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() { private String getPortString() {
return port > 0 ? (":" + Integer.toString(port)) : ""; 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 * @return BSim server info in URL
* @throws MalformedURLException if unable to form supported URL * @throws MalformedURLException if unable to form supported URL
*/ */
@ -236,6 +359,58 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
return dbType; 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. * Get the server hostname or IP address as originally specified.
* @return 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 @Override
public int hashCode() { public int hashCode() {
// use dbType.ordinal; enum hashcodes vary from run to run // 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 @Override
@ -293,7 +475,8 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
return false; return false;
if (obj instanceof BSimServerInfo other) { if (obj instanceof BSimServerInfo other) {
return Objects.equals(dbName, other.dbName) && dbType == other.dbType && 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; return false;
} }

View file

@ -33,9 +33,7 @@ import ghidra.features.bsim.query.facade.SFOverviewInfo;
import ghidra.features.bsim.query.facade.SFQueryInfo; import ghidra.features.bsim.query.facade.SFQueryInfo;
import ghidra.features.bsim.query.protocol.*; import ghidra.features.bsim.query.protocol.*;
import ghidra.framework.Application; import ghidra.framework.Application;
import ghidra.program.model.data.DataUtilities;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.StringUtilities;
public interface FunctionDatabase extends AutoCloseable { 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 ErrorCategory category;
public String message; public String message;
public Error(ErrorCategory cat, String msg) { public BSimError(ErrorCategory cat, String msg) {
category = cat; category = cat;
message = msg; message = msg;
} }
@ -121,11 +119,10 @@ public interface FunctionDatabase extends AutoCloseable {
* Issue password change request to the server. * Issue password change request to the server.
* The method {@link #isPasswordChangeAllowed()} must be invoked first to ensure that * The method {@link #isPasswordChangeAllowed()} must be invoked first to ensure that
* the user password may be changed. * the user password may be changed.
* @param username to change
* @param newPassword is password data * @param newPassword is password data
* @return null if change was successful, or the error message * @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) { if (getStatus() != Status.Ready) {
return "Connection not established"; return "Connection not established";
} }
@ -134,7 +131,7 @@ public interface FunctionDatabase extends AutoCloseable {
} }
PasswordChange passwordChange = new PasswordChange(); PasswordChange passwordChange = new PasswordChange();
try { try {
passwordChange.username = username; passwordChange.username = getUserName();
passwordChange.newPassword = newPassword; passwordChange.newPassword = newPassword;
ResponsePassword response = passwordChange.execute(this); ResponsePassword response = passwordChange.execute(this);
if (!response.changeSuccessful) { if (!response.changeSuccessful) {
@ -162,14 +159,6 @@ public interface FunctionDatabase extends AutoCloseable {
*/ */
public String getUserName(); 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 * @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 * If the last query failed to produce a response, use this method to recover the error message
* @return a String describing the error * @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. * Send a query to the database. The response is returned as a QueryResponseRecord.

View file

@ -92,7 +92,7 @@ public abstract class AbstractSQLFunctionDatabase<VF extends LSHVectorFactory>
protected final VF vectorFactory; // Factory used to generate LSHVector objects protected final VF vectorFactory; // Factory used to generate LSHVector objects
private Error lasterror; private BSimError lasterror;
private Status status; private Status status;
private boolean isinit; private boolean isinit;
@ -1120,11 +1120,6 @@ public abstract class AbstractSQLFunctionDatabase<VF extends LSHVectorFactory>
return null; return null;
} }
@Override
public void setUserName(String userName) {
// ignore
}
@Override @Override
public LSHVectorFactory getLSHVectorFactory() { public LSHVectorFactory getLSHVectorFactory() {
return vectorFactory; return vectorFactory;
@ -1155,7 +1150,7 @@ public abstract class AbstractSQLFunctionDatabase<VF extends LSHVectorFactory>
} }
@Override @Override
public Error getLastError() { public BSimError getLastError() {
return lasterror; return lasterror;
} }
@ -1188,7 +1183,7 @@ public abstract class AbstractSQLFunctionDatabase<VF extends LSHVectorFactory>
} }
catch (CancelledSQLException e) { catch (CancelledSQLException e) {
status = Status.Error; status = Status.Error;
lasterror = new Error(ErrorCategory.AuthenticationCancelled, lasterror = new BSimError(ErrorCategory.AuthenticationCancelled,
"Authentication cancelled by user"); "Authentication cancelled by user");
return false; return false;
} }
@ -1201,19 +1196,19 @@ public abstract class AbstractSQLFunctionDatabase<VF extends LSHVectorFactory>
} }
String msg = cause.getMessage(); String msg = cause.getMessage();
if (msg.contains("already in use:")) { if (msg.contains("already in use:")) {
lasterror = new Error(ErrorCategory.Initialization, lasterror = new BSimError(ErrorCategory.Initialization,
"Database already in use by another process"); "Database already in use by another process");
} }
else if (msg.contains("authentication failed") || else if (msg.contains("authentication failed") ||
msg.contains("requires a valid client certificate")) { msg.contains("requires a valid client certificate")) {
lasterror = 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 ")) { 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 { else {
lasterror = new Error(ErrorCategory.Initialization, lasterror = new BSimError(ErrorCategory.Initialization,
"Database error on initialization: " + cause.getMessage()); "Database error on initialization: " + cause.getMessage());
} }
return false; return false;
@ -1628,29 +1623,29 @@ public abstract class AbstractSQLFunctionDatabase<VF extends LSHVectorFactory>
lasterror = null; lasterror = null;
try { try {
if (!(query instanceof CreateDatabase) && !initialize()) { 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; return null;
} }
query.buildResponseTemplate(); query.buildResponseTemplate();
QueryResponseRecord response = doQuery(query, db); QueryResponseRecord response = doQuery(query, db);
if (response == null) { if (response == null) {
lasterror = new Error(ErrorCategory.Fatal, "Unknown query type"); lasterror = new BSimError(ErrorCategory.Fatal, "Unknown query type");
query.clearResponse(); query.clearResponse();
} }
} }
catch (DatabaseNonFatalException err) { catch (DatabaseNonFatalException err) {
lasterror = new Error(ErrorCategory.Nonfatal, lasterror = new BSimError(ErrorCategory.Nonfatal,
"Skipping -" + query.getName() + "- : " + err.getMessage()); "Skipping -" + query.getName() + "- : " + err.getMessage());
query.clearResponse(); query.clearResponse();
} }
catch (LSHException err) { catch (LSHException err) {
lasterror = new Error(ErrorCategory.Fatal, lasterror = new BSimError(ErrorCategory.Fatal,
"Fatal error during -" + query.getName() + "- : " + err.getMessage()); "Fatal error during -" + query.getName() + "- : " + err.getMessage());
query.clearResponse(); query.clearResponse();
} }
catch (SQLException err) { catch (SQLException err) {
lasterror = new Error(ErrorCategory.Fatal, lasterror = new BSimError(ErrorCategory.Fatal,
"SQL error during -" + query.getName() + "- : " + err.getMessage()); "SQL error during -" + query.getName() + "- : " + err.getMessage());
query.clearResponse(); query.clearResponse();
} }

View file

@ -34,7 +34,7 @@ public class FunctionDatabaseProxy implements FunctionDatabase {
private DatabaseInformation info; private DatabaseInformation info;
private LSHVectorFactory vectorFactory; private LSHVectorFactory vectorFactory;
private URL httpURL; private URL httpURL;
private Error lasterror; private BSimError lasterror;
private Status status; private Status status;
private boolean isinit; private boolean isinit;
private XmlErrorHandler xmlErrorHandler; private XmlErrorHandler xmlErrorHandler;
@ -83,11 +83,6 @@ public class FunctionDatabaseProxy implements FunctionDatabase {
return ClientUtil.getUserName(); return ClientUtil.getUserName();
} }
@Override
public void setUserName(String userName) {
// Not currently implemented
}
@Override @Override
public LSHVectorFactory getLSHVectorFactory() { public LSHVectorFactory getLSHVectorFactory() {
return vectorFactory; return vectorFactory;
@ -123,7 +118,8 @@ public class FunctionDatabaseProxy implements FunctionDatabase {
} }
if (httpURL == null) { if (httpURL == null) {
status = Status.Error; status = Status.Error;
lasterror = new FunctionDatabase.Error(ErrorCategory.Initialization, "MalformedURL"); lasterror =
new FunctionDatabase.BSimError(ErrorCategory.Initialization, "MalformedURL");
return false; return false;
} }
QueryInfo queryInfo = new QueryInfo(); QueryInfo queryInfo = new QueryInfo();
@ -145,7 +141,7 @@ public class FunctionDatabaseProxy implements FunctionDatabase {
} }
@Override @Override
public Error getLastError() { public BSimError getLastError() {
return lasterror; return lasterror;
} }
@ -168,7 +164,8 @@ public class FunctionDatabaseProxy implements FunctionDatabase {
ResponseError respError = new ResponseError(); ResponseError respError = new ResponseError();
respError.restoreXml(parser, vectorFactory); respError.restoreXml(parser, vectorFactory);
parser.dispose(); parser.dispose();
lasterror = new FunctionDatabase.Error(ErrorCategory.Fatal, respError.errorMessage); lasterror =
new FunctionDatabase.BSimError(ErrorCategory.Fatal, respError.errorMessage);
query.clearResponse(); query.clearResponse();
return null; return null;
} }
@ -184,7 +181,7 @@ public class FunctionDatabaseProxy implements FunctionDatabase {
return response; return response;
} }
catch (Exception ex) { catch (Exception ex) {
lasterror = new FunctionDatabase.Error(ErrorCategory.Connection, ex.getMessage()); lasterror = new FunctionDatabase.BSimError(ErrorCategory.Connection, ex.getMessage());
status = Status.Error; status = Status.Error;
query.clearResponse(); query.clearResponse();
return null; return null;

View file

@ -190,8 +190,9 @@ public final class PostgresFunctionDatabase
@Override @Override
protected void generateRawDatabase() throws SQLException { protected void generateRawDatabase() throws SQLException {
BSimServerInfo serverInfo = postgresDs.getServerInfo(); BSimServerInfo serverInfo = postgresDs.getServerInfo();
BSimServerInfo defaultServerInfo = new BSimServerInfo(DBType.postgres, BSimServerInfo defaultServerInfo =
serverInfo.getServerName(), serverInfo.getPort(), DEFAULT_DATABASE_NAME); new BSimServerInfo(DBType.postgres, serverInfo.getUserInfo(),
serverInfo.getServerName(), serverInfo.getPort(), DEFAULT_DATABASE_NAME);
String createdbstring = "CREATE DATABASE \"" + serverInfo.getDBName() + '"'; String createdbstring = "CREATE DATABASE \"" + serverInfo.getDBName() + '"';
BSimPostgresDataSource defaultDs = BSimPostgresDataSource defaultDs =
BSimPostgresDBConnectionManager.getDataSource(defaultServerInfo); BSimPostgresDBConnectionManager.getDataSource(defaultServerInfo);
@ -502,14 +503,6 @@ public final class PostgresFunctionDatabase
return postgresDs.getUserName(); 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 @Override
public QueryResponseRecord doQuery(BSimQuery<?> query, Connection c) public QueryResponseRecord doQuery(BSimQuery<?> query, Connection c)
throws SQLException, LSHException, DatabaseNonFatalException { throws SQLException, LSHException, DatabaseNonFatalException {

View file

@ -31,8 +31,6 @@ public class ElasticConnection {
protected String hostURL; // http://hostname:port protected String hostURL; // http://hostname:port
protected String httpURLbase; // Main URL to elasticsearch protected String httpURLbase; // Main URL to elasticsearch
private HttpURLConnection connection = null;
private Writer writer;
private int lastResponseCode; private int lastResponseCode;
public ElasticConnection(String url, String repo) { public ElasticConnection(String url, String repo) {
@ -41,61 +39,13 @@ public class ElasticConnection {
} }
public void close() { public void close() {
if (connection != null) { // nothing to do - http connections do not persist
connection.disconnect();
}
} }
public boolean lastRequestSuccessful() { public boolean lastRequestSuccessful() {
return (lastResponseCode >= 200) && (lastResponseCode < 300); 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 * Assuming the writer has been closed and connection.getResponseCode() is called
* placing the value in lastResponseCode, read the response and parse into a JSONObject * 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 IOException for problems with the socket
* @throws ParseException for JSON parse errors * @throws ParseException for JSON parse errors
*/ */
private JSONObject grabResponse() throws IOException, ParseException { private JSONObject grabResponse(HttpURLConnection connection)
throws IOException, ParseException {
JSONParser parser = new JSONParser(); JSONParser parser = new JSONParser();
Reader reader; InputStream in;
if (lastRequestSuccessful()) { if (lastRequestSuccessful()) {
reader = new InputStreamReader(connection.getInputStream()); in = connection.getInputStream();
} }
else { 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); JSONObject jsonObject = (JSONObject) parser.parse(reader);
return jsonObject; return jsonObject;
} }
@ -156,12 +112,18 @@ public class ElasticConnection {
*/ */
public JSONObject executeRawStatement(String command, String path, String body) public JSONObject executeRawStatement(String command, String path, String body)
throws ElasticException { throws ElasticException {
HttpURLConnection connection = null;
try { try {
startHttpRawRequest(command, path); URL httpURL = new URL(hostURL + path);
writer.write(body); connection = (HttpURLConnection) httpURL.openConnection();
writer.close(); 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(); lastResponseCode = connection.getResponseCode();
JSONObject resp = grabResponse(); JSONObject resp = grabResponse(connection);
if (!lastRequestSuccessful()) { if (!lastRequestSuccessful()) {
throw new ElasticException(parseErrorJSON(resp)); throw new ElasticException(parseErrorJSON(resp));
} }
@ -173,6 +135,11 @@ public class ElasticConnection {
catch (ParseException e) { catch (ParseException e) {
throw new ElasticException("Error parsing response: " + e.getMessage()); 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) public void executeStatementNoResponse(String command, String path, String body)
throws ElasticException { throws ElasticException {
HttpURLConnection connection = null;
try { try {
startHttpRequest(command, path); URL httpURL = new URL(httpURLbase + path);
writer.write(body); connection = (HttpURLConnection) httpURL.openConnection();
writer.close(); 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(); lastResponseCode = connection.getResponseCode();
JSONObject resp = grabResponse(); JSONObject resp = grabResponse(connection);
if (!lastRequestSuccessful()) { if (!lastRequestSuccessful()) {
throw new ElasticException(parseErrorJSON(resp)); throw new ElasticException(parseErrorJSON(resp));
} }
@ -201,6 +174,11 @@ public class ElasticConnection {
catch (ParseException e) { catch (ParseException e) {
throw new ElasticException("Error parsing response: " + e.getMessage()); 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) public JSONObject executeStatement(String command, String path, String body)
throws ElasticException { throws ElasticException {
HttpURLConnection connection = null;
try { try {
startHttpRequest(command, path); URL httpURL = new URL(httpURLbase + path);
writer.write(body); connection = (HttpURLConnection) httpURL.openConnection();
writer.close(); 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(); lastResponseCode = connection.getResponseCode();
JSONObject resp = grabResponse(); JSONObject resp = grabResponse(connection);
if (!lastRequestSuccessful()) { if (!lastRequestSuccessful()) {
throw new ElasticException(parseErrorJSON(resp)); throw new ElasticException(parseErrorJSON(resp));
} }
@ -230,6 +214,11 @@ public class ElasticConnection {
catch (ParseException e) { catch (ParseException e) {
throw new ElasticException("Error parsing response: " + e.getMessage()); 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) public JSONObject executeStatementExpectFailure(String command, String path, String body)
throws ElasticException { throws ElasticException {
HttpURLConnection connection = null;
try { try {
startHttpRequest(command, path); URL httpURL = new URL(httpURLbase + path);
writer.write(body); connection = (HttpURLConnection) httpURL.openConnection();
writer.close(); 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(); lastResponseCode = connection.getResponseCode();
JSONObject resp = grabResponse(); JSONObject resp = grabResponse(connection);
return resp; return resp;
} }
catch (IOException e) { catch (IOException e) {
@ -257,6 +252,11 @@ public class ElasticConnection {
catch (ParseException e) { catch (ParseException e) {
throw new ElasticException("Error parsing response: " + e.getMessage()); 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 * @throws ElasticException for any problems with the connection
*/ */
public JSONObject executeBulk(String path, String body) throws ElasticException { public JSONObject executeBulk(String path, String body) throws ElasticException {
HttpURLConnection connection = null;
try { try {
startHttpBulkRequest(path); URL httpURL = new URL(hostURL + path);
writer.write(body); connection = (HttpURLConnection) httpURL.openConnection();
writer.close(); 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(); lastResponseCode = connection.getResponseCode();
JSONObject resp = grabResponse(); JSONObject resp = grabResponse(connection);
if (!lastRequestSuccessful()) { if (!lastRequestSuccessful()) {
throw new ElasticException(parseErrorJSON(resp)); throw new ElasticException(parseErrorJSON(resp));
} }
@ -285,13 +291,22 @@ public class ElasticConnection {
catch (ParseException e) { catch (ParseException e) {
throw new ElasticException("Error parsing response: " + e.getMessage()); throw new ElasticException("Error parsing response: " + e.getMessage());
} }
finally {
if (connection != null) {
connection.disconnect();
}
}
} }
public JSONObject executeURIOnly(String command, String path) throws ElasticException { public JSONObject executeURIOnly(String command, String path) throws ElasticException {
HttpURLConnection connection = null;
try { try {
startHttpURICommand(command, path); URL httpURL = new URL(httpURLbase + path);
connection = (HttpURLConnection) httpURL.openConnection();
connection.setRequestMethod(command);
connection.setDoOutput(true);
lastResponseCode = connection.getResponseCode(); lastResponseCode = connection.getResponseCode();
JSONObject resp = grabResponse(); JSONObject resp = grabResponse(connection);
if (!lastRequestSuccessful()) { if (!lastRequestSuccessful()) {
throw new ElasticException(parseErrorJSON(resp)); throw new ElasticException(parseErrorJSON(resp));
} }
@ -303,5 +318,10 @@ public class ElasticConnection {
catch (ParseException e) { catch (ParseException e) {
throw new ElasticException("Error parsing response: " + e.getMessage()); throw new ElasticException("Error parsing response: " + e.getMessage());
} }
finally {
if (connection != null) {
connection.disconnect();
}
}
} }
} }

View file

@ -60,14 +60,13 @@ public class ElasticDatabase implements FunctionDatabase {
public static final int MAX_VECTOR_BULK = 200; // Maximum vectors ingested in one bulk request 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 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 ConnectionType connectionType = ConnectionType.Unencrypted_No_Authentication;
private DatabaseInformation info; // Information about the active database private DatabaseInformation info; // Information about the active database
private Base64VectorFactory vectorFactory; // factory used to create BSim feature vectors 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 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 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 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 Status status; // status of the connection
private boolean initialized; // true if the connection has been successfully initialized 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"); throw new MalformedURLException("URL path must indicate the repository only");
} }
repository = path.substring(1); repository = path.substring(1);
this.serverInfo = this.serverInfo = new BSimServerInfo(DBType.elastic, null, baseURL.getHost(),
new BSimServerInfo(DBType.elastic, baseURL.getHost(), baseURL.getPort(), repository); baseURL.getPort(), repository);
this.baseURL = fullURL.substring(0, fullURL.length() - path.length()); this.baseURL = fullURL.substring(0, fullURL.length() - path.length());
lastError = null; lastError = null;
@ -2398,15 +2397,7 @@ public class ElasticDatabase implements FunctionDatabase {
@Override @Override
public String getUserName() { public String getUserName() {
if (userName != null) { return serverInfo.getUserName();
return userName;
}
return ClientUtil.getUserName();
}
@Override
public void setUserName(String userName) {
this.userName = userName;
} }
@Override @Override
@ -2450,14 +2441,14 @@ public class ElasticDatabase implements FunctionDatabase {
vectorFactory.set(config.weightfactory, config.idflookup, config.info.settings); vectorFactory.set(config.weightfactory, config.idflookup, config.info.settings);
} }
catch (ElasticException err) { catch (ElasticException err) {
lastError = new Error(ErrorCategory.Initialization, lastError = new BSimError(ErrorCategory.Initialization,
"Database error on initialization: " + err.getMessage()); "Database error on initialization: " + err.getMessage());
status = Status.Error; status = Status.Error;
return false; return false;
} }
catch (NoDatabaseException err) { catch (NoDatabaseException err) {
info = null; info = null;
lastError = new Error(ErrorCategory.Nodatabase, lastError = new BSimError(ErrorCategory.Nodatabase,
"Database has not been created yet: " + err.getMessage()); "Database has not been created yet: " + err.getMessage());
initialized = true; initialized = true;
status = Status.Ready; status = Status.Ready;
@ -2480,14 +2471,14 @@ public class ElasticDatabase implements FunctionDatabase {
} }
@Override @Override
public Error getLastError() { public BSimError getLastError() {
return lastError; return lastError;
} }
@Override @Override
public QueryResponseRecord query(BSimQuery<?> query) { public QueryResponseRecord query(BSimQuery<?> query) {
if ((!isInitialized()) && (!(query instanceof CreateDatabase))) { 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; return null;
} }
lastError = null; lastError = null;
@ -2554,22 +2545,22 @@ public class ElasticDatabase implements FunctionDatabase {
fdbPasswordChange((PasswordChange) query); fdbPasswordChange((PasswordChange) query);
} }
else { else {
lastError = new Error(ErrorCategory.Fatal, "Unknown query type"); lastError = new BSimError(ErrorCategory.Fatal, "Unknown query type");
query.clearResponse(); query.clearResponse();
} }
} }
catch (DatabaseNonFatalException err) { catch (DatabaseNonFatalException err) {
lastError = new Error(ErrorCategory.Nonfatal, lastError = new BSimError(ErrorCategory.Nonfatal,
"Skipping -" + query.getName() + "- : " + err.getMessage()); "Skipping -" + query.getName() + "- : " + err.getMessage());
query.clearResponse(); query.clearResponse();
} }
catch (LSHException err) { catch (LSHException err) {
lastError = new Error(ErrorCategory.Fatal, lastError = new BSimError(ErrorCategory.Fatal,
"Fatal error during -" + query.getName() + "- : " + err.getMessage()); "Fatal error during -" + query.getName() + "- : " + err.getMessage());
query.clearResponse(); query.clearResponse();
} }
catch (ElasticException err) { catch (ElasticException err) {
lastError = new Error(ErrorCategory.Fatal, lastError = new BSimError(ErrorCategory.Fatal,
"Elastic error during -" + query.getName() + "- : " + err.getMessage()); "Elastic error during -" + query.getName() + "- : " + err.getMessage());
query.clearResponse(); query.clearResponse();
} }

View file

@ -384,7 +384,7 @@ public class SimilarFunctionQueryService implements AutoCloseable {
return database.getLSHVectorFactory(); return database.getLSHVectorFactory();
} }
public FunctionDatabase.Error getLastError() { public FunctionDatabase.BSimError getLastError() {
if (database == null) { if (database == null) {
return null; return null;
} }

View file

@ -17,7 +17,8 @@ package ghidra.features.bsim.query.ingest;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.*; import java.net.MalformedURLException;
import java.net.URL;
import java.util.*; import java.util.*;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -32,10 +33,8 @@ import ghidra.features.bsim.query.protocol.QueryName;
import ghidra.framework.*; import ghidra.framework.*;
import ghidra.framework.client.ClientUtil; import ghidra.framework.client.ClientUtil;
import ghidra.framework.client.HeadlessClientAuthenticator; import ghidra.framework.client.HeadlessClientAuthenticator;
import ghidra.framework.data.DomainObjectAdapter;
import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.net.SSLContextInitializer; import ghidra.net.SSLContextInitializer;
import ghidra.program.database.ProgramDB;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
@ -335,10 +334,6 @@ public class BSimLaunchable implements GhidraLaunchable {
optionValueMap.put(option, params[i]); optionValueMap.put(option, params[i]);
} }
} }
String connectingUserName = optionValueMap.get(USER_OPTION);
if (connectingUserName == null) {
connectingUserName = optionValueMap.put(USER_OPTION, ClientUtil.getUserName());
}
return subParams; return subParams;
} }
@ -981,9 +976,9 @@ public class BSimLaunchable implements GhidraLaunchable {
" <config_template> - large_32 | medium_32 | medium_64 | medium_cpool | medium_nosize \n" + " <config_template> - large_32 | medium_32 | medium_64 | medium_cpool | medium_nosize \n" +
"\n" + "\n" +
"BSim URL Forms (bsimURL):\n" + "BSim URL Forms (bsimURL):\n" +
" postgresql://<hostname>[:<port>]/<dbname>\n" + " postgresql://[username@]<hostname>[:<port>]/<dbname>\n" +
" elastic://<hostname>[:<port>]/<dbname>\n" + " elastic://[username@]<hostname>[:<port>]/<dbname>\n" +
" https://<hostname>[:<port>]/<dbname>\n" + " https://[username@]<hostname>[:<port>]/<dbname>\n" +
" file:/[<local-dirpath>/]<dbname>\n" + " file:/[<local-dirpath>/]<dbname>\n" +
"\n" + "\n" +
"Ghidra URL Forms (ghidraURL):\n" + "Ghidra URL Forms (ghidraURL):\n" +
@ -1010,7 +1005,13 @@ public class BSimLaunchable implements GhidraLaunchable {
run(params); run(params);
} }
catch (MalformedURLException e) { 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 System.exit(22); // EINVAL
} }
catch (IllegalArgumentException e) { catch (IllegalArgumentException e) {
@ -1019,7 +1020,13 @@ public class BSimLaunchable implements GhidraLaunchable {
System.exit(22); // EINVAL System.exit(22); // EINVAL
} }
catch (Exception e) { 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 System.exit(1); // Misc Error
} }
} }
@ -1084,6 +1091,10 @@ public class BSimLaunchable implements GhidraLaunchable {
ghidra.framework.protocol.ghidra.Handler.registerHandler(); ghidra.framework.protocol.ghidra.Handler.registerHandler();
ghidra.features.bsim.query.postgresql.Handler.registerHandler(); ghidra.features.bsim.query.postgresql.Handler.registerHandler();
if (connectingUserName == null) {
// Force default login name
connectingUserName = ClientUtil.getUserName();
}
HeadlessClientAuthenticator.installHeadlessClientAuthenticator(connectingUserName, certPath, HeadlessClientAuthenticator.installHeadlessClientAuthenticator(connectingUserName, certPath,
true); true);
} }

View file

@ -16,7 +16,6 @@
package ghidra.features.bsim.query.ingest; package ghidra.features.bsim.query.ingest;
import java.io.*; import java.io.*;
import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.*; import java.util.*;
@ -29,9 +28,8 @@ import org.xml.sax.SAXException;
import generic.lsh.vector.LSHVectorFactory; import generic.lsh.vector.LSHVectorFactory;
import ghidra.app.decompiler.DecompileException; import ghidra.app.decompiler.DecompileException;
import ghidra.features.bsim.query.*; 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.ErrorCategory;
import ghidra.features.bsim.query.FunctionDatabase.Status;
import ghidra.features.bsim.query.client.Configuration; import ghidra.features.bsim.query.client.Configuration;
import ghidra.features.bsim.query.client.tables.ExeTable.ExeTableOrderColumn; import ghidra.features.bsim.query.client.tables.ExeTable.ExeTableOrderColumn;
import ghidra.features.bsim.query.description.*; 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 // FIXME: May need to use Msg.showError for popup messages in GUI workbench case
private final BSimServerInfo bsimServerInfo; // may be null private final BSimServerInfo bsimServerInfo; // may be null
private final String connectingUserName;
private FunctionDatabase querydb; 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 * @param bsimServerInfo the BSim database server info. May be {@code null} if use limited to
* signature and update generation only (based upon configuration template). * signature and update generation only (based upon configuration template).
* @param connectingUserName user name to use for BSim server authentication. May be null if * @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()}). * not required or default should be used (see {@link ClientUtil#getUserName()}). If specified
* @throws MalformedURLException if the given URL string cannot be parsed * 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) public BulkSignatures(BSimServerInfo bsimServerInfo, String connectingUserName) {
throws MalformedURLException { 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.bsimServerInfo = bsimServerInfo;
this.connectingUserName =
connectingUserName != null ? connectingUserName : ClientUtil.getUserName();
} }
private void checkBSimServerOperation() { private void checkBSimServerOperation() {
@ -93,9 +112,6 @@ public class BulkSignatures implements AutoCloseable {
checkBSimServerOperation(); checkBSimServerOperation();
querydb = BSimClientFactory.buildClient(bsimServerInfo, async); querydb = BSimClientFactory.buildClient(bsimServerInfo, async);
if (querydb.getStatus() == Status.Unconnected) { // may have previously connected
querydb.setUserName(connectingUserName);
}
if (!querydb.initialize()) { if (!querydb.initialize()) {
throw new IOException(querydb.getLastError().message); throw new IOException(querydb.getLastError().message);
@ -103,7 +119,7 @@ public class BulkSignatures implements AutoCloseable {
DatabaseInformation info = querydb.getInfo(); DatabaseInformation info = querydb.getInfo();
if (info == null) { if (info == null) {
Error lastError = querydb.getLastError(); BSimError lastError = querydb.getLastError();
if (lastError != null && lastError.category == ErrorCategory.Nodatabase) { if (lastError != null && lastError.category == ErrorCategory.Nodatabase) {
throw new IOException(lastError.message); throw new IOException(lastError.message);
} }
@ -190,7 +206,7 @@ public class BulkSignatures implements AutoCloseable {
continue; continue;
} }
if (insertreq.execute(querydb) == null) { if (insertreq.execute(querydb) == null) {
Error lastError = querydb.getLastError(); BSimError lastError = querydb.getLastError();
if ((lastError.category == ErrorCategory.Format) || if ((lastError.category == ErrorCategory.Format) ||
(lastError.category == ErrorCategory.Nonfatal)) { (lastError.category == ErrorCategory.Nonfatal)) {
Msg.warn(this, file.getName() + ": " + lastError.message); Msg.warn(this, file.getName() + ": " + lastError.message);
@ -216,7 +232,7 @@ public class BulkSignatures implements AutoCloseable {
loadSignatureXml(file, update.manage); loadSignatureXml(file, update.manage);
ResponseUpdate respup = update.execute(querydb); ResponseUpdate respup = update.execute(querydb);
if (respup == null) { if (respup == null) {
Error lastError = querydb.getLastError(); BSimError lastError = querydb.getLastError();
if ((lastError.category == ErrorCategory.Format) || if ((lastError.category == ErrorCategory.Format) ||
(lastError.category == ErrorCategory.Nonfatal)) { (lastError.category == ErrorCategory.Nonfatal)) {
Msg.warn(this, file.getName() + ": " + lastError.message); Msg.warn(this, file.getName() + ": " + lastError.message);
@ -400,9 +416,6 @@ public class BulkSignatures implements AutoCloseable {
checkBSimServerOperation(); checkBSimServerOperation();
querydb = BSimClientFactory.buildClient(bsimServerInfo, true); 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? // TODO: Should this output differ for command-line vs workbench? debug only?
try { try {
@ -533,7 +546,7 @@ public class BulkSignatures implements AutoCloseable {
establishQueryServerConnection(true); establishQueryServerConnection(true);
ResponseDelete respdel = query.execute(querydb); ResponseDelete respdel = query.execute(querydb);
if (respdel == null) { if (respdel == null) {
Error lastError = querydb.getLastError(); BSimError lastError = querydb.getLastError();
throw new LSHException("Could not perform delete: " + lastError.message); throw new LSHException("Could not perform delete: " + lastError.message);
} }
@ -561,7 +574,7 @@ public class BulkSignatures implements AutoCloseable {
query.doRebuild = false; query.doRebuild = false;
ResponseAdjustIndex response = query.execute(querydb); ResponseAdjustIndex response = query.execute(querydb);
if (response == null) { if (response == null) {
Error lastError = querydb.getLastError(); BSimError lastError = querydb.getLastError();
throw new LSHException("Could not drop index: " + lastError.message); throw new LSHException("Could not drop index: " + lastError.message);
} }
String dbDetail = "for database " + info.databasename + " (" + bsimServerInfo + ")"; String dbDetail = "for database " + info.databasename + " (" + bsimServerInfo + ")";
@ -590,7 +603,7 @@ public class BulkSignatures implements AutoCloseable {
System.out.println("Starting rebuild ..."); System.out.println("Starting rebuild ...");
ResponseAdjustIndex response = query.execute(querydb); ResponseAdjustIndex response = query.execute(querydb);
if (response == null) { if (response == null) {
Error lastError = querydb.getLastError(); BSimError lastError = querydb.getLastError();
throw new LSHException("Could not rebuild index: " + lastError.message); throw new LSHException("Could not rebuild index: " + lastError.message);
} }
String dbDetail = "for database " + info.databasename + " (" + bsimServerInfo + ")"; String dbDetail = "for database " + info.databasename + " (" + bsimServerInfo + ")";
@ -617,7 +630,7 @@ public class BulkSignatures implements AutoCloseable {
PrewarmRequest request = new PrewarmRequest(); PrewarmRequest request = new PrewarmRequest();
ResponsePrewarm response = request.execute(querydb); ResponsePrewarm response = request.execute(querydb);
if (response == null) { if (response == null) {
Error lastError = querydb.getLastError(); BSimError lastError = querydb.getLastError();
throw new LSHException("Prewarm failed: " + lastError.message); throw new LSHException("Prewarm failed: " + lastError.message);
} }
String dbDetail = "for database " + info.databasename + " (" + bsimServerInfo + ")"; String dbDetail = "for database " + info.databasename + " (" + bsimServerInfo + ")";
@ -662,7 +675,7 @@ public class BulkSignatures implements AutoCloseable {
ResponseExe response = exeQuery.execute(querydb); ResponseExe response = exeQuery.execute(querydb);
if (response == null) { if (response == null) {
Error lastError = querydb.getLastError(); BSimError lastError = querydb.getLastError();
throw new LSHException("Could not perform getexeinfo: " + lastError.message); throw new LSHException("Could not perform getexeinfo: " + lastError.message);
} }
@ -735,7 +748,7 @@ public class BulkSignatures implements AutoCloseable {
req.description = description; req.description = description;
ResponseInfo resp = req.execute(querydb); ResponseInfo resp = req.execute(querydb);
if (resp == null) { if (resp == null) {
Error lastError = querydb.getLastError(); BSimError lastError = querydb.getLastError();
throw new LSHException("Could not change metadata: " + lastError.message); throw new LSHException("Could not change metadata: " + lastError.message);
} }
info = resp.info; info = resp.info;
@ -764,7 +777,7 @@ public class BulkSignatures implements AutoCloseable {
ResponseInfo resp = req.execute(querydb); ResponseInfo resp = req.execute(querydb);
if (resp == null) { if (resp == null) {
Error lastError = querydb.getLastError(); BSimError lastError = querydb.getLastError();
throw new LSHException("Could not install new category: " + lastError.message); throw new LSHException("Could not install new category: " + lastError.message);
} }
info = resp.info; info = resp.info;
@ -798,7 +811,7 @@ public class BulkSignatures implements AutoCloseable {
req.tag_name = dequoteString(tagName); req.tag_name = dequoteString(tagName);
ResponseInfo resp = req.execute(querydb); ResponseInfo resp = req.execute(querydb);
if (resp == null) { if (resp == null) {
Error lastError = querydb.getLastError(); BSimError lastError = querydb.getLastError();
throw new LSHException(lastError.message); throw new LSHException(lastError.message);
} }
info = resp.info; info = resp.info;
@ -859,7 +872,7 @@ public class BulkSignatures implements AutoCloseable {
while (count != 0) { while (count != 0) {
ResponsePair responsePair = query.execute(querydb); ResponsePair responsePair = query.execute(querydb);
if (responsePair == null) { if (responsePair == null) {
Error lastError = querydb.getLastError(); BSimError lastError = querydb.getLastError();
throw new LSHException(lastError.message); throw new LSHException(lastError.message);
} }
for (PairNote note : responsePair.notes) { for (PairNote note : responsePair.notes) {
@ -893,7 +906,7 @@ public class BulkSignatures implements AutoCloseable {
establishQueryServerConnection(true); establishQueryServerConnection(true);
ResponseName resp = query.execute(querydb); ResponseName resp = query.execute(querydb);
if (resp == null) { if (resp == null) {
Error lastError = querydb.getLastError(); BSimError lastError = querydb.getLastError();
throw new LSHException(lastError.message); throw new LSHException(lastError.message);
} }
resp.printRaw(outStream, querydb.getLSHVectorFactory(), 0); resp.printRaw(outStream, querydb.getLSHVectorFactory(), 0);
@ -941,7 +954,7 @@ public class BulkSignatures implements AutoCloseable {
query.fillinCallgraph = info.trackcallgraph; query.fillinCallgraph = info.trackcallgraph;
ResponseName responseName = query.execute(querydb); ResponseName responseName = query.execute(querydb);
if (responseName == null) { if (responseName == null) {
Error lastError = querydb.getLastError(); BSimError lastError = querydb.getLastError();
throw new LSHException(lastError.message); throw new LSHException(lastError.message);
} }
if (!responseName.uniqueexecutable) { if (!responseName.uniqueexecutable) {

View file

@ -20,8 +20,10 @@ import java.util.*;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import docking.DialogComponentProvider;
import docking.DockingWindowManager; import docking.DockingWindowManager;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.widgets.textfield.GFormattedTextField;
import ghidra.app.services.ProgramManager; import ghidra.app.services.ProgramManager;
import ghidra.features.bsim.gui.*; import ghidra.features.bsim.gui.*;
import ghidra.features.bsim.gui.overview.BSimOverviewProvider; import ghidra.features.bsim.gui.overview.BSimOverviewProvider;
@ -90,9 +92,11 @@ public class BSimSearchPluginScreenShots extends GhidraScreenShotGenerator {
@Test @Test
public void testManageServersDialog() { public void testManageServersDialog() {
addTestServer(new BSimServerInfo(DBType.postgres, "100.50.123.5", 123, "testDB")); addTestServer(
addTestServer(new BSimServerInfo(DBType.postgres, "100.50.123.5", 134, "anotherDB")); new BSimServerInfo(DBType.postgres, "mylogin", "100.50.123.5", 123, "testDB"));
addTestServer(new BSimServerInfo(DBType.file, "100.50.123.5", 134, "/bsim/database1")); addTestServer(
new BSimServerInfo(DBType.postgres, "mylogin", "100.50.123.5", 134, "anotherDB"));
addTestServer(new BSimServerInfo("/bsim/database1"));
DockingActionIf action = getAction(plugin, "Manage BSim Servers"); DockingActionIf action = getAction(plugin, "Manage BSim Servers");
performAction(action, false); performAction(action, false);
@ -106,7 +110,11 @@ public class BSimSearchPluginScreenShots extends GhidraScreenShotGenerator {
public void testAddServerDialog() { public void testAddServerDialog() {
CreateBsimServerInfoDialog dialog = new CreateBsimServerInfoDialog(); CreateBsimServerInfoDialog dialog = new CreateBsimServerInfoDialog();
runSwingLater(() -> DockingWindowManager.showDialog(dialog)); 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); captureDialog(dialog);
dialog.close(); dialog.close();
} }

View file

@ -178,7 +178,7 @@ public class BSimSearchPluginTest extends AbstractBSimPluginTest {
private FunctionDatabase database; private FunctionDatabase database;
public TestBSimServerInfo(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; this.database = database;
} }

View file

@ -24,7 +24,7 @@ import org.junit.*;
import ghidra.features.bsim.query.*; import ghidra.features.bsim.query.*;
import ghidra.features.bsim.query.BSimServerInfo.DBType; 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.description.DatabaseInformation;
import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource; import ghidra.features.bsim.query.file.BSimH2FileDBConnectionManager.BSimH2FileDataSource;
import ghidra.features.bsim.query.protocol.CreateDatabase; import ghidra.features.bsim.query.protocol.CreateDatabase;
@ -68,7 +68,7 @@ public class BSimH2DatabaseManagerTest extends AbstractGhidraHeadedIntegrationTe
} }
private BSimServerInfo getBsimServerInfo(String name) { private BSimServerInfo getBsimServerInfo(String name) {
return new BSimServerInfo(DBType.file, null, -1, getDbName(name)); return new BSimServerInfo(getDbName(name));
} }
private BSimServerInfo createDatabase(String databaseName) { private BSimServerInfo createDatabase(String databaseName) {
@ -103,7 +103,7 @@ public class BSimH2DatabaseManagerTest extends AbstractGhidraHeadedIntegrationTe
ResponseInfo response = command.execute(h2Database); ResponseInfo response = command.execute(h2Database);
if (response == null) { if (response == null) {
if (expectedError != null) { if (expectedError != null) {
Error lastError = h2Database.getLastError(); BSimError lastError = h2Database.getLastError();
assertNotNull(lastError); assertNotNull(lastError);
assertTrue(lastError.message.contains(expectedError)); assertTrue(lastError.message.contains(expectedError));
} }
@ -186,7 +186,7 @@ public class BSimH2DatabaseManagerTest extends AbstractGhidraHeadedIntegrationTe
BSimServerInfo serverInfo = getBsimServerInfo("test"); BSimServerInfo serverInfo = getBsimServerInfo("test");
try (FunctionDatabase fdb = serverInfo.getFunctionDatabase(false)) { try (FunctionDatabase fdb = serverInfo.getFunctionDatabase(false)) {
assertFalse(fdb.initialize()); assertFalse(fdb.initialize());
Error lastError = fdb.getLastError(); BSimError lastError = fdb.getLastError();
assertNotNull(lastError); assertNotNull(lastError);
assertTrue(lastError.message.startsWith("Database does not exist: ")); assertTrue(lastError.message.startsWith("Database does not exist: "));
} }

View file

@ -35,7 +35,7 @@ import ghidra.app.util.headless.HeadlessOptions;
import ghidra.features.bsim.gui.filters.ExecutableCategoryBSimFilterType; import ghidra.features.bsim.gui.filters.ExecutableCategoryBSimFilterType;
import ghidra.features.bsim.gui.filters.HasNamedChildBSimFilterType; import ghidra.features.bsim.gui.filters.HasNamedChildBSimFilterType;
import ghidra.features.bsim.query.*; 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.client.tables.ExeTable.ExeTableOrderColumn;
import ghidra.features.bsim.query.description.*; import ghidra.features.bsim.query.description.*;
import ghidra.features.bsim.query.ingest.BSimLaunchable; import ghidra.features.bsim.query.ingest.BSimLaunchable;
@ -264,7 +264,7 @@ public class BSimServerTest {
private static void testForError(QueryResponseRecord response) throws LSHException { private static void testForError(QueryResponseRecord response) throws LSHException {
if (response == null) { if (response == null) {
Error lastError = client.getLastError(); BSimError lastError = client.getLastError();
if (lastError == null) { if (lastError == null) {
throw new LSHException("Unknown error"); throw new LSHException("Unknown error");
} }

View file

@ -106,11 +106,6 @@ public class FunctionDatabaseTestDouble implements SQLFunctionDatabase {
return ClientUtil.getUserName(); return ClientUtil.getUserName();
} }
@Override
public void setUserName(String userName) {
// Currently not implemented
}
@Override @Override
public String getURLString() { public String getURLString() {
return urlString; return urlString;
@ -141,8 +136,8 @@ public class FunctionDatabaseTestDouble implements SQLFunctionDatabase {
} }
@Override @Override
public Error getLastError() { public BSimError getLastError() {
return new Error(ErrorCategory.Unused, errorString); return new BSimError(ErrorCategory.Unused, errorString);
} }
void setErrorString(String errorString) { void setErrorString(String errorString) {

View file

@ -23,7 +23,7 @@ public class TestBSimServerInfo extends BSimServerInfo {
private FunctionDatabase database; private FunctionDatabase database;
public TestBSimServerInfo(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; this.database = database;
} }

View file

@ -38,7 +38,8 @@ public class PasswordDialog extends DialogComponentProvider {
private JPasswordField passwordField; private JPasswordField passwordField;
private JComboBox<String> choiceCB; private JComboBox<String> choiceCB;
private JCheckBox anonymousAccess; private JCheckBox anonymousAccess;
boolean okPressed = false; private boolean okPressed = false;
private String defaultUserID;
/** /**
* Construct a new PasswordDialog. * Construct a new PasswordDialog.
@ -47,7 +48,7 @@ public class PasswordDialog extends DialogComponentProvider {
* @param serverName name of server or keystore pathname * @param serverName name of server or keystore pathname
* @param passPrompt password prompt to show in the dialog; may be null, in which case * @param passPrompt password prompt to show in the dialog; may be null, in which case
* "Password:" is displayed next to the password field * "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 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 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 * @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 * @param includeAnonymousOption true signals to add a checkbox to request anonymous login
*/ */
public PasswordDialog(String title, String serverType, String serverName, String passPrompt, 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) { int defaultChoice, boolean includeAnonymousOption) {
this(title, serverType, serverName, passPrompt, namePrompt, defaultUserID); this(title, serverType, serverName, passPrompt, userIdPrompt, defaultUserID);
if (choicePrompt != null) { if (choicePrompt != null) {
workPanel.add(new GLabel(choicePrompt)); workPanel.add(new GLabel(choicePrompt));
choiceCB = new GComboBox<>(choices); choiceCB = new GComboBox<>(choices);
@ -94,12 +95,12 @@ public class PasswordDialog extends DialogComponentProvider {
* @param serverName name of server or keystore pathname * @param serverName name of server or keystore pathname
* @param passPrompt password prompt to show in the dialog; may be null, in which case * @param passPrompt password prompt to show in the dialog; may be null, in which case
* "Password:" is displayed next to the password field * "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 defaultUserID default name when prompting for a name
*/ */
public PasswordDialog(String title, String serverType, String serverName, String passPrompt, public PasswordDialog(String title, String serverType, String serverName, String passPrompt,
String namePrompt, String defaultUserID) { String userIdPrompt, String defaultUserID) {
this(title, serverType, serverName, passPrompt, namePrompt, defaultUserID, true); 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 serverName name of server or keystore pathname
* @param passPrompt password prompt to show in the dialog; may be null, in which case * @param passPrompt password prompt to show in the dialog; may be null, in which case
* "Password:" is displayed next to the password field * "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 defaultUserID default name when prompting for a name
* @param hasMessages true if the client will set messages on this dialog. If true, the * @param hasMessages true if the client will set messages on this dialog. If true, the
* dialog's minimum size will be increased * dialog's minimum size will be increased
*/ */
public PasswordDialog(String title, String serverType, String serverName, String passPrompt, 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); super(title, true);
this.defaultUserID = defaultUserID;
setRememberSize(false); setRememberSize(false);
setTransient(true); setTransient(true);
@ -132,8 +136,8 @@ public class PasswordDialog extends DialogComponentProvider {
workPanel.add(new GLabel(serverName)); workPanel.add(new GLabel(serverName));
} }
if (namePrompt != null) { if (userIdPrompt != null) {
workPanel.add(new GLabel(namePrompt)); workPanel.add(new GLabel(userIdPrompt));
nameField = new JTextField(defaultUserID, 16); nameField = new JTextField(defaultUserID, 16);
nameField.setName("NAME-ENTRY-COMPONENT"); nameField.setName("NAME-ENTRY-COMPONENT");
workPanel.add(nameField); 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 / Name entered in the password field
* @return the user ID entered in the password field * @return the user ID / Name entered in the password field
*/ */
public String getUserID() { public String getUserID() {
return nameField != null ? nameField.getText().trim() : null; return nameField != null ? nameField.getText().trim() : defaultUserID;
} }
/** /**

View file

@ -16,11 +16,12 @@
package ghidra.framework.client; package ghidra.framework.client;
import java.awt.Component; import java.awt.Component;
import java.net.Authenticator; import java.net.*;
import java.net.PasswordAuthentication;
import javax.security.auth.callback.*; import javax.security.auth.callback.*;
import org.apache.commons.lang3.StringUtils;
import docking.DockingWindowManager; import docking.DockingWindowManager;
import docking.widgets.*; import docking.widgets.*;
import ghidra.framework.preferences.Preferences; import ghidra.framework.preferences.Preferences;
@ -35,23 +36,63 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider
private Authenticator authenticator = new Authenticator() { private Authenticator authenticator = new Authenticator() {
@Override @Override
protected PasswordAuthentication getPasswordAuthentication() { protected PasswordAuthentication getPasswordAuthentication() {
Msg.debug(this, "PasswordAuthentication requested for " + getRequestingURL());
NameCallback nameCb = null; String serverName = getRequestingHost();
if (!"NO_NAME".equals(getRequestingScheme())) { URL requestingURL = getRequestingURL();
nameCb = new NameCallback("Name: ", ClientUtil.getUserName());
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(); String prompt = getRequestingPrompt();
if (prompt == null) { if (StringUtils.isBlank(prompt) || "security".equals(prompt)) {
prompt = "Password:"; prompt = "Password:"; // assume dialog will show user name via nameCb
} }
PasswordCallback passCb = new PasswordCallback(prompt, false); PasswordCallback passCb = new PasswordCallback(prompt, false);
try { try {
ServerPasswordPrompt pp = new ServerPasswordPrompt("Connection Authentication", ServerPasswordPrompt pp = new ServerPasswordPrompt("Connection Authentication",
"Server", getRequestingHost(), nameCb, passCb, null, null, null); "Server", serverName, nameCb, passCb, null, null, null);
SystemUtilities.runSwingNow(pp); SystemUtilities.runSwingNow(pp);
if (pp.okWasPressed()) { if (pp.okWasPressed()) {
return new PasswordAuthentication(nameCb != null ? nameCb.getName() : null, return new PasswordAuthentication(nameCb.getName(), passCb.getPassword());
passCb.getPassword());
} }
} }
finally { 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 @Override
public Authenticator getAuthenticator() { public Authenticator getAuthenticator() {
return authenticator; return authenticator;
@ -165,10 +221,25 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider
choicePrompt = choiceCb.getPrompt(); choicePrompt = choiceCb.getPrompt();
choices = choiceCb.getChoices(); choices = choiceCb.getChoices();
} }
PasswordDialog pwdDialog =
new PasswordDialog(title, serverType, serverName, passCb.getPrompt(), String defaultUserName = null;
nameCb != null ? nameCb.getPrompt() : null, getDefaultUserName(), choicePrompt, String namePrompt = null;
choices, getDefaultChoice(), anonymousCb != 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) { if (errorMsg != null) {
pwdDialog.setErrorText(errorMsg); pwdDialog.setErrorText(errorMsg);
} }

View file

@ -39,38 +39,58 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
private final static char[] BADPASSWORD = "".toCharArray(); private final static char[] BADPASSWORD = "".toCharArray();
private static Object sshPrivateKey; private static Object sshPrivateKey;
private static String userID = ClientUtil.getUserName(); // default username private static String defaultUserName = ClientUtil.getUserName();
private static boolean passwordPromptAllowed; private static boolean passwordPromptAllowed;
private Authenticator authenticator = new Authenticator() { private Authenticator authenticator = new Authenticator() {
@Override @Override
protected PasswordAuthentication getPasswordAuthentication() { protected PasswordAuthentication getPasswordAuthentication() {
Msg.debug(this, "PasswordAuthentication requested for " + getRequestingURL());
String usage = null; if (defaultUserName == null) {
String prompt = getRequestingPrompt(); throw new IllegalStateException("Default user name is unknown");
if ("security".equals(prompt)) {
prompt = null; // squash generic "security" prompt
} }
URL requestingURL = getRequestingURL();
String serverName = getRequestingHost();
URL requestingURL = getRequestingURL(); // may be null
String pwd = null;
String userName = defaultUserName;
if (requestingURL != null) { if (requestingURL != null) {
URL minimalURL = null; String userInfo = requestingURL.getUserInfo();
try { if (userInfo != null) {
minimalURL = new URL(requestingURL, "/"); // 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 Msg.debug(this, "PasswordAuthentication requested for " + serverName);
String host = getRequestingHost();
prompt = (host != null ? (host + " ") : "") + "(" + userID + ") Password:"; 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 * Install headless client authenticator for Ghidra Server
* @param username optional username to be used with a Ghidra Server which * @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 * @param keystorePath optional PKI or SSH keystore path. May also be specified
* as resource path for SSH key. * as resource path for SSH key.
* @param allowPasswordPrompt if true the user may be prompted for passwords * @param allowPasswordPrompt if true the user may be prompted for passwords
@ -97,7 +118,7 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
boolean allowPasswordPrompt) throws IOException { boolean allowPasswordPrompt) throws IOException {
passwordPromptAllowed = allowPasswordPrompt; passwordPromptAllowed = allowPasswordPrompt;
if (username != null) { if (username != null) {
userID = username; defaultUserName = username;
} }
// clear existing key store settings // clear existing key store settings
@ -175,7 +196,7 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
passwordPrompt += "\n"; passwordPrompt += "\n";
} }
if (prompt == null) { if (StringUtils.isBlank(prompt)) {
prompt = "Password:"; prompt = "Password:";
} }
@ -233,17 +254,39 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
anonymousCb.setAnonymousAccessRequested(true); anonymousCb.setAnonymousAccessRequested(true);
return true; return true;
} }
if (defaultUserName == null) {
throw new IllegalStateException("Default user name is unknown");
}
if (choiceCb != null) { if (choiceCb != null) {
choiceCb.setSelectedIndex(1); 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; String usage = null;
if (serverName != null) { if (serverName != null) {
usage = serverType + ": " + serverName; 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); passCb.setPassword(password);
return password != null; return password != null;
} }
@ -278,7 +321,7 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
return false; return false;
} }
if (nameCb != null) { if (nameCb != null) {
nameCb.setName(userID); nameCb.setName(defaultUserName);
} }
try { try {
sshCb.sign(sshPrivateKey); sshCb.sign(sshPrivateKey);

View file

@ -687,6 +687,10 @@ public class GhidraURL {
if (StringUtils.isBlank(host)) { if (StringUtils.isBlank(host)) {
throw new IllegalArgumentException("host required"); 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)) { if (StringUtils.isBlank(repositoryName)) {
throw new IllegalArgumentException("repository name required"); throw new IllegalArgumentException("repository name required");
} }

View file

@ -109,6 +109,9 @@ public class GhidraURLConnection extends URLConnection {
public GhidraURLConnection(URL url, GhidraProtocolHandler protocolHandler) public GhidraURLConnection(URL url, GhidraProtocolHandler protocolHandler)
throws MalformedURLException { throws MalformedURLException {
super(url); super(url);
if (url.getUserInfo() != null) {
throw new MalformedURLException("User info not supported by Ghidra URLs");
}
if (protocolHandler == null) { if (protocolHandler == null) {
throw new IllegalArgumentException("missing required protocol handler"); throw new IllegalArgumentException("missing required protocol handler");
} }