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

View file

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

View file

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

View file

@ -326,7 +326,12 @@
<DD>
<P>Creates a new empty repository. A URL and configuration template (<SPAN class=
"bold"><STRONG>config_template</STRONG></SPAN>) is required. The new database name
is taken from the path element of the URL.</P>
is taken from the path element of the URL. See <A class="xref" href=
"DatabaseConfiguration.html#CreateDatabase" title="Creating a Database">&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=
"bold"><STRONG>config_template</STRONG></SPAN>) are defined within the Ghidra
@ -792,21 +797,21 @@
<TD>PostgreSQL</TD>
<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>
<TD>Elasticsearch</TD>
<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>
<TD>Elasticsearch</TD>
<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>
@ -820,6 +825,24 @@
<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
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

View file

@ -527,6 +527,34 @@
that are different from the standard PostgreSQL defaults. To provide site specific
configuration, changes should be made to the normal PostgreSQL configuration files.</P>
</DIV>
<DIV class="sect3">
<DIV class="titlepage">
<DIV>
<DIV>
<H4 class="title"><A name="PostgresFirewall"></A>PostgreSQL Firewall Considerations</H4>
</DIV>
</DIV>
</DIV>
<P>Remote client access to the PostgreSQL server may have network firewalls
which must be considered and properly configured to allow network access.
Both dedicated network firewalls and OS-level firewalls must be considered.</P>
<P>Firewall configurations are beyond the scope of this document, however for simple
single-node installations on Linux the <CODE class="computeroutput">firewall-cmd</CODE>
may be used to allow incoming connections to the appropriate port (TCP port 5432 is the
default for PostgreSQL). This does not consider network firewall devices which may
also impact connectivity.</P>
<PRE><CODE class&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 class="sect2">
@ -562,14 +590,23 @@
<P>In order to make use of Elasticsearch with BSim, the database administrator must
install the <SPAN class="emphasis"><EM>lsh.zip</EM></SPAN> plug-in as part of the
Elasticsearch deployment. The plug-in is available in the Ghidra add-on named <SPAN
class="emphasis"><EM>BSimElasticPlugin</EM></SPAN>, which unpacks into a standard
Ghidra installation. The file <SPAN class="emphasis"><EM>lsh.zip</EM></SPAN> is a
standard Elasticsearch plug-in that must be installed on every node of the cluster
Elasticsearch deployment. The plug-in is available in the Ghidra extension named <SPAN
class="emphasis"><EM>BSimElasticPlugin</EM></SPAN>
(<CODE class="computeroutput">&lt;ghidra-install-dir&gt;/Extensions/Ghidra/ghidra_11.2.1_U_20241105_BSimElasticPlugin.zip</CODE>).
The extension may be unzipped to a temporary location or use Ghidra's Install Extensions
capability which will unpack it into the users platform-specific config directory
indicated in the detail view of the Install Extensions window. The extension is not
used directly by Ghidra and is only needed for the
<SPAN class="emphasis"><EM>lsh.zip</EM></SPAN> elasticsearch plug-in
which must be installed on every node of the cluster
before a BSim repository can be created. The description below shows how to enable
the BSim plug-in for a single node, but this will need to be repeated for any
additional nodes.</P>
<P><IMG border="0" src="help/shared/tip.png" alt="Tip: ">Refer to the <I>README.md</I>
file within the unpacked extension for important plugin details. The plugin file
stipulates a specific elasticsearch version and may require an adjustment and repack.</P>
<P>Assuming the add-on has been unpacked, the plug-in can be installed to a single node
using the <SPAN class="emphasis"><EM>elasticsearch-plugin</EM></SPAN> command in the
<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">
<TR>
<TD><CODE class="computeroutput">bin/elasticsearch-plugin install
file:///path/to/ghidra/Ghidra/contrib/BSimElasticPlugin/data/lsh.zip</CODE></TD>
file:///path/to/ghidra/extension/BSimElasticPlugin/data/lsh.zip</CODE></TD>
</TR>
</TABLE>
</DIV>
@ -610,7 +647,7 @@
</DIV>
</DIV>
<P>The open Elasticsearch distribution starts with up with password authentication
<P>The open Elasticsearch distribution starts with password authentication
enabled by default. When a node is started up for the first time, as described above, an
<SPAN class="bold"><STRONG>elastic</STRONG></SPAN> user is created with a randomly
generated password that is reported, once, to the console. For a toy deployment, it may
@ -665,6 +702,34 @@ curl -k -u elastic:XXXXXX -X POST "https://localhost:9200/_security/user/ghidrau
class="xref" href="CommandLineReference.html#URLs">&ldquo;Ghidra and BSim
URLs&rdquo;</A> for additional information about URLs.</P>
</DIV>
<DIV class="sect3">
<DIV class="titlepage">
<DIV>
<DIV>
<H4 class="title"><A name="ElasticFirewall"></A>Elasticsearch Firewall Considerations</H4>
</DIV>
</DIV>
</DIV>
<P>Remote client access to the Elasticsearch server/cluster may have network firewalls
which must be considered and properly configured to allow network access.
Both dedicated network firewalls and OS-level firewalls must be considered.</P>
<P>Firewall configurations are beyond the scope of this document, however for simple
single-node installations on Linux the <CODE class="computeroutput">firewall-cmd</CODE>
may be used to allow incoming connections to the appropriate port (TCP port 9200 is the
default for elasticsearch). This does not consider network firewall devices which may
also impact connectivity.</P>
<PRE><CODE class&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>
@ -672,8 +737,7 @@ curl -k -u elastic:XXXXXX -X POST "https://localhost:9200/_security/user/ghidrau
<DIV class="titlepage">
<DIV>
<DIV>
<H2 class="title" style="clear: both"><A name="CreateDatabase"></A>Creating a
Database</H2>
<H2 class="title" style="clear: both"><A name="CreateDatabase"></A>Creating a Database</H2>
</DIV>
</DIV>
</DIV>

View file

