diff --git a/Ghidra/Features/BSim/ghidra_scripts/AddProgramToH2BSimDatabaseScript.java b/Ghidra/Features/BSim/ghidra_scripts/AddProgramToH2BSimDatabaseScript.java
index adbe50e621..de1238652f 100644
--- a/Ghidra/Features/BSim/ghidra_scripts/AddProgramToH2BSimDatabaseScript.java
+++ b/Ghidra/Features/BSim/ghidra_scripts/AddProgramToH2BSimDatabaseScript.java
@@ -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",
diff --git a/Ghidra/Features/BSim/ghidra_scripts/CreateH2BSimDatabaseScript.java b/Ghidra/Features/BSim/ghidra_scripts/CreateH2BSimDatabaseScript.java
index f30b1c65d5..1d8763bd5d 100644
--- a/Ghidra/Features/BSim/ghidra_scripts/CreateH2BSimDatabaseScript.java
+++ b/Ghidra/Features/BSim/ghidra_scripts/CreateH2BSimDatabaseScript.java
@@ -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 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);
}
}
diff --git a/Ghidra/Features/BSim/ghidra_scripts/QueryWithFiltersScript.java b/Ghidra/Features/BSim/ghidra_scripts/QueryWithFiltersScript.java
index 73d6f5a103..4a474b506e 100755
--- a/Ghidra/Features/BSim/ghidra_scripts/QueryWithFiltersScript.java
+++ b/Ghidra/Features/BSim/ghidra_scripts/QueryWithFiltersScript.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -130,7 +130,7 @@ public class QueryWithFiltersScript extends GhidraScript {
String dbUrl =
askString("", "Enter the URL of the BSim database:", "ghidra://localhost/bsimDb");
queryService.initializeDatabase(dbUrl);
- FunctionDatabase.Error error = queryService.getLastError();
+ FunctionDatabase.BSimError error = queryService.getLastError();
if (error != null && error.category == ErrorCategory.Nodatabase) {
println("Database [" + dbUrl + "] cannot be found (does it exist?)");
return;
diff --git a/Ghidra/Features/BSim/src/main/help/help/topics/BSim/CommandLineReference.html b/Ghidra/Features/BSim/src/main/help/help/topics/BSim/CommandLineReference.html
index a3de12661f..57cfba217f 100644
--- a/Ghidra/Features/BSim/src/main/help/help/topics/BSim/CommandLineReference.html
+++ b/Ghidra/Features/BSim/src/main/help/help/topics/BSim/CommandLineReference.html
@@ -326,7 +326,12 @@
Creates a new empty repository. A URL and configuration template (config_template) is required. The new database name
- is taken from the path element of the URL.
+ is taken from the path element of the URL. See “Creating a
+ Database” for more details and discussion on configuration template use.
+ See “Creating Database Templates“
+ if a standard template will not suffice.
Supported configuration templates (config_template) are defined within the Ghidra
@@ -792,21 +797,21 @@
PostgreSQL |
postgresql://<hostname>[:<port>]/<dbname> |
+ "computeroutput">postgresql://[<username>@]<hostname>[:<port>]/<dbname>
Elasticsearch |
https://<hostname>[:<port>]/<dbname> |
+ "computeroutput">https://[<username>@]<hostname>[:<port>]/<dbname>
Elasticsearch |
elastic://<hostname>[:<port>]/<dbname> |
+ "computeroutput">elastic://[<username>@]<hostname>[:<port>]/<dbname>
@@ -819,6 +824,24 @@
The use of the https and elastic is equivalent.
+
+
The inclusion of a
+ <username>
within a BSim URL
+ supercedes the concurrent use of the --user
+ option which can still be used to control login to the Ghidra Server. When a
+ <username>
has been specified within a BSim URL
+ a <password>
may be included if neccessary,
+ albeit highly discouraged (e.g.,
+ postgresql://username:password@hostname/dbname
).
+ The password is appended to the username with a colon (':') separator and has limitations
+ on the characters which may be used.
+
+
Inclusion of a user
+ <password>
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.
For local file URLs, the absolute path the H2 database *.mv.db file
must be specified without the *.mv.db extension. Only the '/' character should be
diff --git a/Ghidra/Features/BSim/src/main/help/help/topics/BSim/DatabaseConfiguration.html b/Ghidra/Features/BSim/src/main/help/help/topics/BSim/DatabaseConfiguration.html
index e9826369c5..e870355987 100644
--- a/Ghidra/Features/BSim/src/main/help/help/topics/BSim/DatabaseConfiguration.html
+++ b/Ghidra/Features/BSim/src/main/help/help/topics/BSim/DatabaseConfiguration.html
@@ -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.
+
+
+
+
+
+
PostgreSQL Firewall Considerations
+
+
+
+
+
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.
+
+
Firewall configurations are beyond the scope of this document, however for simple
+ single-node installations on Linux the firewall-cmd
+ 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.
+
+
+ sudo firewall-cmd --permanent --add-port=5432/tcp && sudo firewall-cmd --reload
+
+
+
NOTE: The above Linux firewall command assumes the firewalld
+ package has been installed on the system.
+
+
@@ -562,13 +590,22 @@
In order to make use of Elasticsearch with BSim, the database administrator must
install the lsh.zip plug-in as part of the
- Elasticsearch deployment. The plug-in is available in the Ghidra add-on named BSimElasticPlugin, which unpacks into a standard
- Ghidra installation. The file lsh.zip 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 BSimElasticPlugin
+ (<ghidra-install-dir>/Extensions/Ghidra/ghidra_11.2.1_U_20241105_BSimElasticPlugin.zip
).
+ 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
+ lsh.zip 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.
+
+
Refer to the README.md
+ file within the unpacked extension for important plugin details. The plugin file
+ stipulates a specific elasticsearch version and may require an adjustment and repack.
Assuming the add-on has been unpacked, the plug-in can be installed to a single node
using the elasticsearch-plugin command in the
@@ -579,7 +616,7 @@
bin/elasticsearch-plugin install
- file:///path/to/ghidra/Ghidra/contrib/BSimElasticPlugin/data/lsh.zip |
+ file:///path/to/ghidra/extension/BSimElasticPlugin/data/lsh.zip
@@ -610,7 +647,7 @@
- The open Elasticsearch distribution starts with up with password authentication
+
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
elastic user is created with a randomly
generated password that is reported, once, to the console. For a toy deployment, it may
@@ -665,6 +702,34 @@ curl -k -u elastic:XXXXXX -X POST "https://localhost:9200/_security/user/ghidrau
class="xref" href="CommandLineReference.html#URLs">“Ghidra and BSim
URLs” for additional information about URLs.
+
+
+
+
+
+
Elasticsearch Firewall Considerations
+
+
+
+
+
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.
+
+
Firewall configurations are beyond the scope of this document, however for simple
+ single-node installations on Linux the firewall-cmd
+ 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.
+
+
+ sudo firewall-cmd --permanent --add-port=9200/tcp && sudo firewall-cmd --reload
+
+
+
NOTE: The above Linux firewall command assumes the firewalld
+ package has been installed on the system.
+
+
@@ -672,8 +737,7 @@ curl -k -u elastic:XXXXXX -X POST "https://localhost:9200/_security/user/ghidrau
-
Creating a
- Database
+
Creating a Database
diff --git a/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/BSimSearch.html b/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/BSimSearch.html
index ffecfeb75d..9772c21b96 100644
--- a/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/BSimSearch.html
+++ b/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/BSimSearch.html
@@ -53,9 +53,23 @@
- 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.
+ The dialog displays a table showing all the currently defined BSim databases/servers.
+ Table columns displayed include:
+
+
+ - Name - Indicates the name of the BSim database. In the cases of an H2 file
+ database this represents the name of the file (hovering on this will show the full absolute path).
+ - Type - The type of database (postgres, elastic or file).
+ - Host - The network host name or IP address for a postgres or
+ elastic database.
+ - Port - The TCP port for a postgres or elastic database.
+ - User - The non-default user name to be used when connecting to a postgres or
+ elastic database. When actively connected to a postgres database
+ the actual name used for authentication will be shown.
+ - Active/Idle Connections - The active/idle database connections for the
+ connection pool associated with a postgres or file database.
+ This field will be blank for these databases when in a disconnected state.
+
There are four primary actions for this dialog:
@@ -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.
- 
- Connect or disconnect an inactive database/server connection. This action is not supported
+ 
Connect
+ or disconnect an inactive database/server connection which has been selected. This action is not supported
by Elastic database servers.
Change password - A change password
@@ -91,9 +105,11 @@
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.
+ needs to be specified. For postgres and elastic, 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 file database,
+ the "..." button is used to launch a filechooser for selecting an existing H2 database file
+ (*.mv.db).
diff --git a/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/images/AddServerDialog.png b/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/images/AddServerDialog.png
index 1210541490..0594435747 100644
Binary files a/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/images/AddServerDialog.png and b/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/images/AddServerDialog.png differ
diff --git a/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/images/ManageServersDialog.png b/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/images/ManageServersDialog.png
index 59f713dc8c..10c432c4e0 100644
Binary files a/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/images/ManageServersDialog.png and b/Ghidra/Features/BSim/src/main/help/help/topics/BSimSearchPlugin/images/ManageServersDialog.png differ
diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/BSimServerManager.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/BSimServerManager.java
index df21cbbbfb..387c682b88 100644
--- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/BSimServerManager.java
+++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/BSimServerManager.java
@@ -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());
diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerDialog.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerDialog.java
index bec9aeee2e..71098dabf3 100644
--- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerDialog.java
+++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerDialog.java
@@ -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");
diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerTableModel.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerTableModel.java
index 8bed9cc704..d5bace2917 100644
--- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerTableModel.java
+++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/BSimServerTableModel.java
@@ -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 {
+
+ @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 {
@@ -176,7 +217,6 @@ public class BSimServerTableModel extends GDynamicColumnTableModel checkForValidDialog());
+ }
+
+ private static final String HOSTNAME_IP_REGEX =
+ "^[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*(\\.[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*)*$";
+ private static final Pattern HOSTNAME_IP_PATTERN = Pattern.compile(HOSTNAME_IP_REGEX);
+
+ private void createHostField() {
+
+ hostField = new GFormattedTextField(FORMATTER_FACTORY, "");
+ hostField.setName("Host");
+ hostField.setText("");
+ hostField.setDefaultValue("");
+ hostField.setIsError(true);
+ hostField.setEditable(true);
+
+ hostField.setInputVerifier(new InputVerifier() {
+ @Override
+ public boolean verify(JComponent input) {
+ setStatus("");
+ String hostname = hostField.getText().trim();
+ if (hostname.length() == 0) {
+ setStatus("");
+ return false;
+ }
+ Matcher hostMatch = HOSTNAME_IP_PATTERN.matcher(hostname);
+ if (!hostMatch.matches()) {
+ setStatus("Unsupported host name or IP address");
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean shouldYieldFocus(JComponent source, JComponent target) {
+ return true;
+ }
+ });
+
+ hostField.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+ e.consume();
+ hostField.setText("");
+ hostField.setDefaultValue("");
+ hostField.setIsError(true);
+ }
+ checkForValidDialog();
+ }
+ });
+
+ hostField.addTextEntryStatusListener(f -> checkForValidDialog());
+ }
+
+ // NOTE: Username pattern based on PostgreSQL restrictions
+ private static final String USERNAME_REGEX = "^[a-zA-Z_][a-zA-Z0-9_$]*$";
+ private static final Pattern USERNAME_PATTERN = Pattern.compile(USERNAME_REGEX);
+
+ private void createUserField() {
+
+ userField = new GFormattedTextField(FORMATTER_FACTORY, "");
+ userField.setName("User");
+ userField.setText("");
+ userField.setDefaultValue("");
+ userField.setEditable(true);
+
+ userField.setInputVerifier(new InputVerifier() {
+ @Override
+ public boolean verify(JComponent input) {
+ setStatus("");
+ String username = userField.getText().trim();
+ if (username.length() == 0) {
+ setStatus("");
+ return true;
+ }
+ Matcher userMatch = USERNAME_PATTERN.matcher(username);
+ if (!userMatch.matches()) {
+ setStatus("Unsupported database user name");
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean shouldYieldFocus(JComponent source, JComponent target) {
+ return true;
+ }
+ });
+
+ userField.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
+ e.consume();
+ userField.setText("");
+ userField.setDefaultValue("");
+ userField.setIsError(false);
+ }
+ checkForValidDialog();
+ }
+ });
+
+ userField.addTextEntryStatusListener(f -> checkForValidDialog());
+ }
+
@Override
BSimServerInfo getServerInfo() {
+ if (nameField.getTextEntryStatus() == Status.INVALID ||
+ userField.getTextEntryStatus() == Status.INVALID ||
+ hostField.getTextEntryStatus() == Status.INVALID) {
+ return null;
+ }
+
+ String user = userField.getText().trim();
+ if (ClientUtil.getUserName().equals(user)) {
+ user = null;
+ }
+
String name = nameField.getText().trim();
String host = hostField.getText().trim();
+
int port = getPort(portField.getText().trim());
if (name.isBlank() || host.isBlank() || port < 0) {
return null;
}
- return new BSimServerInfo(type, host, port, name);
+
+ return new BSimServerInfo(type, user, host, port, name);
}
}
@@ -291,7 +488,7 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
if (file.isDirectory()) {
return null;
}
- return new BSimServerInfo(DBType.file, null, -1, path);
+ return new BSimServerInfo(path);
}
}
@@ -303,24 +500,24 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
public NotifyingTextField(String initialText) {
super(20);
setText(initialText);
- getDocument().addDocumentListener(new DocumentListener() {
+ getDocument().addDocumentListener(new MyFieldListener());
+ }
+ }
- @Override
- public void insertUpdate(DocumentEvent e) {
- checkForValidDialog();
- }
+ class MyFieldListener implements DocumentListener {
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ checkForValidDialog();
+ }
- @Override
- public void removeUpdate(DocumentEvent e) {
- checkForValidDialog();
- }
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ checkForValidDialog();
+ }
- @Override
- public void changedUpdate(DocumentEvent e) {
- checkForValidDialog();
- }
-
- });
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ checkForValidDialog();
}
}
diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java
index bd3ac7176c..cf912e746a 100644
--- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java
+++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java
@@ -25,7 +25,6 @@ import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import org.apache.commons.dbcp2.BasicDataSource;
-import org.apache.commons.lang3.StringUtils;
import ghidra.features.bsim.query.BSimServerInfo.DBType;
import ghidra.features.bsim.query.FunctionDatabase.ConnectionType;
@@ -283,10 +282,12 @@ public class BSimPostgresDBConnectionManager {
*/
private Connection connect() throws SQLException, CancelledException {
- String userName = bds.getUsername();
- bds.setUsername(StringUtils.isBlank(userName) ? ClientUtil.getUserName() : userName);
- bds.setPassword(null);
- connectionType = ConnectionType.SSL_No_Authentication;
+ String loginError = null;
+
+ serverInfo.setUserInfo(bds);
+
+ connectionType = serverInfo.hasPassword() ? ConnectionType.SSL_Password_Authentication
+ : ConnectionType.SSL_No_Authentication;
try {
// Specify SSL connection properties
setSSLProperties();
@@ -299,6 +300,10 @@ public class BSimPostgresDBConnectionManager {
if (e.getMessage().contains("password-based authentication") ||
e.getMessage().contains("SCRAM-based") ||
e.getMessage().contains("password authentication failed")) {
+ if (serverInfo.hasPassword()) {
+ loginError = "Access denied: " + serverInfo;
+ Msg.error(this, loginError);
+ }
// Use Ghidra's authentication infrastructure
connectionType = ConnectionType.SSL_Password_Authentication; // Try again with a password
// fallthru to second attempt at getConnection
@@ -319,7 +324,6 @@ public class BSimPostgresDBConnectionManager {
" idle=" + bds.getNumIdle());
}
- String loginError = null;
while (true) {
ClientAuthenticator clientAuthenticator = null;
if (connectionType == ConnectionType.SSL_Password_Authentication) {
@@ -327,9 +331,11 @@ public class BSimPostgresDBConnectionManager {
if (clientAuthenticator == null) { // Make sure authenticator is registered
throw new SQLException("No registered authenticator");
}
- NameCallback nameCb = new NameCallback("User ID:");
- nameCb.setName(bds.getUsername());
- PasswordCallback passCb = new PasswordCallback("Password:", false);
+ NameCallback nameCb = new NameCallback("User ID:", bds.getUsername());
+ if (!serverInfo.hasDefaultLogin()) {
+ nameCb.setName(bds.getUsername());
+ }
+ PasswordCallback passCb = new PasswordCallback(" ", false); // force use of default prompting
try {
if (!clientAuthenticator.processPasswordCallbacks(
"BSim Database Authentication", "BSim Database Server",
@@ -338,9 +344,8 @@ public class BSimPostgresDBConnectionManager {
}
bds.setPassword(new String(passCb.getPassword()));
// User may have specified new username, or this may return NULL
- userName = nameCb.getName();
- if (!StringUtils.isBlank(userName)) {
- bds.setUsername(userName);
+ if (serverInfo.hasDefaultLogin()) {
+ bds.setUsername(nameCb.getName());
}
}
finally {
diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimServerInfo.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimServerInfo.java
index 2fe9cfbc74..05ccc40925 100644
--- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimServerInfo.java
+++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimServerInfo.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -16,12 +16,15 @@
package ghidra.features.bsim.query;
import java.io.Closeable;
-import java.net.MalformedURLException;
-import java.net.URL;
+import java.net.*;
+import java.nio.charset.StandardCharsets;
import java.util.Objects;
+import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.lang3.StringUtils;
+import ghidra.framework.client.ClientUtil;
+
public class BSimServerInfo implements Comparable {
/**
@@ -48,6 +51,7 @@ public class BSimServerInfo implements Comparable {
}
private final DBType dbType;
+ private final String userinfo; // username[:password]
private final String host;
private final int port;
private final String dbName;
@@ -56,6 +60,62 @@ public class BSimServerInfo implements Comparable {
/**
* Construct a new {@link BSimServerInfo} object
+ *
+ * @param dbType BSim DB type
+ * @param userinfo connection user info, {@code username[:password]} (ignored for {@link DBType#file}).
+ * If blank, {@link ClientUtil#getUserName()} is used.
+ * @param host host name (ignored for {@link DBType#file})
+ * @param port port number (ignored for {@link DBType#file})
+ * @param dbName name of database (simple database name except for {@link DBType#file}
+ * which should reflect an absolute file path. On Windows OS the path may start with a
+ * drive letter.
+ * @throws IllegalArgumentException if invalid arguments are specified
+ */
+ public BSimServerInfo(DBType dbType, String userinfo, String host, int port, String dbName) {
+ Objects.requireNonNull(dbType, "DBType must be specified");
+ this.dbType = dbType;
+
+ if ((dbType == DBType.postgres || dbType == DBType.elastic) && StringUtils.isEmpty(host)) {
+ throw new IllegalArgumentException("host required");
+ }
+
+ dbName = dbName.trim();
+ if (StringUtils.isEmpty(dbName)) {
+ throw new IllegalArgumentException("Non-empty dbName required");
+ }
+
+ if (dbType == DBType.file) {
+ host = null;
+ port = -1;
+ userinfo = null;
+ dbName = cleanupFilename(dbName);
+ }
+ else {
+ if (dbName.contains("/") || dbName.contains("\\")) { // may want additional validation
+ throw new IllegalArgumentException("Invalid " + dbType + " dbName: " + dbName);
+ }
+ userinfo = cleanupUserInfo(userinfo);
+ if (port <= 0) {
+ port = -1;
+ }
+ if (dbType == DBType.postgres && port <= 0) {
+ port = DEFAULT_POSTGRES_PORT;
+ }
+ if (dbType == DBType.elastic && port <= 0) {
+ port = DEFAULT_ELASTIC_PORT;
+ }
+ }
+
+ this.userinfo = userinfo;
+ this.host = host;
+ this.port = port;
+ this.dbName = dbName;
+ }
+
+ /**
+ * Construct a new {@link BSimServerInfo} object. For non-file database the user's defaut
+ * username is used (see {@link ClientUtil#getUserName()}).
+ *
* @param dbType BSim DB type
* @param host host name (ignored for {@link DBType#file})
* @param port port number (ignored for {@link DBType#file})
@@ -65,49 +125,34 @@ public class BSimServerInfo implements Comparable {
* @throws IllegalArgumentException if invalid arguments are specified
*/
public BSimServerInfo(DBType dbType, String host, int port, String dbName) {
- Objects.requireNonNull(dbType, "DBType must be specified");
- this.dbType = dbType;
-
- if ((dbType == DBType.postgres || dbType == DBType.elastic) && StringUtils.isEmpty(host)) {
- throw new IllegalArgumentException("host required");
- }
- this.host = host;
-
- if (port <= 0) {
- port = -1;
- }
- if (dbType == DBType.postgres && port <= 0) {
- port = DEFAULT_POSTGRES_PORT;
- }
- if (dbType == DBType.elastic && port <= 0) {
- port = DEFAULT_ELASTIC_PORT;
- }
- this.port = port;
+ this(dbType, null, host, port, dbName);
+ }
+ /**
+ * Construct a new {@link BSimServerInfo} object for a {@link DBType#file} type database.
+ *
+ * @param dbName name of database which should reflect an absolute file path.
+ * On Windows OS the path may start with a drive letter.
+ * @throws IllegalArgumentException if invalid arguments are specified
+ */
+ public BSimServerInfo(String dbName) {
+ dbType = DBType.file;
+ userinfo = null;
+ host = null;
+ port = -1;
dbName = dbName.trim();
if (StringUtils.isEmpty(dbName)) {
throw new IllegalArgumentException("Non-empty dbName required");
}
- if (dbType == DBType.file) {
- // transform dbName into acceptable H2 DB file path
- dbName = dbName.replace("\\", "/");
- if ((!dbName.startsWith("/") && !isWindowsFilePath(dbName)) || dbName.endsWith("/")) {
- throw new IllegalArgumentException("Invalid absolute file path: " + dbName);
- }
- if (!dbName.endsWith(H2_FILE_EXTENSION)) {
- dbName += H2_FILE_EXTENSION;
- }
- }
- else if (dbName.contains("/") || dbName.contains("\\")) { // may want additional validation
- throw new IllegalArgumentException("Invalid " + dbType + " dbName: " + dbName);
- }
- this.dbName = dbName;
+ this.dbName = cleanupFilename(dbName);
}
/**
* Construct a new {@link BSimServerInfo} object from a suitable database URL
* (i.e., {@code postgresql:}, {@code https:}, {@code elastic:}, {@code file:}).
- * @param url supported BSim database URL
+ *
+ * @param url supported BSim database URL. For non-file URLs, the hostname or
+ * address may be preceeded by a DB username (e.g., postgresql://user@host:port/dbname
* @throws IllegalArgumentException if unsupported URL protocol specified
*/
public BSimServerInfo(URL url) throws IllegalArgumentException {
@@ -118,18 +163,21 @@ public class BSimServerInfo implements Comparable {
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 {
}
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 {
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 {
}
/**
- * 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 {
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 {
@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 {
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;
}
diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/FunctionDatabase.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/FunctionDatabase.java
index 6998da5447..cfd2430352 100755
--- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/FunctionDatabase.java
+++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/FunctionDatabase.java
@@ -33,9 +33,7 @@ import ghidra.features.bsim.query.facade.SFOverviewInfo;
import ghidra.features.bsim.query.facade.SFQueryInfo;
import ghidra.features.bsim.query.protocol.*;
import ghidra.framework.Application;
-import ghidra.program.model.data.DataUtilities;
import ghidra.util.Msg;
-import ghidra.util.StringUtilities;
public interface FunctionDatabase extends AutoCloseable {
@@ -85,11 +83,11 @@ public interface FunctionDatabase extends AutoCloseable {
}
}
- public static class Error { // Error structure returned by getLastError
+ public static class BSimError { // Error structure returned by getLastError
public ErrorCategory category;
public String message;
- public Error(ErrorCategory cat, String msg) {
+ public BSimError(ErrorCategory cat, String msg) {
category = cat;
message = msg;
}
@@ -120,12 +118,11 @@ public interface FunctionDatabase extends AutoCloseable {
/**
* Issue password change request to the server.
* The method {@link #isPasswordChangeAllowed()} must be invoked first to ensure that
- * the user password may be changed.
- * @param username to change
+ * the user password may be changed.
* @param newPassword is password data
* @return null if change was successful, or the error message
*/
- public default String changePassword(String username, char[] newPassword) {
+ public default String changePassword(char[] newPassword) {
if (getStatus() != Status.Ready) {
return "Connection not established";
}
@@ -134,7 +131,7 @@ public interface FunctionDatabase extends AutoCloseable {
}
PasswordChange passwordChange = new PasswordChange();
try {
- passwordChange.username = username;
+ passwordChange.username = getUserName();
passwordChange.newPassword = newPassword;
ResponsePassword response = passwordChange.execute(this);
if (!response.changeSuccessful) {
@@ -162,14 +159,6 @@ public interface FunctionDatabase extends AutoCloseable {
*/
public String getUserName();
- /**
- * Set a specific user name for connection. Must be called before connection is initialized.
- * If this method is not called, connection will use user name of process
- *
- * @param userName the user name
- */
- public void setUserName(String userName);
-
/**
* @return factory the database is using to create LSHVector objects
*/
@@ -218,7 +207,7 @@ public interface FunctionDatabase extends AutoCloseable {
* If the last query failed to produce a response, use this method to recover the error message
* @return a String describing the error
*/
- public Error getLastError();
+ public BSimError getLastError();
/**
* Send a query to the database. The response is returned as a QueryResponseRecord.
diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/client/AbstractSQLFunctionDatabase.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/client/AbstractSQLFunctionDatabase.java
index a143d806d4..77a80624ff 100644
--- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/client/AbstractSQLFunctionDatabase.java
+++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/client/AbstractSQLFunctionDatabase.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -92,7 +92,7 @@ public abstract class AbstractSQLFunctionDatabase
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
return null;
}
- @Override
- public void setUserName(String userName) {
- // ignore
- }
-
@Override
public LSHVectorFactory getLSHVectorFactory() {
return vectorFactory;
@@ -1155,7 +1150,7 @@ public abstract class AbstractSQLFunctionDatabase
}
@Override
- public Error getLastError() {
+ public BSimError getLastError() {
return lasterror;
}
@@ -1188,7 +1183,7 @@ public abstract class AbstractSQLFunctionDatabase
}
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
}
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
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();
}
diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/client/FunctionDatabaseProxy.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/client/FunctionDatabaseProxy.java
index 4f972bc1f6..39ba6e2869 100755
--- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/client/FunctionDatabaseProxy.java
+++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/client/FunctionDatabaseProxy.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -34,7 +34,7 @@ public class FunctionDatabaseProxy implements FunctionDatabase {
private DatabaseInformation info;
private LSHVectorFactory vectorFactory;
private URL httpURL;
- private Error lasterror;
+ private BSimError lasterror;
private Status status;
private boolean isinit;
private XmlErrorHandler xmlErrorHandler;
@@ -83,11 +83,6 @@ public class FunctionDatabaseProxy implements FunctionDatabase {
return ClientUtil.getUserName();
}
- @Override
- public void setUserName(String userName) {
- // Not currently implemented
- }
-
@Override
public LSHVectorFactory getLSHVectorFactory() {
return vectorFactory;
@@ -123,7 +118,8 @@ public class FunctionDatabaseProxy implements FunctionDatabase {
}
if (httpURL == null) {
status = Status.Error;
- lasterror = new FunctionDatabase.Error(ErrorCategory.Initialization, "MalformedURL");
+ lasterror =
+ new FunctionDatabase.BSimError(ErrorCategory.Initialization, "MalformedURL");
return false;
}
QueryInfo queryInfo = new QueryInfo();
@@ -145,7 +141,7 @@ public class FunctionDatabaseProxy implements FunctionDatabase {
}
@Override
- public Error getLastError() {
+ public BSimError getLastError() {
return lasterror;
}
@@ -168,7 +164,8 @@ public class FunctionDatabaseProxy implements FunctionDatabase {
ResponseError respError = new ResponseError();
respError.restoreXml(parser, vectorFactory);
parser.dispose();
- lasterror = new FunctionDatabase.Error(ErrorCategory.Fatal, respError.errorMessage);
+ lasterror =
+ new FunctionDatabase.BSimError(ErrorCategory.Fatal, respError.errorMessage);
query.clearResponse();
return null;
}
@@ -184,7 +181,7 @@ public class FunctionDatabaseProxy implements FunctionDatabase {
return response;
}
catch (Exception ex) {
- lasterror = new FunctionDatabase.Error(ErrorCategory.Connection, ex.getMessage());
+ lasterror = new FunctionDatabase.BSimError(ErrorCategory.Connection, ex.getMessage());
status = Status.Error;
query.clearResponse();
return null;
diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/client/PostgresFunctionDatabase.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/client/PostgresFunctionDatabase.java
index f8fd89c7ed..bb3bccc2df 100755
--- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/client/PostgresFunctionDatabase.java
+++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/client/PostgresFunctionDatabase.java
@@ -190,8 +190,9 @@ public final class PostgresFunctionDatabase
@Override
protected void generateRawDatabase() throws SQLException {
BSimServerInfo serverInfo = postgresDs.getServerInfo();
- BSimServerInfo defaultServerInfo = new BSimServerInfo(DBType.postgres,
- serverInfo.getServerName(), serverInfo.getPort(), DEFAULT_DATABASE_NAME);
+ BSimServerInfo defaultServerInfo =
+ new BSimServerInfo(DBType.postgres, serverInfo.getUserInfo(),
+ serverInfo.getServerName(), serverInfo.getPort(), DEFAULT_DATABASE_NAME);
String createdbstring = "CREATE DATABASE \"" + serverInfo.getDBName() + '"';
BSimPostgresDataSource defaultDs =
BSimPostgresDBConnectionManager.getDataSource(defaultServerInfo);
@@ -502,14 +503,6 @@ public final class PostgresFunctionDatabase
return postgresDs.getUserName();
}
- @Override
- public void setUserName(String userName) {
- if (postgresDs.getStatus() == Status.Ready) {
- throw new IllegalStateException("Connection has already been established");
- }
- postgresDs.setPreferredUserName(userName);
- }
-
@Override
public QueryResponseRecord doQuery(BSimQuery> query, Connection c)
throws SQLException, LSHException, DatabaseNonFatalException {
diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/elastic/ElasticConnection.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/elastic/ElasticConnection.java
index 67fff9e3e4..473118dc88 100755
--- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/elastic/ElasticConnection.java
+++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/elastic/ElasticConnection.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -31,8 +31,6 @@ public class ElasticConnection {
protected String hostURL; // http://hostname:port
protected String httpURLbase; // Main URL to elasticsearch
- private HttpURLConnection connection = null;
- private Writer writer;
private int lastResponseCode;
public ElasticConnection(String url, String repo) {
@@ -41,61 +39,13 @@ public class ElasticConnection {
}
public void close() {
- if (connection != null) {
- connection.disconnect();
- }
+ // nothing to do - http connections do not persist
}
public boolean lastRequestSuccessful() {
return (lastResponseCode >= 200) && (lastResponseCode < 300);
}
- /**
- * Start a new request to the elastic server. This establishes the OutputStream for writing the body of the request
- * @param command is the type of command
- * @param path is the overarching index/type/ path
- * @throws IOException for problems with the socket
- */
- public void startHttpRequest(String command, String path) throws IOException {
- URL httpURL = new URL(httpURLbase + path);
- connection = (HttpURLConnection) httpURL.openConnection();
- connection.setRequestMethod(command);
- connection.setRequestProperty("Content-Type", "application/json");
- connection.setDoOutput(true);
- writer = new OutputStreamWriter(connection.getOutputStream());
- }
-
- public void startHttpBulkRequest(String bulkCommand) throws IOException {
- URL httpURL = new URL(hostURL + bulkCommand);
- connection = (HttpURLConnection) httpURL.openConnection();
- connection.setRequestMethod(POST);
- connection.setRequestProperty("Content-Type", "application/x-ndjson");
- connection.setDoOutput(true);
- writer = new OutputStreamWriter(connection.getOutputStream());
- }
-
- public void startHttpRawRequest(String command, String path) throws IOException {
- URL httpURL = new URL(hostURL + path);
- connection = (HttpURLConnection) httpURL.openConnection();
- connection.setRequestMethod(command);
- connection.setRequestProperty("Content-Type", "application/json");
- connection.setDoOutput(true);
- writer = new OutputStreamWriter(connection.getOutputStream());
- }
-
- /**
- * Start a request with no input body, URI only
- * @param command is the command to issue
- * @param path is the overarching request path: index/...
- * @throws IOException for problems with the socket
- */
- public void startHttpURICommand(String command, String path) throws IOException {
- URL httpURL = new URL(httpURLbase + path);
- connection = (HttpURLConnection) httpURL.openConnection();
- connection.setRequestMethod(command);
- connection.setDoOutput(true);
- }
-
/**
* Assuming the writer has been closed and connection.getResponseCode() is called
* placing the value in lastResponseCode, read the response and parse into a JSONObject
@@ -103,15 +53,21 @@ public class ElasticConnection {
* @throws IOException for problems with the socket
* @throws ParseException for JSON parse errors
*/
- private JSONObject grabResponse() throws IOException, ParseException {
+ private JSONObject grabResponse(HttpURLConnection connection)
+ throws IOException, ParseException {
JSONParser parser = new JSONParser();
- Reader reader;
+ InputStream in;
if (lastRequestSuccessful()) {
- reader = new InputStreamReader(connection.getInputStream());
+ in = connection.getInputStream();
}
else {
- reader = new InputStreamReader(connection.getErrorStream());
+ in = connection.getErrorStream();
}
+ if (in == null) {
+ // Connection error occurred
+ throw new IOException(connection.getResponseMessage());
+ }
+ Reader reader = new InputStreamReader(in);
JSONObject jsonObject = (JSONObject) parser.parse(reader);
return jsonObject;
}
@@ -156,12 +112,18 @@ public class ElasticConnection {
*/
public JSONObject executeRawStatement(String command, String path, String body)
throws ElasticException {
+ HttpURLConnection connection = null;
try {
- startHttpRawRequest(command, path);
- writer.write(body);
- writer.close();
+ URL httpURL = new URL(hostURL + path);
+ connection = (HttpURLConnection) httpURL.openConnection();
+ connection.setRequestMethod(command);
+ connection.setRequestProperty("Content-Type", "application/json");
+ connection.setDoOutput(true);
+ try (Writer writer = new OutputStreamWriter(connection.getOutputStream())) {
+ writer.write(body);
+ }
lastResponseCode = connection.getResponseCode();
- JSONObject resp = grabResponse();
+ JSONObject resp = grabResponse(connection);
if (!lastRequestSuccessful()) {
throw new ElasticException(parseErrorJSON(resp));
}
@@ -173,6 +135,11 @@ public class ElasticConnection {
catch (ParseException e) {
throw new ElasticException("Error parsing response: " + e.getMessage());
}
+ finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
}
@@ -185,12 +152,18 @@ public class ElasticConnection {
*/
public void executeStatementNoResponse(String command, String path, String body)
throws ElasticException {
+ HttpURLConnection connection = null;
try {
- startHttpRequest(command, path);
- writer.write(body);
- writer.close();
+ URL httpURL = new URL(httpURLbase + path);
+ connection = (HttpURLConnection) httpURL.openConnection();
+ connection.setRequestMethod(command);
+ connection.setRequestProperty("Content-Type", "application/json");
+ connection.setDoOutput(true);
+ try (Writer writer = new OutputStreamWriter(connection.getOutputStream())) {
+ writer.write(body);
+ }
lastResponseCode = connection.getResponseCode();
- JSONObject resp = grabResponse();
+ JSONObject resp = grabResponse(connection);
if (!lastRequestSuccessful()) {
throw new ElasticException(parseErrorJSON(resp));
}
@@ -201,6 +174,11 @@ public class ElasticConnection {
catch (ParseException e) {
throw new ElasticException("Error parsing response: " + e.getMessage());
}
+ finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
}
/**
@@ -213,12 +191,18 @@ public class ElasticConnection {
*/
public JSONObject executeStatement(String command, String path, String body)
throws ElasticException {
+ HttpURLConnection connection = null;
try {
- startHttpRequest(command, path);
- writer.write(body);
- writer.close();
+ URL httpURL = new URL(httpURLbase + path);
+ connection = (HttpURLConnection) httpURL.openConnection();
+ connection.setRequestMethod(command);
+ connection.setRequestProperty("Content-Type", "application/json");
+ connection.setDoOutput(true);
+ try (Writer writer = new OutputStreamWriter(connection.getOutputStream())) {
+ writer.write(body);
+ }
lastResponseCode = connection.getResponseCode();
- JSONObject resp = grabResponse();
+ JSONObject resp = grabResponse(connection);
if (!lastRequestSuccessful()) {
throw new ElasticException(parseErrorJSON(resp));
}
@@ -230,6 +214,11 @@ public class ElasticConnection {
catch (ParseException e) {
throw new ElasticException("Error parsing response: " + e.getMessage());
}
+ finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
}
/**
@@ -243,12 +232,18 @@ public class ElasticConnection {
*/
public JSONObject executeStatementExpectFailure(String command, String path, String body)
throws ElasticException {
+ HttpURLConnection connection = null;
try {
- startHttpRequest(command, path);
- writer.write(body);
- writer.close();
+ URL httpURL = new URL(httpURLbase + path);
+ connection = (HttpURLConnection) httpURL.openConnection();
+ connection.setRequestMethod(command);
+ connection.setRequestProperty("Content-Type", "application/json");
+ connection.setDoOutput(true);
+ try (Writer writer = new OutputStreamWriter(connection.getOutputStream())) {
+ writer.write(body);
+ }
lastResponseCode = connection.getResponseCode();
- JSONObject resp = grabResponse();
+ JSONObject resp = grabResponse(connection);
return resp;
}
catch (IOException e) {
@@ -257,6 +252,11 @@ public class ElasticConnection {
catch (ParseException e) {
throw new ElasticException("Error parsing response: " + e.getMessage());
}
+ finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
}
/**
@@ -268,12 +268,18 @@ public class ElasticConnection {
* @throws ElasticException for any problems with the connection
*/
public JSONObject executeBulk(String path, String body) throws ElasticException {
+ HttpURLConnection connection = null;
try {
- startHttpBulkRequest(path);
- writer.write(body);
- writer.close();
+ URL httpURL = new URL(hostURL + path);
+ connection = (HttpURLConnection) httpURL.openConnection();
+ connection.setRequestMethod(POST);
+ connection.setRequestProperty("Content-Type", "application/x-ndjson");
+ connection.setDoOutput(true);
+ try (Writer writer = new OutputStreamWriter(connection.getOutputStream())) {
+ writer.write(body);
+ }
lastResponseCode = connection.getResponseCode();
- JSONObject resp = grabResponse();
+ JSONObject resp = grabResponse(connection);
if (!lastRequestSuccessful()) {
throw new ElasticException(parseErrorJSON(resp));
}
@@ -285,13 +291,22 @@ public class ElasticConnection {
catch (ParseException e) {
throw new ElasticException("Error parsing response: " + e.getMessage());
}
+ finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
}
public JSONObject executeURIOnly(String command, String path) throws ElasticException {
+ HttpURLConnection connection = null;
try {
- startHttpURICommand(command, path);
+ URL httpURL = new URL(httpURLbase + path);
+ connection = (HttpURLConnection) httpURL.openConnection();
+ connection.setRequestMethod(command);
+ connection.setDoOutput(true);
lastResponseCode = connection.getResponseCode();
- JSONObject resp = grabResponse();
+ JSONObject resp = grabResponse(connection);
if (!lastRequestSuccessful()) {
throw new ElasticException(parseErrorJSON(resp));
}
@@ -303,5 +318,10 @@ public class ElasticConnection {
catch (ParseException e) {
throw new ElasticException("Error parsing response: " + e.getMessage());
}
+ finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
}
}
diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/elastic/ElasticDatabase.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/elastic/ElasticDatabase.java
index 2d49fb8fb1..5a44869994 100755
--- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/elastic/ElasticDatabase.java
+++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/elastic/ElasticDatabase.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -60,14 +60,13 @@ public class ElasticDatabase implements FunctionDatabase {
public static final int MAX_VECTOR_BULK = 200; // Maximum vectors ingested in one bulk request
private ElasticConnection connection; // Low-level connection to the database
- private String userName = null; // User name for server authentication
private ConnectionType connectionType = ConnectionType.Unencrypted_No_Authentication;
private DatabaseInformation info; // Information about the active database
private Base64VectorFactory vectorFactory; // factory used to create BSim feature vectors
private final BSimServerInfo serverInfo; // NOTE: does not reflect the use of http vs https
private final String baseURL; // Base URL for connecting to elasticsearch, i.e. http://hostname:9200
private final String repository; // Name of the repository, prefix to all elasticsearch indices
- private Error lastError; // Info on error caused by last action taken on this interface (null if no error)
+ private BSimError lastError; // Info on error caused by last action taken on this interface (null if no error)
private Status status; // status of the connection
private boolean initialized; // true if the connection has been successfully initialized
@@ -2022,8 +2021,8 @@ public class ElasticDatabase implements FunctionDatabase {
throw new MalformedURLException("URL path must indicate the repository only");
}
repository = path.substring(1);
- this.serverInfo =
- new BSimServerInfo(DBType.elastic, baseURL.getHost(), baseURL.getPort(), repository);
+ this.serverInfo = new BSimServerInfo(DBType.elastic, null, baseURL.getHost(),
+ baseURL.getPort(), repository);
this.baseURL = fullURL.substring(0, fullURL.length() - path.length());
lastError = null;
@@ -2398,15 +2397,7 @@ public class ElasticDatabase implements FunctionDatabase {
@Override
public String getUserName() {
- if (userName != null) {
- return userName;
- }
- return ClientUtil.getUserName();
- }
-
- @Override
- public void setUserName(String userName) {
- this.userName = userName;
+ return serverInfo.getUserName();
}
@Override
@@ -2450,14 +2441,14 @@ public class ElasticDatabase implements FunctionDatabase {
vectorFactory.set(config.weightfactory, config.idflookup, config.info.settings);
}
catch (ElasticException err) {
- lastError = new Error(ErrorCategory.Initialization,
+ lastError = new BSimError(ErrorCategory.Initialization,
"Database error on initialization: " + err.getMessage());
status = Status.Error;
return false;
}
catch (NoDatabaseException err) {
info = null;
- lastError = new Error(ErrorCategory.Nodatabase,
+ lastError = new BSimError(ErrorCategory.Nodatabase,
"Database has not been created yet: " + err.getMessage());
initialized = true;
status = Status.Ready;
@@ -2480,14 +2471,14 @@ public class ElasticDatabase implements FunctionDatabase {
}
@Override
- public Error getLastError() {
+ public BSimError getLastError() {
return lastError;
}
@Override
public QueryResponseRecord query(BSimQuery> query) {
if ((!isInitialized()) && (!(query instanceof CreateDatabase))) {
- lastError = new Error(ErrorCategory.Nodatabase, "The database does not exist");
+ lastError = new BSimError(ErrorCategory.Nodatabase, "The database does not exist");
return null;
}
lastError = null;
@@ -2554,22 +2545,22 @@ public class ElasticDatabase implements FunctionDatabase {
fdbPasswordChange((PasswordChange) query);
}
else {
- lastError = new Error(ErrorCategory.Fatal, "Unknown query type");
+ lastError = new BSimError(ErrorCategory.Fatal, "Unknown query type");
query.clearResponse();
}
}
catch (DatabaseNonFatalException err) {
- lastError = new Error(ErrorCategory.Nonfatal,
+ lastError = new BSimError(ErrorCategory.Nonfatal,
"Skipping -" + query.getName() + "- : " + err.getMessage());
query.clearResponse();
}
catch (LSHException err) {
- lastError = new Error(ErrorCategory.Fatal,
+ lastError = new BSimError(ErrorCategory.Fatal,
"Fatal error during -" + query.getName() + "- : " + err.getMessage());
query.clearResponse();
}
catch (ElasticException err) {
- lastError = new Error(ErrorCategory.Fatal,
+ lastError = new BSimError(ErrorCategory.Fatal,
"Elastic error during -" + query.getName() + "- : " + err.getMessage());
query.clearResponse();
}
diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/facade/SimilarFunctionQueryService.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/facade/SimilarFunctionQueryService.java
index 062b7ee460..ecd975bf6d 100755
--- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/facade/SimilarFunctionQueryService.java
+++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/facade/SimilarFunctionQueryService.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -384,7 +384,7 @@ public class SimilarFunctionQueryService implements AutoCloseable {
return database.getLSHVectorFactory();
}
- public FunctionDatabase.Error getLastError() {
+ public FunctionDatabase.BSimError getLastError() {
if (database == null) {
return null;
}
diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/BSimLaunchable.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/BSimLaunchable.java
index 73cfd81d0a..6cebdb57ff 100644
--- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/BSimLaunchable.java
+++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/BSimLaunchable.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -17,7 +17,8 @@ package ghidra.features.bsim.query.ingest;
import java.io.File;
import java.io.IOException;
-import java.net.*;
+import java.net.MalformedURLException;
+import java.net.URL;
import java.util.*;
import org.apache.commons.lang3.StringUtils;
@@ -32,10 +33,8 @@ import ghidra.features.bsim.query.protocol.QueryName;
import ghidra.framework.*;
import ghidra.framework.client.ClientUtil;
import ghidra.framework.client.HeadlessClientAuthenticator;
-import ghidra.framework.data.DomainObjectAdapter;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.net.SSLContextInitializer;
-import ghidra.program.database.ProgramDB;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.CancelledException;
@@ -335,10 +334,6 @@ public class BSimLaunchable implements GhidraLaunchable {
optionValueMap.put(option, params[i]);
}
}
- String connectingUserName = optionValueMap.get(USER_OPTION);
- if (connectingUserName == null) {
- connectingUserName = optionValueMap.put(USER_OPTION, ClientUtil.getUserName());
- }
return subParams;
}
@@ -981,9 +976,9 @@ public class BSimLaunchable implements GhidraLaunchable {
" - large_32 | medium_32 | medium_64 | medium_cpool | medium_nosize \n" +
"\n" +
"BSim URL Forms (bsimURL):\n" +
- " postgresql://[:]/\n" +
- " elastic://[:]/\n" +
- " https://[:]/\n" +
+ " postgresql://[username@][:]/\n" +
+ " elastic://[username@][:]/\n" +
+ " https://[username@][:]/\n" +
" file:/[/]\n" +
"\n" +
"Ghidra URL Forms (ghidraURL):\n" +
@@ -1010,7 +1005,13 @@ public class BSimLaunchable implements GhidraLaunchable {
run(params);
}
catch (MalformedURLException e) {
- Msg.error(this, "Invalid URL specified: " + e.getMessage());
+ String msg = e.getMessage();
+ if (msg == null) {
+ e.printStackTrace();
+ }
+ else {
+ Msg.error(this, "Invalid URL specified: " + msg);
+ }
System.exit(22); // EINVAL
}
catch (IllegalArgumentException e) {
@@ -1019,7 +1020,13 @@ public class BSimLaunchable implements GhidraLaunchable {
System.exit(22); // EINVAL
}
catch (Exception e) {
- Msg.error(this, e.getMessage());
+ String msg = e.getMessage();
+ if (msg == null) {
+ e.printStackTrace();
+ }
+ else {
+ Msg.error(this, msg);
+ }
System.exit(1); // Misc Error
}
}
@@ -1059,7 +1066,7 @@ public class BSimLaunchable implements GhidraLaunchable {
// Use BSim log config to ensure we get desired console output
System.setProperty(LoggingInitialization.LOG4J2_CONFIGURATION_PROPERTY,
- BSIM_LOGGING_CONFIGURATION_FILE);
+ BSIM_LOGGING_CONFIGURATION_FILE);
ApplicationConfiguration config;
switch (type) {
@@ -1084,6 +1091,10 @@ public class BSimLaunchable implements GhidraLaunchable {
ghidra.framework.protocol.ghidra.Handler.registerHandler();
ghidra.features.bsim.query.postgresql.Handler.registerHandler();
+ if (connectingUserName == null) {
+ // Force default login name
+ connectingUserName = ClientUtil.getUserName();
+ }
HeadlessClientAuthenticator.installHeadlessClientAuthenticator(connectingUserName, certPath,
true);
}
diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/BulkSignatures.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/BulkSignatures.java
index 586760eff6..76e2ae4179 100755
--- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/BulkSignatures.java
+++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/ingest/BulkSignatures.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -16,7 +16,6 @@
package ghidra.features.bsim.query.ingest;
import java.io.*;
-import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
@@ -29,9 +28,8 @@ import org.xml.sax.SAXException;
import generic.lsh.vector.LSHVectorFactory;
import ghidra.app.decompiler.DecompileException;
import ghidra.features.bsim.query.*;
-import ghidra.features.bsim.query.FunctionDatabase.Error;
+import ghidra.features.bsim.query.FunctionDatabase.BSimError;
import ghidra.features.bsim.query.FunctionDatabase.ErrorCategory;
-import ghidra.features.bsim.query.FunctionDatabase.Status;
import ghidra.features.bsim.query.client.Configuration;
import ghidra.features.bsim.query.client.tables.ExeTable.ExeTableOrderColumn;
import ghidra.features.bsim.query.description.*;
@@ -52,7 +50,6 @@ public class BulkSignatures implements AutoCloseable {
// FIXME: May need to use Msg.showError for popup messages in GUI workbench case
private final BSimServerInfo bsimServerInfo; // may be null
- private final String connectingUserName;
private FunctionDatabase querydb;
@@ -61,14 +58,36 @@ public class BulkSignatures implements AutoCloseable {
* @param bsimServerInfo the BSim database server info. May be {@code null} if use limited to
* signature and update generation only (based upon configuration template).
* @param connectingUserName user name to use for BSim server authentication. May be null if
- * not required or default should be used (see {@link ClientUtil#getUserName()}).
- * @throws MalformedURLException if the given URL string cannot be parsed
+ * not required or default should be used (see {@link ClientUtil#getUserName()}). If specified
+ * a new {@link BSimServerInfo} instance will be created with the user information set. This
+ * argument is ignored if DB user specified by {@code bsimServerInfo}.
*/
- public BulkSignatures(BSimServerInfo bsimServerInfo, String connectingUserName)
- throws MalformedURLException {
+ public BulkSignatures(BSimServerInfo bsimServerInfo, String connectingUserName) {
+ if (bsimServerInfo != null && !StringUtils.isBlank(connectingUserName)) {
+ if (!bsimServerInfo.hasDefaultLogin()) {
+ String username = bsimServerInfo.getUserName();
+ if (!username.equals(connectingUserName)) {
+ Msg.warn(this, "BSim DB server info specifies user '" + username +
+ "'. Ignoring user name option: '" + connectingUserName + "'");
+ }
+ }
+ else {
+ bsimServerInfo = new BSimServerInfo(bsimServerInfo.getDBType(), connectingUserName,
+ bsimServerInfo.getServerName(), bsimServerInfo.getPort(),
+ bsimServerInfo.getDBName());
+ }
+ }
+ this.bsimServerInfo = bsimServerInfo;
+ }
+
+ /**
+ * Constructor
+ * @param bsimServerInfo the BSim database server info. May be {@code null} if use limited to
+ * signature and update generation only (based upon configuration template). If specified,
+ * this object will convey the connecting user name.
+ */
+ public BulkSignatures(BSimServerInfo bsimServerInfo) {
this.bsimServerInfo = bsimServerInfo;
- this.connectingUserName =
- connectingUserName != null ? connectingUserName : ClientUtil.getUserName();
}
private void checkBSimServerOperation() {
@@ -93,9 +112,6 @@ public class BulkSignatures implements AutoCloseable {
checkBSimServerOperation();
querydb = BSimClientFactory.buildClient(bsimServerInfo, async);
- if (querydb.getStatus() == Status.Unconnected) { // may have previously connected
- querydb.setUserName(connectingUserName);
- }
if (!querydb.initialize()) {
throw new IOException(querydb.getLastError().message);
@@ -103,7 +119,7 @@ public class BulkSignatures implements AutoCloseable {
DatabaseInformation info = querydb.getInfo();
if (info == null) {
- Error lastError = querydb.getLastError();
+ BSimError lastError = querydb.getLastError();
if (lastError != null && lastError.category == ErrorCategory.Nodatabase) {
throw new IOException(lastError.message);
}
@@ -190,7 +206,7 @@ public class BulkSignatures implements AutoCloseable {
continue;
}
if (insertreq.execute(querydb) == null) {
- Error lastError = querydb.getLastError();
+ BSimError lastError = querydb.getLastError();
if ((lastError.category == ErrorCategory.Format) ||
(lastError.category == ErrorCategory.Nonfatal)) {
Msg.warn(this, file.getName() + ": " + lastError.message);
@@ -216,7 +232,7 @@ public class BulkSignatures implements AutoCloseable {
loadSignatureXml(file, update.manage);
ResponseUpdate respup = update.execute(querydb);
if (respup == null) {
- Error lastError = querydb.getLastError();
+ BSimError lastError = querydb.getLastError();
if ((lastError.category == ErrorCategory.Format) ||
(lastError.category == ErrorCategory.Nonfatal)) {
Msg.warn(this, file.getName() + ": " + lastError.message);
@@ -400,9 +416,6 @@ public class BulkSignatures implements AutoCloseable {
checkBSimServerOperation();
querydb = BSimClientFactory.buildClient(bsimServerInfo, true);
- if (querydb.getStatus() == Status.Unconnected) { // may have previously connected
- querydb.setUserName(connectingUserName);
- }
// TODO: Should this output differ for command-line vs workbench? debug only?
try {
@@ -533,7 +546,7 @@ public class BulkSignatures implements AutoCloseable {
establishQueryServerConnection(true);
ResponseDelete respdel = query.execute(querydb);
if (respdel == null) {
- Error lastError = querydb.getLastError();
+ BSimError lastError = querydb.getLastError();
throw new LSHException("Could not perform delete: " + lastError.message);
}
@@ -561,7 +574,7 @@ public class BulkSignatures implements AutoCloseable {
query.doRebuild = false;
ResponseAdjustIndex response = query.execute(querydb);
if (response == null) {
- Error lastError = querydb.getLastError();
+ BSimError lastError = querydb.getLastError();
throw new LSHException("Could not drop index: " + lastError.message);
}
String dbDetail = "for database " + info.databasename + " (" + bsimServerInfo + ")";
@@ -590,7 +603,7 @@ public class BulkSignatures implements AutoCloseable {
System.out.println("Starting rebuild ...");
ResponseAdjustIndex response = query.execute(querydb);
if (response == null) {
- Error lastError = querydb.getLastError();
+ BSimError lastError = querydb.getLastError();
throw new LSHException("Could not rebuild index: " + lastError.message);
}
String dbDetail = "for database " + info.databasename + " (" + bsimServerInfo + ")";
@@ -617,7 +630,7 @@ public class BulkSignatures implements AutoCloseable {
PrewarmRequest request = new PrewarmRequest();
ResponsePrewarm response = request.execute(querydb);
if (response == null) {
- Error lastError = querydb.getLastError();
+ BSimError lastError = querydb.getLastError();
throw new LSHException("Prewarm failed: " + lastError.message);
}
String dbDetail = "for database " + info.databasename + " (" + bsimServerInfo + ")";
@@ -662,7 +675,7 @@ public class BulkSignatures implements AutoCloseable {
ResponseExe response = exeQuery.execute(querydb);
if (response == null) {
- Error lastError = querydb.getLastError();
+ BSimError lastError = querydb.getLastError();
throw new LSHException("Could not perform getexeinfo: " + lastError.message);
}
@@ -735,7 +748,7 @@ public class BulkSignatures implements AutoCloseable {
req.description = description;
ResponseInfo resp = req.execute(querydb);
if (resp == null) {
- Error lastError = querydb.getLastError();
+ BSimError lastError = querydb.getLastError();
throw new LSHException("Could not change metadata: " + lastError.message);
}
info = resp.info;
@@ -764,7 +777,7 @@ public class BulkSignatures implements AutoCloseable {
ResponseInfo resp = req.execute(querydb);
if (resp == null) {
- Error lastError = querydb.getLastError();
+ BSimError lastError = querydb.getLastError();
throw new LSHException("Could not install new category: " + lastError.message);
}
info = resp.info;
@@ -798,7 +811,7 @@ public class BulkSignatures implements AutoCloseable {
req.tag_name = dequoteString(tagName);
ResponseInfo resp = req.execute(querydb);
if (resp == null) {
- Error lastError = querydb.getLastError();
+ BSimError lastError = querydb.getLastError();
throw new LSHException(lastError.message);
}
info = resp.info;
@@ -859,7 +872,7 @@ public class BulkSignatures implements AutoCloseable {
while (count != 0) {
ResponsePair responsePair = query.execute(querydb);
if (responsePair == null) {
- Error lastError = querydb.getLastError();
+ BSimError lastError = querydb.getLastError();
throw new LSHException(lastError.message);
}
for (PairNote note : responsePair.notes) {
@@ -893,7 +906,7 @@ public class BulkSignatures implements AutoCloseable {
establishQueryServerConnection(true);
ResponseName resp = query.execute(querydb);
if (resp == null) {
- Error lastError = querydb.getLastError();
+ BSimError lastError = querydb.getLastError();
throw new LSHException(lastError.message);
}
resp.printRaw(outStream, querydb.getLSHVectorFactory(), 0);
@@ -941,7 +954,7 @@ public class BulkSignatures implements AutoCloseable {
query.fillinCallgraph = info.trackcallgraph;
ResponseName responseName = query.execute(querydb);
if (responseName == null) {
- Error lastError = querydb.getLastError();
+ BSimError lastError = querydb.getLastError();
throw new LSHException(lastError.message);
}
if (!responseName.uniqueexecutable) {
diff --git a/Ghidra/Features/BSim/src/screen/java/help/screenshot/BSimSearchPluginScreenShots.java b/Ghidra/Features/BSim/src/screen/java/help/screenshot/BSimSearchPluginScreenShots.java
index c18470ef4a..82b4c616b5 100755
--- a/Ghidra/Features/BSim/src/screen/java/help/screenshot/BSimSearchPluginScreenShots.java
+++ b/Ghidra/Features/BSim/src/screen/java/help/screenshot/BSimSearchPluginScreenShots.java
@@ -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();
}
diff --git a/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/gui/BSimSearchPluginTest.java b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/gui/BSimSearchPluginTest.java
index 4523a79ff1..3f40419dcd 100755
--- a/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/gui/BSimSearchPluginTest.java
+++ b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/gui/BSimSearchPluginTest.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -178,7 +178,7 @@ public class BSimSearchPluginTest extends AbstractBSimPluginTest {
private FunctionDatabase database;
public TestBSimServerInfo(FunctionDatabase database) {
- super(DBType.postgres, "0.0.0.0", 123, "testDB");
+ super(DBType.postgres, null, "0.0.0.0", 123, "testDB");
this.database = database;
}
diff --git a/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/file/BSimH2DatabaseManagerTest.java b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/file/BSimH2DatabaseManagerTest.java
index f3dd96c173..32aa009ca5 100644
--- a/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/file/BSimH2DatabaseManagerTest.java
+++ b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/file/BSimH2DatabaseManagerTest.java
@@ -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: "));
}
diff --git a/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/test/BSimServerTest.java b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/test/BSimServerTest.java
index 07154c507b..8408f218b5 100755
--- a/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/test/BSimServerTest.java
+++ b/Ghidra/Features/BSim/src/test.slow/java/ghidra/features/bsim/query/test/BSimServerTest.java
@@ -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");
}
diff --git a/Ghidra/Features/BSim/src/test/java/ghidra/features/bsim/query/facade/FunctionDatabaseTestDouble.java b/Ghidra/Features/BSim/src/test/java/ghidra/features/bsim/query/facade/FunctionDatabaseTestDouble.java
index 760beba768..05e85b863d 100755
--- a/Ghidra/Features/BSim/src/test/java/ghidra/features/bsim/query/facade/FunctionDatabaseTestDouble.java
+++ b/Ghidra/Features/BSim/src/test/java/ghidra/features/bsim/query/facade/FunctionDatabaseTestDouble.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -106,11 +106,6 @@ public class FunctionDatabaseTestDouble implements SQLFunctionDatabase {
return ClientUtil.getUserName();
}
- @Override
- public void setUserName(String userName) {
- // Currently not implemented
- }
-
@Override
public String getURLString() {
return urlString;
@@ -141,8 +136,8 @@ public class FunctionDatabaseTestDouble implements SQLFunctionDatabase {
}
@Override
- public Error getLastError() {
- return new Error(ErrorCategory.Unused, errorString);
+ public BSimError getLastError() {
+ return new BSimError(ErrorCategory.Unused, errorString);
}
void setErrorString(String errorString) {
diff --git a/Ghidra/Features/BSim/src/test/java/ghidra/features/bsim/query/facade/TestBSimServerInfo.java b/Ghidra/Features/BSim/src/test/java/ghidra/features/bsim/query/facade/TestBSimServerInfo.java
index d95cf4bd85..92091e4715 100644
--- a/Ghidra/Features/BSim/src/test/java/ghidra/features/bsim/query/facade/TestBSimServerInfo.java
+++ b/Ghidra/Features/BSim/src/test/java/ghidra/features/bsim/query/facade/TestBSimServerInfo.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -23,7 +23,7 @@ public class TestBSimServerInfo extends BSimServerInfo {
private FunctionDatabase database;
public TestBSimServerInfo(FunctionDatabase database) {
- super(DBType.postgres, "100.50.123.5", 123, "testDB");
+ super(DBType.postgres, null, "100.50.123.5", 123, "testDB");
this.database = database;
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/PasswordDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/PasswordDialog.java
index 8a84b609cd..43d06e5cc7 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/PasswordDialog.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/PasswordDialog.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -38,7 +38,8 @@ public class PasswordDialog extends DialogComponentProvider {
private JPasswordField passwordField;
private JComboBox 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;
}
/**
diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/DefaultClientAuthenticator.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/DefaultClientAuthenticator.java
index b41a615b6b..8f5a3f3d18 100644
--- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/DefaultClientAuthenticator.java
+++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/DefaultClientAuthenticator.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -16,11 +16,12 @@
package ghidra.framework.client;
import java.awt.Component;
-import java.net.Authenticator;
-import java.net.PasswordAuthentication;
+import java.net.*;
import javax.security.auth.callback.*;
+import org.apache.commons.lang3.StringUtils;
+
import docking.DockingWindowManager;
import docking.widgets.*;
import ghidra.framework.preferences.Preferences;
@@ -35,23 +36,63 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider
private Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
- Msg.debug(this, "PasswordAuthentication requested for " + getRequestingURL());
- NameCallback nameCb = null;
- if (!"NO_NAME".equals(getRequestingScheme())) {
- nameCb = new NameCallback("Name: ", ClientUtil.getUserName());
+
+ String serverName = getRequestingHost();
+ URL requestingURL = getRequestingURL();
+
+ String pwd = null;
+ String userName = ClientUtil.getUserName();
+ boolean useDefaultUser = true;
+
+ if (requestingURL != null) {
+ String userInfo = requestingURL.getUserInfo();
+ if (userInfo != null) {
+ // Use user info from URL
+ int pwdSep = userInfo.indexOf(':');
+ if (pwdSep < 0) {
+ userName = userInfo;
+ useDefaultUser = false;
+ }
+ else {
+ pwd = userInfo.substring(pwdSep + 1);
+ if (pwdSep != 0) {
+ userName = userInfo.substring(0, pwdSep);
+ useDefaultUser = false;
+ }
+ }
+ }
+
+ URL minimalURL = DefaultClientAuthenticator.getMinimalURL(requestingURL);
+ if (minimalURL != null) {
+ serverName = minimalURL.toExternalForm();
+ }
}
+
+ Msg.debug(this, "PasswordAuthentication requested for " + serverName);
+
+ if (pwd != null) {
+ // Requesting URL specified password
+ return new PasswordAuthentication(userName, pwd.toCharArray());
+ }
+
+ NameCallback nameCb = new NameCallback("Name: ", userName);
+ if (!useDefaultUser) {
+ // Prevent modification of user name by password prompting
+ nameCb.setName(userName);
+ }
+
+ // Prompt for password
String prompt = getRequestingPrompt();
- if (prompt == null) {
- prompt = "Password:";
+ if (StringUtils.isBlank(prompt) || "security".equals(prompt)) {
+ prompt = "Password:"; // assume dialog will show user name via nameCb
}
PasswordCallback passCb = new PasswordCallback(prompt, false);
try {
ServerPasswordPrompt pp = new ServerPasswordPrompt("Connection Authentication",
- "Server", getRequestingHost(), nameCb, passCb, null, null, null);
+ "Server", serverName, nameCb, passCb, null, null, null);
SystemUtilities.runSwingNow(pp);
if (pp.okWasPressed()) {
- return new PasswordAuthentication(nameCb != null ? nameCb.getName() : null,
- passCb.getPassword());
+ return new PasswordAuthentication(nameCb.getName(), passCb.getPassword());
}
}
finally {
@@ -61,6 +102,21 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider
}
};
+ /**
+ * Produce minimal URL (i.e., protocol, host and port)
+ * @param url request URL
+ * @return minimal URL
+ */
+ public static URL getMinimalURL(URL url) {
+ try {
+ return new URL(url, "/");
+ }
+ catch (MalformedURLException e) {
+ // ignore
+ }
+ return null;
+ }
+
@Override
public Authenticator getAuthenticator() {
return authenticator;
@@ -165,10 +221,25 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider
choicePrompt = choiceCb.getPrompt();
choices = choiceCb.getChoices();
}
- PasswordDialog pwdDialog =
- new PasswordDialog(title, serverType, serverName, passCb.getPrompt(),
- nameCb != null ? nameCb.getPrompt() : null, getDefaultUserName(), choicePrompt,
- choices, getDefaultChoice(), anonymousCb != null);
+
+ String defaultUserName = null;
+ String namePrompt = null;
+ if (nameCb != null) {
+ defaultUserName = nameCb.getName();
+ if (defaultUserName == null) {
+ // Name entry only permitted with name callback where name has not be pre-set
+ defaultUserName = nameCb.getDefaultName();
+ namePrompt = nameCb.getPrompt();
+ }
+ }
+ if (defaultUserName == null) {
+ defaultUserName = getDefaultUserName();
+ }
+
+ PasswordDialog pwdDialog = new PasswordDialog(title, serverType, serverName,
+ passCb.getPrompt(), namePrompt, defaultUserName, choicePrompt, choices,
+ getDefaultChoice(), anonymousCb != null);
+
if (errorMsg != null) {
pwdDialog.setErrorText(errorMsg);
}
diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/HeadlessClientAuthenticator.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/HeadlessClientAuthenticator.java
index 78d9cee859..2eaccec7e0 100644
--- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/HeadlessClientAuthenticator.java
+++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/HeadlessClientAuthenticator.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -39,38 +39,58 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
private final static char[] BADPASSWORD = "".toCharArray();
private static Object sshPrivateKey;
- private static String userID = ClientUtil.getUserName(); // default username
+ private static String defaultUserName = ClientUtil.getUserName();
private static boolean passwordPromptAllowed;
private Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
- Msg.debug(this, "PasswordAuthentication requested for " + getRequestingURL());
- String usage = null;
- String prompt = getRequestingPrompt();
- if ("security".equals(prompt)) {
- prompt = null; // squash generic "security" prompt
+
+ if (defaultUserName == null) {
+ throw new IllegalStateException("Default user name is unknown");
}
- URL requestingURL = getRequestingURL();
+
+ String serverName = getRequestingHost();
+ URL requestingURL = getRequestingURL(); // may be null
+
+ String pwd = null;
+ String userName = defaultUserName;
+
if (requestingURL != null) {
- URL minimalURL = null;
- try {
- minimalURL = new URL(requestingURL, "/");
+ String userInfo = requestingURL.getUserInfo();
+ if (userInfo != null) {
+ // Use user info from URL
+ int pwdSep = userInfo.indexOf(':');
+ if (pwdSep < 0) {
+ userName = userInfo;
+ }
+ else {
+ pwd = userInfo.substring(pwdSep + 1);
+ if (pwdSep != 0) {
+ userName = userInfo.substring(0, pwdSep);
+ }
+ }
}
- catch (MalformedURLException e) {
- // ignore
+
+ URL minimalURL = DefaultClientAuthenticator.getMinimalURL(requestingURL);
+ if (minimalURL != null) {
+ serverName = minimalURL.toExternalForm();
}
- usage = "Access password requested for " +
- (minimalURL != null ? minimalURL.toExternalForm()
- : requestingURL.getAuthority());
- prompt = "Password:";
}
- if (prompt == null) {
- // Assume Ghidra Server access
- String host = getRequestingHost();
- prompt = (host != null ? (host + " ") : "") + "(" + userID + ") Password:";
+
+ Msg.debug(this, "PasswordAuthentication requested for " + serverName);
+
+ if (pwd != null) {
+ // Requesting URL specified password
+ return new PasswordAuthentication(userName, pwd.toCharArray());
}
- return new PasswordAuthentication(userID, getPassword(usage, prompt));
+
+ String usage = "Access password requested for " + serverName;
+ String prompt = getRequestingPrompt();
+ if (StringUtils.isBlank(prompt) || "security".equals(prompt)) {
+ prompt = "Password for " + userName +":";
+ }
+ return new PasswordAuthentication(userName, getPassword(usage, prompt));
}
};
@@ -85,7 +105,8 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
/**
* Install headless client authenticator for Ghidra Server
* @param username optional username to be used with a Ghidra Server which
- * allows username to be specified
+ * allows username to be specified. If null, {@link ClientUtil#getUserName()}
+ * will be used.
* @param keystorePath optional PKI or SSH keystore path. May also be specified
* as resource path for SSH key.
* @param allowPasswordPrompt if true the user may be prompted for passwords
@@ -97,7 +118,7 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
boolean allowPasswordPrompt) throws IOException {
passwordPromptAllowed = allowPasswordPrompt;
if (username != null) {
- userID = username;
+ defaultUserName = username;
}
// clear existing key store settings
@@ -175,7 +196,7 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
passwordPrompt += "\n";
}
- if (prompt == null) {
+ if (StringUtils.isBlank(prompt)) {
prompt = "Password:";
}
@@ -233,17 +254,39 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
anonymousCb.setAnonymousAccessRequested(true);
return true;
}
+
+ if (defaultUserName == null) {
+ throw new IllegalStateException("Default user name is unknown");
+ }
+
if (choiceCb != null) {
choiceCb.setSelectedIndex(1);
}
- if (nameCb != null && userID != null) {
- nameCb.setName(userID);
+
+ String userName = null;
+ if (nameCb != null) {
+ userName = nameCb.getName();
+ if (userName == null) {
+ userName = nameCb.getDefaultName();
+ }
}
+ if (userName == null) {
+ userName = defaultUserName;
+ }
+
+ if (nameCb != null) {
+ nameCb.setName(defaultUserName);
+ }
+
String usage = null;
if (serverName != null) {
usage = serverType + ": " + serverName;
}
- char[] password = getPassword(usage, passCb.getPrompt());
+
+ // Ignore prompt specified by passCb
+ String prompt = "Password for " + userName +":";
+
+ char[] password = getPassword(usage, prompt);
passCb.setPassword(password);
return password != null;
}
@@ -278,7 +321,7 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
return false;
}
if (nameCb != null) {
- nameCb.setName(userID);
+ nameCb.setName(defaultUserName);
}
try {
sshCb.sign(sshPrivateKey);
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURL.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURL.java
index 1c09ae4d27..53cb117531 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURL.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURL.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -687,6 +687,10 @@ public class GhidraURL {
if (StringUtils.isBlank(host)) {
throw new IllegalArgumentException("host required");
}
+ // TODO: Need to improve checks and use of URL encoding
+ if (host.indexOf('@') >= 0) { // prevent user info with hostname
+ throw new IllegalArgumentException("invalid host name");
+ }
if (StringUtils.isBlank(repositoryName)) {
throw new IllegalArgumentException("repository name required");
}
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLConnection.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLConnection.java
index 447b97bea3..75517863bd 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLConnection.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLConnection.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -109,6 +109,9 @@ public class GhidraURLConnection extends URLConnection {
public GhidraURLConnection(URL url, GhidraProtocolHandler protocolHandler)
throws MalformedURLException {
super(url);
+ if (url.getUserInfo() != null) {
+ throw new MalformedURLException("User info not supported by Ghidra URLs");
+ }
if (protocolHandler == null) {
throw new IllegalArgumentException("missing required protocol handler");
}