From 735cb1a5c3cf476eba88c7914824115d7c202ea3 Mon Sep 17 00:00:00 2001 From: ghidra1 Date: Wed, 3 Jan 2024 17:29:45 -0500 Subject: [PATCH] GP-4173 Complete transition to using --option params with bsim and bsim_ctl commands --- .../Extensions/BSimElasticPlugin/INSTALL.txt | 2 +- .../topics/BSim/CommandLineReference.html | 277 +++--- .../topics/BSim/DatabaseConfiguration.html | 32 +- .../help/help/topics/BSim/IngestProcess.html | 50 +- .../bsim/query/BSimControlLaunchable.java | 793 ++++++++++-------- .../features/bsim/query/FunctionDatabase.java | 2 +- .../bsim/query/ingest/BSimLaunchable.java | 359 ++++---- .../bsim/query/ingest/BulkSignatures.java | 18 +- Ghidra/RuntimeScripts/Linux/support/bsim | 2 +- .../RuntimeScripts/Windows/support/bsim.bat | 5 +- .../BSim/BSimTutorial_BSim_Command_Line.html | 4 +- .../BSim/BSimTutorial_BSim_Command_Line.md | 4 +- 12 files changed, 830 insertions(+), 718 deletions(-) mode change 100755 => 100644 Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimControlLaunchable.java diff --git a/Ghidra/Extensions/BSimElasticPlugin/INSTALL.txt b/Ghidra/Extensions/BSimElasticPlugin/INSTALL.txt index f3ec8944df..b84395214f 100755 --- a/Ghidra/Extensions/BSimElasticPlugin/INSTALL.txt +++ b/Ghidra/Extensions/BSimElasticPlugin/INSTALL.txt @@ -69,7 +69,7 @@ This is equivalent to: Use a command-line like this to generate and commit signatures from a Ghidra Server repository to the Elasticsearch database created above: - bsim generatesigs ghidra://1.2.3.4/repo bsim=elastic://1.2.3.4:9200/repo + bsim generatesigs ghidra://1.2.3.4/repo --bsim elastic://1.2.3.4:9200/repo Within Ghidra's BSim client, enter the same URL into the database connection panel in order to place queries to your Elasticsearch deployment. See the BSim 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 6b66cbe78e..cf4d16f9bf 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 @@ -39,18 +39,18 @@
 
-    bsim_ctl start           </datadir-path [auth=pki|password|trust] [--noLocalAuth] [cafile=</cacert-path>] [dn=".."]
+    bsim_ctl start           </datadir-path> [--auth|-a pki|password|trust] [--noLocalAuth] [--cafile </cacert-path>] [--dn "<distinguished-name>"]
     bsim_ctl stop            </datadir-path> [--force]
-    bsim_ctl adduser         </datadir-path> <username> [dn=".."]
+    bsim_ctl adduser         </datadir-path> <username> [--dn "<distinguished-name>"]
     bsim_ctl dropuser        </datadir-path> <username>
     bsim_ctl resetpassword   <username>
-    bsim_ctl changeauth      </datadir-path> [auth=pki|password|trust] [--noLocalAuth] [cafile=</cacert-path>] [dn=".."]
+    bsim_ctl changeauth      </datadir-path> [--auth|-a pki|password|trust] [--noLocalAuth] [--cafile </cacert-path>] [--dn "<distinguished-name>"]
     bsim_ctl changeprivilege <username> admin|user
     
     Global Options:
-        port=<portnum>
-        user=<username>
-        cert=</certfile-path>
+        --port|-p <portnum>
+        --user|-u <username>
+        --cert </certfile-path>
 
 
@@ -59,9 +59,11 @@ starting and stopping a BSim server using the PostgreSQL back-end. The utility cannot be used with either an Elasticsearch server or a local H2 database. All commands must be run on the machine hosting the server. - Optional parameters for a given command are indicated by square brackets '[' and ']'. - Options with an '=' character require a user specified value. If the value string requires - space characters, it should be enclosed in double quotes.

+ Optional parameters for a given command are indicated by square brackets '[' and ']' + and always start with either '-' or '--' characters. If an associated value is required + and contains space characters it should be enclosed in double quotes. Options which require + a value may be separated by a space or a equal "=" character (e.g., + --auth=password).
@@ -77,42 +79,42 @@ database with admin privileges.

During a restart, any authentication options (with the exception of the global - cert= option) are unnecessary and will + --cert option) are unnecessary and will be ignored. The PostgreSQL server will be restarted with the already established settings. To actually change the settings, use the changeauth command before restarting.

-