@ -53,9 +53,23 @@
<BR>
<P>The dialog displays a table showing all the currently defined BSim databases/servers. Each
entry shows a name for the BSim database, its type (postgres, elastic, or file), a host ip
and port (if applicable), and finally the number of active connections.</P>
<P>The dialog displays a table showing all the currently defined BSim databases/servers.
Table columns displayed include:</P>
<UL>
<LI><B>Name</B> - Indicates the name of the BSim database. In the cases of an H2 <I>file</I>
database this represents the name of the file (hovering on this will show the full absolute path).</LI>
<LI><B>Type</B> - The type of database (<I>postgres</I>, <I>elastic</I> or <I>file</I>).</LI>
<LI><B>Host</B> - The network host name or IP address for a <I>postgres</I> or
<I>elastic</I> database.</LI>
<LI><B>Port</B> - The TCP port for a <I>postgres</I> or <I>elastic</I> database.</LI>
<LI><B>User</B> - The non-default user name to be used when connecting to a <I>postgres</I> or
<I>elastic</I> database. When actively connected to a <I>postgres</I> database
the actual name used for authentication will be shown.</LI>
<LI><B>Active/Idle Connections</B> - The active/idle database connections for the
connection pool associated with a <I>postgres</I> or <I>file</I> database.
This field will be blank for these databases when in a disconnected state.</LI>
</UL>
<P>There are four primary actions for this dialog:</P>
<A name="Manage_Servers_Actions"></A>
@ -68,8 +82,8 @@
selected entry will be deleted. This action will force an immediate disconnect for an
active/idle connection and should be used with care.</LI>
<LI><IMG alt="" src="icon.bsim.disconnected"><IMG alt="" src="icon.bsim.connected">&nbsp;
Connect or disconnect an inactive database/server connection. This action is not supported
<LI><IMG alt="" src="icon.bsim.disconnected"><IMG alt="" src="icon.bsim.connected">&nbsp;Connect
or disconnect an inactive database/server connection which has been selected. This action is not supported
by Elastic database servers.</LI>
<LI><IMG alt="" src="icon.bsim.change.password">&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
needs to be specified. For postgres and elastic, you need to enter host and port. For file,
you see a button for using a filechooser to pick the file that is the local BSim H2
database.</P>
needs to be specified. For <I>postgres</I> and <I>elastic</I>, you must enter a database name,
hostname/address, and port. Specify a host name of "localhost" for a server running on the local system.
Invalid or missing entries are shown in red. For a <I>file</I> database,
the "..." button is used to launch a filechooser for selecting an existing H2 database file
(*.mv.db).</P>
</BLOCKQUOTE>
</BLOCKQUOTE>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

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

View file

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

View file

