B4J Library SSHJ - ssh, scp, sftp for Java

This is a wrapper for: https://github.com/hierynomus/sshj

Dependencies:
#AdditionalJar: slf4j-api-1.7.25
#AdditionalJar: sshj-0.23.0
#AdditionalJar: eddsa-0.2.0
#AdditionalJar: bcprov-jdk15on-159



You can download this libraries from:
slf4j-api-1.7.25: https://mvnrepository.com/artifact/org.slf4j/slf4j-api
sshj-0.23.0: https://mvnrepository.com/artifact/com.hierynomus/sshj
eddsa-0.2.0: https://mvnrepository.com/artifact/net.i2p.crypto/eddsa
(B4J) bcprov-jdk15on-159: https://www.bouncycastle.org/latest_releases.html
(B4A) prov-1.58.0.0.jar: https://mvnrepository.com/artifact/com.madgag.spongycastle/prov

*NOTE1* I tested this only with B4J, it should work for Android too!
*NOTE2* Also when running in Release you should set #MergeLibraries: False because of the bouncy castle dependency (bcprov-jdk15on-159) which is a signed jar, and when running with #MergeLibraries: True that jar is decompiled and compiled in your jar and therefore losses its signing.
*NOTE3* For Android you need to download spongy castle instead of bouncy castle. You can download it from https://mvnrepository.com/artifact/com.madgag.spongycastle/prov. I don't know if NOTE2 applies in this case.

First steps:
B4X:
    Dim ssh As SSHJ
    ssh.Initialize("ssh")
    '...... or ......
    ssh.Initialize2("ssh", 15) ' where 15 is the KeepAliveInterval - usefull for portforward and future sftp.
As connecting through ssh to a server wants to mean that is secure you have to add the server hostkey:
B4X:
ssh.LoadKnownHosts 'loads the known_hosts file from some default locations.
'or
ssh.LoadKnownHosts2("/tmp/known_hosts")
'or
ssh.AddHostKeyVerifier("SHA1:2Fo8c/96zv32xc8GZWbOGYOlRak=")
'or finnaly if you really trust the server and do not want to verify it's key
ssh.AddHostKeyPromiscuousVerifier
Next step is the authentication. You can supply multiple authentication methods:
B4X:
ssh.AddAuthPassword("youruserpass")
'or/and
ssh.AddAuthPublicKey("/location/to/my/key", "null if not encrypted")
'or/and
ssh.AddAuthPublicKey2("key-key-key-key...", "empty string if public key is in private key string", "null if not encrypted")
Finnaly now we connect:
B4X:
ssh.Connect("hostname/ip", 22, "yourusername")

All the execution methods (read details in ide) - Exec, Shell, SCPUpload, SCPDownload, LocalPortforwarder, RemotePortForwarder and all methods of SFTPClient - are async and work with Wait For feature of B4X.

Like always if you encounter problems i will do my best to help, but I'm no expert!

Edit by Erel: You need to add this line for it to work with the built-in packager:
B4X:
#PackagerProperty: VMArgs = --add-opens b4j/org.bouncycastle.jcajce.provider.asymmetric.ec=java.base
 

Attachments

  • SSHJ-v1.40.zip
    46.8 KB · Views: 1,110
Last edited by a moderator:

JackKirk

Well-Known Member
Licensed User
Longtime User
Firstly, thank you for this effort - it has greatly helped me in trying to understand how I could implement SSH.