auth=type - specifies the authentication type (pki | +

--auth|-a <type> - specifies the authentication type (pki | password | trust) for a new database: trust for no authentication, password for password authentication, and pki for authentication using public key certificates. With the pki setting, both the cafile= and the dn= options also need to be provided; additionally - the cert= option must be provided unless + "bold">--cafile and the --dn options also need to be provided; additionally + the --cert option must be provided unless the --noLocalAuth option is also given.

--noLocalAuth - used together with - the auth= option causes + the --auth option causes authentication to not be required for local connections, i.e. localhost.

-

cafile=/cafile-path - specifies an absolute path to a +

--cafile </cafile-path> - specifies an absolute path to a certificate authority file and is required for auth=pki. This file should contain the + "command">--auth pki. This file should contain the certificates the PostgreSQL server will use to authenticate in PEM format concatenated together.

-

dn=name - specifies the Distinguished Name for the admin - user and is required for auth=pki.

+

--dn <distinguished-name> - specifies the Distinguished + Name for the admin user and is required for + --auth pki.

-

port=portnum - specifies the port the PostgreSQL server will +

--port|-p <portnum> - specifies the port the PostgreSQL server will listen on. For port numbers other than the default 5432, URLs and other command-lines must explicitly specify the port, when connecting to the server. This option only effects the initial start of a server. For subsequent (re)starts this @@ -143,10 +145,10 @@ (read-only) privileges, unless a subsequent changeprivilege command is used.

-

dn=name - specifies the Distinguished Name of the new user, +

--dn <distinguished-name> - specifies the Distinguished Name of the new user, which is required if the database enabled auth=pki. This option can be used to provide a + "command">--auth pki. This option can be used to provide a Distinguished Name to a preexisting user, if the PostgreSQL server's authentication strategy is changed.

@@ -170,23 +172,22 @@ the same meaning as for the start command.

-

port=portnum - changes the port the PostgreSQL server will +

--port|-p <portnum> - changes the port the PostgreSQL server will listen on. If this option is not present, the server will continue to listen on the same port.

-

auth=type - changes the authentication type (pki | +

--auth|-a <type> - changes the authentication type (pki | password | trust) used by the PostgreSQL server. No change is made if the option is not present. If the option is present, omitting the --noLocalAuth causes local connections to require authentication. This command does not affect the presence or absence of passwords or Distinguished Names for existing users.

-

dn=name - specifies the Distinguished Name for the admin - user and is required for auth=pki.

+

--dn <distinguished-name> - specifies the Distinguished Name for the admin + user and is required for --auth pki.

resetpassword, and changeprivilege.

-

port=portnum - specifies the port on which to connect with +

--port|-p <portnum> - specifies the port on which to connect with the PostgreSQL server.

-

user=username - specifies a user name to use when connecting +

--user|-u <username> - specifies a user name to use when connecting to the PostgreSQL server.

-

cert=/certfile-path - provides the absolute file path to the +

--cert </certfile-path> - provides the absolute file path to the user's certificate when connecting to a PostgreSQL server that requires PKI authentication.

@@ -249,31 +250,31 @@
-
-    bsim createdatabase  <bsimURL> <config_template> [name="<name>"] [owner="<owner>"] [description="<text>"] [--nocallgraph]
-    bsim setmetadata     <bsimURL> [name="<name>"] [owner="<owner>"] [description="<text>"]\n" + 
+
+    bsim createdatabase  <bsimURL> <config_template> [--name|-n "<name>"] [--owner|-o "<owner>"] [--description|-d "<text>"] [--nocallgraph]
+    bsim setmetadata     <bsimURL> [--name|-n "<name>"] [--owner|-o "<owner>"] [--description|-d "<text>"]\n" + 
     bsim addexecategory  <bsimURL> <category_name> [--date]
     bsim addfunctiontag  <bsimURL> <tag_name>
     bsim dropindex       <bsimURL>
     bsim rebuildindex    <bsimURL>
     bsim prewarm         <bsimURL>
-    bsim generatesigs    <ghidraURL> </xmldirectory> config=<config_template> [--overwrite]
-    bsim generatesigs    <ghidraURL> </xmldirectory> bsim=<bsimURL> [--commit] [--overwrite]
-    bsim generatesigs    <ghidraURL> bsim=<bsimURL>
-    bsim commitsigs      <bsimURL> </xmldirectory> [md5=<hash>] [override=<ghidraURL>]
-    bsim generateupdates <ghidraURL> </xmldirectory> config=<config_template> [--overwrite]
-    bsim generateupdates <ghidraURL> </xmldirectory> bsim=<bsimURL> [--commit] [--overwrite]
-    bsim generateupdates <ghidraURL> bsim=<bsimURL>
+    bsim generatesigs    <ghidraURL> </xmldirectory> --config|-c <config_template> [--overwrite]
+    bsim generatesigs    <ghidraURL> </xmldirectory> --bsim|-b <bsimURL> [--commit] [--overwrite]
+    bsim generatesigs    <ghidraURL> --bsim|-b <bsimURL>
+    bsim commitsigs      <bsimURL> </xmldirectory> [--md5|-m <hash>] [--override <ghidraURL>]
+    bsim generateupdates <ghidraURL> </xmldirectory> --config|-c <config_template> [--overwrite]
+    bsim generateupdates <ghidraURL> </xmldirectory> --bsim|-b <bsimURL> [--commit] [--overwrite]
+    bsim generateupdates <ghidraURL> --bsim|-b <bsimURL>
     bsim commitupdates   <bsimURL> </xmldirectory>
-    bsim listexes        <bsimURL> [md5=<hash>] [name=<exe_name>] [arch=<languageID>] [compiler=<cspecID>] [sortcol=<column_name>] [limit=<exe_count>] [--includelibs]
-    bsim getexecount     <bsimURL> [md5=<hash>] [name=<exe_name>] [arch=<languageID>] [compiler=<cspecID>] [--includelibs]
-    bsim delete          <bsimURL> [md5=<hash>] [name=<exe_name> [arch=<languageID>] [compiler=<cspecID>]]
-    bsim listfuncs       <bsimURL> [md5=<hash>] [name=<exe_name> [arch=<languageID>] [compiler=<cspecID>]] [--printselfsig] [--callgraph] [--printjustexe] [maxfunc=<max_count>]
-    bsim dumpsigs        <bsimURL> </xmldirectory> [md5=<hash>] [name=<exe_name> [arch=<languageID>] [compiler=<cspecID>]]
+    bsim listexes        <bsimURL> [--md5|-m <hash>] [--name|-n <exe_name>] [--arch|-a <languageID>] [--compiler <cspecID>] [--sortcol|-s md5|name] [--limit|-l <exe_count>] [--includelibs]
+    bsim getexecount     <bsimURL> [--md5|-m <hash>] [--name|-n <exe_name>] [--arch|-a <languageID>] [--compiler <cspecID>] [--includelibs]
+    bsim delete          <bsimURL> [--md5|-m <hash>] [--name|-n <exe_name> [--arch|-a <languageID>] [--compiler <cspecID>]]
+    bsim listfuncs       <bsimURL> [--md5|-m <hash>] [--name|-n <exe_name> [--arch|-a <languageID>] [--compiler <cspecID>]] [--printselfsig] [--callgraph] [--printjustexe] [--maxfunc <max_count>]
+    bsim dumpsigs        <bsimURL> </xmldirectory> [--md5|-m <hash>] [--name|-n <exe_name> [--arch|-a <languageID>] [--compiler <cspecID>]]
     
     Global options:
-        user=<username>
-        cert=<certfile-path>
+        --user|-u <username>
+        --cert <certfile-path>
 
 
@@ -294,7 +295,12 @@ "command">https:
, or file: protocol specified. The elastic: protocol is equivalent to and may be used in - place of the https: protocol.

+ place of the https: protocol. + Optional parameters for a given command are indicated by square brackets '[' and ']' + and always start with either '-' or '--' characters. If an associated value is required + and contains space characters it should be enclosed in double quotes. Options which require + a value may be separated by a space or a equal "=" character (e.g., + --name=myname).

@@ -313,19 +319,18 @@ (large_32, medium_32, medium_64, medium_cpool, medium_nosize).

-

name= - specifies a formal, more +

--name|-n - specifies a formal, more descriptive, name for the repository that can be used for the BSim client display.

-

owner= - gives a descriptive name +

--owner|-o - gives a descriptive name for the owner of the repository and/or the data it will contain.

-

description= - specifies a short +

--description|-d - specifies a short string describing the intended contents of the new repository.

-

--nocallgraph=yes/no - disables storing call relationships between - ingested functions. Default is to store call relationships.

+

--nocallgraph - disables storing call + relationships between ingested functions. Default is to store call relationships.

description metadata associated with a BSim server. A BSim server URL is required.

-

name= - specifies a formal, more +

--name|-n - specifies a formal, more descriptive, name for the repository that can be used for the BSim client display.

-

owner= - gives a descriptive name +

--owner|-o - gives a descriptive name for the owner of the repository and/or the data it will contain.

-

description= - specifies a short +

--description|-d - specifies a short string describing the intended contents of the new repository.

@@ -413,17 +418,16 @@ a Ghidra Server repository or project as specified by a Ghidra URL. The generated signatures may be retained as XML "sigs_" files within a specified XML storage directory and/or committed to a specified BSim database specified with the bsim=bsimURL option. If an XML storage directory is not + class="command">--bsim
option. If an XML storage directory is not specified, a BSim URL must be specified to which the data will be committed.

-

The config=config-template option may be specified when generating +

The --config|-c <config-template> option may be specified when generating XML "sigs_" signature files in the absence of a BSim database (see createdatabase for supported configurations). The generated files will be written to the specified XML storage directory. Creation of the signature - files can also be achieved by specifying the bsim=bsimURL - option instead of the config= option.

+ files can also be achieved by specifying the --bsim + option instead of the --config option.

The --overwrite option may be specified when an XML storage directory has also been @@ -444,8 +448,8 @@ repository and a path to a directory containing the "sigs_" XML files to commit are required.

-

override=ghidraURL - causes any Ghidra repository/project URL, +

--override <ghidraURL> - causes any Ghidra repository/project URL, describing the storage repository and path of executables that was recorded in the "sigs_" XML files during signature generation, to be overridden during the commit operation with the specified Ghidra URL.

@@ -461,17 +465,16 @@ function tags, categories, etc. are changed. Signatures are not affected. The generated updates may be retained as XML "update_" files within a specified XML storage directory and/or committed to a specified BSim database specified with the - bsim=bsimURL option. If an XML storage directory is not + --bsim option. If an XML storage directory is not specified, a BSim URL must be specified to which the data will be committed.

-

The config=config-template option may be specified when generating +

The --config|-c <config-template> option may be specified when generating XML "update_" files in the absence of a BSim database (see createdatabase for supported configurations). The generated files will be written to the specified XML storage directory. Creation of the update - files can also be achieved by specifying the bsim=bsimURL - option instead of the config= option.

+ files can also be achieved by specifying the --bsim + option instead of the --config option.

The --overwrite option may be specified when an XML storage directory has also been @@ -499,30 +502,27 @@

List all executable program records within a specified BSim database repository which satisfy the specified criteria. A BSim URL specifying the repository must be provided, and one of two options, md5= or name=, that indicate the specific executable must + "command">--md5 or --name , that indicate the specific executable must also be given. All matching executable records will be listed.

-

md5=32-hexdigits - specifies an executable via its MD5 - checksum.

+

--md5|-m - specifies an executable via its MD5 + checksum as 32 hexidecimal digits.

-

name= - specifies an executable +

--name|-n - specifies an executable name which may match one or more executable records.

-

arch= - specifies an architecture +

--arch|-a - specifies an architecture as a Ghidra processor id which will be used to filter executables.

-

compiler= - specifies a compiler +

--compiler - specifies a compiler specification id which will be used to filter executables.

-

sortcol=column - Indicates which display column should be used - to sort the results (MD5 | NAME; default: - MD5).

+

--sortcol|-s - Specifies which display + column should be used to sort the results (md5 | name; default: + md5).

-

limit=max_count - specifies the maximum number of executables +

--limit|-l - specifies the maximum number of executables to be listed which match the search criteria (default=20, a value of 0 indicates no limit).

@@ -538,21 +538,20 @@

Get the total number of executable program records within a specified BSim database repository which satisfy the specified criteria. A BSim URL specifying the repository must be provided, and one of two options, md5= or name=, that indicate the specific executable must + "command">--md5 or --name, that indicate the specific executable must also be given. All matching executable records will be listed.

-

md5=32-hexdigits - specifies an executable via its MD5 - checksum.

+

--md5|-m - specifies an executable via its MD5 + checksum as 32 hexidecimal digits.

-

name= - specifies an executable +

--name|-n - specifies an executable name which may match one or more executable records.

-

arch= - specifies an architecture +

--arch|-a - specifies an architecture as a Ghidra processor id which will be used to filter executables.

-

compiler= - specifies a compiler +

--compiler - specifies a compiler specification id which will be used to filter executables.

--includelibs - If specified, executable @@ -565,26 +564,25 @@

Remove all records associated with a specific executable from a BSim repository. A BSim URL specifying the repository must be provided, and one of two options, - md5= or name=, that indicate the specific executable must + --md5 or --name, that indicate the specific executable must also be given. All associated executable and function records are removed. If an executable cannot be uniquely identified an error will result.

-

md5=32-hexdigits - specifies the executable via its MD5 - checksum.

+

--md5|-m - specifies an executable via its MD5 + checksum as 32 hexidecimal digits.

-

name= - specifies an executable +

--name|-n - specifies an executable name which may match one or more executable records.

-

arch= - specifies an architecture +

--arch|-a - specifies an architecture as a Ghidra processor id, when the name option is not enough to uniquely specify the + "command">--name option is not enough to uniquely specify the executable.

-

compiler= - specifies a compiler - id string, when the name option is +

--compiler - specifies a compiler + id string, when the --name option is not enough to uniquely specify the executable.

@@ -594,25 +592,24 @@

List all function records associated with a specific executable from a BSim repository. A BSim URL specifying the repository must be provided, and one of two - options, md5= or name=, that indicate the specific executable must + options, --md5 or --name, that indicate the specific executable must also be given. All associated executable and function records are listed. If an executable cannot be uniquely identified an error will result.

-

md5=32-hexdigits - specifies the executable via its MD5 - checksum.

+

--md5|-m - specifies an executable via its MD5 + checksum as 32 hexidecimal digits.

-

name= - specifies an executable +

--name|-n - specifies an executable name which may match one or more executable records.

-

arch= - specifies an architecture +

--arch|-a - specifies an architecture as a Ghidra processor id, when the name option is not enough to uniquely specify the + "command">--name option is not enough to uniquely specify the executable.

-

compiler= - specifies a compiler - id string, when the name option is +

--compiler - specifies a compiler + id string, when the --name option is not enough to uniquely specify the executable.

--printselfsig - If specified, each @@ -628,8 +625,7 @@ also specified only the called libraries will be listed and not the specified functions.

-

maxfunc=max_count - specifies the maximum number of functions to +

--maxfunc - specifies the maximum number of functions to be listed which correspond to the identified executable (default=1000, a value of 0 indicates no limit).

@@ -641,25 +637,24 @@

Dump signature and metadata from a BSim repository for a specific executable to a "sigs_" XML file. A BSim server URL and a path to a directory where the new file will be stored must be given. One of two options, md5= or name=, that specify the particular executable + "command">--md5 or --name, that specify the particular executable must also be given. If an executable cannot be uniquely identified an error will result.

-

md5=32-hexdigits - specifies an executable via its MD5 - checksum.

+

--md5|-m - specifies an executable via its MD5 + checksum as 32 hexidecimal digits.

-

name= - specifies an executable +

--name|-n - specifies an executable name which may match one or more executable records.

-

arch= - specifies an architecture +

--arch|-a - specifies an architecture as a Ghidra processor id, when the name option is not enough to uniquely specify the + "command">--name option is not enough to uniquely specify the executable.

-

compiler= - specifies a compiler +

--compiler - specifies a compiler specification id, when the name option is not enough to uniquely specify the + "command">--name option is not enough to uniquely specify the executable.

@@ -671,12 +666,12 @@

These options apply to all bsim commands.

-

user=name - specifies a user to masquerade as when connecting +

--user|-u <username> - specifies a user to masquerade as when connecting to the server.

-

cert=path - provides a path to the user's certificate when +

--cert <certfile-path> - provides a path to the user's certificate when connecting to a server that requires PKI authentication.

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 a3cb2d7d04..9509130c56 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 @@ -214,7 +214,7 @@ $(ROOT)/support/bsim_ctl start /path/to/datadir - port=8000 + --port 8000
@@ -236,7 +236,7 @@ be reused.

The start command can take an optional - port= parameter. This can be used to specify + --port parameter. This can be used to specify a non-standard port for the PostgreSQL server to listen on. In this case, any subsequent reference to the BSim server, in the Ghidra client, or with the bsim command described below, must specify the port. @@ -293,7 +293,7 @@

bsim_ctl start /path/to/datadir - auth=trust

+ --auth trust

This is currently the default. No authentication is performed and privilege is granted based on the user name presented. Masquerading is possible.

@@ -304,7 +304,7 @@

bsim_ctl start /path/to/datadir - auth=password

+ --auth password

Users are authenticated via password. A default password 'changeme' is established when the new user is created. Passwords can be changed by the user @@ -315,12 +315,12 @@

pki
-

bsim_ctl start /path/to/datadir auth=pki - ca=/path/to/rootcert

+

bsim_ctl start /path/to/datadir --auth pki + --cafile "/path/to/rootcert"

Users are authenticated by PKI certificates. Upon initialization, the BSim server must be provided (via the ca= option) a file containing the public keys + "command">--cafile option) a file containing the public keys for the certificate authorities used to issue user's certificates. The file consists of the authoritative certificates in PEM format concatenated together.

@@ -338,7 +338,7 @@

With PKI authentication enabled, at the time a new user role is established with the server, the X.509 Distinguished Name, as bound to the user's certificate, must be associated with the user name via the dn= option. See --dn option. See “Adding Users to the Database”.

@@ -358,7 +358,7 @@ + /datadir/path --auth password
$(ROOT)/support/bsim_ctl changeauth - /datadir/path auth=password
@@ -401,7 +401,7 @@ $(ROOT)/support/bsim_ctl adduser username dn="C=US,ST=MD,CN=Firstname User" + "emphasis">username --dn "C=US,ST=MD,CN=Firstname User"
@@ -410,7 +410,7 @@ initially be set to 'changeme'. If PKI authentication has been set for the server, The Distinguished Name, as bound to the new user's certificated must be provided when issuing the adduser command, via the - dn= option. The Distinguished Name must + --dn option. The Distinguished Name must be presented as a string containing a comma separated sequence of attribute/value pairs that uniquely identifies a certificate. Currently, the Common Name (CN=) is the only attribute inspected by the PostgreSQL server, so other attributes can be omitted.

@@ -836,17 +836,17 @@ curl -k -u elastic:XXXXXX -X POST "https://localhost:9200/_security/user/ghidrau + "emphasis">bsimURL --name "BSim Database" + "emphasis">bsimURL --owner "Administrators" + "emphasis">bsimURL --description "Files of interest"
$(ROOT)/support/bsim setmetadata bsimURL "name=BSim Database"
$(ROOT)/support/bsim setmetadata bsimURL "owner=Administrators"
$(ROOT)/support/bsim setmetadata bsimURL "description=Files of interest"
@@ -855,8 +855,8 @@ curl -k -u elastic:XXXXXX -X POST "https://localhost:9200/_security/user/ghidrau changed at any time and do not otherwise affect the records contained in the database. Multiple command-line parameters can be fed to bsim setmetadata so long as each one starts with name=, owner=, or - description= respectively. Quoting may be + "bold">--name, --owner, or + --description respectively. Quoting of values may be necessary to get some strings to be interpreted as a single command-line parameter.

diff --git a/Ghidra/Features/BSim/src/main/help/help/topics/BSim/IngestProcess.html b/Ghidra/Features/BSim/src/main/help/help/topics/BSim/IngestProcess.html index 68562457a9..8265d68ca1 100644 --- a/Ghidra/Features/BSim/src/main/help/help/topics/BSim/IngestProcess.html +++ b/Ghidra/Features/BSim/src/main/help/help/topics/BSim/IngestProcess.html @@ -112,20 +112,20 @@ "command">bsim generatesigs command. Signatures may be written as XML files to a local directory and/or committed directly to a specified BSim database. If not immediately committing to a database and only storing the XML files an appropriate - database config= may be specified in lieu of a BSim database URL - (bsimURL) if database specific executable categories and function tags are not - utilized. Use of the config= option does not require a running BSim server.

+ database configuration may be specified using the --config option in lieu of a BSim database URL + (--bsim <bsimURL>) if database specific executable categories and function tags are not + utilized. Use of the --config option does not require a running BSim server.

+ --bsim <bsimURL>
$(ROOT)/support/bsim generatesigs - <ghidraURL> </xmldirectory> config=<config_template> + <ghidraURL> </xmldirectory> --config <config_template> [--overwrite]
$(ROOT)/support/bsim generatesigs <ghidraURL> </xmldirectory> - bsim=<bsimURL> [--commit] [--overwrite]
+ --bsim <bsimURL> [--commit] [--overwrite]
$(ROOT)/support/bsim generatesigs <ghidraURL> - bsim=<bsimURL>
@@ -137,7 +137,7 @@ $(ROOT)/support/bsim generatesigs ghidra://localhost/repo/folder /xmldirectory - bsim=postgresql://localhost/repo + --bsim postgresql://localhost/repo
@@ -148,7 +148,7 @@
$(ROOT)/support/bsim generatesigs - ghidra://localhost/repo/folder /xmldirectory bsim=postgresql://localhost/repo + ghidra://localhost/repo/folder /xmldirectory --bsim postgresql://localhost/repo --commit
@@ -176,7 +176,7 @@ the signature generation process, such as database specific executable categories or function tags. As in the example above, configuration information is pulled from the BSim server and signatures are generated from the Ghidra Server - executables. If the config= + executables. If the --config option is used, assuming the template it specifies is the same one used to create the database and there are no executable categories or function tags, the BSim server does not need to be running.

@@ -199,7 +199,7 @@ + postgresql://localhost/repo /xmldirectory [--override <ghidraURL>]
$(ROOT)/support/bsim commitsigs - postgresql://localhost/repo /xmldirectory [override=ghidraURL]
@@ -215,7 +215,7 @@ "emphasis">repository and path associated with it in the form of a ghidra:// URL that was recorded when the XML files were generated. This path can be overridden with the - optional override= parameter where a revised + optional --override option where a revised Ghidra URL may be specified.

The bsim commitsigs command can be @@ -526,21 +526,21 @@ public void adjustTags(Address myaddress) throws Exception {
$(ROOT)/support/bsim delete bsimURL md5=<bsimURL> --md5 7abf...
$(ROOT)/support/bsim delete bsimURL name=<bsimURL> --name ...
-

In the md5 form, you specify the 32 character +

In the --md5 form, you specify the 32 character hex representation of the md5 hash of the executable, which should identify it - uniquely. Using the name form, there is the + uniquely. Using the --name form, there is the possibility that the name is not unique, in which case the command will fail.

If a unique executable is identified, its metadata record will be removed, and the @@ -580,11 +580,11 @@ public void adjustTags(Address myaddress) throws Exception { @@ -596,11 +596,13 @@ public void adjustTags(Address myaddress) throws Exception { stripped down metadata XML files for every executable contained within the repository folder specified by the ghidraURL. Just like the generatesigs command, it can take an optional config=config_template parameter, which - allows the command to execute without the BSim server running. It can also take an + class="bold">--config <config_template> parameter, which + allows the command to execute without the BSim server running, otherwise a --bsim <bsimURL> + parameter is required. It can also take an optional --overwrite parameter, causing it - to overwrite any previously generated XML files. If a - bsim=bsimURL is specified with the --commit + to overwrite any previously generated XML files. If the + --bsim option is specified with the --commit option updates will be committed directly to the database. A BSim database commit is always performed using the specified bsimURL if an xmldirectory is not specified.

@@ -635,7 +637,7 @@ public void adjustTags(Address myaddress) throws Exception {
$(ROOT)/support/bsim generateupdates - <ghidraURL> </xmldirectory> config=<config_template> + <ghidraURL> </xmldirectory> --config <config_template> [--overwrite]
$(ROOT)/support/bsim generateupdates <ghidraURL> </xmldirectory> - bsim=<bsimURL> [--commit] [--overwrite]
- $(ROOT)/support/bsim generateupdates <ghidraURL> bsim=<bsimURL>
+ --bsim <bsimURL> [--commit] [--overwrite]
+ $(ROOT)/support/bsim generateupdates <ghidraURL> --bsim <bsimURL>

$(ROOT)/support/bsim commitupdates <bsimURL> </xmldirectory>
+ "emphasis"><bsimURL>
$(ROOT)/support/bsim dropindex bsimURL
@@ -646,7 +648,7 @@ public void adjustTags(Address myaddress) throws Exception { + "emphasis"><bsimURL>
$(ROOT)/support/bsim rebuildindex bsimURL
diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimControlLaunchable.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimControlLaunchable.java old mode 100755 new mode 100644 index d5f7cc7c06..e318d598f3 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimControlLaunchable.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimControlLaunchable.java @@ -29,6 +29,7 @@ import javax.naming.ldap.LdapName; import javax.naming.ldap.Rdn; import javax.security.auth.DestroyFailedException; +import org.apache.commons.lang3.StringUtils; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; @@ -40,7 +41,7 @@ import ghidra.framework.client.ClientUtil; import ghidra.net.ApplicationKeyManagerUtils; import ghidra.util.Msg; import ghidra.util.exception.AssertException; -import ghidra.util.task.TaskMonitor; +import ghidra.util.exception.CancelledException; import ghidra.util.xml.SpecXmlUtils; import ghidra.xml.NonThreadedXmlPullParserImpl; import ghidra.xml.XmlPullParser; @@ -48,22 +49,68 @@ import utilities.util.FileUtilities; public class BSimControlLaunchable implements GhidraLaunchable { - public static final String PORT_OPTION = "port="; - public static final String CAFILE_OPTION = "cafile="; - public static final String AUTH_OPTION = "auth="; - public static final String DN_OPTION = "dn="; - public static final String CERT_OPTION = "cert="; + // bsim_ctl commands + public final static String COMMAND_START = "start"; + public final static String COMMAND_STOP = "stop"; + public final static String COMMAND_RESET_PASSWORD = "resetpassword"; + public final static String COMMAND_CHANGE_PRIVILEGE = "changeprivilege"; + public final static String COMMAND_ADDUSER = "adduser"; + public final static String COMMAND_DROPUSER = "dropuser"; + public final static String COMMAND_CHANGEAUTH = "changeauth"; + + // Options that require a value argument + public static final String CAFILE_OPTION = "--cafile"; + public static final String AUTH_OPTION = "--auth"; + public static final String DN_OPTION = "--dn"; + + // Global options that require a value argument + public static final String PORT_OPTION = "--port"; + public static final String USER_OPTION = "--user"; + public static final String CERT_OPTION = "--cert"; + + // Define set of options that require a second value argument + private static final Set VALUE_OPTIONS = + Set.of(PORT_OPTION, USER_OPTION, CERT_OPTION, CAFILE_OPTION, AUTH_OPTION, DN_OPTION); + + private static final Set GLOBAL_OPTIONS = Set.of(PORT_OPTION, USER_OPTION, CERT_OPTION); + + // Boolean options public static final String NO_LOCAL_AUTH_OPTION = "--noLocalAuth"; - public static final String USER_OPTION = "user="; public static final String FORCE_OPTION = "--force"; - private final static String START_COMMAND = "start"; - private final static String STOP_COMMAND = "stop"; - private final static String PASSWORD_COMMAND = "resetpassword"; - private final static String PRIVILEGE_COMMAND = "changeprivilege"; - private final static String ADDUSER_COMMAND = "adduser"; - private final static String DROPUSER_COMMAND = "dropuser"; - private final static String RESET_COMMAND = "changeauth"; + private static final Map SHORTCUT_OPTION_MAP = new HashMap<>(); + static { + SHORTCUT_OPTION_MAP.put("-a", AUTH_OPTION); + SHORTCUT_OPTION_MAP.put("-p", PORT_OPTION); + SHORTCUT_OPTION_MAP.put("-u", USER_OPTION); + } + + //@formatter:off + // Populate ALLOWED_OPTION_MAP for each command + private static final Set START_OPTIONS = + Set.of(AUTH_OPTION, DN_OPTION, NO_LOCAL_AUTH_OPTION, CAFILE_OPTION); + private static final Set STOP_OPTIONS = + Set.of(FORCE_OPTION); + private static final Set RESET_PASSWORD_OPTIONS = Set.of(); + private static final Set CHANGE_PRIVILEGE_OPTIONS = Set.of(); + private static final Set ADDUSER_OPTIONS = + Set.of(DN_OPTION); + private static final Set DROPUSER_OPTIONS = Set.of(); + private static final Set CHANGEAUTH_OPTIONS = Set.of( + AUTH_OPTION, NO_LOCAL_AUTH_OPTION, CAFILE_OPTION); + + //@formatter:on + private static final Map> ALLOWED_OPTION_MAP = new HashMap<>(); + static { + ALLOWED_OPTION_MAP.put(COMMAND_START, START_OPTIONS); + ALLOWED_OPTION_MAP.put(COMMAND_STOP, STOP_OPTIONS); + ALLOWED_OPTION_MAP.put(COMMAND_RESET_PASSWORD, RESET_PASSWORD_OPTIONS); + ALLOWED_OPTION_MAP.put(COMMAND_CHANGE_PRIVILEGE, CHANGE_PRIVILEGE_OPTIONS); + ALLOWED_OPTION_MAP.put(COMMAND_ADDUSER, ADDUSER_OPTIONS); + ALLOWED_OPTION_MAP.put(COMMAND_DROPUSER, DROPUSER_OPTIONS); + ALLOWED_OPTION_MAP.put(COMMAND_CHANGEAUTH, CHANGEAUTH_OPTIONS); + } + private final static String POSTGRES = "postgresql"; private final static String POSTGRES_BUILD_SCRIPT = "Ghidra/Features/BSim/make-postgres.sh"; private final static String POSTGRES_CONFIGFILE = "postgresql.conf"; @@ -80,73 +127,245 @@ public class BSimControlLaunchable implements GhidraLaunchable { private final static int AUTHENTICATION_NONE = 0; private final static int AUTHENTICATION_PASSWORD = 1; private final static int AUTHENTICATION_PKI = 2; - private GhidraApplicationLayout layout = null; // For holding on to JDBC logger so we can filter messages - private File dataDirectory; // Directory containing postgres datafiles - private File postgresRoot; // Directory containing postgres software - private File postgresControl; // "pg_ctl" utility within postgres software - private File certAuthorityFile = null; // Certificate authority file provided by the user - private String certParameter = null; // Path to certificate provided by user - private String distinguishedName = null; // Certificate distinguished name provided by the user - private String commonName = null; // Common name extracted from distinguishedName - private String connectingUserName = null; // User-name used to establish connection - private String specifiedUserName = null; // -username- (add/drop) operation is being performed on - private boolean adminPrivilegeRequested = false; // true is attempting to give user admin privileges - private boolean forceShutdown = false; // Whether or not to force a shutdown (--force) - private String loadLibraryVar = null; // Environment variable pointing to postgres shared libraries - private String loadLibraryValue = null; // Directory containing shared libraries within postgres software - private int port = -1; // Port over which to connect to postgres server, (-1 indicates default port is used) - private int localAuthentication = AUTHENTICATION_NONE; // Type of authentication required for local connections - private int hostAuthentication = AUTHENTICATION_NONE; // Type of authentication for remote connections - private boolean authConfigPresent = false; // True if the [auth=..] option or the [--noLocalAuth] is present - private File passwordFile = null; // File containing newly established password - private char[] adminPasswordData = null; // Password data being sent to postgres server for authentication + + private GhidraApplicationLayout layout; + + private File dataDirectory; // Directory containing postgres datafiles + private File postgresRoot; // Directory containing postgres software + private File postgresControl; // "pg_ctl" utility within postgres software + private File certAuthorityFile; // Certificate authority file provided by the user + private String certParameter; // Path to certificate provided by user + private String distinguishedName; // Certificate distinguished name provided by the user + private String commonName; // Common name extracted from distinguishedName + private String connectingUserName; // User-name used to establish connection + private String specifiedUserName; // -username- (add/drop) operation is being performed on + private boolean adminPrivilegeRequested; // true is attempting to give user admin privileges + private boolean forceShutdown; // Whether or not to force a shutdown (--force) + private String loadLibraryVar; // Environment variable pointing to postgres shared libraries + private String loadLibraryValue; // Directory containing shared libraries within postgres software + private int port; // Port over which to connect to postgres server, (-1 indicates default port is used) + private int localAuthentication; // Type of authentication required for local connections + private int hostAuthentication; // Type of authentication for remote connections + private boolean authConfigPresent; // True if the [auth=..] option or the [--noLocalAuth] is present + private File passwordFile; // File containing newly established password + private char[] adminPasswordData; // Password data being sent to postgres server for authentication // Database connection that can be persisted so we don't need to recreate one // for every call. private Connection localConnection; /** - * Exception triggered by missing, unknown, or improperly formatted command-line arguments + * Constructor for launching from the console */ - public static class ArgumentException extends Exception { - public ArgumentException(String message) { - super(message); - } + public BSimControlLaunchable() { + } + + private void clearParams() { + dataDirectory = null; + postgresRoot = null; + postgresControl = null; + certAuthorityFile = null; + certParameter = null; + distinguishedName = null; + commonName = null; + connectingUserName = null; + specifiedUserName = null; + adminPrivilegeRequested = false; + forceShutdown = false; + loadLibraryVar = null; + loadLibraryValue = null; + port = -1; + localAuthentication = AUTHENTICATION_NONE; + hostAuthentication = AUTHENTICATION_NONE; + authConfigPresent = false; + passwordFile = null; + adminPasswordData = null; } /** - * Class for processing standard output or standard error for processes invoked by BSimControl - * The streams can be optionally suppressed or dumped to System.out + * Read required parameters followed by optional parameters + * @param params is the original array of command line parameters */ - private class IOThread extends Thread { - private BufferedReader shellOutput; // Reader for the particular output stream - private boolean suppressOutput; // If false, shell output is printed on the console + private String readCommandLine(String[] params) throws IllegalArgumentException, IOException { - public IOThread(InputStream input, boolean suppressOut) { - shellOutput = new BufferedReader(new InputStreamReader(input)); - suppressOutput = suppressOut; + int slot = 0; + + checkRequiredParam(params, slot, "command"); + String command = params[slot++]; + + switch (command) { + case COMMAND_START: + scanDataDirectory(params, slot++); + break; + case COMMAND_STOP: + scanDataDirectory(params, slot++); + break; + case COMMAND_ADDUSER: + scanDataDirectory(params, slot++); + scanUsername(params, slot++); + break; + case COMMAND_DROPUSER: + scanDataDirectory(params, slot++); + scanUsername(params, slot++); + break; + case COMMAND_RESET_PASSWORD: + scanUsername(params, slot++); + break; + case COMMAND_CHANGEAUTH: + scanDataDirectory(params, slot++); + break; + case COMMAND_CHANGE_PRIVILEGE: + scanUsername(params, slot++); + scanPrivilege(params, slot++); + break; + default: + throw new IllegalArgumentException("Unknown command: " + command); } - @Override - public void run() { - String line = null; - try { - while ((line = shellOutput.readLine()) != null) { - if (!suppressOutput) { - System.out.println(line); - } + readOptions(command, params, slot); + + return command; + } + + /** + * Read in any optional parameters, strip them from the parameter stream + * @param command command name + * @param params is the original array of command line parameters + * @param discard number of params already consumed + */ + private void readOptions(String command, String[] params, int discard) { + + boolean sawNoLocalAuth = false; + + Set allowedParams = ALLOWED_OPTION_MAP.get(command); + if (allowedParams == null) { + throw new IllegalArgumentException("Unsupported command: " + command); + } + + for (int i = discard; i < params.length; ++i) { + String optionName = params[i]; + String value = null; + + if (optionName.startsWith("-")) { + // although not prefered, allow option value to be specified as --option=value + int ix = optionName.indexOf("="); + if (ix > 1) { + value = optionName.substring(ix + 1); + optionName = optionName.substring(0, ix); } } - catch (Exception e) { - // DO NOT USE LOGGING HERE (class loader) - System.err.println("Unexpected Exception: " + e.getMessage()); - e.printStackTrace(System.err); + + String option = optionName; + + if (optionName.startsWith("-") && !optionName.startsWith("--")) { + option = SHORTCUT_OPTION_MAP.get(optionName); // map option to -- long form + if (option == null) { + throw new IllegalArgumentException("Unsupported option use: " + optionName); + } } + + if (!option.startsWith("--")) { + throw new IllegalArgumentException("Unexpected argument: " + option); + } + + if (!GLOBAL_OPTIONS.contains(option) && !allowedParams.contains(option)) { + throw new IllegalArgumentException("Unsupported option use: " + optionName); + } + + if (!VALUE_OPTIONS.contains(option)) { + // option without value arg + if (value != null) { + throw new IllegalArgumentException( + "Unsupported option specification: " + optionName + "="); + } + } + else if (StringUtils.isBlank(value)) { + // consume next param as option value + if (++i == params.length) { + throw new IllegalArgumentException("Missing option value: " + optionName); + } + value = params[i]; + } + + switch (option) { + case PORT_OPTION: + port = parsePositiveIntegerOption(optionName, value); + break; + case USER_OPTION: + connectingUserName = value; + break; + case CERT_OPTION: + certParameter = value; + break; + case CAFILE_OPTION: + certAuthorityFile = new File(value); + break; + case AUTH_OPTION: + authConfigPresent = true; + String type = value; + if (type.equals("pki")) { + hostAuthentication = AUTHENTICATION_PKI; + localAuthentication = AUTHENTICATION_PKI; + } + else if (type.equals("password")) { + hostAuthentication = AUTHENTICATION_PASSWORD; + localAuthentication = AUTHENTICATION_PASSWORD; + } + else if (type.equals("trust") || type.equals("none")) { + hostAuthentication = AUTHENTICATION_NONE; + localAuthentication = AUTHENTICATION_NONE; + } + else { + throw new IllegalArgumentException("Unknown authentication method: " + + type + " : options are trust, password or pki"); + } + break; + case DN_OPTION: + distinguishedName = value; + validateDistinguishedName(); + break; + case NO_LOCAL_AUTH_OPTION: + sawNoLocalAuth = true; + break; + case FORCE_OPTION: + forceShutdown = true; + break; + default: + throw new AssertionError("Missing option handling: " + option); + } + } + + if (sawNoLocalAuth) { // Turn off authentication for local connections + authConfigPresent = true; + localAuthentication = AUTHENTICATION_NONE; + } + if (connectingUserName == null) { + connectingUserName = ClientUtil.getUserName(); } } - public BSimControlLaunchable() { - // Constructor for main launcher + private void checkRequiredParam(String[] params, int index, String name) { + if (params.length <= index) { + throw new IllegalArgumentException("Missing required parameter: " + name); + } + String p = params[index]; + if (p.startsWith("--")) { + throw new IllegalArgumentException( + "Missing required parameter (" + name + ") before specified option: " + p); + } + } + + private int parsePositiveIntegerOption(String option, String optionValue) { + try { + int value = Integer.valueOf(optionValue); + if (value < 0) { + throw new IllegalArgumentException("Negative value not permitted for " + option); + } + return value; + } + catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid integer value specified for " + option); + } } /** @@ -179,9 +398,12 @@ public class BSimControlLaunchable implements GhidraLaunchable { * Parse the -distinguishedName- String, verifying it is has the correct format for a * X509 certificate distinguished name. Try to extract the common name portion of the * distinguished name and assign it to -commonName- - * @throws ArgumentException if the distinguished name is improperly formatted or the common name is missing + * @throws IllegalArgumentException if the distinguished name is improperly formatted or the common name is missing */ - private void validateDistinguishedName() throws ArgumentException { + private void validateDistinguishedName() throws IllegalArgumentException { + if (distinguishedName == null) { + return; + } commonName = null; try { LdapName ldapName = new LdapName(distinguishedName); @@ -192,11 +414,11 @@ public class BSimControlLaunchable implements GhidraLaunchable { } } if (commonName == null) { - throw new ArgumentException("Missing common name attribute"); + throw new IllegalArgumentException("Missing common name attribute"); } } catch (Exception e) { - throw new ArgumentException("Improperly formatted distinguished name"); + throw new IllegalArgumentException("Improperly formatted distinguished name"); } } @@ -297,6 +519,10 @@ public class BSimControlLaunchable implements GhidraLaunchable { * @throws IOException if the password file cannot be deleted */ private void cleanupPasswordData() throws IOException { + + clearPasswordData(adminPasswordData); + adminPasswordData = null; + if (passwordFile != null) { if (!passwordFile.delete()) { throw new IOException( @@ -304,8 +530,6 @@ public class BSimControlLaunchable implements GhidraLaunchable { } passwordFile = null; } - clearPasswordData(adminPasswordData); - adminPasswordData = null; } /** @@ -367,18 +591,6 @@ public class BSimControlLaunchable implements GhidraLaunchable { } } - /** - * Initialize enough of Ghidra to allow navigation of configuration files and to allow SSL connections - * @throws IOException if the headless authenticator cannot be initialized - * @throws ClassNotFoundException if the postgres driver class cannot be found - */ - private void initializeApplication() throws IOException, ClassNotFoundException { - if (layout != null) { - // Initialize application environment consistent with bsim command - BSimLaunchable.initializeApplication(layout, 0, connectingUserName, certParameter); - } - } - /** * Create a local connection to a postgres server. A full SSL connection is created using * Ghidra's infrastructure. If the initial connection fails because password authentication @@ -678,7 +890,7 @@ public class BSimControlLaunchable implements GhidraLaunchable { else if (hostAuthentication == AUTHENTICATION_PKI) { if (commonName == null) { throw new GeneralSecurityException( - "Distinguished name required for " + connectingUserName + " (dn=\"..\")"); + "Distinguished name option (--dn) required for " + connectingUserName); } checkCertAuthorityFile(); } @@ -717,19 +929,19 @@ public class BSimControlLaunchable implements GhidraLaunchable { /** * Scan the PostgreSQL data directory from the command-line * Make sure the directory exists and establish the File object -dataDirectory- - * @param params are the command-line options - * @param slot is the position to retrieve the data directory - * @throws ArgumentException if the data directory is invalid + * @param params are the command-line arguments + * @param slot is the position to retrieve the data directory argument + * @throws IllegalArgumentException if the data directory is invalid * @throws IOException if the canonical file cannot be retrieved */ private void scanDataDirectory(String[] params, int slot) - throws ArgumentException, IOException { + throws IllegalArgumentException, IOException { if (params.length <= slot) { - throw new ArgumentException("Missing data directory"); + throw new IllegalArgumentException("Missing data directory"); } dataDirectory = new File(params[slot]); if (!dataDirectory.isDirectory()) { - throw new ArgumentException( + throw new IllegalArgumentException( "Data directory " + dataDirectory.getAbsolutePath() + " does not exist"); } dataDirectory = dataDirectory.getCanonicalFile(); @@ -737,13 +949,13 @@ public class BSimControlLaunchable implements GhidraLaunchable { /** * Scan the username from the command-line - * @param params are the command-line options - * @param slot is the position to retrieve the username - * @throws ArgumentException if the user name is not in the given params + * @param params are the command-line arguments + * @param slot is the position to retrieve the username argument + * @throws IllegalArgumentException if the user name is not in the given params */ - private void scanUsername(String[] params, int slot) throws ArgumentException { + private void scanUsername(String[] params, int slot) throws IllegalArgumentException { if (params.length <= slot) { - throw new ArgumentException("Missing username"); + throw new IllegalArgumentException("Missing username"); } specifiedUserName = params[slot]; } @@ -751,13 +963,13 @@ public class BSimControlLaunchable implements GhidraLaunchable { /** * Scan command-line for a particular privilege level. Administrator privileges are * requested with the exact String "admin", anything is a request for a read-only user - * @param params are the command-line options - * @param slot is the position to retrieve the user name - * @throws ArgumentException the privilege parameter is missing + * @param params are the command-line arguments + * @param slot is the position to retrieve the user name argument + * @throws IllegalArgumentException the privilege parameter is missing */ - private void scanPrivilege(String[] params, int slot) throws ArgumentException { + private void scanPrivilege(String[] params, int slot) throws IllegalArgumentException { if (params.length <= slot) { - throw new ArgumentException("Missing desired privilege (admin or user)"); + throw new IllegalArgumentException("Missing desired privilege (admin or user)"); } if (params[slot].equals("admin")) { adminPrivilegeRequested = true; @@ -766,7 +978,7 @@ public class BSimControlLaunchable implements GhidraLaunchable { adminPrivilegeRequested = false; } else { - throw new ArgumentException("Expecting privilege option (admin or user)"); + throw new IllegalArgumentException("Expecting privilege option (admin or user)"); } } @@ -789,7 +1001,7 @@ public class BSimControlLaunchable implements GhidraLaunchable { if (localAuthentication == AUTHENTICATION_PKI && certParameter == null) { throw new GeneralSecurityException( - "Path to certificate necessary to start server (cert=/path/to/cert)"); + "Path to certificate necessary to start server (--cert /path/to/cert)"); } File logFile = new File(dataDirectory, "logfile"); List command = new ArrayList(); @@ -883,58 +1095,6 @@ public class BSimControlLaunchable implements GhidraLaunchable { FileUtilities.copyFile(copyFile, identFile, false, null); } - /** - * Returns a list of all users registered with the BSim server. - * - * Note: This will return all users minus those created by Postgres (those that - * start with 'pg_'. - * - * @param dataDirectory the location of the Postgres database files - * @return map of database users and their admin status - * @throws Exception if there's a problem initializing the Application or searching for Postgres - */ - public Map getUserRolesCommand(String dataDirectory) throws Exception { - - String[] params = { dataDirectory }; - scanDataDirectory(params, 0); - initializeApplication(); - discoverPostgresInstall(); - - if (connectingUserName == null) { - connectingUserName = ClientUtil.getUserName(); - } - - adminPasswordData = null; - - localConnection = getOrCreateLocalConnection(); - - StringBuilder buffer = new StringBuilder(); - buffer.append("SELECT rolname, rolsuper from pg_roles"); - try (Statement st = localConnection.createStatement()) { - Map userToAdminMap = new HashMap<>(); - try (ResultSet rs = st.executeQuery(buffer.toString())) { - while (rs.next()) { - String user = rs.getString(1); - - if (user.startsWith("pg_")) { // default postgres role - ignore - continue; - } - Boolean isAdmin = rs.getBoolean(2); - userToAdminMap.put(user, isAdmin); - } - return userToAdminMap; - } - } - catch (SQLException e) { - Msg.error(this, "Error retrieving user roles from the Postgres database", e); - } - finally { - localConnection.close(); - } - - return Collections.emptyMap(); - } - /** * Add a new user to the currently running server on the local host. * A connection is established, using the local interface, and the "CREATE ROLE" command @@ -1073,7 +1233,7 @@ public class BSimControlLaunchable implements GhidraLaunchable { * @throws SAXException if the {@link #tuneConfig(File, File, File, File, File)} call fails * @throws GeneralSecurityException if there is no Distinguished Name supplied */ - private void resetCommand() + private void changeAuthCommand() throws IOException, InterruptedException, SAXException, GeneralSecurityException { discoverPostgresInstall(); File configFile = new File(dataDirectory, POSTGRES_CONFIGFILE); @@ -1195,7 +1355,7 @@ public class BSimControlLaunchable implements GhidraLaunchable { } } - private void privilegeCommand() throws Exception { + private void changePrivilegeCommand() throws Exception { localConnection = getOrCreateLocalConnection(); try { if (adminPrivilegeRequested) { @@ -1214,231 +1374,158 @@ public class BSimControlLaunchable implements GhidraLaunchable { } } - /** - * Parse the command-line. First argument is always a command, which may - * require additional arguments. Additional optional arguments may follow - * @param params is the array of command-line arguments - * @throws ArgumentException if the data directory cannot be scanned or the authentication method is invalid - * @throws IOException if the data directory cannot be scanned - */ - private void readCommandLine(String[] params) throws ArgumentException, IOException { - String command = params[0]; - int slot = 1; - if (command.equals(START_COMMAND)) { - scanDataDirectory(params, slot); - slot += 1; - } - else if (command.equals(STOP_COMMAND)) { - scanDataDirectory(params, slot); - slot += 1; - } - else if (command.equals(ADDUSER_COMMAND)) { - scanDataDirectory(params, slot); - slot += 1; - scanUsername(params, slot); - slot += 1; - } - else if (command.equals(DROPUSER_COMMAND)) { - scanDataDirectory(params, slot); - slot += 1; - scanUsername(params, slot); - slot += 1; - } - else if (command.equals(PASSWORD_COMMAND)) { - scanUsername(params, slot); - slot += 1; - } - else if (command.equals(RESET_COMMAND)) { - scanDataDirectory(params, slot); - slot += 1; - } - else if (command.equals(PRIVILEGE_COMMAND)) { - scanUsername(params, slot); - slot += 1; - scanPrivilege(params, slot); - slot += 1; - } - else { - throw new ArgumentException("Unknown command: " + command); - } - - // Scan for optional arguments - boolean sawNoLocalAuth = false; - for (int i = slot; i < params.length; ++i) { - String option = params[i]; - if (option.startsWith(PORT_OPTION)) { - port = Integer.parseInt(option.substring(PORT_OPTION.length())); - } - else if (option.startsWith(CAFILE_OPTION)) { - certAuthorityFile = new File(option.substring(CAFILE_OPTION.length())); - } - else if (option.startsWith(AUTH_OPTION)) { - authConfigPresent = true; - String type = option.substring(AUTH_OPTION.length()); - if (type.equals("pki")) { - hostAuthentication = AUTHENTICATION_PKI; - localAuthentication = AUTHENTICATION_PKI; - } - else if (type.equals("password")) { - hostAuthentication = AUTHENTICATION_PASSWORD; - localAuthentication = AUTHENTICATION_PASSWORD; - } - else if (type.equals("trust") || type.equals("none")) { - hostAuthentication = AUTHENTICATION_NONE; - localAuthentication = AUTHENTICATION_NONE; - } - else { - throw new ArgumentException( - "Unknown authentication method: " + type + " : options are trust, pki"); - } - } - else if (option.startsWith(DN_OPTION)) { - distinguishedName = option.substring(DN_OPTION.length()); - validateDistinguishedName(); - } - else if (option.startsWith(CERT_OPTION)) { - certParameter = option.substring(CERT_OPTION.length()); - } - else if (option.startsWith(NO_LOCAL_AUTH_OPTION)) { - sawNoLocalAuth = true; - } - else if (option.equals(FORCE_OPTION)) { - forceShutdown = true; - } - else if (option.startsWith(USER_OPTION)) { - connectingUserName = option.substring(USER_OPTION.length()); - } - else { - throw new ArgumentException("Unknown option: " + option); - } - } - if (sawNoLocalAuth) { // Turn off authentication for local connections - authConfigPresent = true; - localAuthentication = AUTHENTICATION_NONE; - } - if (connectingUserName == null) { - connectingUserName = ClientUtil.getUserName(); - } - } - /** * Runs the command specified by the given set of params. * - * @param params the command parameters - * @param monitor the task monitor - * @throws Exception if there is a problem executing the command - */ - public void run(String[] params, TaskMonitor monitor) throws Exception { - String command = params[0]; - try { - readCommandLine(params); - initializeApplication(); - if (command.equals(START_COMMAND)) { - startCommand(); - } - else if (command.equals(STOP_COMMAND)) { - stopCommand(); - } - else if (command.equals(ADDUSER_COMMAND)) { - addUserCommand(); - } - else if (command.equals(DROPUSER_COMMAND)) { - dropUserCommand(); - } - else if (command.equals(RESET_COMMAND)) { - resetCommand(); - } - else if (command.equals(PASSWORD_COMMAND)) { - passwordCommand(); - } - else if (command.equals(PRIVILEGE_COMMAND)) { - privilegeCommand(); - } - } - catch (SAXException e1) { - System.err.println("Error in server configuation data"); - System.err.println(e1.getMessage()); - throw e1; - } - catch (IOException e1) { - System.err.println("Error configuring PostgreSQL for BSim"); - System.err.println(e1.getMessage()); - throw e1; - } - catch (InterruptedException e) { - System.err.println("Command was interrupted"); - System.err.println(e.getMessage()); - throw e; - } - catch (SQLException e) { - System.err.println("Error connecting to the database"); - System.err.println(e.getMessage()); - throw e; - } - catch (GeneralSecurityException e) { - System.err.println("Error establishing server certificate"); - System.err.println(e.getMessage()); - throw e; - } - catch (ArgumentException e) { - System.err.println("Error in command line arguments"); - System.err.println(e.getMessage()); - throw e; - } - catch (ClassNotFoundException e) { - System.err.println("Could not find PostgreSQL JDBC driver"); - System.err.println(e.getMessage()); - throw e; - } - try { - cleanupPasswordData(); - } - catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * Runs the command specified by the given set of params. - * - * @param params the command parameters - * @throws Exception if there is a problem executing the command + * @param params the parameters specifying the command + * @throws IllegalArgumentException if invalid params have been specified + * @throws Exception if there's an error during the operation + * @throws CancelledException if processing is cancelled */ public void run(String[] params) throws Exception { - run(params, TaskMonitor.DUMMY); + try { + clearParams(); + + String command = readCommandLine(params); + + initializeApplication(); + + switch (command) { + case COMMAND_START: + startCommand(); + break; + case COMMAND_STOP: + stopCommand(); + break; + case COMMAND_ADDUSER: + addUserCommand(); + break; + case COMMAND_DROPUSER: + dropUserCommand(); + break; + case COMMAND_CHANGEAUTH: + changeAuthCommand(); + break; + case COMMAND_RESET_PASSWORD: + passwordCommand(); + break; + case COMMAND_CHANGE_PRIVILEGE: + changePrivilegeCommand(); + break; + default: + throw new IllegalArgumentException("Unknown command: " + command); + } + } + finally { + try { + cleanupPasswordData(); + } + catch (IOException e) { + e.printStackTrace(); + } + } } @Override public void launch(GhidraApplicationLayout ghidraLayout, String[] params) { if (params.length <= 1) { //@formatter:off - System.err.println("USAGE:"); - System.err.println(" bsim_ctl start [auth=pki|password|trust] [--noLocalAuth] [cafile=] [dn=\"..\"]"); - System.err.println(" stop [--force]"); - System.err.println(" adduser [dn=\"..\"]"); - System.err.println(" dropuser "); - System.err.println(" changeauth [auth=pki|password|trust] [--noLocalAuth] [cafile=]"); - System.err.println(" resetpassword "); - System.err.println(" changeprivilege admin|user"); + System.err.println("USAGE: bsim_ctl [command] required-args... [OPTIONS...}\n"); + System.err.println(" start [--auth|-a pki|password|trust] [--noLocalAuth] [--cafile \"\"] [--dn \"\"]"); + System.err.println(" stop [--force]"); + System.err.println(" adduser [--dn \"\"]"); + System.err.println(" dropuser "); + System.err.println(" changeauth [--auth|-a pki|password|trust] [--noLocalAuth] [--cafile \"\"]"); + System.err.println(" resetpassword "); + System.err.println(" changeprivilege admin|user"); System.err.println(); System.err.println("Global options:"); - System.err.println(" port="); - System.err.println(" user="); - System.err.println(" cert="); + System.err.println(" --port|-p "); + System.err.println(" --user|-u "); + System.err.println(" --cert "); + System.err.println(); + System.err.println("NOTE: Options with values may also be specified using the form: --option=value\n"); System.err.println(); //@formatter:on return; } layout = ghidraLayout; // Save layout for when we need to initialize application + boolean success = false; try { run(params); + success = true; } - catch (RuntimeException e) { - e.printStackTrace(); - System.exit(1); + catch (SAXException e1) { + System.err.println("Error in server configuation data"); + System.err.println(e1.getMessage()); + } + catch (InterruptedException e) { + System.err.println("Command was interrupted"); + System.err.println(e.getMessage()); + } + catch (SQLException e) { + System.err.println("Error connecting to the database"); + System.err.println(e.getMessage()); + } + catch (GeneralSecurityException e) { + System.err.println("Error establishing server certificate"); + System.err.println(e.getMessage()); + } + catch (IllegalArgumentException e) { + System.err.println("Error in command line arguments"); + System.err.println(e.getMessage()); } catch (Exception e) { + System.err.println("Unexpected error"); + e.printStackTrace(); + } + + if (!success) { System.exit(1); } } + + /** + * Initialize enough of Ghidra to allow navigation of configuration files and to allow SSL connections + * @throws IOException if the headless authenticator cannot be initialized + * @throws ClassNotFoundException if the postgres driver class cannot be found + */ + private void initializeApplication() throws IOException, ClassNotFoundException { + if (layout != null) { + // Initialize application environment consistent with bsim command + BSimLaunchable.initializeApplication(layout, 0, connectingUserName, certParameter); + } + } + + /** + * Class for processing standard output or standard error for processes invoked by BSimControl + * The streams can be optionally suppressed or dumped to System.out + */ + private class IOThread extends Thread { + private BufferedReader shellOutput; // Reader for the particular output stream + private boolean suppressOutput; // If false, shell output is printed on the console + + public IOThread(InputStream input, boolean suppressOut) { + shellOutput = new BufferedReader(new InputStreamReader(input)); + suppressOutput = suppressOut; + } + + @Override + public void run() { + String line = null; + try { + while ((line = shellOutput.readLine()) != null) { + if (!suppressOutput) { + System.out.println(line); + } + } + } + catch (Exception e) { + // DO NOT USE LOGGING HERE (class loader) + System.err.println("Unexpected Exception: " + e.getMessage()); + e.printStackTrace(System.err); + } + } + } + } 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 c9e2967107..4a85e5e54d 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 @@ -245,7 +245,7 @@ public interface FunctionDatabase extends AutoCloseable { public static boolean checkSettingsForInsert(DescriptionManager manage, DatabaseInformation info) throws LSHException, DatabaseNonFatalException { if (manage.numFunctions() == 0) { - throw new DatabaseNonFatalException("ls ~/junk" + ""); + throw new DatabaseNonFatalException("Empty signature file"); } int res = info.checkSignatureSettings(manage.getMajorVersion(), manage.getMinorVersion(), manage.getSettings()); 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 9eedf9917d..b8a5a88a7e 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 @@ -20,6 +20,7 @@ import java.io.IOException; import java.net.*; import java.util.*; +import org.apache.commons.lang3.StringUtils; import org.xml.sax.SAXException; import ghidra.GhidraApplicationLayout; @@ -77,27 +78,29 @@ public class BSimLaunchable implements GhidraLaunchable { private static Set COMMANDS_WITH_REPO_ACCESS = Set.of(COMMAND_GENERATE_SIGS, COMMAND_GENERATE_UPDATES); - /** - * Constants for the option parameters that can be set in the various commands. - */ - private static final String BSIM_URL_OPTION = "bsim="; - private static final String GHIDRA_URL_OPTION = "ghidra="; - private static final String NAME_OPTION = "name="; - private static final String OWNER_OPTION = "owner="; - private static final String DESCRIPTION_OPTION = "description="; - private static final String OVERRIDE_OPTION = "override="; - private static final String CONFIG_OPTION = "config="; - private static final String MD5_OPTION = "md5="; - private static final String MAX_FUNC_OPTION = "maxfunc="; - private static final String FILTER_OPTION = "filter="; - private static final String ARCH_OPTION = "arch="; - private static final String COMPILER_OPTION = "compiler="; - private static final String LIMIT_OPTION = "limit="; - private static final String SORT_COL_OPTION = "sortcol="; + // Options that require a value argument + private static final String BSIM_URL_OPTION = "--bsim"; + private static final String NAME_OPTION = "--name"; + private static final String OWNER_OPTION = "--owner"; + private static final String DESCRIPTION_OPTION = "--description"; + private static final String OVERRIDE_OPTION = "--override"; + private static final String CONFIG_OPTION = "--config"; + private static final String MD5_OPTION = "--md5"; + private static final String MAX_FUNC_OPTION = "--maxfunc"; + private static final String ARCH_OPTION = "--arch"; + private static final String COMPILER_OPTION = "--compiler"; + private static final String LIMIT_OPTION = "--limit"; + private static final String SORT_COL_OPTION = "--sortcol"; - // Global options - private static final String USER_OPTION = "user="; - private static final String CERT_OPTION = "cert="; + // Global options that require a value argument + private static final String USER_OPTION = "--user"; + private static final String CERT_OPTION = "--cert"; + + // Define set of options that require a second value argument + private static final Set VALUE_OPTIONS = + Set.of(USER_OPTION, CERT_OPTION, BSIM_URL_OPTION, NAME_OPTION, OWNER_OPTION, + DESCRIPTION_OPTION, OVERRIDE_OPTION, CONFIG_OPTION, MD5_OPTION, MAX_FUNC_OPTION, + ARCH_OPTION, COMPILER_OPTION, LIMIT_OPTION, SORT_COL_OPTION); private static final Set GLOBAL_OPTIONS = Set.of(CERT_OPTION, USER_OPTION); @@ -111,12 +114,30 @@ public class BSimLaunchable implements GhidraLaunchable { private static final String CALL_GRAPH_OPTION = "--callgraph"; private static final String PRINT_JUST_EXE_OPTION = "--printjustexe"; + private static final Map SHORTCUT_OPTION_MAP = new HashMap<>(); + static { + SHORTCUT_OPTION_MAP.put("-a", ARCH_OPTION); + SHORTCUT_OPTION_MAP.put("-b", BSIM_URL_OPTION); + SHORTCUT_OPTION_MAP.put("-c", CONFIG_OPTION); + SHORTCUT_OPTION_MAP.put("-d", DESCRIPTION_OPTION); + SHORTCUT_OPTION_MAP.put("-l", LIMIT_OPTION); + SHORTCUT_OPTION_MAP.put("-m", MD5_OPTION); + SHORTCUT_OPTION_MAP.put("-n", NAME_OPTION); + SHORTCUT_OPTION_MAP.put("-o", OWNER_OPTION); + SHORTCUT_OPTION_MAP.put("-s", SORT_COL_OPTION); + SHORTCUT_OPTION_MAP.put("-u", USER_OPTION); + //SHORTCUT_OPTION_MAP.put("", OVERRIDE_OPTION); + //SHORTCUT_OPTION_MAP.put("", MAX_FUNC_OPTION); + //SHORTCUT_OPTION_MAP.put("", COMPILER_OPTION); + //SHORTCUT_OPTION_MAP.put("", CERT_OPTION); + } + //@formatter:off // Populate ALLOWED_OPTION_MAP for each command private static final Set CREATE_DATABASE_OPTIONS = Set.of(NAME_OPTION, OWNER_OPTION, DESCRIPTION_OPTION, NO_CALLGRAPH_OPTION); private static final Set COMMIT_SIGS_OPTIONS = - Set.of(OVERRIDE_OPTION, GHIDRA_URL_OPTION); // url requires override param + Set.of(OVERRIDE_OPTION, MD5_OPTION); // url requires override param private static final Set COMMIT_UPDATES_OPTIONS = Set.of(); private static final Set DELETE_OPTIONS = Set.of(MD5_OPTION, NAME_OPTION, ARCH_OPTION, COMPILER_OPTION); // one or more params required @@ -164,23 +185,7 @@ public class BSimLaunchable implements GhidraLaunchable { private URL ghidraURL; private URL bsimURL; - private String bsimURLOption; // Command-line option: bsim=.. - private String ghidraURLOption; // Command-line option: ghidra=.. - private String nameOption; // Command-line option: name=.. - private String ownerOption; // Command-line option: owner=.. - private String archOption; // Command-line option: arch=.. - private String compOption; // Command-line option: compiler=.. - private String descOption; // Command-line option: description= - private String filterOption; // Command-line option: filter= - private String configOption; // Command-line option: config=.. - private String md5Option; // Command-line option: md5=.. - private Integer maxFunc; // Command-line option: maxfunc=.. - private String certOption; // Command-line option: cert=.. - private String connectingUserName; // Command-line option: user=.. - private Integer limitOption; // Command-line option: limit=.. - private String sortColumn; // Command-line option: sortcol=.. - private boolean overrideOption; // Command-line option: override= - + private Map optionValueMap = new HashMap<>(); private Set booleanOptions = new HashSet<>(); private GhidraApplicationLayout layout; @@ -199,22 +204,8 @@ public class BSimLaunchable implements GhidraLaunchable { private void clearParams() { ghidraURL = null; bsimURL = null; - bsimURLOption = null; - ghidraURLOption = null; - connectingUserName = null; - nameOption = null; - ownerOption = null; - archOption = null; - compOption = null; - descOption = null; - filterOption = null; - configOption = null; - md5Option = null; - certOption = null; - limitOption = null; - sortColumn = null; - overrideOption = false; booleanOptions.clear(); + optionValueMap.clear(); } private BulkSignatures getBulkSignatures() @@ -223,6 +214,7 @@ public class BSimLaunchable implements GhidraLaunchable { if (bsimURL != null) { serverInfo = new BSimServerInfo(bsimURL); } + String connectingUserName = optionValueMap.get(USER_OPTION); return new BulkSignatures(serverInfo, connectingUserName); } @@ -281,6 +273,8 @@ public class BSimLaunchable implements GhidraLaunchable { */ private List readOptions(String command, String[] params, int discard) { + boolean sawOptions = false; + Set allowedParams = ALLOWED_OPTION_MAP.get(command); if (allowedParams == null) { throw new IllegalArgumentException("Unsupported command: " + command); @@ -288,101 +282,61 @@ public class BSimLaunchable implements GhidraLaunchable { List subParams = new ArrayList(); for (int i = discard; i < params.length; ++i) { - String option = params[i]; + String optionName = params[i]; + String value = null; - int ix = option.indexOf('='); - if (ix > 0) { - String checkOption = option.substring(0, ix + 1); // include '=' in option name - if (!GLOBAL_OPTIONS.contains(checkOption) && !allowedParams.contains(checkOption)) { - throw new IllegalArgumentException("Unsupported option use: " + checkOption); + if (optionName.startsWith("-")) { + // although not prefered, allow option value to be specified as --option=value + int ix = optionName.indexOf("="); + if (ix > 1) { + value = optionName.substring(ix + 1); + optionName = optionName.substring(0, ix); } } - else if (option.startsWith("--")) { - if (!GLOBAL_OPTIONS.contains(option) && !allowedParams.contains(option)) { - throw new IllegalArgumentException("Unsupported option use: " + option); + + String option = optionName; + + if (optionName.startsWith("-") && !optionName.startsWith("--")) { + option = SHORTCUT_OPTION_MAP.get(optionName); // map option to -- long form + if (option == null) { + throw new IllegalArgumentException("Unsupported option use: " + optionName); } - booleanOptions.add(option); + } + + if (!option.startsWith("--")) { + if (sawOptions) { + throw new IllegalArgumentException("Unexpected argument: " + option); + } + subParams.add(params[i]); continue; } - if (option.startsWith(BSIM_URL_OPTION)) { - bsimURLOption = option.substring(BSIM_URL_OPTION.length()); + sawOptions = true; + if (!GLOBAL_OPTIONS.contains(option) && !allowedParams.contains(option)) { + throw new IllegalArgumentException("Unsupported option use: " + optionName); } - else if (option.startsWith(GHIDRA_URL_OPTION)) { - ghidraURLOption = option.substring(GHIDRA_URL_OPTION.length()); - } - else if (option.startsWith(NAME_OPTION)) { - nameOption = option.substring(NAME_OPTION.length()); - } - else if (option.startsWith(OWNER_OPTION)) { - ownerOption = option.substring(OWNER_OPTION.length()); - } - else if (option.startsWith(DESCRIPTION_OPTION)) { - descOption = option.substring(DESCRIPTION_OPTION.length()); - } - else if (option.startsWith(OVERRIDE_OPTION)) { - overrideOption = true; - ghidraURLOption = option.substring(OVERRIDE_OPTION.length()); - } - else if (option.startsWith(CONFIG_OPTION)) { - configOption = option.substring(CONFIG_OPTION.length()); - } - else if (option.startsWith(MD5_OPTION)) { - md5Option = option.substring(MD5_OPTION.length()); - } - else if (option.startsWith(MAX_FUNC_OPTION)) { - String val = option.substring(MAX_FUNC_OPTION.length()); - try { - maxFunc = Integer.valueOf(val); - if (maxFunc < 0) { - throw new IllegalArgumentException( - "Negative value not permitted for maxfunc"); - } - } - catch (NumberFormatException e) { - throw new IllegalArgumentException("Invalid decimal value for maxfunc: " + val); + if (!VALUE_OPTIONS.contains(option)) { + // consume option without value arg as a boolean option + if (value != null) { + throw new IllegalArgumentException( + "Unsupported option specification: " + optionName + "="); } + booleanOptions.add(option); } - else if (option.startsWith(FILTER_OPTION)) { - filterOption = option.substring(FILTER_OPTION.length()); - } - else if (option.startsWith(CERT_OPTION)) { // global option - certOption = option.substring(CERT_OPTION.length()); - } - else if (option.startsWith(USER_OPTION)) { // global option - connectingUserName = option.substring(USER_OPTION.length()); - } - else if (option.startsWith(ARCH_OPTION)) { - archOption = option.substring(ARCH_OPTION.length()); - } - else if (option.startsWith(COMPILER_OPTION)) { - compOption = option.substring(COMPILER_OPTION.length()); - } - else if (option.startsWith(LIMIT_OPTION)) { - String val = option.substring(LIMIT_OPTION.length()); - try { - limitOption = Integer.valueOf(val); - if (limitOption < 0) { - throw new IllegalArgumentException( - "Negative value not permitted for limit"); - } - } - catch (NumberFormatException e) { - throw new IllegalArgumentException("Invalid decimal value for limit: " + val); - } - } - else if (option.startsWith(SORT_COL_OPTION)) { - sortColumn = option.substring(SORT_COL_OPTION.length()); - } - else if (params[i].startsWith("--") || params[i].contains("=")) { - throw new IllegalArgumentException("Unknown option: " + params[i]); + else if (!StringUtils.isBlank(value)) { + optionValueMap.put(option, value); } else { - subParams.add(params[i]); + // consume next param as option value + if (++i == params.length) { + throw new IllegalArgumentException("Missing option value: " + optionName); + } + optionValueMap.put(option, params[i]); } } + String connectingUserName = optionValueMap.get(USER_OPTION); if (connectingUserName == null) { - connectingUserName = ClientUtil.getUserName(); + connectingUserName = optionValueMap.put(USER_OPTION, ClientUtil.getUserName()); } return subParams; } @@ -398,6 +352,23 @@ public class BSimLaunchable implements GhidraLaunchable { } } + private Integer parsePositiveIntegerOption(String option) { + String optionValue = optionValueMap.get(option); + if (optionValue == null) { + return null; + } + try { + int value = Integer.valueOf(optionValue); + if (value < 0) { + throw new IllegalArgumentException("Negative value not permitted for " + option); + } + return value; + } + catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid integer value specified for " + option); + } + } + /** * Runs the command specified by the given set of params. * @@ -409,6 +380,8 @@ public class BSimLaunchable implements GhidraLaunchable { */ public void run(String[] params, TaskMonitor monitor) throws Exception, CancelledException { + clearParams(); + checkRequiredParam(params, 0, "command"); String command = params[0]; if (!COMMAND_SET.contains(command)) { @@ -420,7 +393,6 @@ public class BSimLaunchable implements GhidraLaunchable { monitor.setCancelEnabled(true); - clearParams(); List subParams = readOptions(command, params, 2); initializeApplication(command); @@ -458,8 +430,10 @@ public class BSimLaunchable implements GhidraLaunchable { doGenerateSigs(subParams, monitor); } else if (COMMAND_COMMIT_SIGS.equals(command)) { - if (overrideOption) { - setupURLs(ghidraURLOption, urlstring); + // --override option specified ghidra URL + String ghidraURLOverride = optionValueMap.get(OVERRIDE_OPTION); + if (ghidraURLOverride != null) { + setupURLs(ghidraURLOverride, urlstring); } else { bsimURL = BSimClientFactory.deriveBSimURL(urlstring); @@ -500,10 +474,12 @@ public class BSimLaunchable implements GhidraLaunchable { } private void processSigAndUpdateOptions(String urlstring) throws MalformedURLException { + String bsimURLOption = optionValueMap.get(BSIM_URL_OPTION); + String configOption = optionValueMap.get(CONFIG_OPTION); if (configOption != null) { if (bsimURLOption != null) { throw new IllegalArgumentException( - "bsim= and config= parameters may not both be present"); + BSIM_URL_OPTION + " and " + CONFIG_OPTION + " options may not both be present"); } setupGhidraURL(urlstring); } @@ -512,7 +488,7 @@ public class BSimLaunchable implements GhidraLaunchable { } else { throw new IllegalArgumentException( - "Must specify either \"bsim=\" or \"config=\" option is required"); + "Must specify either " + BSIM_URL_OPTION + " or " + CONFIG_OPTION + " option"); } } @@ -543,6 +519,10 @@ public class BSimLaunchable implements GhidraLaunchable { String configTemplate = params.get(0); boolean noTrackCallGraph = booleanOptions.contains(NO_CALLGRAPH_OPTION); + String nameOption = optionValueMap.get(NAME_OPTION); + String ownerOption = optionValueMap.get(OWNER_OPTION); + String descOption = optionValueMap.get(DESCRIPTION_OPTION); + try (BulkSignatures bsim = getBulkSignatures()) { bsim.createDatabase(configTemplate, nameOption, ownerOption, descOption, !noTrackCallGraph); @@ -551,19 +531,20 @@ public class BSimLaunchable implements GhidraLaunchable { private void doGenerateSigs(List params, TaskMonitor monitor) throws Exception, CancelledException { - // concurrent bsim= and config= option use already checked + // concurrent --bsim and --config option use already checked if (params.size() > 1) { throw new IllegalArgumentException("Invalid generatesigs parameter use!"); } boolean commitOption = booleanOptions.contains(COMMIT_OPTION); boolean overwriteOption = booleanOptions.contains(OVERWRITE_OPTION); + String configOption = optionValueMap.get(CONFIG_OPTION); String xmlDirectory = null; if (params.size() == 1) { xmlDirectory = params.get(0); if (configOption != null && commitOption) { throw new IllegalArgumentException( - "Invalid option use with config= option: " + COMMIT_OPTION); + "Invalid option use with " + CONFIG_OPTION + " option: " + COMMIT_OPTION); } } else { @@ -588,19 +569,20 @@ public class BSimLaunchable implements GhidraLaunchable { private void doGenerateUpdates(List params, TaskMonitor monitor) throws Exception, CancelledException { - // concurrent bsim= and config= option use already checked + // concurrent --bsim and --config option use already checked if (params.size() > 1) { throw new IllegalArgumentException("Invalid generateupdates parameter use!"); } boolean commitOption = booleanOptions.contains(COMMIT_OPTION); boolean overwriteOption = booleanOptions.contains(OVERWRITE_OPTION); + String configOption = optionValueMap.get(CONFIG_OPTION); String xmlDirectory = null; if (params.size() == 1) { xmlDirectory = params.get(0); if (configOption != null && commitOption) { throw new IllegalArgumentException( - "Invalid option use with config= option: " + COMMIT_OPTION); + "Invalid option use with " + CONFIG_OPTION + " option: " + COMMIT_OPTION); } } @@ -640,17 +622,16 @@ public class BSimLaunchable implements GhidraLaunchable { if (params.size() < 1) { throw new IllegalArgumentException("Missing directory containing signature files"); } - if (!overrideOption && ghidraURLOption != null) { - throw new IllegalArgumentException( - "The \"ghidra=\" option use requires \"override\" option"); - } + String xmlDirectory = params.get(0); File dir = checkDirectory(xmlDirectory); + boolean hasOverride = optionValueMap.containsKey(OVERRIDE_OPTION); + String md5Filter = optionValueMap.get(MD5_OPTION); + try (BulkSignatures bsim = getBulkSignatures()) { - bsim.sendXmlToQueryServer(dir, overrideOption ? ghidraURL : null, filterOption, - monitor); + bsim.sendXmlToQueryServer(dir, hasOverride ? ghidraURL : null, md5Filter, monitor); } } @@ -678,10 +659,17 @@ public class BSimLaunchable implements GhidraLaunchable { } private void fillinSingleExeSpecifier(ExeSpecifier spec) throws IllegalArgumentException { + + String md5Option = optionValueMap.get(MD5_OPTION); + String nameOption = optionValueMap.get(NAME_OPTION); + String archOption = optionValueMap.get(ARCH_OPTION); + String compOption = optionValueMap.get(COMPILER_OPTION); + if (md5Option != null) { if (!isAllNull(nameOption, archOption, compOption)) { throw new IllegalArgumentException( - "The name=, arch= and compiler= options are not valid when md5= option is specified."); + "The " + NAME_OPTION + ", " + ARCH_OPTION + ", " + COMPILER_OPTION + + " options are not valid when " + MD5_OPTION + " option is specified."); } spec.exemd5 = md5Option; } @@ -691,14 +679,18 @@ public class BSimLaunchable implements GhidraLaunchable { spec.execompname = compOption; } else { - throw new IllegalArgumentException("Must specify either \"md5=\" or \"name=\" option"); + throw new IllegalArgumentException( + "Must specify either " + MD5_OPTION + " or " + NAME_OPTION + " option"); } } private void doListFunctions(List params) throws IOException, LSHException { + Integer maxFunc = parsePositiveIntegerOption(MAX_FUNC_OPTION); + QueryName query = new QueryName(); fillinSingleExeSpecifier(query.spec); + if (maxFunc != null) { query.maxfunc = maxFunc; } @@ -792,17 +784,28 @@ public class BSimLaunchable implements GhidraLaunchable { private void doListExes(List params) throws IOException, LSHException { int limit = DEFAULT_LIST_EXE_LIMIT; + Integer limitOption = parsePositiveIntegerOption(LIMIT_OPTION); if (limitOption != null) { limit = limitOption; } boolean includeLibs = booleanOptions.contains(INCLUDE_LIBS_OPTION); + String md5Option = optionValueMap.get(MD5_OPTION); + String nameOption = optionValueMap.get(NAME_OPTION); + String archOption = optionValueMap.get(ARCH_OPTION); + String compOption = optionValueMap.get(COMPILER_OPTION); + String sortColumnOption = optionValueMap.get(SORT_COL_OPTION); try (BulkSignatures bsim = getBulkSignatures()) { List exeList = bsim.getExes(limit, md5Option, nameOption, archOption, - compOption, sortColumn, includeLibs); + compOption, sortColumnOption, includeLibs); for (ExecutableRecord exeRec : exeList) { Msg.info(this, exeRec.printRaw()); } + String summary = exeList.size() + " executables found"; + if (limit > 0 && limit == exeList.size()) { + summary += " (results limit reached)"; + } + Msg.info(this, summary); } } @@ -814,7 +817,13 @@ public class BSimLaunchable implements GhidraLaunchable { * @throws LSHException if there's an error issuing the query */ private void doGetCount(List params) throws IOException, LSHException { + boolean includeFakes = booleanOptions.contains(INCLUDE_LIBS_OPTION); + String md5Option = optionValueMap.get(MD5_OPTION); + String nameOption = optionValueMap.get(NAME_OPTION); + String archOption = optionValueMap.get(ARCH_OPTION); + String compOption = optionValueMap.get(COMPILER_OPTION); + try (BulkSignatures bsim = getBulkSignatures()) { int count = bsim.getCount(md5Option, nameOption, archOption, compOption, includeFakes); System.out.println("Matching executable count: " + count); @@ -832,9 +841,14 @@ public class BSimLaunchable implements GhidraLaunchable { * @throws LSHException if there's an error issuing the query */ private void doInstallMetadata(List params) throws IOException, LSHException { + + String nameOption = optionValueMap.get(NAME_OPTION); + String ownerOption = optionValueMap.get(OWNER_OPTION); + String descOption = optionValueMap.get(DESCRIPTION_OPTION); + if (isAllNull(nameOption, ownerOption, descOption)) { - throw new IllegalArgumentException( - "Missing one or more metadata options: [name=..] [owner=..] [description=..]"); + throw new IllegalArgumentException("Missing one or more metadata options: " + + NAME_OPTION + ", " + OWNER_OPTION + ", " + DESCRIPTION_OPTION); } try (BulkSignatures bsim = getBulkSignatures()) { @@ -920,30 +934,30 @@ public class BSimLaunchable implements GhidraLaunchable { //@formatter:off System.err.println( "USAGE: bsim [command] required-args... [OPTIONS...]\n" + - " createdatabase [name=\"\"] [owner=\"\"] [description=\"\"] [--nocallgraph]\n" + - " setmetadata [name=\"\"] [owner=\"\"] [description=\"\"]\n" + + " createdatabase [--name|-n \"\"] [--owner|-o \"\"] [--description|-d \"\"] [--nocallgraph]\n" + + " setmetadata [--name|-n \"\"] [--owner|-o \"\"] [--description|-d \"\"]\n" + " addexecategory [--date]\n" + " addfunctiontag \n" + " dropindex \n" + " rebuildindex \n" + " prewarm \n" + - " generatesigs config= [--overwrite]\n" + - " generatesigs bsim= [--commit] [--overwrite]\n" + - " generatesigs bsim=\n" + - " commitsigs [md5=] [override=]\n" + - " generateupdates config= [--overwrite]\n" + - " generateupdates bsim= [--commit] [--overwrite]\n" + - " generateupdates bsim=\n" + + " generatesigs --config|-c [--overwrite]\n" + + " generatesigs --bsim|-b [--commit] [--overwrite]\n" + + " generatesigs --bsim|-b \n" + + " commitsigs [--md5|-m ] [--override ]\n" + + " generateupdates --config|-c [--overwrite]\n" + + " generateupdates --bsim|-b [--commit] [--overwrite]\n" + + " generateupdates --bsim|-b \n" + " commitupdates \n" + - " listexes [md5=] [name=] [arch=] [compiler=] [sortcol=] [limit=] [--includelibs]\n" + - " getexecount [md5=] [name=] [arch=] [compiler=] [--includelibs]\n" + - " delete [md5=] [name= [arch=] [compiler=]]\n" + - " listfuncs [md5=] [name= [arch=] [compiler=]] [--printselfsig] [--callgraph] [--printjustexe] [maxfunc=]\n" + - " dumpsigs [md5=] [name= [arch=] [compiler=]]\n" + + " listexes [--md5|-m ] [--name|-n ] [--arch|-a ] [--compiler ] [--sortcol|-s md5|name] [--limit|-l ] [--includelibs]\n" + + " getexecount [--md5|-m ] [--name|-n ] [--arch|-a ] [--compiler ] [--includelibs]\n" + + " delete [--md5|-m ] [--name|-n [--arch|-a ] [--compiler ]]\n" + + " listfuncs [--md5|-m ] [--name|-n [--arch|-a ] [--compiler ]] [--printselfsig] [--callgraph] [--printjustexe] [--maxfunc ]\n" + + " dumpsigs [--md5|-m ] [--name|-n [--arch|-a ] [--compiler ]]\n" + "\n" + "Global options:\n" + - " user=\n" + - " cert=\n" + + " --user|-u \n" + + " --cert \n" + "\n" + "Enumerated Options:\n" + " - large_32 | medium_32 | medium_64 | medium_cpool | medium_nosize \n" + @@ -957,7 +971,8 @@ public class BSimLaunchable implements GhidraLaunchable { "Ghidra URL Forms (ghidraURL):\n" + " ghidra://[:]/[/]\n" + " ghidra:/[/][?/]\n" + - "\n"); + "\n" + + "NOTE: Options with values may also be specified using the form: --option=value\n"); //@formatter:on } @@ -991,6 +1006,10 @@ public class BSimLaunchable implements GhidraLaunchable { private void initializeApplication(String command) throws IOException { int initType = COMMANDS_WITH_REPO_ACCESS.contains(command) ? 2 : 1; if (layout != null) { + + String connectingUserName = optionValueMap.get(USER_OPTION); + String certOption = optionValueMap.get(CERT_OPTION); + initializeApplication(layout, initType, connectingUserName, certOption); } } 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 17a91b39f1..a7d381c490 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 @@ -37,6 +37,7 @@ import ghidra.features.bsim.query.client.tables.ExeTable.ExeTableOrderColumn; import ghidra.features.bsim.query.description.*; import ghidra.features.bsim.query.protocol.*; import ghidra.features.bsim.query.protocol.ResponseDelete.DeleteResult; +import ghidra.framework.Application; import ghidra.framework.client.ClientUtil; import ghidra.framework.protocol.ghidra.GhidraURL; import ghidra.program.model.listing.*; @@ -184,6 +185,10 @@ public class BulkSignatures implements AutoCloseable { insertreq.path_override = GhidraURL.getProjectPathname(ghidraOverrideURL); } loadSignatureXml(file, insertreq.manage); + if (insertreq.manage.numFunctions() == 0) { + Msg.warn(this, file.getName() + ": does not define any functions"); + continue; + } if (insertreq.execute(querydb) == null) { Error lastError = querydb.getLastError(); if ((lastError.category == ErrorCategory.Format) || @@ -646,7 +651,7 @@ public class BulkSignatures implements AutoCloseable { establishQueryServerConnection(false); ExeTableOrderColumn sortEnum; if (sortCol != null) { - sortEnum = ExeTableOrderColumn.valueOf(sortCol); + sortEnum = ExeTableOrderColumn.valueOf(sortCol.toUpperCase()); } else { sortEnum = ExeTableOrderColumn.MD5; @@ -961,11 +966,12 @@ public class BulkSignatures implements AutoCloseable { protected File establishTemporaryDirectory(String xmldir) throws IOException { File dir; if (xmldir == null) { - String tempDirString = System.getProperty("java.io.tmpdir"); - if (tempDirString == null) { + File tmpDir = Application.getUserTempDirectory(); + if (tmpDir == null) { throw new IOException("Could not find temporary directory"); } - dir = new File(tempDirString, "bulkinsert_xml"); + dir = new File(tmpDir, "bulkinsert_xml"); + deleteTemporaryDirectory(dir); } else { dir = new File(xmldir); @@ -983,6 +989,9 @@ public class BulkSignatures implements AutoCloseable { } private void deleteTemporaryDirectory(File tempDir) throws IOException { + if (!tempDir.exists()) { + return; + } File[] listFiles = tempDir.listFiles(); if (listFiles == null) { throw new IOException( @@ -1106,6 +1115,7 @@ public class BulkSignatures implements AutoCloseable { if (manager.numFunctions() == 0) { Msg.warn(this, program.getDomainFile().getName() + " contains no functions with signatures"); + return; } FileWriter fwrite = new FileWriter(file); manager.saveXml(fwrite); diff --git a/Ghidra/RuntimeScripts/Linux/support/bsim b/Ghidra/RuntimeScripts/Linux/support/bsim index 3048a31c81..0254b785bb 100755 --- a/Ghidra/RuntimeScripts/Linux/support/bsim +++ b/Ghidra/RuntimeScripts/Linux/support/bsim @@ -3,7 +3,7 @@ # Command-line script for interacting with a BSim database # maximum heap memory (may be increased) -MAXMEM=1G +MAXMEM=2G # launch mode (fg, bg, debug, debug-suspend) LAUNCH_MODE=fg diff --git a/Ghidra/RuntimeScripts/Windows/support/bsim.bat b/Ghidra/RuntimeScripts/Windows/support/bsim.bat index fcc059ca19..da05434b38 100644 --- a/Ghidra/RuntimeScripts/Windows/support/bsim.bat +++ b/Ghidra/RuntimeScripts/Windows/support/bsim.bat @@ -3,9 +3,8 @@ @echo off setlocal -:: Maximum heap memory may be changed if default is inadequate. This will generally be up to 1/4 of -:: the physical memory available to the OS. Uncomment MAXMEM setting if non-default value is needed. -::set MAXMEM=2G +:: maximum heap memory (may be increased) +set MAXMEM=2G :: launch mode (fg, bg, debug, debug-suspend) set LAUNCH_MODE=fg diff --git a/GhidraDocs/GhidraClass/BSim/BSimTutorial_BSim_Command_Line.html b/GhidraDocs/GhidraClass/BSim/BSimTutorial_BSim_Command_Line.html index aef41cf5d3..5daff89a45 100755 --- a/GhidraDocs/GhidraClass/BSim/BSimTutorial_BSim_Command_Line.html +++ b/GhidraDocs/GhidraClass/BSim/BSimTutorial_BSim_Command_Line.html @@ -23,13 +23,13 @@ Non-shared projects are locked when open, and the lock will prevent the signatur

cd <ghidra_install_dir>/support
 mkdir ~/bsim_sigs
-./bsim generatesigs ghidra:/<ghidra_project_dir>/postgres_object_files bsim=file:/<database_dir>/example ~/bsim_sigs
+./bsim generatesigs ghidra:/<ghidra_project_dir>/postgres_object_files --bsim file:/<database_dir>/example ~/bsim_sigs
 
  • The ghidra:/ argument is the local project which holds the analyzed binaries. Note that there is only one forward slash in the URL for a local project.
  • -
  • The bsim= argument is the URL of the BSim database. +
  • The --bsim argument is the URL of the BSim database. This command does not add any signatures to the database, but it does query the database for its settings.
diff --git a/GhidraDocs/GhidraClass/BSim/BSimTutorial_BSim_Command_Line.md b/GhidraDocs/GhidraClass/BSim/BSimTutorial_BSim_Command_Line.md index 01b03cc887..6cef1ab59f 100644 --- a/GhidraDocs/GhidraClass/BSim/BSimTutorial_BSim_Command_Line.md +++ b/GhidraDocs/GhidraClass/BSim/BSimTutorial_BSim_Command_Line.md @@ -22,12 +22,12 @@ To generate the signature files, execute the following commands in a shell (adju ```bash cd /support mkdir ~/bsim_sigs -./bsim generatesigs ghidra://postgres_object_files bsim=file://example ~/bsim_sigs +./bsim generatesigs ghidra://postgres_object_files --bsim file://example ~/bsim_sigs ``` - The ``ghidra:/`` argument is the local project which holds the analyzed binaries. Note that there is only one forward slash in the URL for a local project. -- The ``bsim=`` argument is the URL of the BSim database. +- The ``--bsim`` argument is the URL of the BSim database. This command does not add any signatures to the database, but it does query the database for its settings. ## Committing Signature Files