@ -21,11 +21,15 @@ import java.util.*;
import javax.swing.Icon;
import javax.swing.JLabel;
import org.apache.commons.lang3.StringUtils;
import docking.widgets.table.*;
import ghidra.docking.settings.Settings;
import ghidra.features.bsim.gui.BSimServerManager;
import ghidra.features.bsim.query.BSimServerInfo;
import ghidra.features.bsim.query.*;
import ghidra.features.bsim.query.BSimPostgresDBConnectionManager.BSimPostgresDataSource;
import ghidra.features.bsim.query.BSimServerInfo.DBType;
import ghidra.framework.client.ClientUtil;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.framework.plugintool.ServiceProviderStub;
import ghidra.util.table.column.AbstractGColumnRenderer;
@ -90,6 +94,7 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
descriptor.addVisibleColumn(new TypeColumn());
descriptor.addVisibleColumn(new HostColumn());
descriptor.addVisibleColumn(new PortColumn());
descriptor.addVisibleColumn(new UserInfoColumn());
descriptor.addVisibleColumn(new ConnectionStatusColumn());
return descriptor;
}
@ -165,6 +170,42 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
}
}
private static class UserInfoColumn
extends AbstractDynamicTableColumn<BSimServerInfo, String, Object> {
@Override
public String getColumnName() {
return "User";
}
@Override
public String getValue(BSimServerInfo serverInfo, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException {
if (serverInfo.hasDefaultLogin()) {
if (serverInfo.getDBType() == DBType.postgres) {
BSimPostgresDataSource ds =
BSimPostgresDBConnectionManager.getDataSourceIfExists(serverInfo);
if (ds != null) {
return ds.getUserName();
}
}
// TODO: how can we determine elastic username?
return "";
}
String info = serverInfo.getUserName();
boolean hasPassword = serverInfo.hasPassword();
if (hasPassword) {
info = info + ":****"; // show w/masked password
}
return info;
}
@Override
public int getColumnPreferredWidth() {
return 100;
}
}
private static class HostColumn
extends AbstractDynamicTableColumn<BSimServerInfo, String, Object> {
@ -176,7 +217,6 @@ public class BSimServerTableModel extends GDynamicColumnTableModel<BSimServerInf
@Override
public String getValue(BSimServerInfo serverInfo, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException {
return serverInfo.getServerName();
}

View file

@ -16,12 +16,17 @@
package ghidra.features.bsim.gui.search.dialog;
import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.*;
import java.io.File;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.*;
import javax.swing.JFormattedTextField.AbstractFormatterFactory;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.DefaultFormatter;
import javax.swing.text.DefaultFormatterFactory;
import docking.DialogComponentProvider;
import docking.widgets.OptionDialog;
@ -29,8 +34,11 @@ import docking.widgets.button.BrowseButton;
import docking.widgets.button.GRadioButton;
import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.filechooser.GhidraFileChooserMode;
import docking.widgets.textfield.GFormattedTextField;
import docking.widgets.textfield.GFormattedTextField.Status;
import ghidra.features.bsim.query.*;
import ghidra.features.bsim.query.BSimServerInfo.DBType;
import ghidra.framework.client.ClientUtil;
import ghidra.util.HelpLocation;
import ghidra.util.filechooser.GhidraFileChooserModel;
import ghidra.util.filechooser.GhidraFileFilter;
@ -45,6 +53,15 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
private static final String POSTGRES = "Postgres";
private static final String ELASTIC = "Elastic";
private static final String FILE_H2 = "File";
private static final AbstractFormatterFactory FORMATTER_FACTORY =
new DefaultFormatterFactory(new DefaultFormatter() {
@Override
public Object stringToValue(String text) {
return text;
}
});
private GRadioButton postgresButton;
private GRadioButton elasticButton;
private GRadioButton fileButton;
@ -68,16 +85,10 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
setHelpLocation(new HelpLocation("BSimSearchPlugin", "Add_Server_Definition_Dialog"));
}
public BSimServerInfo getBsimServerInfo() {
BSimServerInfo getBsimServerInfo() {
return result;
}
@Override
public void setHelpLocation(HelpLocation helpLocation) {
// TODO Auto-generated method stub
super.setHelpLocation(helpLocation);
}
@Override
protected void okCallback() {
BSimServerInfo serverInfo = activePanel.getServerInfo();
@ -88,7 +99,7 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
}
}
public boolean acceptServer(BSimServerInfo serverInfo) {
private boolean acceptServer(BSimServerInfo serverInfo) {
// FIXME: Use task to correct dialog parenting issue caused by password prompt
String errorMessage = null;
try (FunctionDatabase database = BSimClientFactory.buildClient(serverInfo, true)) {
@ -159,6 +170,7 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
if (postgresButton.isSelected()) {
cardLayout.show(cardPanel, POSTGRES);
activePanel = postgresPanel;
}
else if (elasticButton.isSelected()) {
cardLayout.show(cardPanel, ELASTIC);
@ -195,8 +207,9 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
}
private class DbPanel extends ServerPanel {
private JTextField nameField;
private JTextField hostField;
private GFormattedTextField nameField;
private GFormattedTextField userField;
private GFormattedTextField hostField;
private JTextField portField;
private DBType type;
@ -204,12 +217,20 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
super(new PairLayout(10, 10));
this.type = type;
nameField = new NotifyingTextField();
hostField = new NotifyingTextField();
portField =
new NotifyingTextField(Integer.toString(BSimServerInfo.DEFAULT_POSTGRES_PORT));
createDBNameField();
createUserField();
createHostField();
int defaultPort = -1;
if (type == BSimServerInfo.DBType.postgres) {
defaultPort = BSimServerInfo.DEFAULT_POSTGRES_PORT;
}
else if (type == BSimServerInfo.DBType.elastic) {
defaultPort = BSimServerInfo.DEFAULT_ELASTIC_PORT;
}
portField = new NotifyingTextField(Integer.toString(defaultPort));
JLabel nameLabel = new JLabel("DB Name:", SwingConstants.RIGHT);
JLabel userLabel = new JLabel("User (optional):", SwingConstants.RIGHT);
JLabel hostLabel = new JLabel("Host:", SwingConstants.RIGHT);
JLabel portLabel = new JLabel("Port:", SwingConstants.RIGHT);
nameLabel.setLabelFor(nameField);
@ -218,21 +239,197 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
add(nameLabel);
add(nameField);
add(userLabel);
add(userField);
add(hostLabel);
add(hostField);
add(portLabel);
add(portField);
}
private void setStatus(String msg) {
CreateBsimServerInfoDialog.this.setStatusText(msg);
}
private void createDBNameField() {
nameField = new GFormattedTextField(FORMATTER_FACTORY, "");
nameField.setName("Name");
nameField.setText("");
nameField.setDefaultValue("");
nameField.setIsError(true);
nameField.setEditable(true);
nameField.setInputVerifier(new InputVerifier() {
@Override
public boolean verify(JComponent input) {
setStatus("");
String dbName = nameField.getText().trim();
if (dbName.length() == 0) {
setStatus("");
return false;
}
// Naming restrictions based upon PostgreSQL and its allowance of unicode chars
for (int i = 0; i < dbName.length(); i++) {
char c = dbName.charAt(i);
if (Character.isLetter(c)) {
continue;
}
if (i == 0 || (!Character.isDigit(c) && c != '_')) {
setStatus("Unsupported database name");
return false;
}
}
return true;
}
@Override
public boolean shouldYieldFocus(JComponent source, JComponent target) {
return true;
}
});
nameField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
e.consume();
nameField.setText("");
nameField.setDefaultValue("");
nameField.setIsError(true);
}
checkForValidDialog();
}
});
nameField.addTextEntryStatusListener(f -> checkForValidDialog());
}
private static final String HOSTNAME_IP_REGEX =
"^[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*(\\.[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*)*$";
private static final Pattern HOSTNAME_IP_PATTERN = Pattern.compile(HOSTNAME_IP_REGEX);
private void createHostField() {
hostField = new GFormattedTextField(FORMATTER_FACTORY, "");
hostField.setName("Host");
hostField.setText("");
hostField.setDefaultValue("");
hostField.setIsError(true);
hostField.setEditable(true);
hostField.setInputVerifier(new InputVerifier() {
@Override
public boolean verify(JComponent input) {
setStatus("");
String hostname = hostField.getText().trim();
if (hostname.length() == 0) {
setStatus("");
return false;
}
Matcher hostMatch = HOSTNAME_IP_PATTERN.matcher(hostname);
if (!hostMatch.matches()) {
setStatus("Unsupported host name or IP address");
return false;
}
return true;
}
@Override
public boolean shouldYieldFocus(JComponent source, JComponent target) {
return true;
}
});
hostField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
e.consume();
hostField.setText("");
hostField.setDefaultValue("");
hostField.setIsError(true);
}
checkForValidDialog();
}
});
hostField.addTextEntryStatusListener(f -> checkForValidDialog());
}
// NOTE: Username pattern based on PostgreSQL restrictions
private static final String USERNAME_REGEX = "^[a-zA-Z_][a-zA-Z0-9_$]*$";
private static final Pattern USERNAME_PATTERN = Pattern.compile(USERNAME_REGEX);
private void createUserField() {
userField = new GFormattedTextField(FORMATTER_FACTORY, "");
userField.setName("User");
userField.setText("");
userField.setDefaultValue("");
userField.setEditable(true);
userField.setInputVerifier(new InputVerifier() {
@Override
public boolean verify(JComponent input) {
setStatus("");
String username = userField.getText().trim();
if (username.length() == 0) {
setStatus("");
return true;
}
Matcher userMatch = USERNAME_PATTERN.matcher(username);
if (!userMatch.matches()) {
setStatus("Unsupported database user name");
return false;
}
return true;
}
@Override
public boolean shouldYieldFocus(JComponent source, JComponent target) {
return true;
}
});
userField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
e.consume();
userField.setText("");
userField.setDefaultValue("");
userField.setIsError(false);
}
checkForValidDialog();
}
});
userField.addTextEntryStatusListener(f -> checkForValidDialog());
}
@Override
BSimServerInfo getServerInfo() {
if (nameField.getTextEntryStatus() == Status.INVALID ||
userField.getTextEntryStatus() == Status.INVALID ||
hostField.getTextEntryStatus() == Status.INVALID) {
return null;
}
String user = userField.getText().trim();
if (ClientUtil.getUserName().equals(user)) {
user = null;
}
String name = nameField.getText().trim();
String host = hostField.getText().trim();
int port = getPort(portField.getText().trim());
if (name.isBlank() || host.isBlank() || port < 0) {
return null;
}
return new BSimServerInfo(type, host, port, name);
return new BSimServerInfo(type, user, host, port, name);
}
}
@ -291,7 +488,7 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
if (file.isDirectory()) {
return null;
}
return new BSimServerInfo(DBType.file, null, -1, path);
return new BSimServerInfo(path);
}
}
@ -303,8 +500,11 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
public NotifyingTextField(String initialText) {
super(20);
setText(initialText);
getDocument().addDocumentListener(new DocumentListener() {
getDocument().addDocumentListener(new MyFieldListener());
}
}
class MyFieldListener implements DocumentListener {
@Override
public void insertUpdate(DocumentEvent e) {
checkForValidDialog();
@ -319,9 +519,6 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
public void changedUpdate(DocumentEvent e) {
checkForValidDialog();
}
});
}
}
}