I am trying to programmatically control a Netonix WISP switch ( https://www.netonix.com/wisp-switch/ws-8-150-dc.html ) at a remote location.

But I am not having much luck doing it with B4J.

I can successfully communicate with it with Putty - the following is an excerpt from a Putty session:
login as: admin
admin@xxx.xxx.xxx.xxx's password:

BusyBox v1.19.4 (2017-03-07 13:42:37 EST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

Netonix Switch# ? <<<< Note that automatically land in a Netonix shell (???)

calibrate Calibrate DC input voltage sensor
clear
cmdline Access command line shell
configure Enter configuration mode
copy Copy from source to destination
exit Exit from EXEC mode
firmware Firmware upgrade
help Description of the interactive help system
ping Send ICMP echo messages
reload Reload system
reset Reset statistics
serial Access the serial port
show Show running system information
terminal Set terminal options
traceroute Execute traceroute

Netonix Switch# cmdline <<<< can go to command line shell

BusyBox v1.19.4 (2017-03-07 13:42:37 EST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

admin@Netonix_Switch:/www# help
Built-in commands:
------------------
. : [ [[ alias bg break cd chdir command continue echo eval exec
exit export false fg getopts hash help jobs kill let local printf
pwd read readonly return set shift source test times trap true
type ulimit umask unalias unset wait

admin@Netonix_Switch:/www# exit <<<< and back to Netonix shell
Netonix Switch# ...

My B4J code looks like:
B4X:
    'See https://www.b4x.com/android/forum/threads/sshj-ssh-scp-sftp-for-java.88615/#post-584009
    Private  ssh As SSHJ
    ssh.Initialize("ssh")

    ssh.AddHostKeyPromiscuousVerifier
    ssh.AddAuthPassword("fortotalrecall1")

    ssh.SetTimeout(30000)
    ssh.SetConnectTimeout(30000)
    ssh.Connect("xxx.xxx.xxx.xxx", 23, "admin")   'Port 23 is port forwarded to port 22 on device

    Private senderfilter As Object = ssh.Exec("help", 0)

    Wait For (senderfilter) ssh_ExecFinished(Success As Boolean, Command_Executed As String, ExitCode As Int, Output As String, Error As String)
    Log("help")
    Log("Success " & Success)
    Log("Command_Executed " & Command_Executed)
    Log("ExitCode " & ExitCode)
    Log("Output " & CRLF & Output)
    Log("Error " & Error)

Which results in a log as follows:
Waiting for debugger to connect...
Program started.
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
help
Success true
Command_Executed help
ExitCode 0
Output
Built-in commands:
------------------
. : [ [[ alias bg break cd chdir command continue echo eval exec
exit export false fg getopts hash help jobs kill let local printf
pwd read readonly return set shift source test times trap true
type ulimit umask unalias unset wait
Error

The SLF4J messages are trivial

But I am automatically landing in the devices command line shell (as evidenced by the identical lists of built-in commands).

Why is my B4J code not behaving identically to Putty?

Or: what BusyBox command can I issue to get to the Netonix shell (I've been googling and experimenting with this for about 6 hours without success).

Any help greatly appreciated - a solution has some serious value to my project.
 

mindful

Active Member
Licensed User
Why is my B4J code not behaving identically to Putty?
This is because putty is a terminal (interactive shell) ... Exec from ssh just executes a command and returns the result (exit code and output) and then the "session" closes. You cant (at least I do not use it for this purpose) use this to run someting like step by step installer or something like that where the program needs some comfirmation from the user .. instead most software can handle attributes (parameters) something like: yum install php -y (the -y stands for confirming the are you sure you want to install ...)
 

JackKirk

Well-Known Member
Licensed User
Longtime User
Mindful,

Thanks for your prompt response.

As I understand it Putty uses SSH (or at least that is the option I am using), so what SSH command/s does it use to be a terminal?

Please excuse if this is a stupid question - first time I have scratched SSH/Putty etc.

Thanks....
 

mindful

Active Member
Licensed User
SSHJ (the java library that this wrapper is based) supports interactive shell (aka terminal), but I haven't wrapped it and I don't have any plans to do so, I can't think of any other reason to use this feature than to write your on terminal.

Quote from wikipedia:
PuTTY (/ˈpʌti/)[3] is a free and open-source terminal emulator, serial console and network file transfer application.

This library is not a terminal emulator.

Secure Shell (SSH) is a cryptographic network protocol for operating network services securely over an unsecured network.

This library make use of this network protocol so is putty.

Please provide more details on what are you trying to accomplish or what is the purpose of using this library, maybe you are looking at it the wrong way and there is a simple way to do what you are trying to do.
 

mindful

Active Member
Licensed User
Ok now I understand ... You could try to issue multiple commands with the Exec method by using ; as separator.

something like:
B4X:
ssh.Exec("conf; interface port 1; poe 24V; exit; exit", 0)
I think this will enable poe with 24V on port 1

The php script you posted uses indeed an ssh connection with interactive shell.

I will have a look at SSHJ library and see if I can do something like that, but no promises.
 

JackKirk

Well-Known Member
Licensed User
Longtime User
mindful,

I have tried stringing several commands together, using && as the concatenation operator.

The concept works but the execution doesn't.

The "conf", "interface port .." etc must be put on the command line interface generated by the "switch -s" command.

The full sequence looks something like:

ssh.Exec("switch -s && conf && interface port 4 && poe off|24v|48v|48vh && exit && exit" & CRLF", 0)

The trouble is that "switch -s" bombs with an "illegal argument" style error - because ssh.Exec is batch not interactive (as you pointed out before) therefor a CLI can't be created and there is nothing to put the "conf" etc on.

If you don't want to enhance your library - I'm guessing something like what Oliver A is suggesting in this post:

https://www.b4x.com/android/forum/threads/php-some-guidance-required.92754/#post-586872

would it be possible for you to suggest some inline java to work around it?

I appreciate your efforts greatly...
 

mindful

Active Member
Licensed User
ssh.Exec("switch -s && conf && interface port 4 && poe off|24v|48v|48vh && exit && exit" & CRLF", 0)

This bombs with illegal arguments because && doesn't mean anything it's just a string. And the way you put poe off|24v|48v will not work because you haven't set a value. | means "or" in some programming languages.

To run multiple commands in bash you use ; as separator.

Can you activate the poe manual port manual via putty using the commands above ? If you can please copy paste the putty window here so I can have a look.

I am not at the office right now but once i get there I'll have a look if I can add the interactive shell or expose something for someone to help you with inline java or javaobject, but the command you tried will not work on any system. So if you comnect via putty and you are able to achive your purpose using manual commands please post the putty window contents here so i can understand better or even better will be a manual of the switch commands.
 

JackKirk

Well-Known Member
Licensed User
Longtime User
mindful,

Sorry, my fault, being too quick on the response - I'm trying lots of different options here.

The && stuff comes from my mucking around with the -m file option in plink.

There && works as a concatenator - and it looks like ; does too.

The off|24v|48v|48vh stuff is "artistic license" - I didn't mean to imply that was what was put in verbatim

Maybe we should just forget my last post.

----------------------------------------------------

This is the help on the switch command:

login as: admin
admin@111.222.333.444's password:


BusyBox v1.19.4 (2017-03-07 13:42:37 EST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

Netonix Switch# cmdline

BusyBox v1.19.4 (2017-03-07 13:42:37 EST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

admin@Netonix_Switch:/www# cd ..
admin@Netonix_Switch:/# ls
bin etc jffs mnt rom sbin tmp var
dev home lib proc root sys usr www
admin@Netonix_Switch:/# cd usr
admin@Netonix_Switch:/usr# cd bin
admin@Netonix_Switch:/usr/bin# switch -help
Usage: switch [-hvmcCFabdegijko] [-q <port>] [-A <0-255>] [-I none | <port>[,<port>] ... ] [-J none | <mirrorport>=<port>[,<port>] ... ] [-k <port>] [-K <port>] [-p <port>] [-r <bc>,<mc>,<uc>]
-a: Get fan level
-A: Set fan level
-b: Get temp
-c: Show port config
-C: Show chip ID
-d: Show port status
-e: Reset port counters
-F: Flush MAC table
-g: Collect data (daemon)
-h: Display this
-i: Show multicast members
-I: Set multicast members
-j: Show mirror configuration
-J: Set mirror configuration
-k: Fan control (daemon)
-l: I2C read
-m: Show port map
-n: Configure PHY LEDs
-o: Get fan speed
-p: Get port counters
-q: Show QoS counters (detailed counters for one port)
-r: Set storm control limits (broadcast, multicast, unicast)
-s: Start CLI <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<this fires up the Netonix CLI, but only works on a terminal
-S: Start CLI on serial console
-t: Enable port forwarding
-T: Disable port forwarding
-u: Get port diagnostic information
-w: Get MAC age time
-W: Set MAC age time
-x: Run MAC aging
-z: Enable loopback on port
-Z: Disable loopback on port
-1: Set port isolation members
-2: Upgrade power supply firmware
-3: Pretty print config
-v: Verbose
admin@Netonix_Switch:/usr/bin#

and this is a sample Putty session doing what I want to do (I have asked for help at every step with ?):
login as: admin
admin@111.222.333.444's password:


BusyBox v1.19.4 (2017-03-07 13:42:37 EST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

Netonix Switch# ?

calibrate Calibrate DC input voltage sensor
clear
cmdline Access command line shell
configure Enter configuration mode
copy Copy from source to destination
exit Exit from EXEC mode
firmware Firmware upgrade
help Description of the interactive help system
ping Send ICMP echo messages
reload Reload system
reset Reset statistics
serial Access the serial port
show Show running system information
terminal Set terminal options
traceroute Execute traceroute

Netonix Switch# config
Netonix Switch(config)# ?

accesscontrol Configure access control for the management interface
auto_backup Automatically backup config when changes are made
credentials Set login credentials
discovery Set device discovery options
end Go back to EXEC mode
erps Configure ERPS
exit Exit from current mode
help Description of the interactive help system
hostname Set system's network name
https-server Set HTTPS server configuration
interface Select an interface to configure
igmp Set IGMP configuration
ip Interface Internet Protocol config commands
ipv6 Interface Internet Protocol V6 config commands
location Supply information about the location of this device
loop_protection Enable loop protection
mac-aging-time Set the MAC table aging time
mirror Set port mirroring configuration.
no Negate a command or set its defaults
ntp Set NTP configuration
pause_frame Enable pause frame protection
power DCDC power supply options
qos Set QoS configuration.
radius Set RADIUS configuration
revert Set revert configuration
smtp-server Set SMTP server configuration
serial Set serial parameters
snmp-server Set SNMP server's configurations
ssh-server Set SSH server configuration
storm-control Set storm control configuration
stp Spanning Tree Protocol
syslog Set syslog configuration
timezone Set system's timezone
vlan VLAN configuration
watchdog Configure watchdog

Netonix Switch(config)# interface ?

port Ethernet Port configuration

Netonix Switch(config)# interface port ?

<1-8> Port number

Netonix Switch(config)# interface port 4
Netonix Switch(port 4)# ?

allowedvlans Configure the allowed VLANs on this port (VLAN trunk port)
bounce Configure scheduled port bounces
dhcpsnooping Enables DHCP Snooping
end Go back to EXEC mode
exit Exit from current mode
flowcontrol Traffic flow control
help Description of the interactive help system
interface Select an interface to configure
isolation Enables port isolation
lag Enable aggregation on this interface
limit Configure bandwidth limits on this interface
mtu Maximum transmission unit
multicast Enable multicast
name Descriptive name for the interface
no Negate a command or set its defaults
poe Power Over Ethernet.
poe_smart Enable PoE Smart
power DCDC power supply options
qos Configure QoS options for this interface
shutdown Shutdown of the interface.
speed Configures interface speed.
stats Flags this port to be included in total statistics
stp Spanning Tree Protocol

Netonix Switch(port 4)# poe ?

<poe> Off, 24V, 48V, 48VH

Netonix Switch(port 4)# poe 24v
Netonix Switch(port 4)# exit
Netonix Switch(config)# exit
% Applying configuration...
Press ENTER to confirm configuration change. If you do not hit enter within 60 seconds the switch will revert to the previous configuration.

I hope this helps...
 

mindful

Active Member
Licensed User
Updated the first post with version 1.40

It now supports emulating a terminal by using the command Shell.

@JackKirk you can try to run the command:
B4X:
    Dim ssh As SSHJ
    ssh.Initialize2("", 15)
    ssh.AddHostKeyPromiscuousVerifier
    ssh.AddAuthPassword("YourPassword")
    ssh.Connect("8.8.8.8", 22, "YourUser")

    Dim sf As Object = ssh.Shell("switch -s" & CRLF & "config" & CRLF & "interface port 4" & CRLF & "poe 24V" & CRLF & "exit" & CRLF & "exit" & CRLF & CRLF, 5)
    Wait For (sf) ShellFinished(Success As Boolean, ShellCommandsExecuted As String, Output As String, Error As String)

    Log("Success = " & Success)
    Log("ShellCommandsExecuted = " & ShellCommandsExecuted)
    Log("Output = " & Output)
    Log("Error = " & Error)
 

mindful

Active Member
Licensed User
The implementation of the Shell command is not a traditional one as it can send one or more commands in one go. It works like the Exec command the only difference is that it's emulating a terminal.

So this can't be used as it should be. More exactly a shell is meant to stay open so you can run a command, get the output, analyze it, declare a variable in the enviroment, run another command in the same shell, analyze it, update that previously set variable, run another command, etc .... so with some gui interface you could do an application like Putty.

Right now the shell and session is closed after running commands, so issuing another Shell command will open a new session with a new shell.

Somewhere in the future I will have the need to make something in my project that acts like putty, and I will put more effort in wrapping it nicely so that it would be easy to use for everyone, but until then this would have to do as for most uses it will work.
 

JackKirk

Well-Known Member
Licensed User
Longtime User
mindful,

Sorry for the delay in responding - I had to go get batteries recharged at the remote test site before I could test your new version.

I am happy - no ecstatic - to report it works beautifully.

You have saved me a major amount of toil installing PHP, fumbling with it and writing a B4J wrapper to run it.

I will be using this to intelligently manage the IP cameras at 5 sites each with 20-30 cameras.

Why? because it is all solar powered - every watt saved is one we don't have to generate and store - and I suspect this will reduce our power budget by between 1/2 and 2/3rds.

I owe you big time...
 

JackKirk

Well-Known Member
Licensed User
Longtime User
And a couple of comments and questions:
  1. Comment: for anyone who stumbles across this who is also trying to manage a Netronix switch there is no need for the initial
    "switch -s" command - when using the .Shell call you automatically land in the "netronix shell" - which is where PuTTy also lands you so it is all very consistent. In fact if you leave the "switch -s" command in it gets pretty upset.
  2. Question: I'm a bit confused with the TimeoutInSeconds parameter in the .Shell call - I'm finding 1 sec gives me everything - do I have a good link? what impacts how much time should be left? is there any relevant documentation?
Thanks again...
 

mindful

Active Member
Licensed User
I'm a bit confused with the TimeoutInSeconds parameter in the .Shell call
In your case a timeout of 1 is all you need, your commands will be executed. This timeout is about when you call some programs that you will need to wait for the output, think something like a scrapper you call the scrapper program and its output will not be available immediately, so this is the reason why this timeout exists, another thing is that you need to provide a timeout because the library doesn't know when the command you called has outputed everything, in most cases a timeout of 1 second will be enough.
 

JackKirk

Well-Known Member
Licensed User
Longtime User
*NOTE2* Also when running in Release you should set #MergeLibraries: False because of the bouncy castle dependency (bcprov-jdk15on-159) which is a signed jar, and when running with #MergeLibraries: True that jar is decompiled and compiled in your jar and therefore losses its signing.

mindful,

I have my camera controller all ready for final testing and have been going through my normal packaging exercises.

My experience with #MergeLibraries is the exact reverse of your NOTE2 in the first post.

That is if I set #MergeLibraries: False the resulting jar will not run - if I set #MergeLibraries: True (or comment it out which probably means it is defaulting to True) then everything works as per Debug mode.

Could you comment?
 

mindful

Active Member
Licensed User
#MergeLibraries: False the resulting jar will not run
When MergeLibraries is set to False then all the libraries in your project will not be merged into jar file. When you run your project in release mode from the ide in the window the outputs you can see a message like: "The following libraries should be distributed in the libs folder:" and then it lists all of the libraries needed to be copied to a folder named libs that sits near your jar.

MergeLibraries - True or False. Whether libraries should be merged into the generated jar file. If set to False then you need to copy the libraries to a folder named libs under the main jar folder (when distributing the app).
source: https://b4x.com/android/forum/threads/modules-attributes.35451/
 

JackKirk

Well-Known Member
Licensed User
Longtime User
When MergeLibraries is set to False then all the libraries in your project will not be merged into jar file. When you run your project in release mode from the ide in the window the outputs you can see a message like: "The following libraries should be distributed in the libs folder:" and then it lists all of the libraries needed to be copied to a folder named libs that sits near your jar.

Could I suggest you try this if you haven't already - it is not what I am seeing.

The only thing in the log when I run in Release mode with "#MergeLibraries: False" commented out is:
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by anywheresoftware.b4j.object.JavaObject (file:/D:/TLC%20Smartphone%20Apps/Tree%20Tops/POC/AWS%20EC2/CameraOnOff/Objects/CameraOnOff.jar) to method sun.management.RuntimeImpl.getInputArguments()
WARNING: Please consider reporting this to the maintainers of anywheresoftware.b4j.object.JavaObject
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

The WARNINGS Erel says to ignore.

The SLF4J I have ignored as they look harmless and haven't affected anything.

I am running B4J 6.01 and Java 9 if that makes any difference.

I also have the jars that you reference in post 1 in the Additional Libraries folder in both my development and run environments.
 
Last edited:

mindful

Active Member
Licensed User
t is not what I am seeing
When you run your app in release mode from the IDE just after you hit play a windows appears with thw build log. There you will see what I meant.

Also when run from the ide in release mode from the IDE you don't need to copy the libraries but when you distribute your app you need to.

So when you run in Release mode from the IDE with MergeLibraries: False the IDE will create a jar file that contains only the code that you wrote, you copy that jar to the production device and then you create a folder named libs that sits near the jar in which you copy all the libraries(jars) that you refernced in your project (B4J included libraries and additional libraries) - all of these libraries names appear in the little windows textarea that is shown after you hit the play arrow to build your project.

That error that you see in the log should be ignored as erel said, it's because you are using java 9 - it's just a warning.

The slfj warning appear because you haven't set a reference to slfj additional jars (it is meant for logging purposes).
 

JackKirk

Well-Known Member
Licensed User
Longtime User
mindful,

Sorry I was looking in the logs rather than the Compile popup.

If I use #MergeLibraries: False I get exactly what you describe.

And the reason the resultant jar was not running was I wasn't constructing the libs folder - when I do that it all works.

My apologies for the run around but I have a lot going on and I wasn't obeying the old dictum "more haste, less speed".

-----------------------------------------------

However that does not explain why if I comment out #MergeLibraries: False and thus create a jar with all the libraries embedded in it - it works also - with no libs folder in sight.

Maybe the documentation you are relying on is out of date?, maybe java 9 + B4J 6.01 don't behave as per your documentation?

Have you actually tested with #MergeLibraries: False commented out in a java 9 + B4J 6.01 environment?

Regards...
 
Top