View file

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

View file

@ -16,12 +16,15 @@
package ghidra.features.bsim.query;
import java.io.Closeable;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.StringUtils;
import ghidra.framework.client.ClientUtil;
public class BSimServerInfo implements Comparable<BSimServerInfo> {
/**
@ -48,6 +51,7 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
}
private final DBType dbType;
private final String userinfo; // username[:password]
private final String host;
private final int port;
private final String dbName;
@ -56,7 +60,10 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
/**
* Construct a new {@link BSimServerInfo} object
*
* @param dbType BSim DB type
* @param userinfo connection user info, {@code username[:password]} (ignored for {@link DBType#file}).
* If blank, {@link ClientUtil#getUserName()} is used.
* @param host host name (ignored for {@link DBType#file})
* @param port port number (ignored for {@link DBType#file})
* @param dbName name of database (simple database name except for {@link DBType#file}
@ -64,15 +71,30 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
* drive letter.
* @throws IllegalArgumentException if invalid arguments are specified
*/
public BSimServerInfo(DBType dbType, String host, int port, String dbName) {
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");
}
this.host = host;
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;
}
@ -82,32 +104,55 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
if (dbType == DBType.elastic && port <= 0) {
port = DEFAULT_ELASTIC_PORT;
}
this.port = port;
}
this.userinfo = userinfo;
this.host = host;
this.port = port;
this.dbName = dbName;
}
/**
* Construct a new {@link BSimServerInfo} object. For non-file database the user's defaut
* username is used (see {@link ClientUtil#getUserName()}).
*
* @param dbType BSim DB type
* @param host host name (ignored for {@link DBType#file})
* @param port port number (ignored for {@link DBType#file})
* @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 host, int port, String dbName) {
this(dbType, null, host, port, dbName);
}
/**
* Construct a new {@link BSimServerInfo} object for a {@link DBType#file} type database.
*
* @param dbName name of database which should reflect an absolute file path.
* On Windows OS the path may start with a drive letter.
* @throws IllegalArgumentException if invalid arguments are specified
*/
public BSimServerInfo(String dbName) {
dbType = DBType.file;
userinfo = null;
host = null;
port = -1;
dbName = dbName.trim();
if (StringUtils.isEmpty(dbName)) {
throw new IllegalArgumentException("Non-empty dbName required");
}
if (dbType == DBType.file) {
// transform dbName into acceptable H2 DB file path
dbName = dbName.replace("\\", "/");
if ((!dbName.startsWith("/") && !isWindowsFilePath(dbName)) || dbName.endsWith("/")) {
throw new IllegalArgumentException("Invalid absolute file path: " + dbName);
}
if (!dbName.endsWith(H2_FILE_EXTENSION)) {
dbName += H2_FILE_EXTENSION;
}
}
else if (dbName.contains("/") || dbName.contains("\\")) { // may want additional validation
throw new IllegalArgumentException("Invalid " + dbType + " dbName: " + dbName);
}
this.dbName = dbName;
this.dbName = cleanupFilename(dbName);
}
/**
* Construct a new {@link BSimServerInfo} object from a suitable database URL
* (i.e., {@code postgresql:}, {@code https:}, {@code elastic:}, {@code file:}).
* @param url supported BSim database URL
*
* @param url supported BSim database URL. For non-file URLs, the hostname or
* address may be preceeded by a DB username (e.g., postgresql://user@host:port/dbname
* @throws IllegalArgumentException if unsupported URL protocol specified
*/
public BSimServerInfo(URL url) throws IllegalArgumentException {
@ -118,18 +163,21 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
if (protocol.equals("postgresql")) {
t = DBType.postgres;
host = checkURLField(url.getHost(), "host");
userinfo = getURLUserInfo(url);
int p = url.getPort();
port = p <= 0 ? DEFAULT_POSTGRES_PORT : p;
}
else if (protocol.equals("https") || protocol.equals("elastic")) {
t = DBType.elastic;
host = checkURLField(url.getHost(), "host");
userinfo = getURLUserInfo(url);
int p = url.getPort();
port = p <= 0 ? DEFAULT_ELASTIC_PORT : p;
}
else if (protocol.startsWith("file")) {
t = DBType.file;
host = null;
userinfo = null;
port = -1;
if (!"".equals(url.getHost())) {
throw new IllegalArgumentException("Remote file URL not supported: " + url);
@ -146,7 +194,7 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
}
path = path.substring(1).strip();
}
path = checkURLField(path, "path");
path = urlDecode(checkURLField(path, "path"));
if (dbType == DBType.file) {
if (path.endsWith("/")) {
throw new IllegalArgumentException("Missing DB filepath in URL: " + url);
@ -162,6 +210,53 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
dbName = path;
}
private static String getURLUserInfo(URL url) {
String userinfo = url.getUserInfo();
if (userinfo == null) {
return null;
}
int pwSep = userinfo.indexOf(':');
String urlUserInfo;
if (pwSep >= 0) {
urlUserInfo = urlDecode(userinfo.substring(0, pwSep)) + ":" +
urlDecode(userinfo.substring(pwSep + 1));
}
else {
urlUserInfo = urlDecode(userinfo);
}
return cleanupUserInfo(urlUserInfo);
}
private static String cleanupUserInfo(String userinfo) {
if (StringUtils.isBlank(userinfo)) {
return null;
}
userinfo = userinfo.trim();
int pwdSep = userinfo.indexOf(':');
if (pwdSep == 0) {
throw new IllegalArgumentException("Invalid userinfo specified");
}
else if (pwdSep > 0 && (userinfo.length() - pwdSep) == 0) {
throw new IllegalArgumentException("Invalid userinfo specified");
}
return userinfo;
}
private static String cleanupFilename(String name) {
// transform dbName into acceptable H2 DB file path
String dbName = name.trim();
dbName = dbName.replace("\\", "/");
if ((!dbName.startsWith("/") && !isWindowsFilePath(dbName)) || dbName.endsWith("/")) {
throw new IllegalArgumentException("Invalid absolute file path: " + dbName);
}
if (!dbName.endsWith(H2_FILE_EXTENSION)) {
dbName += H2_FILE_EXTENSION;
}
return dbName;
}
private static String checkURLField(String val, String name) {
if (StringUtils.isEmpty(val)) {
throw new IllegalArgumentException("Invalid " + name + " in URL");
@ -199,29 +294,57 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
}
/**
* Return BSim server info in URL format
* Return BSim server info in URL format.
* Warning: If userinfo with password has been specified it will be returned in the URL.
* @return BSim server info in URL format
*/
public String toURLString() {
switch (dbType) {
case postgres:
return "postgresql://" + host + getPortString() + "/" + dbName;
return "postgresql://" + formatURLUserInfo() + host + getPortString() + "/" +
urlEncode(dbName);
case elastic:
return "https://" + host + getPortString() + "/" + dbName;
return "https://" + formatURLUserInfo() + host + getPortString() + "/" +
urlEncode(dbName);
case file: // h2:
return "file:" + dbName;
return "file:" + urlEncode(dbName);
}
throw new RuntimeException("Unsupported DBType: " + dbType);
}
private static String urlEncode(String text) {
return URLEncoder.encode(text, StandardCharsets.UTF_8);
}
private static String urlDecode(String text) {
return URLDecoder.decode(text, StandardCharsets.UTF_8);
}
private String formatURLUserInfo() {
if (userinfo == null) {
return "";
}
int pwSep = userinfo.indexOf(':');
String urlUserInfo;
if (pwSep >= 0) {
urlUserInfo = urlEncode(userinfo.substring(0, pwSep)) + ":" +
urlEncode(userinfo.substring(pwSep + 1));
}
else {
urlUserInfo = urlEncode(userinfo);
}
return urlUserInfo + "@";
}
private String getPortString() {
return port > 0 ? (":" + Integer.toString(port)) : "";
}
/**
* Return BSim server info in URL
* Return BSim server info in URL.
* Warning: If userinfo with password has been specified it will be returned in the URL.
* @return BSim server info in URL
* @throws MalformedURLException if unable to form supported URL
*/
@ -236,6 +359,58 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
return dbType;
}
public void setUserInfo(BasicDataSource bds) {
bds.setUsername(getUserName());
if (hasPassword()) {
bds.setPassword(userinfo.substring(userinfo.indexOf(':') + 1));
}
}
/**
* Determine if user information includes password.
* NOTE: Use of passwords with this object and URLs is discouraged.
* @return true if user information includes password which
*/
public boolean hasPassword() {
return userinfo != null && userinfo.contains(":");
}
/**
* Determine of user info was stipulated during construction
* @return true if user info was stipulated during construction
*/
public boolean hasDefaultLogin() {
return userinfo == null;
}
/**
* Get the remote database user name to be used when establishing a connection.
* User name obtained from the user information which was provided during instantiation.
* @return remote database user information (null for {@link DBType#file}).
*/
public String getUserName() {
if (dbType == DBType.file) {
return null;
}
if (userinfo == null) {
return ClientUtil.getUserName();
}
String username = userinfo;
int pwdSep = userinfo.indexOf(':');
if (pwdSep > 0) {
username = userinfo.substring(0, pwdSep);
}
return username;
}
/**
* Get the remote database user information to be used when establishing a connection.
* @return remote database user information (null for {@link DBType#file}).
*/
public String getUserInfo() {
return userinfo;
}
/**
* Get the server hostname or IP address as originally specified.
* @return hostname or IP address as originally specified
@ -282,7 +457,14 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
@Override
public int hashCode() {
// use dbType.ordinal; enum hashcodes vary from run to run
return Objects.hash(dbName, dbType.ordinal(), host, port);
int hashcode = Objects.hash(dbName, dbType.ordinal(), host, port);
// Due to the use of hashcode by BSimServerManager for persisting server entries
// we cannot change the hashing function above and must only incorporate inclusion
// of userinfo if it is specified.
if (userinfo != null) {
hashcode = 31 * hashcode + userinfo.hashCode();
}
return hashcode;
}
@Override
@ -293,7 +475,8 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
return false;
if (obj instanceof BSimServerInfo other) {
return Objects.equals(dbName, other.dbName) && dbType == other.dbType &&
Objects.equals(host, other.host) && port == other.port;
Objects.equals(userinfo, other.userinfo) && Objects.equals(host, other.host) &&
port == other.port;
}
return false;
}

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.protocol.*;
import ghidra.framework.Application;
import ghidra.program.model.data.DataUtilities;
import ghidra.util.Msg;
import ghidra.util.StringUtilities;
public interface FunctionDatabase extends AutoCloseable {
@ -85,11 +83,11 @@ public interface FunctionDatabase extends AutoCloseable {
}
}
public static class Error { // Error structure returned by getLastError
public static class BSimError { // Error structure returned by getLastError
public ErrorCategory category;
public String message;
public Error(ErrorCategory cat, String msg) {
public BSimError(ErrorCategory cat, String msg) {
category = cat;
message = msg;
}
@ -121,11 +119,10 @@ public interface FunctionDatabase extends AutoCloseable {
* Issue password change request to the server.
* The method {@link #isPasswordChangeAllowed()} must be invoked first to ensure that
* the user password may be changed.
* @param username to change
* @param newPassword is password data
* @return null if change was successful, or the error message
*/
public default String changePassword(String username, char[] newPassword) {
public default String changePassword(char[] newPassword) {
if (getStatus() != Status.Ready) {
return "Connection not established";
}
@ -134,7 +131,7 @@ public interface FunctionDatabase extends AutoCloseable {
}
PasswordChange passwordChange = new PasswordChange();
try {
passwordChange.username = username;
passwordChange.username = getUserName();
passwordChange.newPassword = newPassword;
ResponsePassword response = passwordChange.execute(this);
if (!response.changeSuccessful) {
@ -162,14 +159,6 @@ public interface FunctionDatabase extends AutoCloseable {
*/
public String getUserName();
/**
* Set a specific user name for connection. Must be called before connection is initialized.
* If this method is not called, connection will use user name of process
*
* @param userName the user name
*/
public void setUserName(String userName);
/**
* @return factory the database is using to create LSHVector objects
*/
@ -218,7 +207,7 @@ public interface FunctionDatabase extends AutoCloseable {
* If the last query failed to produce a response, use this method to recover the error message
* @return a String describing the error
*/
public Error getLastError();
public BSimError getLastError();
/**
* Send a query to the database. The response is returned as a QueryResponseRecord.

View file

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

View file

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

View file

@ -190,7 +190,8 @@ public final class PostgresFunctionDatabase
@Override
protected void generateRawDatabase() throws SQLException {
BSimServerInfo serverInfo = postgresDs.getServerInfo();
BSimServerInfo defaultServerInfo = new BSimServerInfo(DBType.postgres,
BSimServerInfo defaultServerInfo =
new BSimServerInfo(DBType.postgres, serverInfo.getUserInfo(),
serverInfo.getServerName(), serverInfo.getPort(), DEFAULT_DATABASE_NAME);
String createdbstring = "CREATE DATABASE \"" + serverInfo.getDBName() + '"';
BSimPostgresDataSource defaultDs =
@ -502,14 +503,6 @@ public final class PostgresFunctionDatabase
return postgresDs.getUserName();
}
@Override
public void setUserName(String userName) {
if (postgresDs.getStatus() == Status.Ready) {
throw new IllegalStateException("Connection has already been established");
}
postgresDs.setPreferredUserName(userName);
}
@Override
public QueryResponseRecord doQuery(BSimQuery<?> query, Connection c)
throws SQLException, LSHException, DatabaseNonFatalException {

View file

@ -31,8 +31,6 @@ public class ElasticConnection {
protected String hostURL; // http://hostname:port
protected String httpURLbase; // Main URL to elasticsearch
private HttpURLConnection connection = null;
private Writer writer;
private int lastResponseCode;
public ElasticConnection(String url, String repo) {
@ -41,61 +39,13 @@ public class ElasticConnection {
}
public void close() {
if (connection != null) {
connection.disconnect();
}
// nothing to do - http connections do not persist
}
public boolean lastRequestSuccessful() {
return (lastResponseCode >= 200) && (lastResponseCode < 300);
}
/**
* Start a new request to the elastic server. This establishes the OutputStream for writing the body of the request
* @param command is the type of command
* @param path is the overarching index/type/<command> path
* @throws IOException for problems with the socket
*/
public void startHttpRequest(String command, String path) throws IOException {
URL httpURL = new URL(httpURLbase + path);
connection = (HttpURLConnection) httpURL.openConnection();
connection.setRequestMethod(command);
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
writer = new OutputStreamWriter(connection.getOutputStream());
}
public void startHttpBulkRequest(String bulkCommand) throws IOException {
URL httpURL = new URL(hostURL + bulkCommand);
connection = (HttpURLConnection) httpURL.openConnection();
connection.setRequestMethod(POST);
connection.setRequestProperty("Content-Type", "application/x-ndjson");
connection.setDoOutput(true);
writer = new OutputStreamWriter(connection.getOutputStream());
}
public void startHttpRawRequest(String command, String path) throws IOException {
URL httpURL = new URL(hostURL + path);
connection = (HttpURLConnection) httpURL.openConnection();
connection.setRequestMethod(command);
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
writer = new OutputStreamWriter(connection.getOutputStream());
}
/**
* Start a request with no input body, URI only
* @param command is the command to issue
* @param path is the overarching request path: index/...
* @throws IOException for problems with the socket
*/
public void startHttpURICommand(String command, String path) throws IOException {
URL httpURL = new URL(httpURLbase + path);
connection = (HttpURLConnection) httpURL.openConnection();
connection.setRequestMethod(command);
connection.setDoOutput(true);
}
/**
* Assuming the writer has been closed and connection.getResponseCode() is called
* placing the value in lastResponseCode, read the response and parse into a JSONObject
@ -103,15 +53,21 @@ public class ElasticConnection {
* @throws IOException for problems with the socket
* @throws ParseException for JSON parse errors
*/
private JSONObject grabResponse() throws IOException, ParseException {
private JSONObject grabResponse(HttpURLConnection connection)
throws IOException, ParseException {
JSONParser parser = new JSONParser();
Reader reader;
InputStream in;
if (lastRequestSuccessful()) {
reader = new InputStreamReader(connection.getInputStream());
in = connection.getInputStream();
}
else {
reader = new InputStreamReader(connection.getErrorStream());
in = connection.getErrorStream();
}
if (in == null) {
// Connection error occurred
throw new IOException(connection.getResponseMessage());
}
Reader reader = new InputStreamReader(in);
JSONObject jsonObject = (JSONObject) parser.parse(reader);
return jsonObject;
}
@ -156,12 +112,18 @@ public class ElasticConnection {
*/
public JSONObject executeRawStatement(String command, String path, String body)
throws ElasticException {
HttpURLConnection connection = null;
try {
startHttpRawRequest(command, path);
URL httpURL = new URL(hostURL + path);
connection = (HttpURLConnection) httpURL.openConnection();
connection.setRequestMethod(command);
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
try (Writer writer = new OutputStreamWriter(connection.getOutputStream())) {
writer.write(body);
writer.close();
}
lastResponseCode = connection.getResponseCode();
JSONObject resp = grabResponse();
JSONObject resp = grabResponse(connection);
if (!lastRequestSuccessful()) {
throw new ElasticException(parseErrorJSON(resp));
}
@ -173,6 +135,11 @@ public class ElasticConnection {
catch (ParseException e) {
throw new ElasticException("Error parsing response: " + e.getMessage());
}
finally {
if (connection != null) {
connection.disconnect();
}
}
}
@ -185,12 +152,18 @@ public class ElasticConnection {
*/
public void executeStatementNoResponse(String command, String path, String body)
throws ElasticException {
HttpURLConnection connection = null;
try {
startHttpRequest(command, path);
URL httpURL = new URL(httpURLbase + path);
connection = (HttpURLConnection) httpURL.openConnection();
connection.setRequestMethod(command);
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
try (Writer writer = new OutputStreamWriter(connection.getOutputStream())) {
writer.write(body);
writer.close();
}
lastResponseCode = connection.getResponseCode();
JSONObject resp = grabResponse();
JSONObject resp = grabResponse(connection);
if (!lastRequestSuccessful()) {
throw new ElasticException(parseErrorJSON(resp));
}
@ -201,6 +174,11 @@ public class ElasticConnection {
catch (ParseException e) {
throw new ElasticException("Error parsing response: " + e.getMessage());
}
finally {
if (connection != null) {
connection.disconnect();
}
}
}
/**
@ -213,12 +191,18 @@ public class ElasticConnection {
*/
public JSONObject executeStatement(String command, String path, String body)
throws ElasticException {
HttpURLConnection connection = null;
try {
startHttpRequest(command, path);
URL httpURL = new URL(httpURLbase + path);
connection = (HttpURLConnection) httpURL.openConnection();
connection.setRequestMethod(command);
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
try (Writer writer = new OutputStreamWriter(connection.getOutputStream())) {
writer.write(body);
writer.close();
}
lastResponseCode = connection.getResponseCode();
JSONObject resp = grabResponse();
JSONObject resp = grabResponse(connection);
if (!lastRequestSuccessful()) {
throw new ElasticException(parseErrorJSON(resp));
}
@ -230,6 +214,11 @@ public class ElasticConnection {
catch (ParseException e) {
throw new ElasticException("Error parsing response: " + e.getMessage());
}
finally {
if (connection != null) {
connection.disconnect();
}
}
}
/**
@ -243,12 +232,18 @@ public class ElasticConnection {
*/
public JSONObject executeStatementExpectFailure(String command, String path, String body)
throws ElasticException {
HttpURLConnection connection = null;
try {
startHttpRequest(command, path);
URL httpURL = new URL(httpURLbase + path);
connection = (HttpURLConnection) httpURL.openConnection();
connection.setRequestMethod(command);
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
try (Writer writer = new OutputStreamWriter(connection.getOutputStream())) {
writer.write(body);
writer.close();
}
lastResponseCode = connection.getResponseCode();
JSONObject resp = grabResponse();
JSONObject resp = grabResponse(connection);
return resp;
}
catch (IOException e) {
@ -257,6 +252,11 @@ public class ElasticConnection {
catch (ParseException e) {
throw new ElasticException("Error parsing response: " + e.getMessage());
}
finally {
if (connection != null) {
connection.disconnect();
}
}
}
/**
@ -268,12 +268,18 @@ public class ElasticConnection {
* @throws ElasticException for any problems with the connection
*/
public JSONObject executeBulk(String path, String body) throws ElasticException {
HttpURLConnection connection = null;
try {
startHttpBulkRequest(path);
URL httpURL = new URL(hostURL + path);
connection = (HttpURLConnection) httpURL.openConnection();
connection.setRequestMethod(POST);
connection.setRequestProperty("Content-Type", "application/x-ndjson");
connection.setDoOutput(true);
try (Writer writer = new OutputStreamWriter(connection.getOutputStream())) {
writer.write(body);
writer.close();
}
lastResponseCode = connection.getResponseCode();
JSONObject resp = grabResponse();
JSONObject resp = grabResponse(connection);
if (!lastRequestSuccessful()) {
throw new ElasticException(parseErrorJSON(resp));
}
@ -285,13 +291,22 @@ public class ElasticConnection {
catch (ParseException e) {
throw new ElasticException("Error parsing response: " + e.getMessage());
}
finally {
if (connection != null) {
connection.disconnect();
}
}
}
public JSONObject executeURIOnly(String command, String path) throws ElasticException {
HttpURLConnection connection = null;
try {
startHttpURICommand(command, path);
URL httpURL = new URL(httpURLbase + path);
connection = (HttpURLConnection) httpURL.openConnection();
connection.setRequestMethod(command);
connection.setDoOutput(true);
lastResponseCode = connection.getResponseCode();
JSONObject resp = grabResponse();
JSONObject resp = grabResponse(connection);
if (!lastRequestSuccessful()) {
throw new ElasticException(parseErrorJSON(resp));
}
@ -303,5 +318,10 @@ public class ElasticConnection {
catch (ParseException e) {
throw new ElasticException("Error parsing response: " + e.getMessage());
}
finally {
if (connection != null) {
connection.disconnect();
}
}
}
}

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
private ElasticConnection connection; // Low-level connection to the database
private String userName = null; // User name for server authentication
private ConnectionType connectionType = ConnectionType.Unencrypted_No_Authentication;
private DatabaseInformation info; // Information about the active database
private Base64VectorFactory vectorFactory; // factory used to create BSim feature vectors
private final BSimServerInfo serverInfo; // NOTE: does not reflect the use of http vs https
private final String baseURL; // Base URL for connecting to elasticsearch, i.e. http://hostname:9200
private final String repository; // Name of the repository, prefix to all elasticsearch indices
private Error lastError; // Info on error caused by last action taken on this interface (null if no error)
private BSimError lastError; // Info on error caused by last action taken on this interface (null if no error)
private Status status; // status of the connection
private boolean initialized; // true if the connection has been successfully initialized
@ -2022,8 +2021,8 @@ public class ElasticDatabase implements FunctionDatabase {
throw new MalformedURLException("URL path must indicate the repository only");
}
repository = path.substring(1);
this.serverInfo =
new BSimServerInfo(DBType.elastic, baseURL.getHost(), baseURL.getPort(), repository);
this.serverInfo = new BSimServerInfo(DBType.elastic, null, baseURL.getHost(),
baseURL.getPort(), repository);
this.baseURL = fullURL.substring(0, fullURL.length() - path.length());
lastError = null;
@ -2398,15 +2397,7 @@ public class ElasticDatabase implements FunctionDatabase {
@Override
public String getUserName() {
if (userName != null) {
return userName;
}
return ClientUtil.getUserName();
}
@Override
public void setUserName(String userName) {
this.userName = userName;
return serverInfo.getUserName();
}
@Override
@ -2450,14 +2441,14 @@ public class ElasticDatabase implements FunctionDatabase {
vectorFactory.set(config.weightfactory, config.idflookup, config.info.settings);
}
catch (ElasticException err) {
lastError = new Error(ErrorCategory.Initialization,
lastError = new BSimError(ErrorCategory.Initialization,
"Database error on initialization: " + err.getMessage());
status = Status.Error;
return false;
}
catch (NoDatabaseException err) {
info = null;
lastError = new Error(ErrorCategory.Nodatabase,
lastError = new BSimError(ErrorCategory.Nodatabase,
"Database has not been created yet: " + err.getMessage());
initialized = true;
status = Status.Ready;
@ -2480,14 +2471,14 @@ public class ElasticDatabase implements FunctionDatabase {
}
@Override
public Error getLastError() {
public BSimError getLastError() {
return lastError;
}
@Override
public QueryResponseRecord query(BSimQuery<?> query) {
if ((!isInitialized()) && (!(query instanceof CreateDatabase))) {
lastError = new Error(ErrorCategory.Nodatabase, "The database does not exist");
lastError = new BSimError(ErrorCategory.Nodatabase, "The database does not exist");
return null;
}
lastError = null;
@ -2554,22 +2545,22 @@ public class ElasticDatabase implements FunctionDatabase {
fdbPasswordChange((PasswordChange) query);
}
else {
lastError = new Error(ErrorCategory.Fatal, "Unknown query type");
lastError = new BSimError(ErrorCategory.Fatal, "Unknown query type");
query.clearResponse();
}
}
catch (DatabaseNonFatalException err) {
lastError = new Error(ErrorCategory.Nonfatal,
lastError = new BSimError(ErrorCategory.Nonfatal,
"Skipping -" + query.getName() + "- : " + err.getMessage());
query.clearResponse();
}
catch (LSHException err) {
lastError = new Error(ErrorCategory.Fatal,
lastError = new BSimError(ErrorCategory.Fatal,
"Fatal error during -" + query.getName() + "- : " + err.getMessage());
query.clearResponse();
}
catch (ElasticException err) {
lastError = new Error(ErrorCategory.Fatal,
lastError = new BSimError(ErrorCategory.Fatal,
"Elastic error during -" + query.getName() + "- : " + err.getMessage());
query.clearResponse();
}

View file

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

View file

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

View file

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

View file

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

View file

@ -178,7 +178,7 @@ public class BSimSearchPluginTest extends AbstractBSimPluginTest {
private FunctionDatabase database;
public TestBSimServerInfo(FunctionDatabase database) {
super(DBType.postgres, "0.0.0.0", 123, "testDB");
super(DBType.postgres, null, "0.0.0.0", 123, "testDB");
this.database = database;
}

View file

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

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

View file

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

View file

@ -23,7 +23,7 @@ public class TestBSimServerInfo extends BSimServerInfo {
private FunctionDatabase database;
public TestBSimServerInfo(FunctionDatabase database) {
super(DBType.postgres, "100.50.123.5", 123, "testDB");
super(DBType.postgres, null, "100.50.123.5", 123, "testDB");
this.database = database;
}

View file

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

View file

@ -16,11 +16,12 @@
package ghidra.framework.client;
import java.awt.Component;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.*;
import javax.security.auth.callback.*;
import org.apache.commons.lang3.StringUtils;
import docking.DockingWindowManager;
import docking.widgets.*;
import ghidra.framework.preferences.Preferences;
@ -35,23 +36,63 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider
private Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
Msg.debug(this, "PasswordAuthentication requested for " + getRequestingURL());
NameCallback nameCb = null;
if (!"NO_NAME".equals(getRequestingScheme())) {
nameCb = new NameCallback("Name: ", ClientUtil.getUserName());
String serverName = getRequestingHost();
URL requestingURL = getRequestingURL();
String pwd = null;
String userName = ClientUtil.getUserName();
boolean useDefaultUser = true;
if (requestingURL != null) {
String userInfo = requestingURL.getUserInfo();
if (userInfo != null) {
// Use user info from URL
int pwdSep = userInfo.indexOf(':');
if (pwdSep < 0) {
userName = userInfo;
useDefaultUser = false;
}
else {
pwd = userInfo.substring(pwdSep + 1);
if (pwdSep != 0) {
userName = userInfo.substring(0, pwdSep);
useDefaultUser = false;
}
}
}
URL minimalURL = DefaultClientAuthenticator.getMinimalURL(requestingURL);
if (minimalURL != null) {
serverName = minimalURL.toExternalForm();
}
}
Msg.debug(this, "PasswordAuthentication requested for " + serverName);
if (pwd != null) {
// Requesting URL specified password
return new PasswordAuthentication(userName, pwd.toCharArray());
}
NameCallback nameCb = new NameCallback("Name: ", userName);
if (!useDefaultUser) {
// Prevent modification of user name by password prompting
nameCb.setName(userName);
}
// Prompt for password
String prompt = getRequestingPrompt();
if (prompt == null) {
prompt = "Password:";
if (StringUtils.isBlank(prompt) || "security".equals(prompt)) {
prompt = "Password:"; // assume dialog will show user name via nameCb
}
PasswordCallback passCb = new PasswordCallback(prompt, false);
try {
ServerPasswordPrompt pp = new ServerPasswordPrompt("Connection Authentication",
"Server", getRequestingHost(), nameCb, passCb, null, null, null);
"Server", serverName, nameCb, passCb, null, null, null);
SystemUtilities.runSwingNow(pp);
if (pp.okWasPressed()) {
return new PasswordAuthentication(nameCb != null ? nameCb.getName() : null,
passCb.getPassword());
return new PasswordAuthentication(nameCb.getName(), passCb.getPassword());
}
}
finally {
@ -61,6 +102,21 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider
}
};
/**
* Produce minimal URL (i.e., protocol, host and port)
* @param url request URL
* @return minimal URL
*/
public static URL getMinimalURL(URL url) {
try {
return new URL(url, "/");
}
catch (MalformedURLException e) {
// ignore
}
return null;
}
@Override
public Authenticator getAuthenticator() {
return authenticator;
@ -165,10 +221,25 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider
choicePrompt = choiceCb.getPrompt();
choices = choiceCb.getChoices();
}
PasswordDialog pwdDialog =
new PasswordDialog(title, serverType, serverName, passCb.getPrompt(),
nameCb != null ? nameCb.getPrompt() : null, getDefaultUserName(), choicePrompt,
choices, getDefaultChoice(), anonymousCb != null);
String defaultUserName = null;
String namePrompt = null;
if (nameCb != null) {
defaultUserName = nameCb.getName();
if (defaultUserName == null) {
// Name entry only permitted with name callback where name has not be pre-set
defaultUserName = nameCb.getDefaultName();
namePrompt = nameCb.getPrompt();
}
}
if (defaultUserName == null) {
defaultUserName = getDefaultUserName();
}
PasswordDialog pwdDialog = new PasswordDialog(title, serverType, serverName,
passCb.getPrompt(), namePrompt, defaultUserName, choicePrompt, choices,
getDefaultChoice(), anonymousCb != null);
if (errorMsg != null) {
pwdDialog.setErrorText(errorMsg);
}

View file

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

View file

@ -687,6 +687,10 @@ public class GhidraURL {
if (StringUtils.isBlank(host)) {
throw new IllegalArgumentException("host required");
}
// TODO: Need to improve checks and use of URL encoding
if (host.indexOf('@') >= 0) { // prevent user info with hostname
throw new IllegalArgumentException("invalid host name");
}
if (StringUtils.isBlank(repositoryName)) {
throw new IllegalArgumentException("repository name required");
}

View file

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