From 2f4ec28b78ce4e3deddd26265563b16f57b6b17c Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Tue, 26 Sep 2023 20:46:17 -0400 Subject: [PATCH 01/49] chore: rename SSHCommand to SSHNPSuccess --- .../noports_core/lib/sshnp/sshnp_impl.dart | 6 ++-- .../noports_core/lib/sshnp/sshnp_result.dart | 24 +++++++------- packages/sshnoports/bin/sshnp.dart | 2 +- .../profile_actions/profile_run_action.dart | 32 +++++++++++++------ 4 files changed, 38 insertions(+), 26 deletions(-) diff --git a/packages/noports_core/lib/sshnp/sshnp_impl.dart b/packages/noports_core/lib/sshnp/sshnp_impl.dart index 95f46dcf1..c295da3d3 100644 --- a/packages/noports_core/lib/sshnp/sshnp_impl.dart +++ b/packages/noports_core/lib/sshnp/sshnp_impl.dart @@ -500,7 +500,7 @@ class SSHNPImpl implements SSHNP { return SSHNPFailed(errorMessage); } // All good - write the ssh command to stdout - return SSHCommand.base( + return SSHNPSuccess.base( localPort: localPort, remoteUsername: remoteUsername, host: 'localhost', @@ -762,7 +762,7 @@ class SSHNPImpl implements SSHNP { return SSHNPFailed('sshnp failed: with sshnpd acknowledgement errors'); } - return SSHCommand.base( + return SSHNPSuccess.base( localPort: localPort, remoteUsername: remoteUsername, host: 'localhost', @@ -803,7 +803,7 @@ class SSHNPImpl implements SSHNP { return SSHNPFailed('sshnp failed: with sshnpd acknowledgement errors'); } - return SSHCommand.base( + return SSHNPSuccess.base( localPort: localPort, remoteUsername: remoteUsername, host: 'localhost', diff --git a/packages/noports_core/lib/sshnp/sshnp_result.dart b/packages/noports_core/lib/sshnp/sshnp_result.dart index 51f48d97d..77e10b897 100644 --- a/packages/noports_core/lib/sshnp/sshnp_result.dart +++ b/packages/noports_core/lib/sshnp/sshnp_result.dart @@ -2,12 +2,10 @@ part of 'sshnp.dart'; abstract class SSHNPResult {} -abstract class SSHNPCommandResult implements SSHNPResult { - String get command; - List get args; -} - -const _optionsWithPrivateKey = ['-o StrictHostKeyChecking=accept-new', '-o IdentitiesOnly=yes']; +const _optionsWithPrivateKey = [ + '-o StrictHostKeyChecking=accept-new', + '-o IdentitiesOnly=yes' +]; class SSHNPFailed implements SSHNPResult { final String message; @@ -22,8 +20,7 @@ class SSHNPFailed implements SSHNPResult { } } -class SSHCommand implements SSHNPCommandResult { - @override +class SSHNPSuccess implements SSHNPResult { final String command = 'ssh'; final int localPort; @@ -37,7 +34,7 @@ class SSHCommand implements SSHNPCommandResult { Process? sshProcess; SSHClient? sshClient; - SSHCommand.base({ + SSHNPSuccess.base({ required this.localPort, required this.remoteUsername, required this.host, @@ -47,20 +44,23 @@ class SSHCommand implements SSHNPCommandResult { this.sshProcess, this.sshClient, }) : sshOptions = [ - if (shouldIncludePrivateKey(privateKeyFileName)) ..._optionsWithPrivateKey, + if (shouldIncludePrivateKey(privateKeyFileName)) + ..._optionsWithPrivateKey, ...(localSshOptions ?? []) ]; static bool shouldIncludePrivateKey(String? privateKeyFileName) => privateKeyFileName != null && privateKeyFileName.isNotEmpty; - @override List get args => [ '-p $localPort', ...sshOptions, if (remoteUsername != null) '$remoteUsername@$host', if (remoteUsername == null) host, - if (shouldIncludePrivateKey(privateKeyFileName)) ...['-i', '$privateKeyFileName'], + if (shouldIncludePrivateKey(privateKeyFileName)) ...[ + '-i', + '$privateKeyFileName' + ], ]; @override diff --git a/packages/sshnoports/bin/sshnp.dart b/packages/sshnoports/bin/sshnp.dart index 1181f9216..be665c84d 100644 --- a/packages/sshnoports/bin/sshnp.dart +++ b/packages/sshnoports/bin/sshnp.dart @@ -77,7 +77,7 @@ void main(List args) async { stderr.write('$res\n'); exit(1); } - if (res is SSHCommand) { + if (res is SSHNPSuccess) { stdout.write('$res\n'); await sshnp.done; exit(0); diff --git a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart index 631d7d55d..cf85dbbd8 100644 --- a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart +++ b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart @@ -28,7 +28,10 @@ class _ProfileRunActionState extends ConsumerState { } Future onStart() async { - ref.read(backgroundSessionFamilyController(widget.params.profileName!).notifier).start(); + ref + .read(backgroundSessionFamilyController(widget.params.profileName!) + .notifier) + .start(); try { SSHNPParams params = SSHNPParams.merge( widget.params, @@ -52,7 +55,10 @@ class _ProfileRunActionState extends ConsumerState { if (sshnpResult is SSHNPFailed) { throw sshnpResult!; } - ref.read(backgroundSessionFamilyController(widget.params.profileName!).notifier).endStartUp(); + ref + .read(backgroundSessionFamilyController(widget.params.profileName!) + .notifier) + .endStartUp(); } catch (e) { Future stop = onStop(); if (mounted) { @@ -63,17 +69,22 @@ class _ProfileRunActionState extends ConsumerState { } Future onStop() async { - if (sshnpResult is SSHCommand) { - (sshnpResult as SSHCommand).sshProcess?.kill(); // DirectSSHViaExec - (sshnpResult as SSHCommand).sshClient?.close(); // DirectSSHViaClient - var sshrvResult = await (sshnpResult as SSHCommand).sshrvResult; + if (sshnpResult is SSHNPSuccess) { + (sshnpResult as SSHNPSuccess).sshProcess?.kill(); // DirectSSHViaExec + (sshnpResult as SSHNPSuccess).sshClient?.close(); // DirectSSHViaClient + var sshrvResult = await (sshnpResult as SSHNPSuccess).sshrvResult; if (sshrvResult is Process) sshrvResult.kill(); // SSHRV via local binary - if (sshrvResult is SocketConnector) sshrvResult.close(); // SSHRV via pure dart + if (sshrvResult is SocketConnector) + sshrvResult.close(); // SSHRV via pure dart } - ref.read(backgroundSessionFamilyController(widget.params.profileName!).notifier).stop(); + ref + .read(backgroundSessionFamilyController(widget.params.profileName!) + .notifier) + .stop(); } - static Widget getIconFromStatus(BackgroundSessionStatus status, BuildContext context) { + static Widget getIconFromStatus( + BackgroundSessionStatus status, BuildContext context) { switch (status) { case BackgroundSessionStatus.stopped: return const Icon(Icons.play_arrow); @@ -90,7 +101,8 @@ class _ProfileRunActionState extends ConsumerState { @override Widget build(BuildContext context) { - final status = ref.watch(backgroundSessionFamilyController(widget.params.profileName!)); + final status = ref + .watch(backgroundSessionFamilyController(widget.params.profileName!)); return ProfileActionButton( onPressed: () async { switch (status) { From 58b4f7e62587d7bfc157afc227448d502a58468c Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Wed, 27 Sep 2023 10:50:23 -0400 Subject: [PATCH 02/49] chore: rename SSHNPCommandResult to SSHNPSuccess --- .../widgets/profile_actions/profile_terminal_action.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_terminal_action.dart b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_terminal_action.dart index d2a205f32..a26f66b82 100644 --- a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_terminal_action.dart +++ b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_terminal_action.dart @@ -59,7 +59,7 @@ class _ProfileTerminalActionState extends ConsumerState { final sessionController = ref.watch(terminalSessionFamilyController(sessionId).notifier); - if (result is SSHNPCommandResult) { + if (result is SSHNPSuccess) { /// Set the command for the new session sessionController.setProcess( command: result.command, args: result.args); From 44a619a80c8a842e114d986cc4ad32faad4c9e42 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Thu, 28 Sep 2023 05:41:05 -0400 Subject: [PATCH 03/49] chore: remove unused code --- .../lib/common/sync_listener.dart | 22 ------------------- packages/noports_core/test/sshnp_test.dart | 3 --- 2 files changed, 25 deletions(-) delete mode 100644 packages/noports_core/lib/common/sync_listener.dart diff --git a/packages/noports_core/lib/common/sync_listener.dart b/packages/noports_core/lib/common/sync_listener.dart deleted file mode 100644 index b158d0c72..000000000 --- a/packages/noports_core/lib/common/sync_listener.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:at_client/at_client.dart'; - -class MySyncProgressListener extends SyncProgressListener { - bool syncComplete = false; - String syncResult = 'syncing'; - - @override - void onSyncProgressEvent(SyncProgress syncProgress) { - if (syncProgress.syncStatus == SyncStatus.failure || - syncProgress.syncStatus == SyncStatus.success) { - syncComplete = true; - } - if (syncProgress.syncStatus == SyncStatus.failure) { - syncResult = 'Failed'; - } - if (syncProgress.syncStatus == SyncStatus.success) { - syncResult = 'Succeeded'; - } - - return; - } -} diff --git a/packages/noports_core/test/sshnp_test.dart b/packages/noports_core/test/sshnp_test.dart index 772d27a44..2c45313c9 100644 --- a/packages/noports_core/test/sshnp_test.dart +++ b/packages/noports_core/test/sshnp_test.dart @@ -1,9 +1,6 @@ -import 'dart:io'; - import 'package:args/args.dart'; import 'package:noports_core/common/utils.dart'; import 'package:noports_core/sshnp/sshnp.dart'; -import 'package:path/path.dart' show dirname; import 'package:test/test.dart'; void main() { From e6372cd23c53b8389e11fa1550152bd87d6c378d Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Thu, 28 Sep 2023 05:51:24 -0400 Subject: [PATCH 04/49] chore: reduce scope pollution of SSHNP's argParser --- .../noports_core/lib/sshnp/sshnp_arg.dart | 97 ++++++++++--------- .../noports_core/lib/sshnp/sshnp_params.dart | 41 +++++--- 2 files changed, 78 insertions(+), 60 deletions(-) diff --git a/packages/noports_core/lib/sshnp/sshnp_arg.dart b/packages/noports_core/lib/sshnp/sshnp_arg.dart index 26448ab2d..d42c2f19e 100644 --- a/packages/noports_core/lib/sshnp/sshnp_arg.dart +++ b/packages/noports_core/lib/sshnp/sshnp_arg.dart @@ -96,21 +96,24 @@ class SSHNPArg { const SSHNPArg( name: 'port', abbr: 'p', - help: 'TCP port to connect back to (only required if --host specified a FQDN/IP)', + help: + 'TCP port to connect back to (only required if --host specified a FQDN/IP)', defaultsTo: SSHNP.defaultPort, type: ArgType.integer, ), const SSHNPArg( name: 'local-port', abbr: 'l', - help: 'Reverse ssh port to listen on, on your local machine, by sshnp default finds a spare port', + help: + 'Reverse ssh port to listen on, on your local machine, by sshnp default finds a spare port', defaultsTo: SSHNP.defaultLocalPort, type: ArgType.integer, ), const SSHNPArg( name: 'ssh-public-key', abbr: 's', - help: 'Public key file from ~/.ssh to be appended to authorized_hosts on the remote device', + help: + 'Public key file from ~/.ssh to be appended to authorized_hosts on the remote device', defaultsTo: SSHNP.defaultSendSshPublicKey, ), const SSHNPArg( @@ -170,7 +173,8 @@ class SSHNPArg { ), const SSHNPArg( name: 'idle-timeout', - help: 'number of seconds after which inactive ssh connections will be closed', + help: + 'number of seconds after which inactive ssh connections will be closed', defaultsTo: defaultIdleTimeout, mandatory: false, format: ArgFormat.option, @@ -197,7 +201,8 @@ class SSHNPArg { ), const SSHNPArg( name: 'config-file', - help: 'Read args from a config file\nMandatory args are not required if already supplied in the config file', + help: + 'Read args from a config file\nMandatory args are not required if already supplied in the config file', commandLineOnly: true, ), const SSHNPArg( @@ -213,48 +218,48 @@ class SSHNPArg { String toString() { return 'SSHNPArg{format: $format, name: $name, abbr: $abbr, help: $help, mandatory: $mandatory, defaultsTo: $defaultsTo, type: $type}'; } -} -ArgParser createArgParser({ - bool isCommandLine = true, - bool withDefaults = true, -}) { - var parser = ArgParser(); - // Basic arguments - for (SSHNPArg arg in SSHNPArg.args) { - if (arg.commandLineOnly && !isCommandLine) { - continue; - } - switch (arg.format) { - case ArgFormat.option: - parser.addOption( - arg.name, - abbr: arg.abbr, - mandatory: arg.mandatory, - defaultsTo: withDefaults ? arg.defaultsTo?.toString() : null, - help: arg.help, - allowed: arg.allowed, - aliases: arg.aliases ?? const [], - ); - break; - case ArgFormat.multiOption: - parser.addMultiOption( - arg.name, - abbr: arg.abbr, - defaultsTo: withDefaults ? arg.defaultsTo as List? : null, - help: arg.help, - ); - break; - case ArgFormat.flag: - parser.addFlag( - arg.name, - abbr: arg.abbr, - defaultsTo: withDefaults ? arg.defaultsTo as bool? : null, - help: arg.help, - negatable: arg.negatable, - ); - break; + static ArgParser createArgParser({ + bool isCommandLine = true, + bool withDefaults = true, + }) { + var parser = ArgParser(); + // Basic arguments + for (SSHNPArg arg in SSHNPArg.args) { + if (arg.commandLineOnly && !isCommandLine) { + continue; + } + switch (arg.format) { + case ArgFormat.option: + parser.addOption( + arg.name, + abbr: arg.abbr, + mandatory: arg.mandatory, + defaultsTo: withDefaults ? arg.defaultsTo?.toString() : null, + help: arg.help, + allowed: arg.allowed, + aliases: arg.aliases ?? const [], + ); + break; + case ArgFormat.multiOption: + parser.addMultiOption( + arg.name, + abbr: arg.abbr, + defaultsTo: withDefaults ? arg.defaultsTo as List? : null, + help: arg.help, + ); + break; + case ArgFormat.flag: + parser.addFlag( + arg.name, + abbr: arg.abbr, + defaultsTo: withDefaults ? arg.defaultsTo as bool? : null, + help: arg.help, + negatable: arg.negatable, + ); + break; + } } + return parser; } - return parser; } diff --git a/packages/noports_core/lib/sshnp/sshnp_params.dart b/packages/noports_core/lib/sshnp/sshnp_params.dart index 447e4713c..f03da97ec 100644 --- a/packages/noports_core/lib/sshnp/sshnp_params.dart +++ b/packages/noports_core/lib/sshnp/sshnp_params.dart @@ -33,7 +33,8 @@ class SSHNPParams { late final String sshClient; /// Special Arguments - final String? profileName; // automatically populated with the filename if from a configFile + final String? + profileName; // automatically populated with the filename if from a configFile final bool listDevices; SSHNPParams({ @@ -67,7 +68,8 @@ class SSHNPParams { // Use default atKeysFilePath if not provided - this.atKeysFilePath = atKeysFilePath ?? getDefaultAtKeysFilePath(homeDirectory, clientAtSign); + this.atKeysFilePath = + atKeysFilePath ?? getDefaultAtKeysFilePath(homeDirectory, clientAtSign); this.sshClient = sshClient ?? SSHNP.defaultSshClient.cliArg; } @@ -83,7 +85,8 @@ class SSHNPParams { /// Merge an SSHNPPartialParams objects into an SSHNPParams /// Params in params2 take precedence over params1 - factory SSHNPParams.merge(SSHNPParams params1, [SSHNPPartialParams? params2]) { + factory SSHNPParams.merge(SSHNPParams params1, + [SSHNPPartialParams? params2]) { params2 ??= SSHNPPartialParams.empty(); return SSHNPParams( profileName: params2.profileName ?? params1.profileName, @@ -106,7 +109,8 @@ class SSHNPParams { remoteSshdPort: params2.remoteSshdPort ?? params1.remoteSshdPort, idleTimeout: params2.idleTimeout ?? params1.idleTimeout, sshClient: params2.sshClient ?? params1.sshClient, - addForwardsToTunnel: params2.addForwardsToTunnel ?? params1.addForwardsToTunnel, + addForwardsToTunnel: + params2.addForwardsToTunnel ?? params1.addForwardsToTunnel, ); } @@ -114,7 +118,8 @@ class SSHNPParams { return SSHNPParams.fromPartial(SSHNPPartialParams.fromFile(fileName)); } - factory SSHNPParams.fromJson(String json) => SSHNPParams.fromPartial(SSHNPPartialParams.fromJson(json)); + factory SSHNPParams.fromJson(String json) => + SSHNPParams.fromPartial(SSHNPPartialParams.fromJson(json)); factory SSHNPParams.fromPartial(SSHNPPartialParams partial) { AtSignLogger logger = AtSignLogger(' SSHNPParams '); @@ -133,7 +138,8 @@ class SSHNPParams { device: partial.device ?? SSHNP.defaultDevice, port: partial.port ?? SSHNP.defaultPort, localPort: partial.localPort ?? SSHNP.defaultLocalPort, - sendSshPublicKey: partial.sendSshPublicKey ?? SSHNP.defaultSendSshPublicKey, + sendSshPublicKey: + partial.sendSshPublicKey ?? SSHNP.defaultSendSshPublicKey, localSshOptions: partial.localSshOptions ?? SSHNP.defaultLocalSshOptions, rsa: partial.rsa ?? defaults.defaultRsa, verbose: partial.verbose ?? defaults.defaultVerbose, @@ -151,7 +157,8 @@ class SSHNPParams { } factory SSHNPParams.fromConfig(String profileName, List lines) { - return SSHNPParams.fromPartial(SSHNPPartialParams.fromConfig(profileName, lines)); + return SSHNPParams.fromPartial( + SSHNPPartialParams.fromConfig(profileName, lines)); } Map toArgs() { @@ -203,7 +210,7 @@ class SSHNPParams { /// e.g. default values from a config file and the rest from the command line class SSHNPPartialParams { // Non param variables - static final ArgParser parser = createArgParser(); + static final ArgParser parser = SSHNPArg.createArgParser(); /// Main Params final String? profileName; @@ -261,7 +268,8 @@ class SSHNPPartialParams { /// Merge two SSHNPPartialParams objects together /// Params in params2 take precedence over params1 - factory SSHNPPartialParams.merge(SSHNPPartialParams params1, [SSHNPPartialParams? params2]) { + factory SSHNPPartialParams.merge(SSHNPPartialParams params1, + [SSHNPPartialParams? params2]) { params2 ??= SSHNPPartialParams.empty(); return SSHNPPartialParams( profileName: params2.profileName ?? params1.profileName, @@ -284,7 +292,8 @@ class SSHNPPartialParams { remoteSshdPort: params2.remoteSshdPort ?? params1.remoteSshdPort, idleTimeout: params2.idleTimeout ?? params1.idleTimeout, sshClient: params2.sshClient ?? params1.sshClient, - addForwardsToTunnel: params2.addForwardsToTunnel ?? params1.addForwardsToTunnel, + addForwardsToTunnel: + params2.addForwardsToTunnel ?? params1.addForwardsToTunnel, ); } @@ -294,13 +303,15 @@ class SSHNPPartialParams { return SSHNPPartialParams.fromMap(args); } - factory SSHNPPartialParams.fromConfig(String profileName, List lines) { + factory SSHNPPartialParams.fromConfig( + String profileName, List lines) { var args = ConfigFileRepository.parseConfigFileContents(lines); args['profile-name'] = profileName; return SSHNPPartialParams.fromMap(args); } - factory SSHNPPartialParams.fromJson(String json) => SSHNPPartialParams.fromMap(jsonDecode(json)); + factory SSHNPPartialParams.fromJson(String json) => + SSHNPPartialParams.fromMap(jsonDecode(json)); factory SSHNPPartialParams.fromMap(Map args) { return SSHNPPartialParams( @@ -333,7 +344,7 @@ class SSHNPPartialParams { factory SSHNPPartialParams.fromArgs(List args) { var params = SSHNPPartialParams.empty(); - var parsedArgs = createArgParser(withDefaults: false).parse(args); + var parsedArgs = SSHNPArg.createArgParser(withDefaults: false).parse(args); if (parsedArgs.wasParsed('config-file')) { var configFileName = parsedArgs['config-file'] as String; @@ -346,7 +357,9 @@ class SSHNPPartialParams { // THIS IS A WORKAROUND IN ORDER TO BE TYPE SAFE IN SSHNPPartialParams.fromArgMap Map parsedArgsMap = { for (var e in parsedArgs.options) - e: SSHNPArg.fromName(e).type == ArgType.integer ? int.tryParse(parsedArgs[e]) : parsedArgs[e] + e: SSHNPArg.fromName(e).type == ArgType.integer + ? int.tryParse(parsedArgs[e]) + : parsedArgs[e] }; return SSHNPPartialParams.merge( From 3606586ca46d3fbfbe3616f39f427c3840e83b33 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Thu, 28 Sep 2023 06:25:14 -0400 Subject: [PATCH 05/49] chore: separate params class, defaults --- .../noports_core/lib/common/default_args.dart | 27 +++++++ .../noports_core/lib/common/defaults.dart | 11 --- .../config_file_repository.dart | 2 +- .../config_key_repository.dart | 2 +- .../lib/sshnp/{ => params}/sshnp_params.dart | 70 +++++++++++-------- packages/noports_core/lib/sshnp/sshnp.dart | 30 +++----- .../noports_core/lib/sshnp/sshnp_arg.dart | 24 +++---- .../noports_core/lib/sshnp/sshnp_impl.dart | 16 ++--- packages/noports_core/lib/sshnpd/sshnpd.dart | 2 +- .../noports_core/lib/sshnpd/sshnpd_impl.dart | 2 +- .../lib/sshnpd/sshnpd_params.dart | 6 +- packages/noports_core/lib/sshrv/sshrv.dart | 4 +- .../noports_core/lib/sshrv/sshrv_impl.dart | 2 +- packages/noports_core/test/sshnp_test.dart | 2 +- packages/sshnoports/bin/sshnp.dart | 1 + 15 files changed, 106 insertions(+), 95 deletions(-) create mode 100644 packages/noports_core/lib/common/default_args.dart delete mode 100644 packages/noports_core/lib/common/defaults.dart rename packages/noports_core/lib/sshnp/{ => params}/sshnp_params.dart (84%) diff --git a/packages/noports_core/lib/common/default_args.dart b/packages/noports_core/lib/common/default_args.dart new file mode 100644 index 000000000..16395a908 --- /dev/null +++ b/packages/noports_core/lib/common/default_args.dart @@ -0,0 +1,27 @@ +import 'package:noports_core/common/supported_ssh_clients.dart'; +import 'package:noports_core/sshrv/sshrv.dart'; + +class DefaultArgs { + const DefaultArgs(); + + static const verbose = false; + static const rsa = false; + static const rootDomain = 'root.atsign.org'; + static const sshrvGenerator = SSHRV.localBinary; + static const localSshdPort = 22; + static const remoteSshdPort = 22; + + /// value in seconds after which idle ssh tunnels will be closed + static const idleTimeout = 15; +} + +class DefaultSSHNPArgs { + static const device = 'default'; + static const port = 22; + static const localPort = 0; + static const sendSshPublicKey = ''; + static const localSshOptions = []; + static const legacyDaemon = true; + static const listDevices = false; + static const sshClient = SupportedSshClient.hostSsh; +} diff --git a/packages/noports_core/lib/common/defaults.dart b/packages/noports_core/lib/common/defaults.dart deleted file mode 100644 index 10b371997..000000000 --- a/packages/noports_core/lib/common/defaults.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:noports_core/sshrv/sshrv.dart'; - -const defaultVerbose = false; -const defaultRsa = false; -const defaultRootDomain = 'root.atsign.org'; -const defaultSshrvGenerator = SSHRV.localBinary; -const defaultLocalSshdPort = 22; -const defaultRemoteSshdPort = 22; - -/// value in seconds after which idle ssh tunnels will be closed -const defaultIdleTimeout = 15; diff --git a/packages/noports_core/lib/sshnp/config_repository/config_file_repository.dart b/packages/noports_core/lib/sshnp/config_repository/config_file_repository.dart index 12e7e6dd5..48cdeb26d 100644 --- a/packages/noports_core/lib/sshnp/config_repository/config_file_repository.dart +++ b/packages/noports_core/lib/sshnp/config_repository/config_file_repository.dart @@ -1,7 +1,7 @@ import 'dart:io'; import 'package:noports_core/common/utils.dart'; -import 'package:noports_core/sshnp/sshnp.dart'; +import 'package:noports_core/sshnp/params/sshnp_params.dart'; import 'package:noports_core/sshnp/sshnp_arg.dart'; import 'package:path/path.dart' as path; diff --git a/packages/noports_core/lib/sshnp/config_repository/config_key_repository.dart b/packages/noports_core/lib/sshnp/config_repository/config_key_repository.dart index 05b540916..eba962187 100644 --- a/packages/noports_core/lib/sshnp/config_repository/config_key_repository.dart +++ b/packages/noports_core/lib/sshnp/config_repository/config_key_repository.dart @@ -1,5 +1,5 @@ import 'package:at_client/at_client.dart'; -import 'package:noports_core/sshnp/sshnp.dart'; +import 'package:noports_core/sshnp/params/sshnp_params.dart'; import 'package:noports_core/sshnpd/sshnpd.dart'; class ConfigKeyRepository { diff --git a/packages/noports_core/lib/sshnp/sshnp_params.dart b/packages/noports_core/lib/sshnp/params/sshnp_params.dart similarity index 84% rename from packages/noports_core/lib/sshnp/sshnp_params.dart rename to packages/noports_core/lib/sshnp/params/sshnp_params.dart index f03da97ec..4a15358f5 100644 --- a/packages/noports_core/lib/sshnp/sshnp_params.dart +++ b/packages/noports_core/lib/sshnp/params/sshnp_params.dart @@ -1,4 +1,12 @@ -part of 'sshnp.dart'; +import 'dart:convert'; + +import 'package:args/args.dart'; +import 'package:at_utils/at_logger.dart'; +import 'package:noports_core/common/utils.dart'; +import 'package:noports_core/sshnp/config_repository/config_file_repository.dart'; +import 'package:noports_core/sshnp/sshnp.dart'; +import 'package:noports_core/sshnp/sshnp_arg.dart'; +import 'package:noports_core/common/default_args.dart'; class SSHNPParams { /// Required Arguments @@ -42,21 +50,21 @@ class SSHNPParams { required this.sshnpdAtSign, required this.host, this.profileName, - this.device = SSHNP.defaultDevice, - this.port = SSHNP.defaultPort, - this.localPort = SSHNP.defaultLocalPort, - this.sendSshPublicKey = SSHNP.defaultSendSshPublicKey, - this.localSshOptions = SSHNP.defaultLocalSshOptions, - this.verbose = defaults.defaultVerbose, - this.rsa = defaults.defaultRsa, + this.device = DefaultSSHNPArgs.device, + this.port = DefaultSSHNPArgs.port, + this.localPort = DefaultSSHNPArgs.localPort, + this.sendSshPublicKey = DefaultSSHNPArgs.sendSshPublicKey, + this.localSshOptions = DefaultSSHNPArgs.localSshOptions, + this.verbose = DefaultArgs.verbose, + this.rsa = DefaultArgs.rsa, this.remoteUsername, String? atKeysFilePath, - this.rootDomain = defaults.defaultRootDomain, - this.localSshdPort = defaults.defaultLocalSshdPort, - this.legacyDaemon = SSHNP.defaultLegacyDaemon, - this.listDevices = SSHNP.defaultListDevices, - this.remoteSshdPort = defaults.defaultRemoteSshdPort, - this.idleTimeout = defaults.defaultIdleTimeout, + this.rootDomain = DefaultArgs.rootDomain, + this.localSshdPort = DefaultArgs.localSshdPort, + this.legacyDaemon = DefaultSSHNPArgs.legacyDaemon, + this.listDevices = DefaultSSHNPArgs.listDevices, + this.remoteSshdPort = DefaultArgs.remoteSshdPort, + this.idleTimeout = DefaultArgs.idleTimeout, String? sshClient, this.addForwardsToTunnel = false, }) { @@ -71,7 +79,7 @@ class SSHNPParams { this.atKeysFilePath = atKeysFilePath ?? getDefaultAtKeysFilePath(homeDirectory, clientAtSign); - this.sshClient = sshClient ?? SSHNP.defaultSshClient.cliArg; + this.sshClient = sshClient ?? DefaultSSHNPArgs.sshClient.cliArg; } factory SSHNPParams.empty() { @@ -135,23 +143,23 @@ class SSHNPParams { clientAtSign: partial.clientAtSign, sshnpdAtSign: partial.sshnpdAtSign, host: partial.host, - device: partial.device ?? SSHNP.defaultDevice, - port: partial.port ?? SSHNP.defaultPort, - localPort: partial.localPort ?? SSHNP.defaultLocalPort, + device: partial.device ?? DefaultSSHNPArgs.device, + port: partial.port ?? DefaultSSHNPArgs.port, + localPort: partial.localPort ?? DefaultSSHNPArgs.localPort, sendSshPublicKey: - partial.sendSshPublicKey ?? SSHNP.defaultSendSshPublicKey, - localSshOptions: partial.localSshOptions ?? SSHNP.defaultLocalSshOptions, - rsa: partial.rsa ?? defaults.defaultRsa, - verbose: partial.verbose ?? defaults.defaultVerbose, + partial.sendSshPublicKey ?? DefaultSSHNPArgs.sendSshPublicKey, + localSshOptions: partial.localSshOptions ?? DefaultSSHNPArgs.localSshOptions, + rsa: partial.rsa ?? DefaultArgs.rsa, + verbose: partial.verbose ?? DefaultArgs.verbose, remoteUsername: partial.remoteUsername, atKeysFilePath: partial.atKeysFilePath, - rootDomain: partial.rootDomain ?? defaults.defaultRootDomain, - localSshdPort: partial.localSshdPort ?? defaults.defaultLocalSshdPort, - listDevices: partial.listDevices ?? SSHNP.defaultListDevices, - legacyDaemon: partial.legacyDaemon ?? SSHNP.defaultLegacyDaemon, - remoteSshdPort: partial.remoteSshdPort ?? defaults.defaultRemoteSshdPort, - idleTimeout: partial.idleTimeout ?? defaults.defaultIdleTimeout, - sshClient: partial.sshClient ?? SSHNP.defaultSshClient.cliArg, + rootDomain: partial.rootDomain ?? DefaultArgs.rootDomain, + localSshdPort: partial.localSshdPort ?? DefaultArgs.localSshdPort, + listDevices: partial.listDevices ?? DefaultSSHNPArgs.listDevices, + legacyDaemon: partial.legacyDaemon ?? DefaultSSHNPArgs.legacyDaemon, + remoteSshdPort: partial.remoteSshdPort ?? DefaultArgs.remoteSshdPort, + idleTimeout: partial.idleTimeout ?? DefaultArgs.idleTimeout, + sshClient: partial.sshClient ?? DefaultSSHNPArgs.sshClient.cliArg, addForwardsToTunnel: partial.addForwardsToTunnel ?? false, ); } @@ -254,8 +262,8 @@ class SSHNPPartialParams { this.verbose, this.rootDomain, this.localSshdPort, - this.listDevices = SSHNP.defaultListDevices, - this.legacyDaemon = SSHNP.defaultLegacyDaemon, + this.listDevices = DefaultSSHNPArgs.listDevices, + this.legacyDaemon = DefaultSSHNPArgs.legacyDaemon, this.remoteSshdPort, this.idleTimeout, this.sshClient, diff --git a/packages/noports_core/lib/sshnp/sshnp.dart b/packages/noports_core/lib/sshnp/sshnp.dart index d34c87db1..8afd3399e 100644 --- a/packages/noports_core/lib/sshnp/sshnp.dart +++ b/packages/noports_core/lib/sshnp/sshnp.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:args/args.dart'; import 'package:at_client/at_client.dart' hide StringBuffer; import 'package:at_commons/at_builders.dart'; import 'package:at_utils/at_logger.dart'; @@ -10,21 +9,18 @@ import 'package:at_utils/at_utils.dart'; import 'package:dartssh2/dartssh2.dart'; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; +import 'package:noports_core/common/default_args.dart'; +import 'package:noports_core/sshnp/params/sshnp_params.dart'; import 'package:path/path.dart' as path; import 'package:noports_core/common/supported_ssh_clients.dart'; import 'package:noports_core/common/utils.dart'; -import 'package:noports_core/sshnp/config_repository/config_file_repository.dart'; -import 'package:noports_core/sshnp/sshnp_arg.dart'; import 'package:noports_core/sshnp/utils.dart'; import 'package:noports_core/sshnpd/sshnpd.dart'; import 'package:noports_core/sshrv/sshrv.dart'; import 'package:noports_core/sshrvd/sshrvd.dart'; import 'package:uuid/uuid.dart'; -import 'package:noports_core/common/defaults.dart' as defaults; - part 'sshnp_impl.dart'; -part 'sshnp_params.dart'; part 'sshnp_result.dart'; abstract class SSHNP { @@ -167,16 +163,6 @@ abstract class SSHNP { /// e.g. controlling a direct ssh tunnel using the pure-dart SSHClient Future get done; - /// Default parameters for sshnp - static const defaultDevice = 'default'; - static const defaultPort = 22; - static const defaultLocalPort = 0; - static const defaultSendSshPublicKey = ''; - static const defaultLocalSshOptions = []; - static const defaultLegacyDaemon = true; - static const defaultListDevices = false; - static const defaultSshClient = SupportedSshClient.hostSsh; - factory SSHNP({ // final fields required AtClient atClient, @@ -185,7 +171,7 @@ abstract class SSHNP { required String username, required String homeDirectory, required String sessionId, - String sendSshPublicKey = SSHNP.defaultSendSshPublicKey, + String sendSshPublicKey = DefaultSSHNPArgs.sendSshPublicKey, required List localSshOptions, bool rsa = false, // volatile fields @@ -194,11 +180,11 @@ abstract class SSHNP { required int localPort, String? remoteUsername, bool verbose = false, - SSHRVGenerator sshrvGenerator = defaults.defaultSshrvGenerator, - int localSshdPort = defaults.defaultLocalSshdPort, - bool legacyDaemon = defaultLegacyDaemon, - int remoteSshdPort = defaults.defaultRemoteSshdPort, - int idleTimeout = defaults.defaultIdleTimeout, + SSHRVGenerator sshrvGenerator = DefaultArgs.sshrvGenerator, + int localSshdPort = DefaultArgs.localSshdPort, + bool legacyDaemon = DefaultSSHNPArgs.legacyDaemon, + int remoteSshdPort = DefaultArgs.remoteSshdPort, + int idleTimeout = DefaultArgs.idleTimeout, required SupportedSshClient sshClient, required bool addForwardsToTunnel, }) { diff --git a/packages/noports_core/lib/sshnp/sshnp_arg.dart b/packages/noports_core/lib/sshnp/sshnp_arg.dart index d42c2f19e..bb7794f4d 100644 --- a/packages/noports_core/lib/sshnp/sshnp_arg.dart +++ b/packages/noports_core/lib/sshnp/sshnp_arg.dart @@ -1,7 +1,7 @@ import 'package:args/args.dart'; import 'package:noports_core/common/supported_ssh_clients.dart'; -import 'package:noports_core/common/defaults.dart'; +import 'package:noports_core/common/default_args.dart'; import 'sshnp.dart'; enum ArgFormat { @@ -85,7 +85,7 @@ class SSHNPArg { name: 'device', abbr: 'd', help: 'Receiving device name', - defaultsTo: SSHNP.defaultDevice, + defaultsTo: DefaultSSHNPArgs.device, ), const SSHNPArg( name: 'host', @@ -98,7 +98,7 @@ class SSHNPArg { abbr: 'p', help: 'TCP port to connect back to (only required if --host specified a FQDN/IP)', - defaultsTo: SSHNP.defaultPort, + defaultsTo: DefaultSSHNPArgs.port, type: ArgType.integer, ), const SSHNPArg( @@ -106,7 +106,7 @@ class SSHNPArg { abbr: 'l', help: 'Reverse ssh port to listen on, on your local machine, by sshnp default finds a spare port', - defaultsTo: SSHNP.defaultLocalPort, + defaultsTo: DefaultSSHNPArgs.localPort, type: ArgType.integer, ), const SSHNPArg( @@ -114,7 +114,7 @@ class SSHNPArg { abbr: 's', help: 'Public key file from ~/.ssh to be appended to authorized_hosts on the remote device', - defaultsTo: SSHNP.defaultSendSshPublicKey, + defaultsTo: DefaultSSHNPArgs.sendSshPublicKey, ), const SSHNPArg( name: 'local-ssh-options', @@ -125,14 +125,14 @@ class SSHNPArg { const SSHNPArg( name: 'verbose', abbr: 'v', - defaultsTo: defaultVerbose, + defaultsTo: DefaultArgs.verbose, help: 'More logging', format: ArgFormat.flag, ), const SSHNPArg( name: 'rsa', abbr: 'r', - defaultsTo: defaultRsa, + defaultsTo: DefaultArgs.rsa, help: 'Use RSA 4096 keys rather than the default ED25519 keys', format: ArgFormat.flag, ), @@ -144,14 +144,14 @@ class SSHNPArg { const SSHNPArg( name: 'root-domain', help: 'atDirectory domain', - defaultsTo: defaultRootDomain, + defaultsTo: DefaultArgs.rootDomain, mandatory: false, format: ArgFormat.option, ), const SSHNPArg( name: 'local-sshd-port', help: 'port on which sshd is listening locally on the client host', - defaultsTo: defaultLocalSshdPort, + defaultsTo: DefaultArgs.localSshdPort, abbr: 'P', mandatory: false, format: ArgFormat.option, @@ -159,14 +159,14 @@ class SSHNPArg { ), const SSHNPArg( name: 'legacy-daemon', - defaultsTo: SSHNP.defaultLegacyDaemon, + defaultsTo: DefaultSSHNPArgs.legacyDaemon, help: 'Request is to a legacy (< 4.0.0) noports daemon', format: ArgFormat.flag, ), const SSHNPArg( name: 'remote-sshd-port', help: 'port on which sshd is listening locally on the device host', - defaultsTo: defaultRemoteSshdPort, + defaultsTo: DefaultArgs.remoteSshdPort, mandatory: false, format: ArgFormat.option, type: ArgType.integer, @@ -175,7 +175,7 @@ class SSHNPArg { name: 'idle-timeout', help: 'number of seconds after which inactive ssh connections will be closed', - defaultsTo: defaultIdleTimeout, + defaultsTo: DefaultArgs.idleTimeout, mandatory: false, format: ArgFormat.option, type: ArgType.integer, diff --git a/packages/noports_core/lib/sshnp/sshnp_impl.dart b/packages/noports_core/lib/sshnp/sshnp_impl.dart index c295da3d3..68367f079 100644 --- a/packages/noports_core/lib/sshnp/sshnp_impl.dart +++ b/packages/noports_core/lib/sshnp/sshnp_impl.dart @@ -175,19 +175,19 @@ class SSHNPImpl implements SSHNP { required this.username, required this.homeDirectory, required this.sessionId, - String sendSshPublicKey = SSHNP.defaultSendSshPublicKey, + String sendSshPublicKey = DefaultSSHNPArgs.sendSshPublicKey, required this.localSshOptions, - this.rsa = defaults.defaultRsa, + this.rsa = DefaultArgs.rsa, required this.host, required this.port, required this.localPort, this.remoteUsername, - this.verbose = defaults.defaultVerbose, - this.sshrvGenerator = defaults.defaultSshrvGenerator, - this.localSshdPort = defaults.defaultLocalSshdPort, - this.legacyDaemon = SSHNP.defaultLegacyDaemon, - this.remoteSshdPort = defaults.defaultRemoteSshdPort, - this.idleTimeout = defaults.defaultIdleTimeout, + this.verbose = DefaultArgs.verbose, + this.sshrvGenerator = DefaultArgs.sshrvGenerator, + this.localSshdPort = DefaultArgs.localSshdPort, + this.legacyDaemon = DefaultSSHNPArgs.legacyDaemon, + this.remoteSshdPort = DefaultArgs.remoteSshdPort, + this.idleTimeout = DefaultArgs.idleTimeout, required this.sshClient, this.addForwardsToTunnel = false, }) { diff --git a/packages/noports_core/lib/sshnpd/sshnpd.dart b/packages/noports_core/lib/sshnpd/sshnpd.dart index 936cd204c..84e3cc094 100644 --- a/packages/noports_core/lib/sshnpd/sshnpd.dart +++ b/packages/noports_core/lib/sshnpd/sshnpd.dart @@ -16,7 +16,7 @@ import 'package:noports_core/version.dart'; import 'package:uuid/uuid.dart'; -import 'package:noports_core/common/defaults.dart' as defaults; +import 'package:noports_core/common/default_args.dart'; part 'sshnpd_impl.dart'; part 'sshnpd_params.dart'; diff --git a/packages/noports_core/lib/sshnpd/sshnpd_impl.dart b/packages/noports_core/lib/sshnpd/sshnpd_impl.dart index ff986a609..6f43df898 100644 --- a/packages/noports_core/lib/sshnpd/sshnpd_impl.dart +++ b/packages/noports_core/lib/sshnpd/sshnpd_impl.dart @@ -60,7 +60,7 @@ class SSHNPDImpl implements SSHNPD { required this.sshClient, this.makeDeviceInfoVisible = false, this.addSshPublicKeys = false, - this.localSshdPort = defaults.defaultLocalSshdPort, + this.localSshdPort = DefaultArgs.localSshdPort, required this.ephemeralPermissions, required this.rsa, }) { diff --git a/packages/noports_core/lib/sshnpd/sshnpd_params.dart b/packages/noports_core/lib/sshnpd/sshnpd_params.dart index f2dd4b585..117b4ad0f 100644 --- a/packages/noports_core/lib/sshnpd/sshnpd_params.dart +++ b/packages/noports_core/lib/sshnpd/sshnpd_params.dart @@ -55,7 +55,7 @@ class SSHNPDParams { addSshPublicKeys = r['sshpublickey']; localSshdPort = - int.tryParse(r['local-sshd-port']) ?? defaults.defaultLocalSshdPort; + int.tryParse(r['local-sshd-port']) ?? DefaultArgs.localSshdPort; ephemeralPermissions = r['ephemeral-permissions']; @@ -131,7 +131,7 @@ class SSHNPDParams { parser.addOption( 'local-sshd-port', help: 'port on which sshd is listening locally on localhost', - defaultsTo: defaults.defaultLocalSshdPort.toString(), + defaultsTo: DefaultArgs.localSshdPort.toString(), mandatory: false, ); @@ -146,7 +146,7 @@ class SSHNPDParams { parser.addFlag( 'rsa', abbr: 'r', - defaultsTo: defaults.defaultRsa, + defaultsTo: DefaultArgs.rsa, help: 'Use RSA 4096 keys rather than the default ED25519 keys', ); diff --git a/packages/noports_core/lib/sshrv/sshrv.dart b/packages/noports_core/lib/sshrv/sshrv.dart index 349f7435d..57bc3c231 100644 --- a/packages/noports_core/lib/sshrv/sshrv.dart +++ b/packages/noports_core/lib/sshrv/sshrv.dart @@ -4,7 +4,7 @@ import 'package:at_utils/at_utils.dart'; import 'package:meta/meta.dart'; import 'package:socket_connector/socket_connector.dart'; -import 'package:noports_core/common/defaults.dart' as defaults; +import 'package:noports_core/common/default_args.dart'; part 'sshrv_impl.dart'; @@ -27,7 +27,7 @@ abstract class SSHRV { static SSHRV localBinary( String host, int streamingPort, { - int localSshdPort = defaults.defaultLocalSshdPort, + int localSshdPort = DefaultArgs.localSshdPort, }) { return SSHRVImpl( host, diff --git a/packages/noports_core/lib/sshrv/sshrv_impl.dart b/packages/noports_core/lib/sshrv/sshrv_impl.dart index 86a979358..d0c6331e6 100644 --- a/packages/noports_core/lib/sshrv/sshrv_impl.dart +++ b/packages/noports_core/lib/sshrv/sshrv_impl.dart @@ -14,7 +14,7 @@ class SSHRVImpl implements SSHRV { const SSHRVImpl( this.host, this.streamingPort, { - this.localSshdPort = defaults.defaultLocalSshdPort, + this.localSshdPort = DefaultArgs.localSshdPort, }); @override diff --git a/packages/noports_core/test/sshnp_test.dart b/packages/noports_core/test/sshnp_test.dart index 2c45313c9..668bc8548 100644 --- a/packages/noports_core/test/sshnp_test.dart +++ b/packages/noports_core/test/sshnp_test.dart @@ -1,6 +1,6 @@ import 'package:args/args.dart'; import 'package:noports_core/common/utils.dart'; -import 'package:noports_core/sshnp/sshnp.dart'; +import 'package:noports_core/sshnp/params/sshnp_params.dart'; import 'package:test/test.dart'; void main() { diff --git a/packages/sshnoports/bin/sshnp.dart b/packages/sshnoports/bin/sshnp.dart index be665c84d..fa4de22c5 100644 --- a/packages/sshnoports/bin/sshnp.dart +++ b/packages/sshnoports/bin/sshnp.dart @@ -4,6 +4,7 @@ import 'dart:io'; // atPlatform packages import 'package:at_utils/at_logger.dart'; +import 'package:noports_core/sshnp/params/sshnp_params.dart'; // local packages import 'package:noports_core/sshnp/sshnp.dart'; From 44ba2405596f8a2903e6e5338151ef77f3d43592 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Thu, 28 Sep 2023 06:25:42 -0400 Subject: [PATCH 06/49] chore: remove unused imports --- packages/noports_core/lib/sshnp/params/sshnp_params.dart | 1 - packages/noports_core/lib/sshnp/sshnp_arg.dart | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/noports_core/lib/sshnp/params/sshnp_params.dart b/packages/noports_core/lib/sshnp/params/sshnp_params.dart index 4a15358f5..0ec53bdd6 100644 --- a/packages/noports_core/lib/sshnp/params/sshnp_params.dart +++ b/packages/noports_core/lib/sshnp/params/sshnp_params.dart @@ -4,7 +4,6 @@ import 'package:args/args.dart'; import 'package:at_utils/at_logger.dart'; import 'package:noports_core/common/utils.dart'; import 'package:noports_core/sshnp/config_repository/config_file_repository.dart'; -import 'package:noports_core/sshnp/sshnp.dart'; import 'package:noports_core/sshnp/sshnp_arg.dart'; import 'package:noports_core/common/default_args.dart'; diff --git a/packages/noports_core/lib/sshnp/sshnp_arg.dart b/packages/noports_core/lib/sshnp/sshnp_arg.dart index bb7794f4d..3a628698e 100644 --- a/packages/noports_core/lib/sshnp/sshnp_arg.dart +++ b/packages/noports_core/lib/sshnp/sshnp_arg.dart @@ -2,7 +2,6 @@ import 'package:args/args.dart'; import 'package:noports_core/common/supported_ssh_clients.dart'; import 'package:noports_core/common/default_args.dart'; -import 'sshnp.dart'; enum ArgFormat { option, From 1687d9ffc60fd76bc6aed09d4d12d57c981fcef5 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Thu, 28 Sep 2023 06:30:45 -0400 Subject: [PATCH 07/49] chore: remove part statements from sshnp --- packages/noports_core/lib/sshnp/sshnp.dart | 15 +++-------- .../noports_core/lib/sshnp/sshnp_impl.dart | 26 ++++++++++++++++--- .../noports_core/lib/sshnp/sshnp_result.dart | 6 ++++- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/packages/noports_core/lib/sshnp/sshnp.dart b/packages/noports_core/lib/sshnp/sshnp.dart index 8afd3399e..bc69fe58f 100644 --- a/packages/noports_core/lib/sshnp/sshnp.dart +++ b/packages/noports_core/lib/sshnp/sshnp.dart @@ -1,27 +1,20 @@ import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; import 'package:at_client/at_client.dart' hide StringBuffer; -import 'package:at_commons/at_builders.dart'; import 'package:at_utils/at_logger.dart'; import 'package:at_utils/at_utils.dart'; import 'package:dartssh2/dartssh2.dart'; -import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:noports_core/common/default_args.dart'; import 'package:noports_core/sshnp/params/sshnp_params.dart'; -import 'package:path/path.dart' as path; import 'package:noports_core/common/supported_ssh_clients.dart'; import 'package:noports_core/common/utils.dart'; -import 'package:noports_core/sshnp/utils.dart'; -import 'package:noports_core/sshnpd/sshnpd.dart'; +import 'package:noports_core/sshnp/sshnp_impl.dart'; +import 'package:noports_core/sshnp/sshnp_result.dart'; import 'package:noports_core/sshrv/sshrv.dart'; -import 'package:noports_core/sshrvd/sshrvd.dart'; -import 'package:uuid/uuid.dart'; -part 'sshnp_impl.dart'; -part 'sshnp_result.dart'; +export 'params/sshnp_params.dart'; +export 'sshnp_result.dart'; abstract class SSHNP { abstract final AtSignLogger logger; diff --git a/packages/noports_core/lib/sshnp/sshnp_impl.dart b/packages/noports_core/lib/sshnp/sshnp_impl.dart index 68367f079..45862d85a 100644 --- a/packages/noports_core/lib/sshnp/sshnp_impl.dart +++ b/packages/noports_core/lib/sshnp/sshnp_impl.dart @@ -1,6 +1,26 @@ -part of 'sshnp.dart'; - -@visibleForTesting +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:at_client/at_client.dart' hide StringBuffer; +import 'package:at_commons/at_builders.dart'; +import 'package:at_utils/at_logger.dart'; +import 'package:dartssh2/dartssh2.dart'; +import 'package:logging/logging.dart'; +import 'package:meta/meta.dart'; +import 'package:noports_core/common/default_args.dart'; +import 'package:noports_core/common/supported_ssh_clients.dart'; +import 'package:noports_core/common/utils.dart'; +import 'package:noports_core/sshnp/sshnp.dart'; +import 'package:noports_core/sshnp/sshnp_result.dart'; +import 'package:noports_core/sshrv/sshrv.dart'; +import 'package:path/path.dart' as path; +import 'package:noports_core/sshnp/utils.dart'; +import 'package:noports_core/sshnpd/sshnpd.dart'; +import 'package:noports_core/sshrvd/sshrvd.dart'; +import 'package:uuid/uuid.dart'; + +@protected class SSHNPImpl implements SSHNP { @override final AtSignLogger logger = AtSignLogger(' sshnp '); diff --git a/packages/noports_core/lib/sshnp/sshnp_result.dart b/packages/noports_core/lib/sshnp/sshnp_result.dart index 77e10b897..20d38bb8b 100644 --- a/packages/noports_core/lib/sshnp/sshnp_result.dart +++ b/packages/noports_core/lib/sshnp/sshnp_result.dart @@ -1,4 +1,8 @@ -part of 'sshnp.dart'; + + +import 'dart:io'; + +import 'package:dartssh2/dartssh2.dart'; abstract class SSHNPResult {} From a503f4789446a835d7f509db00d3c35762ec9f58 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Thu, 28 Sep 2023 06:54:12 -0400 Subject: [PATCH 08/49] chore: move literally everything into a src folder to protect it, and re-expose under libraries --- .../noports_core/lib/config_repository.dart | 5 +++ .../lib/{ => src}/common/default_args.dart | 6 ++-- .../common/supported_ssh_clients.dart | 0 .../lib/{ => src}/common/utils.dart | 0 .../sshnp/params}/config_file_repository.dart | 6 ++-- .../sshnp/params}/config_key_repository.dart | 6 ++-- .../sshnp/params}/sshnp_arg.dart | 4 +-- .../{ => src}/sshnp/params/sshnp_params.dart | 11 ++++--- .../lib/{ => src}/sshnp/sshnp.dart | 17 +++++----- .../lib/{ => src}/sshnp/sshnp_impl.dart | 22 ++++++------- .../lib/{ => src}/sshnp/sshnp_result.dart | 0 .../lib/{ => src}/sshnp/utils.dart | 4 +-- .../lib/{ => src}/sshnpd/sshnpd.dart | 10 +++--- .../lib/{ => src}/sshnpd/sshnpd_impl.dart | 14 ++++----- .../lib/{ => src}/sshnpd/sshnpd_params.dart | 0 .../lib/{ => src}/sshrv/sshrv.dart | 2 +- .../lib/{ => src}/sshrv/sshrv_impl.dart | 0 .../{ => src}/sshrvd/socket_connector.dart | 0 .../lib/{ => src}/sshrvd/sshrvd.dart | 4 +-- .../lib/{ => src}/sshrvd/sshrvd_impl.dart | 0 .../lib/{ => src}/sshrvd/sshrvd_params.dart | 0 packages/noports_core/lib/sshnp.dart | 5 +++ packages/noports_core/lib/sshnpd.dart | 3 ++ packages/noports_core/lib/sshrv.dart | 3 ++ packages/noports_core/lib/sshrvd.dart | 3 ++ packages/noports_core/lib/utils.dart | 5 +++ packages/noports_core/test/sshnp_test.dart | 4 +-- packages/noports_core/test/sshnpd_test.dart | 6 ++-- packages/sshnoports/bin/sshnp.dart | 5 ++- packages/sshnoports/bin/sshnpd.dart | 2 +- packages/sshnoports/bin/sshrv.dart | 2 +- packages/sshnoports/bin/sshrvd.dart | 2 +- .../sshnoports/lib/create_at_client_cli.dart | 7 +++-- .../lib}/service_factories.dart | 0 .../src/controllers/config_controller.dart | 31 ++++++++++++------- .../home_screen_action_callbacks.dart | 28 +++++++++++------ .../profile_action_callbacks.dart | 25 +++++++++------ .../profile_actions/profile_run_action.dart | 4 +-- .../profile_terminal_action.dart | 4 +-- .../profile_bar/profile_bar_actions.dart | 2 +- .../widgets/profile_form/profile_form.dart | 2 +- .../repository/authentication_repository.dart | 4 +-- 42 files changed, 152 insertions(+), 106 deletions(-) create mode 100644 packages/noports_core/lib/config_repository.dart rename packages/noports_core/lib/{ => src}/common/default_args.dart (82%) rename packages/noports_core/lib/{ => src}/common/supported_ssh_clients.dart (100%) rename packages/noports_core/lib/{ => src}/common/utils.dart (100%) rename packages/noports_core/lib/{sshnp/config_repository => src/sshnp/params}/config_file_repository.dart (96%) rename packages/noports_core/lib/{sshnp/config_repository => src/sshnp/params}/config_key_repository.dart (90%) rename packages/noports_core/lib/{sshnp => src/sshnp/params}/sshnp_arg.dart (98%) rename packages/noports_core/lib/{ => src}/sshnp/params/sshnp_params.dart (97%) rename packages/noports_core/lib/{ => src}/sshnp/sshnp.dart (95%) rename packages/noports_core/lib/{ => src}/sshnp/sshnp_impl.dart (98%) rename packages/noports_core/lib/{ => src}/sshnp/sshnp_result.dart (100%) rename packages/noports_core/lib/{ => src}/sshnp/utils.dart (95%) rename packages/noports_core/lib/{ => src}/sshnpd/sshnpd.dart (94%) rename packages/noports_core/lib/{ => src}/sshnpd/sshnpd_impl.dart (98%) rename packages/noports_core/lib/{ => src}/sshnpd/sshnpd_params.dart (100%) rename packages/noports_core/lib/{ => src}/sshrv/sshrv.dart (96%) rename packages/noports_core/lib/{ => src}/sshrv/sshrv_impl.dart (100%) rename packages/noports_core/lib/{ => src}/sshrvd/socket_connector.dart (100%) rename packages/noports_core/lib/{ => src}/sshrvd/sshrvd.dart (93%) rename packages/noports_core/lib/{ => src}/sshrvd/sshrvd_impl.dart (100%) rename packages/noports_core/lib/{ => src}/sshrvd/sshrvd_params.dart (100%) create mode 100644 packages/noports_core/lib/sshnp.dart create mode 100644 packages/noports_core/lib/sshnpd.dart create mode 100644 packages/noports_core/lib/sshrv.dart create mode 100644 packages/noports_core/lib/sshrvd.dart create mode 100644 packages/noports_core/lib/utils.dart rename packages/{noports_core/lib/common => sshnoports/lib}/service_factories.dart (100%) diff --git a/packages/noports_core/lib/config_repository.dart b/packages/noports_core/lib/config_repository.dart new file mode 100644 index 000000000..e42b169b7 --- /dev/null +++ b/packages/noports_core/lib/config_repository.dart @@ -0,0 +1,5 @@ +library sshnp_core_config_repository; + +export 'src/sshnp/params/config_file_repository.dart'; +export 'src/sshnp/params/config_key_repository.dart'; +export 'src/sshnp/params/sshnp_params.dart'; \ No newline at end of file diff --git a/packages/noports_core/lib/common/default_args.dart b/packages/noports_core/lib/src/common/default_args.dart similarity index 82% rename from packages/noports_core/lib/common/default_args.dart rename to packages/noports_core/lib/src/common/default_args.dart index 16395a908..5aca06041 100644 --- a/packages/noports_core/lib/common/default_args.dart +++ b/packages/noports_core/lib/src/common/default_args.dart @@ -1,9 +1,11 @@ -import 'package:noports_core/common/supported_ssh_clients.dart'; -import 'package:noports_core/sshrv/sshrv.dart'; +import 'package:noports_core/src/common/supported_ssh_clients.dart'; +import 'package:noports_core/sshrv.dart'; class DefaultArgs { const DefaultArgs(); + static const namespace = 'sshnp'; + static const verbose = false; static const rsa = false; static const rootDomain = 'root.atsign.org'; diff --git a/packages/noports_core/lib/common/supported_ssh_clients.dart b/packages/noports_core/lib/src/common/supported_ssh_clients.dart similarity index 100% rename from packages/noports_core/lib/common/supported_ssh_clients.dart rename to packages/noports_core/lib/src/common/supported_ssh_clients.dart diff --git a/packages/noports_core/lib/common/utils.dart b/packages/noports_core/lib/src/common/utils.dart similarity index 100% rename from packages/noports_core/lib/common/utils.dart rename to packages/noports_core/lib/src/common/utils.dart diff --git a/packages/noports_core/lib/sshnp/config_repository/config_file_repository.dart b/packages/noports_core/lib/src/sshnp/params/config_file_repository.dart similarity index 96% rename from packages/noports_core/lib/sshnp/config_repository/config_file_repository.dart rename to packages/noports_core/lib/src/sshnp/params/config_file_repository.dart index 48cdeb26d..d568658ad 100644 --- a/packages/noports_core/lib/sshnp/config_repository/config_file_repository.dart +++ b/packages/noports_core/lib/src/sshnp/params/config_file_repository.dart @@ -1,8 +1,8 @@ import 'dart:io'; -import 'package:noports_core/common/utils.dart'; -import 'package:noports_core/sshnp/params/sshnp_params.dart'; -import 'package:noports_core/sshnp/sshnp_arg.dart'; +import 'package:noports_core/src/common/utils.dart'; +import 'package:noports_core/src/sshnp/params/sshnp_params.dart'; +import 'package:noports_core/src/sshnp/params/sshnp_arg.dart'; import 'package:path/path.dart' as path; class ConfigFileRepository { diff --git a/packages/noports_core/lib/sshnp/config_repository/config_key_repository.dart b/packages/noports_core/lib/src/sshnp/params/config_key_repository.dart similarity index 90% rename from packages/noports_core/lib/sshnp/config_repository/config_key_repository.dart rename to packages/noports_core/lib/src/sshnp/params/config_key_repository.dart index eba962187..41bacfcc3 100644 --- a/packages/noports_core/lib/sshnp/config_repository/config_key_repository.dart +++ b/packages/noports_core/lib/src/sshnp/params/config_key_repository.dart @@ -1,10 +1,10 @@ import 'package:at_client/at_client.dart'; -import 'package:noports_core/sshnp/params/sshnp_params.dart'; -import 'package:noports_core/sshnpd/sshnpd.dart'; +import 'package:noports_core/src/common/default_args.dart'; +import 'package:noports_core/src/sshnp/params/sshnp_params.dart'; class ConfigKeyRepository { static const String _keyPrefix = 'profile_'; - static const String _configNamespace = 'profiles.${SSHNPD.namespace}'; + static const String _configNamespace = 'profiles.${DefaultArgs.namespace}'; static String toProfileName(AtKey atKey, {bool replaceSpaces = true}) { var profileName = atKey.key!.split('.').first; diff --git a/packages/noports_core/lib/sshnp/sshnp_arg.dart b/packages/noports_core/lib/src/sshnp/params/sshnp_arg.dart similarity index 98% rename from packages/noports_core/lib/sshnp/sshnp_arg.dart rename to packages/noports_core/lib/src/sshnp/params/sshnp_arg.dart index 3a628698e..043513a7b 100644 --- a/packages/noports_core/lib/sshnp/sshnp_arg.dart +++ b/packages/noports_core/lib/src/sshnp/params/sshnp_arg.dart @@ -1,7 +1,7 @@ import 'package:args/args.dart'; -import 'package:noports_core/common/supported_ssh_clients.dart'; +import 'package:noports_core/src/common/supported_ssh_clients.dart'; -import 'package:noports_core/common/default_args.dart'; +import 'package:noports_core/src/common/default_args.dart'; enum ArgFormat { option, diff --git a/packages/noports_core/lib/sshnp/params/sshnp_params.dart b/packages/noports_core/lib/src/sshnp/params/sshnp_params.dart similarity index 97% rename from packages/noports_core/lib/sshnp/params/sshnp_params.dart rename to packages/noports_core/lib/src/sshnp/params/sshnp_params.dart index 0ec53bdd6..1c252c32f 100644 --- a/packages/noports_core/lib/sshnp/params/sshnp_params.dart +++ b/packages/noports_core/lib/src/sshnp/params/sshnp_params.dart @@ -2,10 +2,10 @@ import 'dart:convert'; import 'package:args/args.dart'; import 'package:at_utils/at_logger.dart'; -import 'package:noports_core/common/utils.dart'; -import 'package:noports_core/sshnp/config_repository/config_file_repository.dart'; -import 'package:noports_core/sshnp/sshnp_arg.dart'; -import 'package:noports_core/common/default_args.dart'; +import 'package:noports_core/src/common/utils.dart'; +import 'package:noports_core/src/sshnp/params/config_file_repository.dart'; +import 'package:noports_core/src/sshnp/params/sshnp_arg.dart'; +import 'package:noports_core/src/common/default_args.dart'; class SSHNPParams { /// Required Arguments @@ -147,7 +147,8 @@ class SSHNPParams { localPort: partial.localPort ?? DefaultSSHNPArgs.localPort, sendSshPublicKey: partial.sendSshPublicKey ?? DefaultSSHNPArgs.sendSshPublicKey, - localSshOptions: partial.localSshOptions ?? DefaultSSHNPArgs.localSshOptions, + localSshOptions: + partial.localSshOptions ?? DefaultSSHNPArgs.localSshOptions, rsa: partial.rsa ?? DefaultArgs.rsa, verbose: partial.verbose ?? DefaultArgs.verbose, remoteUsername: partial.remoteUsername, diff --git a/packages/noports_core/lib/sshnp/sshnp.dart b/packages/noports_core/lib/src/sshnp/sshnp.dart similarity index 95% rename from packages/noports_core/lib/sshnp/sshnp.dart rename to packages/noports_core/lib/src/sshnp/sshnp.dart index bc69fe58f..22eed992e 100644 --- a/packages/noports_core/lib/sshnp/sshnp.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp.dart @@ -5,16 +5,13 @@ import 'package:at_utils/at_logger.dart'; import 'package:at_utils/at_utils.dart'; import 'package:dartssh2/dartssh2.dart'; import 'package:meta/meta.dart'; -import 'package:noports_core/common/default_args.dart'; -import 'package:noports_core/sshnp/params/sshnp_params.dart'; -import 'package:noports_core/common/supported_ssh_clients.dart'; -import 'package:noports_core/common/utils.dart'; -import 'package:noports_core/sshnp/sshnp_impl.dart'; -import 'package:noports_core/sshnp/sshnp_result.dart'; -import 'package:noports_core/sshrv/sshrv.dart'; - -export 'params/sshnp_params.dart'; -export 'sshnp_result.dart'; +import 'package:noports_core/src/common/default_args.dart'; +import 'package:noports_core/src/sshnp/params/sshnp_params.dart'; +import 'package:noports_core/src/common/supported_ssh_clients.dart'; +import 'package:noports_core/src/common/utils.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl.dart'; +import 'package:noports_core/src/sshnp/sshnp_result.dart'; +import 'package:noports_core/src/sshrv/sshrv.dart'; abstract class SSHNP { abstract final AtSignLogger logger; diff --git a/packages/noports_core/lib/sshnp/sshnp_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl.dart similarity index 98% rename from packages/noports_core/lib/sshnp/sshnp_impl.dart rename to packages/noports_core/lib/src/sshnp/sshnp_impl.dart index 45862d85a..4d6430a02 100644 --- a/packages/noports_core/lib/sshnp/sshnp_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl.dart @@ -8,16 +8,16 @@ import 'package:at_utils/at_logger.dart'; import 'package:dartssh2/dartssh2.dart'; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; -import 'package:noports_core/common/default_args.dart'; -import 'package:noports_core/common/supported_ssh_clients.dart'; -import 'package:noports_core/common/utils.dart'; -import 'package:noports_core/sshnp/sshnp.dart'; -import 'package:noports_core/sshnp/sshnp_result.dart'; -import 'package:noports_core/sshrv/sshrv.dart'; +import 'package:noports_core/src/common/default_args.dart'; +import 'package:noports_core/src/common/supported_ssh_clients.dart'; +import 'package:noports_core/src/common/utils.dart'; +import 'package:noports_core/src/sshnp/params/sshnp_params.dart'; +import 'package:noports_core/src/sshnp/sshnp.dart'; +import 'package:noports_core/src/sshnp/sshnp_result.dart'; +import 'package:noports_core/src/sshrv/sshrv.dart'; +import 'package:noports_core/src/sshrvd/sshrvd.dart'; import 'package:path/path.dart' as path; -import 'package:noports_core/sshnp/utils.dart'; -import 'package:noports_core/sshnpd/sshnpd.dart'; -import 'package:noports_core/sshrvd/sshrvd.dart'; +import 'package:noports_core/src/sshnp/utils.dart'; import 'package:uuid/uuid.dart'; @protected @@ -1025,7 +1025,7 @@ class SSHNPImpl implements SSHNP { Future<(Iterable, Iterable, Map)> listDevices() async { // get all the keys device_info.*.sshnpd - var scanRegex = 'device_info\\.$asciiMatcher\\.${SSHNPD.namespace}'; + var scanRegex = 'device_info\\.$asciiMatcher\\.${DefaultArgs.namespace}'; var atKeys = await _getAtKeysRemote(regex: scanRegex, sharedBy: sshnpdAtSign); @@ -1070,7 +1070,7 @@ class SSHNPImpl implements SSHNP { ..key = "ping.$devicename" ..sharedBy = clientAtSign ..sharedWith = entryKey.sharedBy - ..namespace = SSHNPD.namespace + ..namespace = DefaultArgs.namespace ..metadata = metaData; unawaited(_notify(pingKey, 'ping')); diff --git a/packages/noports_core/lib/sshnp/sshnp_result.dart b/packages/noports_core/lib/src/sshnp/sshnp_result.dart similarity index 100% rename from packages/noports_core/lib/sshnp/sshnp_result.dart rename to packages/noports_core/lib/src/sshnp/sshnp_result.dart diff --git a/packages/noports_core/lib/sshnp/utils.dart b/packages/noports_core/lib/src/sshnp/utils.dart similarity index 95% rename from packages/noports_core/lib/sshnp/utils.dart rename to packages/noports_core/lib/src/sshnp/utils.dart index 5000d4e31..c165040ed 100644 --- a/packages/noports_core/lib/sshnp/utils.dart +++ b/packages/noports_core/lib/src/sshnp/utils.dart @@ -1,7 +1,7 @@ import 'dart:io'; -import 'package:noports_core/common/utils.dart'; +import 'package:noports_core/src/common/utils.dart'; import 'package:at_utils/at_logger.dart'; -import 'package:noports_core/sshnp/sshnp.dart'; +import 'package:noports_core/src/sshnp/sshnp.dart'; Future cleanUpAfterReverseSsh(SSHNP sshnp) async { if (!sshnp.initialized) { diff --git a/packages/noports_core/lib/sshnpd/sshnpd.dart b/packages/noports_core/lib/src/sshnpd/sshnpd.dart similarity index 94% rename from packages/noports_core/lib/sshnpd/sshnpd.dart rename to packages/noports_core/lib/src/sshnpd/sshnpd.dart index 84e3cc094..f4053ce46 100644 --- a/packages/noports_core/lib/sshnpd/sshnpd.dart +++ b/packages/noports_core/lib/src/sshnpd/sshnpd.dart @@ -9,21 +9,19 @@ import 'package:dartssh2/dartssh2.dart'; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; -import 'package:noports_core/common/supported_ssh_clients.dart'; -import 'package:noports_core/common/utils.dart'; -import 'package:noports_core/sshrv/sshrv.dart'; +import 'package:noports_core/src/common/supported_ssh_clients.dart'; +import 'package:noports_core/src/common/utils.dart'; +import 'package:noports_core/src/sshrv/sshrv.dart'; import 'package:noports_core/version.dart'; import 'package:uuid/uuid.dart'; -import 'package:noports_core/common/default_args.dart'; +import 'package:noports_core/src/common/default_args.dart'; part 'sshnpd_impl.dart'; part 'sshnpd_params.dart'; abstract class SSHNPD { - static const String namespace = 'sshnp'; - abstract final AtSignLogger logger; /// The [AtClient] used to communicate with sshnpd and sshrvd diff --git a/packages/noports_core/lib/sshnpd/sshnpd_impl.dart b/packages/noports_core/lib/src/sshnpd/sshnpd_impl.dart similarity index 98% rename from packages/noports_core/lib/sshnpd/sshnpd_impl.dart rename to packages/noports_core/lib/src/sshnpd/sshnpd_impl.dart index 6f43df898..b191e49ea 100644 --- a/packages/noports_core/lib/sshnpd/sshnpd_impl.dart +++ b/packages/noports_core/lib/src/sshnpd/sshnpd_impl.dart @@ -145,7 +145,7 @@ class SSHNPDImpl implements SSHNPD { ..key = 'username.$device' ..sharedBy = deviceAtsign ..sharedWith = managerAtsign - ..namespace = SSHNPD.namespace + ..namespace = DefaultArgs.namespace ..metadata = metaData; try { @@ -165,9 +165,9 @@ class SSHNPDImpl implements SSHNPD { logger.info('Starting heartbeat'); startHeartbeat(); - logger.info('Subscribing to $device\\.${SSHNPD.namespace}@'); + logger.info('Subscribing to $device\\.${DefaultArgs.namespace}@'); notificationService - .subscribe(regex: '$device\\.${SSHNPD.namespace}@', shouldDecrypt: true) + .subscribe(regex: '$device\\.${DefaultArgs.namespace}@', shouldDecrypt: true) .listen( _notificationHandler, onError: (e) => logger.severe('Notification Failed:$e'), @@ -221,7 +221,7 @@ class SSHNPDImpl implements SSHNPD { String notificationKey = notification.key .replaceAll('${notification.to}:', '') - .replaceAll('.$device.${SSHNPD.namespace}${notification.from}', '') + .replaceAll('.$device.${DefaultArgs.namespace}${notification.from}', '') // convert to lower case as the latest AtClient converts notification // keys to lower case when received .toLowerCase(); @@ -275,7 +275,7 @@ class SSHNPDImpl implements SSHNPD { ..key = 'heartbeat.$device' ..sharedBy = deviceAtsign ..sharedWith = notification.from - ..namespace = SSHNPD.namespace + ..namespace = DefaultArgs.namespace ..metadata = (Metadata() ..isPublic = false ..isEncrypted = true @@ -606,7 +606,7 @@ class SSHNPDImpl implements SSHNPD { ..key = '$sessionId.$device' ..sharedBy = deviceAtsign ..sharedWith = requestingAtsign - ..namespace = SSHNPD.namespace + ..namespace = DefaultArgs.namespace ..metadata = (Metadata() ..isPublic = false ..isEncrypted = true @@ -854,7 +854,7 @@ class SSHNPDImpl implements SSHNPD { ..key = 'device_info.$device' ..sharedBy = deviceAtsign ..sharedWith = managerAtsign - ..namespace = SSHNPD.namespace + ..namespace = DefaultArgs.namespace ..metadata = metaData; if (!makeDeviceInfoVisible) { diff --git a/packages/noports_core/lib/sshnpd/sshnpd_params.dart b/packages/noports_core/lib/src/sshnpd/sshnpd_params.dart similarity index 100% rename from packages/noports_core/lib/sshnpd/sshnpd_params.dart rename to packages/noports_core/lib/src/sshnpd/sshnpd_params.dart diff --git a/packages/noports_core/lib/sshrv/sshrv.dart b/packages/noports_core/lib/src/sshrv/sshrv.dart similarity index 96% rename from packages/noports_core/lib/sshrv/sshrv.dart rename to packages/noports_core/lib/src/sshrv/sshrv.dart index 57bc3c231..943828b5e 100644 --- a/packages/noports_core/lib/sshrv/sshrv.dart +++ b/packages/noports_core/lib/src/sshrv/sshrv.dart @@ -4,7 +4,7 @@ import 'package:at_utils/at_utils.dart'; import 'package:meta/meta.dart'; import 'package:socket_connector/socket_connector.dart'; -import 'package:noports_core/common/default_args.dart'; +import 'package:noports_core/src/common/default_args.dart'; part 'sshrv_impl.dart'; diff --git a/packages/noports_core/lib/sshrv/sshrv_impl.dart b/packages/noports_core/lib/src/sshrv/sshrv_impl.dart similarity index 100% rename from packages/noports_core/lib/sshrv/sshrv_impl.dart rename to packages/noports_core/lib/src/sshrv/sshrv_impl.dart diff --git a/packages/noports_core/lib/sshrvd/socket_connector.dart b/packages/noports_core/lib/src/sshrvd/socket_connector.dart similarity index 100% rename from packages/noports_core/lib/sshrvd/socket_connector.dart rename to packages/noports_core/lib/src/sshrvd/socket_connector.dart diff --git a/packages/noports_core/lib/sshrvd/sshrvd.dart b/packages/noports_core/lib/src/sshrvd/sshrvd.dart similarity index 93% rename from packages/noports_core/lib/sshrvd/sshrvd.dart rename to packages/noports_core/lib/src/sshrvd/sshrvd.dart index 1a12747d1..3fe29e52d 100644 --- a/packages/noports_core/lib/sshrvd/sshrvd.dart +++ b/packages/noports_core/lib/src/sshrvd/sshrvd.dart @@ -8,8 +8,8 @@ import 'package:at_utils/at_logger.dart'; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; -import 'package:noports_core/common/utils.dart'; -import 'package:noports_core/sshrvd/socket_connector.dart'; +import 'package:noports_core/src/common/utils.dart'; +import 'package:noports_core/src/sshrvd/socket_connector.dart'; part 'sshrvd_impl.dart'; part 'sshrvd_params.dart'; diff --git a/packages/noports_core/lib/sshrvd/sshrvd_impl.dart b/packages/noports_core/lib/src/sshrvd/sshrvd_impl.dart similarity index 100% rename from packages/noports_core/lib/sshrvd/sshrvd_impl.dart rename to packages/noports_core/lib/src/sshrvd/sshrvd_impl.dart diff --git a/packages/noports_core/lib/sshrvd/sshrvd_params.dart b/packages/noports_core/lib/src/sshrvd/sshrvd_params.dart similarity index 100% rename from packages/noports_core/lib/sshrvd/sshrvd_params.dart rename to packages/noports_core/lib/src/sshrvd/sshrvd_params.dart diff --git a/packages/noports_core/lib/sshnp.dart b/packages/noports_core/lib/sshnp.dart new file mode 100644 index 000000000..8732581be --- /dev/null +++ b/packages/noports_core/lib/sshnp.dart @@ -0,0 +1,5 @@ +library noports_core_sshnp; + +export 'src/sshnp/sshnp.dart'; +export 'src/sshnp/sshnp_result.dart'; +export 'src/sshnp/params/sshnp_params.dart'; diff --git a/packages/noports_core/lib/sshnpd.dart b/packages/noports_core/lib/sshnpd.dart new file mode 100644 index 000000000..bcddffa8d --- /dev/null +++ b/packages/noports_core/lib/sshnpd.dart @@ -0,0 +1,3 @@ +library noports_core_sshnpd; + +export 'src/sshnpd/sshnpd.dart'; diff --git a/packages/noports_core/lib/sshrv.dart b/packages/noports_core/lib/sshrv.dart new file mode 100644 index 000000000..3a2b323b9 --- /dev/null +++ b/packages/noports_core/lib/sshrv.dart @@ -0,0 +1,3 @@ +library noports_core_sshrv; + +export 'src/sshrv/sshrv.dart'; \ No newline at end of file diff --git a/packages/noports_core/lib/sshrvd.dart b/packages/noports_core/lib/sshrvd.dart new file mode 100644 index 000000000..db465357b --- /dev/null +++ b/packages/noports_core/lib/sshrvd.dart @@ -0,0 +1,3 @@ +library noports_core_sshrvd; + +export 'src/sshrvd/sshrvd.dart'; \ No newline at end of file diff --git a/packages/noports_core/lib/utils.dart b/packages/noports_core/lib/utils.dart new file mode 100644 index 000000000..6050058bb --- /dev/null +++ b/packages/noports_core/lib/utils.dart @@ -0,0 +1,5 @@ +library noports_core_utils; + +export 'src/common/utils.dart'; +export 'src/sshnp/utils.dart'; +export 'src/common/default_args.dart'; diff --git a/packages/noports_core/test/sshnp_test.dart b/packages/noports_core/test/sshnp_test.dart index 668bc8548..608bfc58b 100644 --- a/packages/noports_core/test/sshnp_test.dart +++ b/packages/noports_core/test/sshnp_test.dart @@ -1,6 +1,6 @@ import 'package:args/args.dart'; -import 'package:noports_core/common/utils.dart'; -import 'package:noports_core/sshnp/params/sshnp_params.dart'; +import 'package:noports_core/src/common/utils.dart'; +import 'package:noports_core/src/sshnp/params/sshnp_params.dart'; import 'package:test/test.dart'; void main() { diff --git a/packages/noports_core/test/sshnpd_test.dart b/packages/noports_core/test/sshnpd_test.dart index d717ff14d..83c361e38 100644 --- a/packages/noports_core/test/sshnpd_test.dart +++ b/packages/noports_core/test/sshnpd_test.dart @@ -1,8 +1,8 @@ -import 'package:noports_core/common/supported_ssh_clients.dart'; -import 'package:noports_core/sshnpd/sshnpd.dart'; +import 'package:noports_core/src/common/supported_ssh_clients.dart'; +import 'package:noports_core/sshnpd.dart'; import 'package:test/test.dart'; import 'package:args/args.dart'; -import 'package:noports_core/common/utils.dart'; +import 'package:noports_core/src/common/utils.dart'; void main() { group('args parser test', () { diff --git a/packages/sshnoports/bin/sshnp.dart b/packages/sshnoports/bin/sshnp.dart index fa4de22c5..1768779bc 100644 --- a/packages/sshnoports/bin/sshnp.dart +++ b/packages/sshnoports/bin/sshnp.dart @@ -4,11 +4,10 @@ import 'dart:io'; // atPlatform packages import 'package:at_utils/at_logger.dart'; -import 'package:noports_core/sshnp/params/sshnp_params.dart'; // local packages -import 'package:noports_core/sshnp/sshnp.dart'; -import 'package:noports_core/sshnp/utils.dart'; +import 'package:noports_core/sshnp.dart'; +import 'package:noports_core/utils.dart'; import 'package:sshnoports/create_at_client_cli.dart'; import 'package:sshnoports/version.dart'; diff --git a/packages/sshnoports/bin/sshnpd.dart b/packages/sshnoports/bin/sshnpd.dart index 44398911b..83f1ad273 100644 --- a/packages/sshnoports/bin/sshnpd.dart +++ b/packages/sshnoports/bin/sshnpd.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:io'; import 'package:at_utils/at_logger.dart'; -import 'package:noports_core/sshnpd/sshnpd.dart'; +import 'package:noports_core/sshnpd.dart'; import 'package:sshnoports/create_at_client_cli.dart'; import 'package:sshnoports/version.dart'; diff --git a/packages/sshnoports/bin/sshrv.dart b/packages/sshnoports/bin/sshrv.dart index 6edfe776a..271e40816 100644 --- a/packages/sshnoports/bin/sshrv.dart +++ b/packages/sshnoports/bin/sshrv.dart @@ -1,6 +1,6 @@ import 'dart:io'; -import 'package:noports_core/sshrv/sshrv.dart'; +import 'package:noports_core/sshrv.dart'; Future main(List args) async { if (args.length < 2 || args.length > 3) { diff --git a/packages/sshnoports/bin/sshrvd.dart b/packages/sshnoports/bin/sshrvd.dart index 2213abfc6..4d067d76c 100644 --- a/packages/sshnoports/bin/sshrvd.dart +++ b/packages/sshnoports/bin/sshrvd.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:io'; import 'package:at_utils/at_logger.dart'; -import 'package:noports_core/sshrvd/sshrvd.dart'; +import 'package:noports_core/sshrvd.dart'; import 'package:sshnoports/create_at_client_cli.dart'; import 'package:sshnoports/version.dart'; diff --git a/packages/sshnoports/lib/create_at_client_cli.dart b/packages/sshnoports/lib/create_at_client_cli.dart index a46afb7c3..d024a428e 100644 --- a/packages/sshnoports/lib/create_at_client_cli.dart +++ b/packages/sshnoports/lib/create_at_client_cli.dart @@ -3,7 +3,7 @@ import 'package:at_client/at_client.dart'; import 'package:at_onboarding_cli/at_onboarding_cli.dart'; import 'package:version/version.dart'; import 'package:path/path.dart' as path; -import 'package:noports_core/common/service_factories.dart'; +import 'service_factories.dart'; Future createAtClientCli({ required String homeDirectory, @@ -31,8 +31,9 @@ Future createAtClientCli({ ..atProtocolEmitted = Version(2, 0, 0) ..rootDomain = rootDomain; - AtOnboardingService onboardingService = - AtOnboardingServiceImpl(atsign, atOnboardingConfig, atServiceFactory: ServiceFactoryWithNoOpSyncService()); + AtOnboardingService onboardingService = AtOnboardingServiceImpl( + atsign, atOnboardingConfig, + atServiceFactory: ServiceFactoryWithNoOpSyncService()); await onboardingService.authenticate(); diff --git a/packages/noports_core/lib/common/service_factories.dart b/packages/sshnoports/lib/service_factories.dart similarity index 100% rename from packages/noports_core/lib/common/service_factories.dart rename to packages/sshnoports/lib/service_factories.dart diff --git a/packages/sshnp_gui/lib/src/controllers/config_controller.dart b/packages/sshnp_gui/lib/src/controllers/config_controller.dart index bd10b1c79..9623d0b01 100644 --- a/packages/sshnp_gui/lib/src/controllers/config_controller.dart +++ b/packages/sshnp_gui/lib/src/controllers/config_controller.dart @@ -3,24 +3,26 @@ import 'dart:async'; import 'package:at_client_mobile/at_client_mobile.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:noports_core/sshnp/sshnp.dart'; -import 'package:noports_core/sshnp/config_repository/config_key_repository.dart'; +import 'package:noports_core/config_repository.dart'; import 'package:sshnp_gui/src/presentation/widgets/utility/custom_snack_bar.dart'; enum ConfigFileWriteState { create, update } /// A provider that exposes the [CurrentConfigController] to the app. -final currentConfigController = AutoDisposeNotifierProvider( +final currentConfigController = + AutoDisposeNotifierProvider( CurrentConfigController.new, ); /// A provider that exposes the [ConfigListController] to the app. -final configListController = AutoDisposeAsyncNotifierProvider>( +final configListController = + AutoDisposeAsyncNotifierProvider>( ConfigListController.new, ); /// A provider that exposes the [ConfigFamilyController] to the app. -final configFamilyController = AutoDisposeAsyncNotifierProviderFamily( +final configFamilyController = AutoDisposeAsyncNotifierProviderFamily< + ConfigFamilyController, SSHNPParams, String>( ConfigFamilyController.new, ); @@ -29,7 +31,8 @@ class CurrentConfigState { final String profileName; final ConfigFileWriteState configFileWriteState; - CurrentConfigState({required this.profileName, required this.configFileWriteState}); + CurrentConfigState( + {required this.profileName, required this.configFileWriteState}); } /// Controller for the current [SSHNPParams] being edited @@ -70,7 +73,8 @@ class ConfigListController extends AutoDisposeAsyncNotifier> { } /// Controller for the family of [SSHNPParams] controllers -class ConfigFamilyController extends AutoDisposeFamilyAsyncNotifier { +class ConfigFamilyController + extends AutoDisposeFamilyAsyncNotifier { @override Future build(String arg) async { AtClient atClient = AtClientManager.getInstance().atClient; @@ -83,11 +87,14 @@ class ConfigFamilyController extends AutoDisposeFamilyAsyncNotifier putConfig(SSHNPParams params, {String? oldProfileName, BuildContext? context}) async { + Future putConfig(SSHNPParams params, + {String? oldProfileName, BuildContext? context}) async { AtClient atClient = AtClientManager.getInstance().atClient; SSHNPParams oldParams = state.value ?? SSHNPParams.empty(); if (oldProfileName != null) { - ref.read(configFamilyController(oldProfileName).notifier).deleteConfig(context: context); + ref + .read(configFamilyController(oldProfileName).notifier) + .deleteConfig(context: context); } if (params.clientAtSign != atClient.getCurrentAtSign()) { params = SSHNPParams.merge( @@ -111,9 +118,11 @@ class ConfigFamilyController extends AutoDisposeFamilyAsyncNotifier deleteConfig({BuildContext? context}) async { try { - await ConfigKeyRepository.deleteParams(arg, atClient: AtClientManager.getInstance().atClient); + await ConfigKeyRepository.deleteParams(arg, + atClient: AtClientManager.getInstance().atClient); ref.read(configListController.notifier).remove(arg); - state = AsyncValue.error('SSHNPParams has been disposed', StackTrace.current); + state = + AsyncValue.error('SSHNPParams has been disposed', StackTrace.current); } catch (e) { if (context?.mounted ?? false) { CustomSnackBar.error(content: 'Failed to delete profile: $arg'); diff --git a/packages/sshnp_gui/lib/src/presentation/widgets/home_screen_actions/home_screen_action_callbacks.dart b/packages/sshnp_gui/lib/src/presentation/widgets/home_screen_actions/home_screen_action_callbacks.dart index 87f5a50e8..c296efbde 100644 --- a/packages/sshnp_gui/lib/src/presentation/widgets/home_screen_actions/home_screen_action_callbacks.dart +++ b/packages/sshnp_gui/lib/src/presentation/widgets/home_screen_actions/home_screen_action_callbacks.dart @@ -3,8 +3,7 @@ import 'dart:io'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:noports_core/sshnp/config_repository/config_file_repository.dart'; -import 'package:noports_core/sshnp/sshnp.dart'; +import 'package:noports_core/config_repository.dart'; import 'package:sshnp_gui/src/controllers/config_controller.dart'; import 'package:sshnp_gui/src/presentation/widgets/home_screen_actions/home_screen_import_dialog.dart'; import 'package:sshnp_gui/src/presentation/widgets/utility/custom_snack_bar.dart'; @@ -15,32 +14,41 @@ class HomeScreenActionCallbacks { if (Platform.isMacOS || Platform.isLinux || Platform.isWindows) { return _importDesktop(ref, context); } - CustomSnackBar.error(content: 'Unable to import profile:\nUnsupported platform'); + CustomSnackBar.error( + content: 'Unable to import profile:\nUnsupported platform'); } - static Future _importDesktop(WidgetRef ref, BuildContext context) async { + static Future _importDesktop( + WidgetRef ref, BuildContext context) async { try { - final XFile? file = await openFile(acceptedTypeGroups: [dotEnvTypeGroup]); + final XFile? file = + await openFile(acceptedTypeGroups: [dotEnvTypeGroup]); if (file == null) return; if (context.mounted) { String initialName = ConfigFileRepository.toProfileName(file.path); - String? profileName = await _getProfileNameFromUser(context, initialName: initialName); + String? profileName = + await _getProfileNameFromUser(context, initialName: initialName); if (profileName == null) return; if (profileName.isEmpty) profileName = initialName; final lines = (await file.readAsString()).split('\n'); - ref.read(configFamilyController(profileName).notifier).putConfig(SSHNPParams.fromConfig(profileName, lines)); + ref + .read(configFamilyController(profileName).notifier) + .putConfig(SSHNPParams.fromConfig(profileName, lines)); } } catch (e) { - CustomSnackBar.error(content: 'Unable to import profile:\n${e.toString()}'); + CustomSnackBar.error( + content: 'Unable to import profile:\n${e.toString()}'); } } - static Future _getProfileNameFromUser(BuildContext context, {String? initialName}) async { + static Future _getProfileNameFromUser(BuildContext context, + {String? initialName}) async { String? profileName; setProfileName(String? p) => profileName = p; await showDialog( context: context, - builder: (_) => HomeScreenImportDialog(setProfileName, initialName: initialName), + builder: (_) => + HomeScreenImportDialog(setProfileName, initialName: initialName), ); return profileName; } diff --git a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_action_callbacks.dart b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_action_callbacks.dart index 17c8287b7..5b8c101d7 100644 --- a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_action_callbacks.dart +++ b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_action_callbacks.dart @@ -5,8 +5,8 @@ import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import 'package:noports_core/common/utils.dart'; -import 'package:noports_core/sshnp/config_repository/config_file_repository.dart'; +import 'package:noports_core/utils.dart'; +import 'package:noports_core/config_repository.dart'; import 'package:sshnp_gui/src/controllers/config_controller.dart'; import 'package:sshnp_gui/src/controllers/navigation_controller.dart'; import 'package:sshnp_gui/src/presentation/widgets/profile_actions/profile_delete_dialog.dart'; @@ -36,17 +36,22 @@ class ProfileActionCallbacks { ); } - static Future export(WidgetRef ref, BuildContext context, String profileName) async { + static Future export( + WidgetRef ref, BuildContext context, String profileName) async { if (Platform.isMacOS || Platform.isLinux || Platform.isWindows) { return _exportDesktop(ref, context, profileName); } - CustomSnackBar.error(content: 'Unable to export profile:\nUnsupported platform'); + CustomSnackBar.error( + content: 'Unable to export profile:\nUnsupported platform'); } - static Future _exportDesktop(WidgetRef ref, BuildContext context, String profileName) async { + static Future _exportDesktop( + WidgetRef ref, BuildContext context, String profileName) async { try { - final suggestedName = ConfigFileRepository.fromProfileName(profileName, basenameOnly: true); - final initialDirectory = getDefaultSshnpConfigDirectory(getHomeDirectory(throwIfNull: true)!); + final suggestedName = + ConfigFileRepository.fromProfileName(profileName, basenameOnly: true); + final initialDirectory = + getDefaultSshnpConfigDirectory(getHomeDirectory(throwIfNull: true)!); final FileSaveLocation? saveLocation = await getSaveLocation( suggestedName: suggestedName, @@ -55,7 +60,8 @@ class ProfileActionCallbacks { ); if (saveLocation == null) return; final params = ref.read(configFamilyController(profileName)); - final fileData = Uint8List.fromList(params.requireValue.toConfig().codeUnits); + final fileData = + Uint8List.fromList(params.requireValue.toConfig().codeUnits); final XFile textFile = XFile.fromData( fileData, mimeType: dotEnvMimeType, @@ -64,7 +70,8 @@ class ProfileActionCallbacks { await textFile.saveTo(saveLocation.path); } catch (e) { - CustomSnackBar.error(content: 'Unable to export profile:\n${e.toString()}'); + CustomSnackBar.error( + content: 'Unable to export profile:\n${e.toString()}'); } } } diff --git a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart index cf85dbbd8..8ef6345ee 100644 --- a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart +++ b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart @@ -4,8 +4,8 @@ import 'package:at_client_mobile/at_client_mobile.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:socket_connector/socket_connector.dart'; -import 'package:noports_core/sshnp/sshnp.dart'; -import 'package:noports_core/sshrv/sshrv.dart'; +import 'package:noports_core/sshnp.dart'; +import 'package:noports_core/sshrv.dart'; import 'package:sshnp_gui/src/controllers/background_session_controller.dart'; import 'package:sshnp_gui/src/presentation/widgets/profile_actions/profile_action_button.dart'; import 'package:sshnp_gui/src/presentation/widgets/utility/custom_snack_bar.dart'; diff --git a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_terminal_action.dart b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_terminal_action.dart index a26f66b82..a9d913741 100644 --- a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_terminal_action.dart +++ b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_terminal_action.dart @@ -2,8 +2,8 @@ import 'package:at_client_mobile/at_client_mobile.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import 'package:noports_core/sshnp/sshnp.dart'; -import 'package:noports_core/sshrv/sshrv.dart'; +import 'package:noports_core/sshnp.dart'; +import 'package:noports_core/sshrv.dart'; import 'package:sshnp_gui/src/controllers/navigation_rail_controller.dart'; import 'package:sshnp_gui/src/controllers/terminal_session_controller.dart'; import 'package:sshnp_gui/src/presentation/widgets/profile_actions/profile_action_button.dart'; diff --git a/packages/sshnp_gui/lib/src/presentation/widgets/profile_bar/profile_bar_actions.dart b/packages/sshnp_gui/lib/src/presentation/widgets/profile_bar/profile_bar_actions.dart index 1c1ef8cf1..b8a66d1a2 100644 --- a/packages/sshnp_gui/lib/src/presentation/widgets/profile_bar/profile_bar_actions.dart +++ b/packages/sshnp_gui/lib/src/presentation/widgets/profile_bar/profile_bar_actions.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:noports_core/sshnp/sshnp.dart'; +import 'package:noports_core/sshnp.dart'; import 'package:sshnp_gui/src/presentation/widgets/profile_actions/profile_actions.dart'; class ProfileBarActions extends StatelessWidget { diff --git a/packages/sshnp_gui/lib/src/presentation/widgets/profile_form/profile_form.dart b/packages/sshnp_gui/lib/src/presentation/widgets/profile_form/profile_form.dart index c33dfc4cd..b03c001f3 100644 --- a/packages/sshnp_gui/lib/src/presentation/widgets/profile_form/profile_form.dart +++ b/packages/sshnp_gui/lib/src/presentation/widgets/profile_form/profile_form.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import 'package:noports_core/sshnp/sshnp.dart'; +import 'package:noports_core/sshnp.dart'; import 'package:sshnp_gui/src/controllers/navigation_rail_controller.dart'; import 'package:sshnp_gui/src/controllers/config_controller.dart'; import 'package:sshnp_gui/src/presentation/widgets/profile_form/custom_text_form_field.dart'; diff --git a/packages/sshnp_gui/lib/src/repository/authentication_repository.dart b/packages/sshnp_gui/lib/src/repository/authentication_repository.dart index ce624757a..b5f2024ff 100644 --- a/packages/sshnp_gui/lib/src/repository/authentication_repository.dart +++ b/packages/sshnp_gui/lib/src/repository/authentication_repository.dart @@ -9,9 +9,9 @@ import 'package:at_utils/at_logger.dart' show AtSignLogger; import 'package:at_utils/at_utils.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:noports_core/utils.dart' show DefaultArgs; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:noports_core/sshnpd/sshnpd.dart'; import 'package:sshnp_gui/src/presentation/widgets/utility/custom_snack_bar.dart'; import 'package:sshnp_gui/src/controllers/navigation_controller.dart'; import 'package:sshnp_gui/src/repository/navigation_repository.dart'; @@ -57,7 +57,7 @@ class AuthenticationRepository { return AtClientPreference() ..rootDomain = AtEnv.rootDomain - ..namespace = SSHNPD.namespace + ..namespace = DefaultArgs.namespace ..hiveStoragePath = dir.path ..commitLogPath = dir.path ..isLocalStoreRequired = true; From 1c21f157a9bd34b828937f52e66be01465c2d11d Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Thu, 28 Sep 2023 07:30:00 -0400 Subject: [PATCH 09/49] chore: rename params to sshnp_params --- packages/noports_core/lib/config_repository.dart | 6 +++--- packages/noports_core/lib/src/sshnp/sshnp.dart | 2 +- packages/noports_core/lib/src/sshnp/sshnp_impl.dart | 2 +- .../{params => sshnp_params}/config_file_repository.dart | 4 ++-- .../{params => sshnp_params}/config_key_repository.dart | 2 +- .../lib/src/sshnp/{params => sshnp_params}/sshnp_arg.dart | 0 .../src/sshnp/{params => sshnp_params}/sshnp_params.dart | 4 ++-- packages/noports_core/lib/sshnp.dart | 2 +- packages/noports_core/test/sshnp_test.dart | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) rename packages/noports_core/lib/src/sshnp/{params => sshnp_params}/config_file_repository.dart (97%) rename packages/noports_core/lib/src/sshnp/{params => sshnp_params}/config_key_repository.dart (96%) rename packages/noports_core/lib/src/sshnp/{params => sshnp_params}/sshnp_arg.dart (100%) rename packages/noports_core/lib/src/sshnp/{params => sshnp_params}/sshnp_params.dart (98%) diff --git a/packages/noports_core/lib/config_repository.dart b/packages/noports_core/lib/config_repository.dart index e42b169b7..5be18936d 100644 --- a/packages/noports_core/lib/config_repository.dart +++ b/packages/noports_core/lib/config_repository.dart @@ -1,5 +1,5 @@ library sshnp_core_config_repository; -export 'src/sshnp/params/config_file_repository.dart'; -export 'src/sshnp/params/config_key_repository.dart'; -export 'src/sshnp/params/sshnp_params.dart'; \ No newline at end of file +export 'src/sshnp/sshnp_params/config_file_repository.dart'; +export 'src/sshnp/sshnp_params/config_key_repository.dart'; +export 'src/sshnp/sshnp_params/sshnp_params.dart'; \ No newline at end of file diff --git a/packages/noports_core/lib/src/sshnp/sshnp.dart b/packages/noports_core/lib/src/sshnp/sshnp.dart index 22eed992e..941a7d35b 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp.dart @@ -6,7 +6,7 @@ import 'package:at_utils/at_utils.dart'; import 'package:dartssh2/dartssh2.dart'; import 'package:meta/meta.dart'; import 'package:noports_core/src/common/default_args.dart'; -import 'package:noports_core/src/sshnp/params/sshnp_params.dart'; +import 'package:noports_core/src/sshnp/sshnp_params/sshnp_params.dart'; import 'package:noports_core/src/common/supported_ssh_clients.dart'; import 'package:noports_core/src/common/utils.dart'; import 'package:noports_core/src/sshnp/sshnp_impl.dart'; diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl.dart index 4d6430a02..f01992086 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl.dart @@ -11,7 +11,7 @@ import 'package:meta/meta.dart'; import 'package:noports_core/src/common/default_args.dart'; import 'package:noports_core/src/common/supported_ssh_clients.dart'; import 'package:noports_core/src/common/utils.dart'; -import 'package:noports_core/src/sshnp/params/sshnp_params.dart'; +import 'package:noports_core/src/sshnp/sshnp_params/sshnp_params.dart'; import 'package:noports_core/src/sshnp/sshnp.dart'; import 'package:noports_core/src/sshnp/sshnp_result.dart'; import 'package:noports_core/src/sshrv/sshrv.dart'; diff --git a/packages/noports_core/lib/src/sshnp/params/config_file_repository.dart b/packages/noports_core/lib/src/sshnp/sshnp_params/config_file_repository.dart similarity index 97% rename from packages/noports_core/lib/src/sshnp/params/config_file_repository.dart rename to packages/noports_core/lib/src/sshnp/sshnp_params/config_file_repository.dart index d568658ad..86670c74f 100644 --- a/packages/noports_core/lib/src/sshnp/params/config_file_repository.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_params/config_file_repository.dart @@ -1,8 +1,8 @@ import 'dart:io'; import 'package:noports_core/src/common/utils.dart'; -import 'package:noports_core/src/sshnp/params/sshnp_params.dart'; -import 'package:noports_core/src/sshnp/params/sshnp_arg.dart'; +import 'package:noports_core/src/sshnp/sshnp_params/sshnp_params.dart'; +import 'package:noports_core/src/sshnp/sshnp_params/sshnp_arg.dart'; import 'package:path/path.dart' as path; class ConfigFileRepository { diff --git a/packages/noports_core/lib/src/sshnp/params/config_key_repository.dart b/packages/noports_core/lib/src/sshnp/sshnp_params/config_key_repository.dart similarity index 96% rename from packages/noports_core/lib/src/sshnp/params/config_key_repository.dart rename to packages/noports_core/lib/src/sshnp/sshnp_params/config_key_repository.dart index 41bacfcc3..087a34d3f 100644 --- a/packages/noports_core/lib/src/sshnp/params/config_key_repository.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_params/config_key_repository.dart @@ -1,6 +1,6 @@ import 'package:at_client/at_client.dart'; import 'package:noports_core/src/common/default_args.dart'; -import 'package:noports_core/src/sshnp/params/sshnp_params.dart'; +import 'package:noports_core/src/sshnp/sshnp_params/sshnp_params.dart'; class ConfigKeyRepository { static const String _keyPrefix = 'profile_'; diff --git a/packages/noports_core/lib/src/sshnp/params/sshnp_arg.dart b/packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_arg.dart similarity index 100% rename from packages/noports_core/lib/src/sshnp/params/sshnp_arg.dart rename to packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_arg.dart diff --git a/packages/noports_core/lib/src/sshnp/params/sshnp_params.dart b/packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_params.dart similarity index 98% rename from packages/noports_core/lib/src/sshnp/params/sshnp_params.dart rename to packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_params.dart index 1c252c32f..8325287c8 100644 --- a/packages/noports_core/lib/src/sshnp/params/sshnp_params.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_params.dart @@ -3,8 +3,8 @@ import 'dart:convert'; import 'package:args/args.dart'; import 'package:at_utils/at_logger.dart'; import 'package:noports_core/src/common/utils.dart'; -import 'package:noports_core/src/sshnp/params/config_file_repository.dart'; -import 'package:noports_core/src/sshnp/params/sshnp_arg.dart'; +import 'package:noports_core/src/sshnp/sshnp_params/config_file_repository.dart'; +import 'package:noports_core/src/sshnp/sshnp_params/sshnp_arg.dart'; import 'package:noports_core/src/common/default_args.dart'; class SSHNPParams { diff --git a/packages/noports_core/lib/sshnp.dart b/packages/noports_core/lib/sshnp.dart index 8732581be..7980ee005 100644 --- a/packages/noports_core/lib/sshnp.dart +++ b/packages/noports_core/lib/sshnp.dart @@ -2,4 +2,4 @@ library noports_core_sshnp; export 'src/sshnp/sshnp.dart'; export 'src/sshnp/sshnp_result.dart'; -export 'src/sshnp/params/sshnp_params.dart'; +export 'src/sshnp/sshnp_params/sshnp_params.dart'; diff --git a/packages/noports_core/test/sshnp_test.dart b/packages/noports_core/test/sshnp_test.dart index 608bfc58b..6ee8e2d0c 100644 --- a/packages/noports_core/test/sshnp_test.dart +++ b/packages/noports_core/test/sshnp_test.dart @@ -1,6 +1,6 @@ import 'package:args/args.dart'; import 'package:noports_core/src/common/utils.dart'; -import 'package:noports_core/src/sshnp/params/sshnp_params.dart'; +import 'package:noports_core/src/sshnp/sshnp_params/sshnp_params.dart'; import 'package:test/test.dart'; void main() { From 15faa63aec6a11151adb5cfe69ad739ee04e2a23 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Thu, 28 Sep 2023 09:27:17 -0400 Subject: [PATCH 10/49] refactor: remove the rest of the part definitions and properly expose the public APIs --- .../noports_core/lib/src/sshnpd/sshnpd.dart | 18 ++------------- .../lib/src/sshnpd/sshnpd_impl.dart | 22 ++++++++++++++++--- .../lib/src/sshnpd/sshnpd_params.dart | 5 ++++- .../noports_core/lib/src/sshrv/sshrv.dart | 6 +---- .../lib/src/sshrv/sshrv_impl.dart | 9 +++++++- .../noports_core/lib/src/sshrvd/sshrvd.dart | 12 ++-------- .../lib/src/sshrvd/sshrvd_impl.dart | 17 +++++++++++--- .../lib/src/sshrvd/sshrvd_params.dart | 3 ++- packages/noports_core/lib/sshnpd.dart | 1 + packages/noports_core/lib/sshrvd.dart | 3 ++- 10 files changed, 55 insertions(+), 41 deletions(-) diff --git a/packages/noports_core/lib/src/sshnpd/sshnpd.dart b/packages/noports_core/lib/src/sshnpd/sshnpd.dart index f4053ce46..c3682afa4 100644 --- a/packages/noports_core/lib/src/sshnpd/sshnpd.dart +++ b/packages/noports_core/lib/src/sshnpd/sshnpd.dart @@ -1,25 +1,11 @@ import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'package:args/args.dart'; import 'package:at_client/at_client.dart' hide StringBuffer; import 'package:at_utils/at_logger.dart'; -import 'package:dartssh2/dartssh2.dart'; -import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; - import 'package:noports_core/src/common/supported_ssh_clients.dart'; -import 'package:noports_core/src/common/utils.dart'; -import 'package:noports_core/src/sshrv/sshrv.dart'; -import 'package:noports_core/version.dart'; - -import 'package:uuid/uuid.dart'; - -import 'package:noports_core/src/common/default_args.dart'; - -part 'sshnpd_impl.dart'; -part 'sshnpd_params.dart'; +import 'package:noports_core/src/sshnpd/sshnpd_impl.dart'; +import 'package:noports_core/src/sshnpd/sshnpd_params.dart'; abstract class SSHNPD { abstract final AtSignLogger logger; diff --git a/packages/noports_core/lib/src/sshnpd/sshnpd_impl.dart b/packages/noports_core/lib/src/sshnpd/sshnpd_impl.dart index b191e49ea..b778700ed 100644 --- a/packages/noports_core/lib/src/sshnpd/sshnpd_impl.dart +++ b/packages/noports_core/lib/src/sshnpd/sshnpd_impl.dart @@ -1,6 +1,22 @@ -part of 'sshnpd.dart'; - -@visibleForTesting +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:at_client/at_client.dart' hide StringBuffer; +import 'package:at_utils/at_logger.dart'; +import 'package:dartssh2/dartssh2.dart'; +import 'package:logging/logging.dart'; +import 'package:meta/meta.dart'; +import 'package:noports_core/src/common/default_args.dart'; +import 'package:noports_core/src/common/supported_ssh_clients.dart'; +import 'package:noports_core/src/common/utils.dart'; +import 'package:noports_core/src/sshrv/sshrv.dart'; +import 'package:noports_core/sshnpd.dart'; +import 'package:noports_core/version.dart'; +import 'package:uuid/uuid.dart'; + + +@protected class SSHNPDImpl implements SSHNPD { @override final AtSignLogger logger = AtSignLogger(' sshnpd '); diff --git a/packages/noports_core/lib/src/sshnpd/sshnpd_params.dart b/packages/noports_core/lib/src/sshnpd/sshnpd_params.dart index 117b4ad0f..4c31eff2e 100644 --- a/packages/noports_core/lib/src/sshnpd/sshnpd_params.dart +++ b/packages/noports_core/lib/src/sshnpd/sshnpd_params.dart @@ -1,4 +1,7 @@ -part of 'sshnpd.dart'; +import 'package:args/args.dart'; +import 'package:noports_core/src/common/default_args.dart'; +import 'package:noports_core/src/common/supported_ssh_clients.dart'; +import 'package:noports_core/src/common/utils.dart'; class SSHNPDParams { late final String device; diff --git a/packages/noports_core/lib/src/sshrv/sshrv.dart b/packages/noports_core/lib/src/sshrv/sshrv.dart index 943828b5e..1e7314495 100644 --- a/packages/noports_core/lib/src/sshrv/sshrv.dart +++ b/packages/noports_core/lib/src/sshrv/sshrv.dart @@ -1,13 +1,9 @@ import 'dart:io'; -import 'package:at_utils/at_utils.dart'; -import 'package:meta/meta.dart'; +import 'package:noports_core/src/sshrv/sshrv_impl.dart'; import 'package:socket_connector/socket_connector.dart'; - import 'package:noports_core/src/common/default_args.dart'; -part 'sshrv_impl.dart'; - typedef SSHRVGenerator = SSHRV Function(String, int, {int localSshdPort}); abstract class SSHRV { diff --git a/packages/noports_core/lib/src/sshrv/sshrv_impl.dart b/packages/noports_core/lib/src/sshrv/sshrv_impl.dart index d0c6331e6..722b4777a 100644 --- a/packages/noports_core/lib/src/sshrv/sshrv_impl.dart +++ b/packages/noports_core/lib/src/sshrv/sshrv_impl.dart @@ -1,4 +1,11 @@ -part of 'sshrv.dart'; +import 'dart:io'; + +import 'package:at_utils/at_utils.dart'; +import 'package:meta/meta.dart'; +import 'package:noports_core/sshrv.dart'; +import 'package:socket_connector/socket_connector.dart'; + +import 'package:noports_core/src/common/default_args.dart'; @visibleForTesting class SSHRVImpl implements SSHRV { diff --git a/packages/noports_core/lib/src/sshrvd/sshrvd.dart b/packages/noports_core/lib/src/sshrvd/sshrvd.dart index 3fe29e52d..a88cdc596 100644 --- a/packages/noports_core/lib/src/sshrvd/sshrvd.dart +++ b/packages/noports_core/lib/src/sshrvd/sshrvd.dart @@ -1,18 +1,10 @@ import 'dart:async'; -import 'dart:io'; -import 'dart:isolate'; -import 'package:args/args.dart'; import 'package:at_client/at_client.dart'; import 'package:at_utils/at_logger.dart'; -import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; - -import 'package:noports_core/src/common/utils.dart'; -import 'package:noports_core/src/sshrvd/socket_connector.dart'; - -part 'sshrvd_impl.dart'; -part 'sshrvd_params.dart'; +import 'package:noports_core/src/sshrvd/sshrvd_impl.dart'; +import 'package:noports_core/src/sshrvd/sshrvd_params.dart'; abstract class SSHRVD { static const String namespace = 'sshrvd'; diff --git a/packages/noports_core/lib/src/sshrvd/sshrvd_impl.dart b/packages/noports_core/lib/src/sshrvd/sshrvd_impl.dart index 576ee247c..f21470cc9 100644 --- a/packages/noports_core/lib/src/sshrvd/sshrvd_impl.dart +++ b/packages/noports_core/lib/src/sshrvd/sshrvd_impl.dart @@ -1,6 +1,17 @@ -part of 'sshrvd.dart'; - -@visibleForTesting +import 'dart:async'; +import 'dart:io'; +import 'dart:isolate'; + +import 'package:at_client/at_client.dart'; +import 'package:at_utils/at_logger.dart'; +import 'package:logging/logging.dart'; +import 'package:meta/meta.dart'; +import 'package:noports_core/src/common/utils.dart'; +import 'package:noports_core/src/sshrvd/socket_connector.dart'; +import 'package:noports_core/src/sshrvd/sshrvd.dart'; +import 'package:noports_core/src/sshrvd/sshrvd_params.dart'; + +@protected class SSHRVDImpl implements SSHRVD { @override final AtSignLogger logger = AtSignLogger(' sshrvd '); diff --git a/packages/noports_core/lib/src/sshrvd/sshrvd_params.dart b/packages/noports_core/lib/src/sshrvd/sshrvd_params.dart index 8d1d13776..b98f919b1 100644 --- a/packages/noports_core/lib/src/sshrvd/sshrvd_params.dart +++ b/packages/noports_core/lib/src/sshrvd/sshrvd_params.dart @@ -1,4 +1,5 @@ -part of 'sshrvd.dart'; +import 'package:args/args.dart'; +import 'package:noports_core/src/common/utils.dart'; class SSHRVDParams { late final String username; diff --git a/packages/noports_core/lib/sshnpd.dart b/packages/noports_core/lib/sshnpd.dart index bcddffa8d..340d9641b 100644 --- a/packages/noports_core/lib/sshnpd.dart +++ b/packages/noports_core/lib/sshnpd.dart @@ -1,3 +1,4 @@ library noports_core_sshnpd; export 'src/sshnpd/sshnpd.dart'; +export 'src/sshnpd/sshnpd_params.dart'; \ No newline at end of file diff --git a/packages/noports_core/lib/sshrvd.dart b/packages/noports_core/lib/sshrvd.dart index db465357b..d31136bbb 100644 --- a/packages/noports_core/lib/sshrvd.dart +++ b/packages/noports_core/lib/sshrvd.dart @@ -1,3 +1,4 @@ library noports_core_sshrvd; -export 'src/sshrvd/sshrvd.dart'; \ No newline at end of file +export 'src/sshrvd/sshrvd.dart'; +export 'src/sshrvd/sshrvd_params.dart'; From e3f6d5877dae54fea7f983116ae4f2119e1bf0ec Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Thu, 28 Sep 2023 19:47:11 -0400 Subject: [PATCH 11/49] refactor: split sshnp_impl by code path --- .../lib/src/common/default_args.dart | 4 +- .../lib/src/common/supported_ssh_clients.dart | 15 +- .../noports_core/lib/src/sshnp/sshnp.dart | 267 +--- .../lib/src/sshnp/sshnp_impl.dart | 1118 ----------------- .../sshnp_impl/sshnp_forward_dart_impl.dart | 222 ++++ .../sshnp_impl/sshnp_forward_exec_impl.dart | 154 +++ .../lib/src/sshnp/sshnp_impl/sshnp_impl.dart | 489 +++++++ .../sshnp/sshnp_impl/sshnp_impl_mixin.dart | 61 + .../sshnp/sshnp_impl/sshnp_legacy_impl.dart | 95 ++ .../sshnp/sshnp_impl/sshnp_reverse_impl.dart | 73 ++ .../lib/src/sshnp/sshnp_mixins.dart | 12 + .../lib/src/sshnp/sshnp_params/sshnp_arg.dart | 2 +- .../src/sshnp/sshnp_params/sshnp_params.dart | 23 +- .../lib/src/sshnp/sshnp_result.dart | 26 +- .../noports_core/lib/src/sshnp/utils.dart | 25 +- .../lib/src/sshnpd/sshnpd_impl.dart | 15 +- .../lib/src/sshnpd/sshnpd_params.dart | 2 +- .../noports_core/lib/src/sshrv/sshrv.dart | 8 +- .../lib/src/sshrv/sshrv_impl.dart | 8 +- packages/noports_core/test/sshnpd_test.dart | 6 +- packages/sshnoports/bin/sshnp.dart | 24 +- packages/sshnoports/bin/sshrv.dart | 2 +- .../profile_actions/profile_run_action.dart | 7 +- .../profile_terminal_action.dart | 4 +- 24 files changed, 1264 insertions(+), 1398 deletions(-) delete mode 100644 packages/noports_core/lib/src/sshnp/sshnp_impl.dart create mode 100644 packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart create mode 100644 packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart create mode 100644 packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart create mode 100644 packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart create mode 100644 packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart create mode 100644 packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart create mode 100644 packages/noports_core/lib/src/sshnp/sshnp_mixins.dart diff --git a/packages/noports_core/lib/src/common/default_args.dart b/packages/noports_core/lib/src/common/default_args.dart index 5aca06041..b957d29a1 100644 --- a/packages/noports_core/lib/src/common/default_args.dart +++ b/packages/noports_core/lib/src/common/default_args.dart @@ -9,7 +9,7 @@ class DefaultArgs { static const verbose = false; static const rsa = false; static const rootDomain = 'root.atsign.org'; - static const sshrvGenerator = SSHRV.localBinary; + static const sshrvGenerator = SSHRV.exec; static const localSshdPort = 22; static const remoteSshdPort = 22; @@ -25,5 +25,5 @@ class DefaultSSHNPArgs { static const localSshOptions = []; static const legacyDaemon = true; static const listDevices = false; - static const sshClient = SupportedSshClient.hostSsh; + static const sshClient = SupportedSshClient.exec; } diff --git a/packages/noports_core/lib/src/common/supported_ssh_clients.dart b/packages/noports_core/lib/src/common/supported_ssh_clients.dart index 71e4e6d43..317114603 100644 --- a/packages/noports_core/lib/src/common/supported_ssh_clients.dart +++ b/packages/noports_core/lib/src/common/supported_ssh_clients.dart @@ -1,7 +1,18 @@ enum SupportedSshClient { - hostSsh(cliArg: '/usr/bin/ssh'), - pureDart(cliArg: 'pure-dart'); + exec(cliArg: '/usr/bin/ssh'), + dart(cliArg: 'pure-dart'); final String cliArg; const SupportedSshClient({required this.cliArg}); + + factory SupportedSshClient.fromCliArg(String cliArg) { + switch (cliArg) { + case '/usr/bin/ssh': + return SupportedSshClient.exec; + case 'pure-dart': + return SupportedSshClient.dart; + default: + throw ArgumentError('Unsupported SSH client: $cliArg'); + } + } } diff --git a/packages/noports_core/lib/src/sshnp/sshnp.dart b/packages/noports_core/lib/src/sshnp/sshnp.dart index 941a7d35b..bb3bd6320 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp.dart @@ -1,223 +1,67 @@ import 'dart:async'; import 'package:at_client/at_client.dart' hide StringBuffer; -import 'package:at_utils/at_logger.dart'; -import 'package:at_utils/at_utils.dart'; -import 'package:dartssh2/dartssh2.dart'; -import 'package:meta/meta.dart'; -import 'package:noports_core/src/common/default_args.dart'; -import 'package:noports_core/src/sshnp/sshnp_params/sshnp_params.dart'; import 'package:noports_core/src/common/supported_ssh_clients.dart'; -import 'package:noports_core/src/common/utils.dart'; -import 'package:noports_core/src/sshnp/sshnp_impl.dart'; -import 'package:noports_core/src/sshnp/sshnp_result.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart'; +import 'package:noports_core/src/sshnp/sshnp_params/sshnp_params.dart'; import 'package:noports_core/src/sshrv/sshrv.dart'; +import 'package:noports_core/src/sshnp/sshnp_result.dart'; -abstract class SSHNP { - abstract final AtSignLogger logger; - - // ==================================================================== - // Final instance variables, injected via constructor - // ==================================================================== - /// The [AtClient] used to communicate with sshnpd and sshrvd - abstract final AtClient atClient; - - /// The atSign of the sshnpd we wish to communicate with - abstract final String sshnpdAtSign; - - /// The device name of the sshnpd we wish to communicate with - abstract final String device; - - /// The user name on this host - abstract final String username; - - /// The home directory on this host - abstract final String homeDirectory; - - /// The sessionId we will use - abstract final String sessionId; - - /// The name of the public key file from ~/.ssh which the client may request - /// be appended to authorized_hosts on the remote device. Note that if the - /// daemon on the remote device is not running with the `-s` flag, then it - /// ignores such requests. - abstract final String publicKeyFileName; - - abstract final List localSshOptions; - - /// When false, we generate [sshPublicKey] and [sshPrivateKey] using ed25519. - /// When true, we generate [sshPublicKey] and [sshPrivateKey] using RSA. - /// Defaults to false - abstract final bool rsa; - - // ==================================================================== - // Volatile instance variables, injected via constructor - // but possibly modified later on - // ==================================================================== - - /// Host that we will send to sshnpd for it to connect to, - /// or the atSign of the sshrvd. - /// If using sshrvd then we will fetch the _actual_ host to use from sshrvd. - abstract String host; - - /// Port that we will send to sshnpd for it to connect to. - /// Required if we are not using sshrvd. - /// If using sshrvd then initial port value will be ignored and instead we - /// will fetch the port from sshrvd. - abstract int port; - - /// Port to which sshnpd will forwardRemote its [SSHClient]. If localPort - /// is set to '0' then - abstract int localPort; - - /// Port that local sshd is listening on localhost interface - /// Default set to [defaultLocalSshdPort] - abstract int localSshdPort; - - /// Port that the remote sshd is listening on localhost interface - /// Default set to [defaultRemoteSshdPort] - abstract int remoteSshdPort; - - // ==================================================================== - // Derived final instance variables, set during construction or init - // ==================================================================== - - /// Set to [AtClient.getCurrentAtSign] during construction - @visibleForTesting - abstract final String clientAtSign; - - /// The username to use on the remote host in the ssh session. Either passed - /// through class constructor or fetched from the sshnpd - /// by [fetchRemoteUserName] during [init] - abstract String? remoteUsername; - - /// Set by [generateSshKeys] during [init]. - /// sshnp generates a new keypair for each ssh session, using ed25519 by - /// default but rsa if the [rsa] flag is set to true. sshnp will write - /// [sshPublicKey] to ~/.ssh/authorized_keys - abstract final String sshPublicKey; - - /// Set by [generateSshKeys] during [init]. - /// sshnp generates a new keypair for each ssh session, using ed25519 by - /// default but rsa if the [rsa] flag is set to true. sshnp will send the - /// [sshPrivateKey] to sshnpd - abstract final String sshPrivateKey; - - /// Namespace will be set to [device].sshnp - abstract final String namespace; - - /// When using sshrvd, this is fetched from sshrvd during [init] - int get sshrvdPort; - - /// Set by constructor to - /// '$homeDirectory${Platform.pathSeparator}.ssh${Platform.pathSeparator}' - abstract final String sshHomeDirectory; - - /// Function used to generate a [SSHRV] instance ([SSHRV.localBinary] by default) - abstract final SSHRV Function(String, int) sshrvGenerator; - - /// true once we have received any response (success or error) from sshnpd - @visibleForTesting - abstract bool sshnpdAck; - - /// true once we have received an error response from sshnpd - @visibleForTesting - abstract bool sshnpdAckErrors; - - /// true once we have received a response from sshrvd - @visibleForTesting - abstract bool sshrvdAck; - - abstract final bool legacyDaemon; - - bool verbose = false; - - /// true once [init] has completed - bool initialized = false; +typedef AtClientGenerator = FutureOr Function( + SSHNPParams params, String namespace); - abstract final bool direct; +typedef UsageCallback = void Function(Object error, StackTrace stackTrace); - /// If ssh tunnel is unused (no active connections via port forwards) for - /// longer than this many seconds, then the connection will be closed. - /// Defaults to [defaults.defaultIdleTimeout] - abstract int idleTimeout; +abstract class SSHNP { + static Future fromParams( + SSHNPParams params, { + AtClient? atClient, + AtClientGenerator? atClientGenerator, + UsageCallback? usageCallback, + SSHRVGenerator? sshrvGenerator, + }) async { + atClient ??= await atClientGenerator?.call( + params, SSHNPImpl.getNamespace(params.device)); + + if (atClient == null) { + throw ArgumentError( + 'atClient must be provided or atClientGenerator must be provided'); + } + + if (params.legacyDaemon) { + return SSHNPLegacyImpl( + atClient: atClient, params: params, sshrvGenerator: sshrvGenerator); + } + + if (!params.host.startsWith('@')) { + return SSHNPReverseImpl( + atClient: atClient, params: params, sshrvGenerator: sshrvGenerator); + } + + switch (SupportedSshClient.fromCliArg(params.sshClient)) { + case SupportedSshClient.exec: + return SSHNPForwardExecImpl(atClient: atClient, params: params); + case SupportedSshClient.dart: + return SSHNPForwardDartImpl(atClient: atClient, params: params); + default: + throw ArgumentError('Unsupported ssh client: ${params.sshClient}'); + } + } - /// The ssh client to use when doing outbound ssh within this program - abstract SupportedSshClient sshClient; + AtClient get atClient; - /// When true, any local forwarding directives included in [localSshOptions] - /// will be added to the initial tunnel ssh request - abstract bool addForwardsToTunnel; + SSHNPParams get params; /// Completes when the SSHNP instance is no longer doing anything /// e.g. controlling a direct ssh tunnel using the pure-dart SSHClient Future get done; - factory SSHNP({ - // final fields - required AtClient atClient, - required String sshnpdAtSign, - required String device, - required String username, - required String homeDirectory, - required String sessionId, - String sendSshPublicKey = DefaultSSHNPArgs.sendSshPublicKey, - required List localSshOptions, - bool rsa = false, - // volatile fields - required String host, - required int port, - required int localPort, - String? remoteUsername, - bool verbose = false, - SSHRVGenerator sshrvGenerator = DefaultArgs.sshrvGenerator, - int localSshdPort = DefaultArgs.localSshdPort, - bool legacyDaemon = DefaultSSHNPArgs.legacyDaemon, - int remoteSshdPort = DefaultArgs.remoteSshdPort, - int idleTimeout = DefaultArgs.idleTimeout, - required SupportedSshClient sshClient, - required bool addForwardsToTunnel, - }) { - return SSHNPImpl( - atClient: atClient, - sshnpdAtSign: sshnpdAtSign, - device: device, - username: username, - homeDirectory: homeDirectory, - sessionId: sessionId, - sendSshPublicKey: sendSshPublicKey, - localSshOptions: localSshOptions, - rsa: rsa, - host: host, - port: port, - localPort: localPort, - remoteUsername: remoteUsername, - verbose: verbose, - sshrvGenerator: sshrvGenerator, - localSshdPort: localSshdPort, - legacyDaemon: legacyDaemon, - remoteSshdPort: remoteSshdPort, - idleTimeout: idleTimeout, - sshClient: sshClient, - addForwardsToTunnel: addForwardsToTunnel, - ); - } - - static Future fromParams( - SSHNPParams p, { - AtClient? atClient, - FutureOr Function(SSHNPParams, String)? atClientGenerator, - void Function(Object, StackTrace)? usageCallback, - SSHRVGenerator sshrvGenerator = SSHRV.localBinary, - }) { - return SSHNPImpl.fromParams( - p, - atClient: atClient, - atClientGenerator: atClientGenerator, - usageCallback: usageCallback, - sshrvGenerator: sshrvGenerator, - ); - } + /// Completes after asynchronous initialization has completed + Future get initialized; /// Must be run after construction, to complete initialization /// - Starts notification subscription to listen for responses from sshnpd @@ -230,19 +74,24 @@ abstract class SSHNP { /// from sshrvd /// - calls [sharePrivateKeyWithSshnpd] /// - calls [sharePublicKeyWithSshnpdIfRequired] - Future init(); + FutureOr init(); /// May only be run after [init] has been run. /// - Sends request to sshnpd; the response listener was started by [init] /// - Waits for success or error response, or time out after 10 secs /// - If got a success response, print the ssh command to use to stdout /// - Clean up temporary files - Future run(); + FutureOr run(); /// Send a ping out to all sshnpd and listen for heartbeats - /// Returns two Iterable: + /// Returns two Iterable and a Map: /// - Iterable of atSigns of sshnpd that responded /// - Iterable of atSigns of sshnpd that did not respond - Future<(Iterable, Iterable, Map)> + /// - Map where the keys are all atSigns included in the maps, and the values being their device info + FutureOr<(Iterable, Iterable, Map)> listDevices(); + + /// - Dispose of any resources used by this SSHNP instance + /// - Clean up temporary files + FutureOr cleanUp(); } diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl.dart deleted file mode 100644 index f01992086..000000000 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl.dart +++ /dev/null @@ -1,1118 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:at_client/at_client.dart' hide StringBuffer; -import 'package:at_commons/at_builders.dart'; -import 'package:at_utils/at_logger.dart'; -import 'package:dartssh2/dartssh2.dart'; -import 'package:logging/logging.dart'; -import 'package:meta/meta.dart'; -import 'package:noports_core/src/common/default_args.dart'; -import 'package:noports_core/src/common/supported_ssh_clients.dart'; -import 'package:noports_core/src/common/utils.dart'; -import 'package:noports_core/src/sshnp/sshnp_params/sshnp_params.dart'; -import 'package:noports_core/src/sshnp/sshnp.dart'; -import 'package:noports_core/src/sshnp/sshnp_result.dart'; -import 'package:noports_core/src/sshrv/sshrv.dart'; -import 'package:noports_core/src/sshrvd/sshrvd.dart'; -import 'package:path/path.dart' as path; -import 'package:noports_core/src/sshnp/utils.dart'; -import 'package:uuid/uuid.dart'; - -@protected -class SSHNPImpl implements SSHNP { - @override - final AtSignLogger logger = AtSignLogger(' sshnp '); - - // ==================================================================== - // Final instance variables, injected via constructor - // ==================================================================== - /// The [AtClient] used to communicate with sshnpd and sshrvd - @override - final AtClient atClient; - - /// The atSign of the sshnpd we wish to communicate with - @override - final String sshnpdAtSign; - - /// The device name of the sshnpd we wish to communicate with - @override - final String device; - - /// The user name on this host - @override - final String username; - - /// The home directory on this host - @override - final String homeDirectory; - - /// The sessionId we will use - @override - final String sessionId; - - @override - late final String publicKeyFileName; - - @override - final List localSshOptions; - - @override - late final int localSshdPort; - - @override - late final int remoteSshdPort; - - /// When false, we generate [sshPublicKey] and [sshPrivateKey] using ed25519. - /// When true, we generate [sshPublicKey] and [sshPrivateKey] using RSA. - /// Defaults to false - @override - final bool rsa; - - // ==================================================================== - // Volatile instance variables, injected via constructor - // but possibly modified later on - // ==================================================================== - - /// Host that we will send to sshnpd for it to connect to, - /// or the atSign of the sshrvd. - /// If using sshrvd then we will fetch the _actual_ host to use from sshrvd. - @override - String host; - - /// Port that we will send to sshnpd for it to connect to. - /// Required if we are not using sshrvd. - /// If using sshrvd then initial port value will be ignored and instead we - /// will fetch the port from sshrvd. - @override - int port; - - /// Port to which sshnpd will forwardRemote its [SSHClient]. If localPort - /// is set to '0' then - @override - int localPort; - - // ==================================================================== - // Derived final instance variables, set during construction or init - // ==================================================================== - - /// Set to [AtClient.getCurrentAtSign] during construction - @override - @visibleForTesting - late final String clientAtSign; - - /// The username to use on the remote host in the ssh session. Either passed - /// through class constructor or fetched from the sshnpd - /// by [fetchRemoteUserName] during [init] - @override - String? remoteUsername; - - /// Set by [generateSshKeys] during [init], if we're not doing direct ssh. - /// sshnp generates a new keypair for each ssh session, using ed25519 by - /// default but rsa if the [rsa] flag is set to true. sshnp will write - /// [sshPublicKey] to ~/.ssh/authorized_keys - @override - late final String sshPublicKey; - - /// Set by [generateSshKeys] during [init]. - /// sshnp generates a new keypair for each ssh session, using ed25519 by - /// default but rsa if the [rsa] flag is set to true. sshnp will send the - /// [sshPrivateKey] to sshnpd - @override - late final String sshPrivateKey; - - /// Namespace will be set to [device].sshnp - @override - late final String namespace; - - /// When using sshrvd, this is fetched from sshrvd during [init] - /// This is only set when using sshrvd - /// (i.e. after [getHostAndPortFromSshrvd] has been called) - @override - int get sshrvdPort => _sshrvdPort; - - late int _sshrvdPort; - - /// Set by constructor to - /// '$homeDirectory${Platform.pathSeparator}.ssh${Platform.pathSeparator}' - @override - late final String sshHomeDirectory; - - /// Function used to generate a [SSHRV] instance ([SSHRV.localbinary] by default) - @override - SSHRVGenerator sshrvGenerator; - - /// true once we have received any response (success or error) from sshnpd - @override - @visibleForTesting - bool sshnpdAck = false; - - /// true once we have received an error response from sshnpd - @override - @visibleForTesting - bool sshnpdAckErrors = false; - - @visibleForTesting - late String ephemeralPrivateKey; - - /// true once we have received a response from sshrvd - @override - @visibleForTesting - bool sshrvdAck = false; - - /// true once [init] has completed - @override - bool initialized = false; - - @override - bool verbose = false; - - @override - late final bool legacyDaemon; - - @override - late final bool direct; - - @override - late final SupportedSshClient sshClient; - - @override - late final int idleTimeout; - - @override - late final bool addForwardsToTunnel; - - final _doneCompleter = Completer(); - - @override - Future get done => _doneCompleter.future; - - SSHNPImpl({ - required this.atClient, - required this.sshnpdAtSign, - required this.device, - required this.username, - required this.homeDirectory, - required this.sessionId, - String sendSshPublicKey = DefaultSSHNPArgs.sendSshPublicKey, - required this.localSshOptions, - this.rsa = DefaultArgs.rsa, - required this.host, - required this.port, - required this.localPort, - this.remoteUsername, - this.verbose = DefaultArgs.verbose, - this.sshrvGenerator = DefaultArgs.sshrvGenerator, - this.localSshdPort = DefaultArgs.localSshdPort, - this.legacyDaemon = DefaultSSHNPArgs.legacyDaemon, - this.remoteSshdPort = DefaultArgs.remoteSshdPort, - this.idleTimeout = DefaultArgs.idleTimeout, - required this.sshClient, - this.addForwardsToTunnel = false, - }) { - namespace = '$device.sshnp'; - clientAtSign = atClient.getCurrentAtSign()!; - logger.hierarchicalLoggingEnabled = true; - logger.logger.level = Level.SHOUT; - - sshHomeDirectory = getDefaultSshDirectory(homeDirectory); - if (!Directory(sshHomeDirectory).existsSync()) { - try { - Directory(sshHomeDirectory).createSync(); - } catch (e, s) { - throw SSHNPFailed( - 'Unable to create ssh home directory $sshHomeDirectory\n' - 'hint: try manually creating $sshHomeDirectory and re-running sshnp', - e, - s, - ); - } - } - - // previously, the default value for sendSshPublicKey was 'false' instead of '' - // immediately set it to '' to avoid the program from attempting to - // search for a public key file called 'false' - if (sendSshPublicKey == 'false' || sendSshPublicKey.isEmpty) { - publicKeyFileName = ''; - } else if (path.normalize(sendSshPublicKey).contains('/') || - path.normalize(sendSshPublicKey).contains(r'\')) { - publicKeyFileName = path.normalize(path.absolute(sendSshPublicKey)); - } else { - publicKeyFileName = path.normalize('$sshHomeDirectory/$sendSshPublicKey'); - } - } - - static Future fromParams( - SSHNPParams p, { - AtClient? atClient, - FutureOr Function(SSHNPParams, String)? atClientGenerator, - void Function(Object, StackTrace)? usageCallback, - SSHRVGenerator sshrvGenerator = SSHRV.localBinary, - }) async { - try { - if (p.clientAtSign == null) { - throw ArgumentError('Option from is mandatory.'); - } - if (p.sshnpdAtSign == null) { - throw ArgumentError('Option to is mandatory.'); - } - - if (p.host == null) { - throw ArgumentError('Option host is mandatory.'); - } - - if (atClient == null && atClientGenerator == null) { - throw StateError('atClient adn atClientGenerator are both null'); - } - - String sessionId = Uuid().v4(); - atClient ??= await atClientGenerator!(p, sessionId); - - if (p.clientAtSign != atClient.getCurrentAtSign()) { - throw ArgumentError( - 'Option from must match the current atSign of the AtClient'); - } - - // Check to see if the port number is in range for TCP ports - if (p.localSshdPort > 65535 || p.localSshdPort < 1) { - throw ArgumentError( - '\nInvalid port number for sshd (1-65535) : ${p.localSshdPort}'); - } - - AtSignLogger.root_level = 'SHOUT'; - if (p.verbose) { - AtSignLogger.root_level = 'INFO'; - } - - var sshnp = SSHNP( - atClient: atClient, - sshnpdAtSign: p.sshnpdAtSign!, - username: p.username, - homeDirectory: p.homeDirectory, - sessionId: sessionId, - device: p.device, - host: p.host!, - port: p.port, - localPort: p.localPort, - localSshOptions: p.localSshOptions, - rsa: p.rsa, - sendSshPublicKey: p.sendSshPublicKey, - remoteUsername: p.remoteUsername, - verbose: p.verbose, - sshrvGenerator: sshrvGenerator, - localSshdPort: p.localSshdPort, - legacyDaemon: p.legacyDaemon, - remoteSshdPort: p.remoteSshdPort, - idleTimeout: p.idleTimeout, - sshClient: SupportedSshClient.values - .firstWhere((c) => c.cliArg == p.sshClient), - addForwardsToTunnel: p.addForwardsToTunnel, - ); - if (p.verbose) { - sshnp.logger.logger.level = Level.INFO; - } - - return sshnp; - } catch (e, s) { - usageCallback?.call(e, s); - if (e is SSHNPFailed) { - rethrow; - } - throw SSHNPFailed('Unknown failure:\n$e', e, s); - } - } - - /// Must be run after construction, to complete initialization - /// - Starts notification subscription to listen for responses from sshnpd - /// - calls [generateSshKeys] which generates the ssh keypair to use - /// ( [sshPublicKey] and [sshPrivateKey] ) - /// - calls [fetchRemoteUserName] to fetch the username to use on the remote - /// host in the ssh session - /// - If not supplied via constructor, finds a spare port for [localPort] - /// - If using sshrv, calls [getHostAndPortFromSshrvd] to fetch host and port - /// from sshrvd - /// - calls [sharePrivateKeyWithSshnpd] - /// - calls [sharePublicKeyWithSshnpdIfRequired] - @override - Future init() async { - if (initialized) { - throw StateError('Cannot init() - already initialized'); - } - - // determine the ssh direction - direct = useDirectSsh(legacyDaemon, host); - try { - if (!(await atSignIsActivated(atClient, sshnpdAtSign))) { - throw ('Device address $sshnpdAtSign is not activated.'); - } - } catch (e, s) { - throw SSHNPFailed( - 'Device address $sshnpdAtSign does not exist or is not activated.', - e, - s); - } - - logger.info('Subscribing to notifications on $sessionId.$namespace@'); - // Start listening for response notifications from sshnpd - atClient.notificationService - .subscribe(regex: '$sessionId.$namespace@', shouldDecrypt: true) - .listen(handleSshnpdResponses); - - if (publicKeyFileName.isNotEmpty && !File(publicKeyFileName).existsSync()) { - throw ('Unable to find ssh public key file : $publicKeyFileName'); - } - - if (publicKeyFileName.isNotEmpty && - !File(publicKeyFileName.replaceAll('.pub', '')).existsSync()) { - throw ('Unable to find matching ssh private key for public key : $publicKeyFileName'); - } - - remoteUsername ?? await fetchRemoteUserName(); - - // find a spare local port - if (localPort == 0) { - try { - ServerSocket serverSocket = - await ServerSocket.bind(InternetAddress.loopbackIPv4, 0); - localPort = serverSocket.port; - await serverSocket.close(); - } catch (e, s) { - throw SSHNPFailed('Unable to find a spare local port', e, s); - } - } - - await sharePublicKeyWithSshnpdIfRequired(); - - // If host has an @ then contact the sshrvd service for some ports - if (host.startsWith('@')) { - await getHostAndPortFromSshrvd(); - } - - // If we're doing reverse (i.e. not direct) then we need to - // 1) generate some ephemeral keys for the daemon to use to ssh back to us - // 2) if legacy then we share the private key via its own notification - if (!direct) { - try { - var (String ephemeralPublicKey, String ephemeralPrivateKey) = - await generateSshKeys( - rsa: rsa, - sessionId: sessionId, - sshHomeDirectory: sshHomeDirectory); - - sshPublicKey = ephemeralPublicKey; - sshPrivateKey = ephemeralPrivateKey; - } catch (e, s) { - throw SSHNPFailed('Failed to generate ephemeral keypair', e, s); - } - - try { - await addEphemeralKeyToAuthorizedKeys( - sshPublicKey: sshPublicKey, - localSshdPort: localSshdPort, - sessionId: sessionId); - } catch (e, s) { - throw SSHNPFailed( - 'Failed to add ephemeral key to authorized_keys', e, s); - } - - if (legacyDaemon) { - await sharePrivateKeyWithSshnpd(); - } - } - - initialized = true; - } - - /// May only be run after [init] has been run. - /// - Sends request to sshnpd; the response listener was started by [init] - /// - Waits for success or error response, or time out after 10 secs - /// - If got a success response, print the ssh command to use to stdout - /// - Clean up temporary files - @override - Future run() async { - if (!initialized) { - return SSHNPFailed('Cannot run() - not initialized'); - } - - late SSHNPResult res; - if (legacyDaemon) { - logger.info('Requesting legacy daemon to start reverse ssh session'); - res = await legacyStartReverseSsh(); - _doneCompleter.complete(); - } else { - if (direct) { - // Note that when direct, this client is initiating the tunnel ssh. - // - // If tunnel is created using /usr/bin/ssh then it is exec'd in the - // background, and the `directSshViaExec` method will call - // _doneCompleter.complete() before it returns. - // - // However if tunnel is created using pure dart SSHClient then the - // tunnel is being managed by the SSHNP instance. In that case, - // _doneCompleter.complete() is called once the tunnel determines - // that there are no more active connections. - logger.info( - 'Requesting daemon to set up socket tunnel for direct ssh session'); - res = await startDirectSsh(); - } else { - logger.info('Requesting daemon to start reverse ssh session'); - res = await startReverseSsh(); - _doneCompleter.complete(); - } - } - - return res; - } - - Future startDirectSsh() async { - // send request to the daemon via notification - await _notify( - AtKey() - ..key = 'ssh_request' - ..namespace = namespace - ..sharedBy = clientAtSign - ..sharedWith = sshnpdAtSign - ..metadata = (Metadata() - ..ttr = -1 - ..ttl = 10000), - signAndWrapAndJsonEncode(atClient, { - 'direct': true, - 'sessionId': sessionId, - 'host': host, - 'port': port - }), - sessionId: sessionId); - - bool acked = await waitForDaemonResponse(); - if (!acked) { - return SSHNPFailed( - 'sshnp timed out: waiting for daemon response\nhint: make sure the device is online'); - } - - if (sshnpdAckErrors) { - return SSHNPFailed('sshnp failed: with sshnpd acknowledgement errors'); - } - // 1) Execute an ssh command setting up local port forwarding. - // Note that this is very similar to what the daemon does when we - // ask for a reverse ssh - logger.info( - 'Starting direct ssh session for $username to $host on port $_sshrvdPort with forwardLocal of $localPort'); - - try { - bool success = false; - String? errorMessage; - Process? process; - SSHClient? client; - switch (sshClient) { - case SupportedSshClient.hostSsh: - (success, errorMessage, process) = await directSshViaExec(); - _doneCompleter.complete(); - break; - case SupportedSshClient.pureDart: - (success, errorMessage, client) = await directSshViaSSHClient(); - break; - } - - if (!success) { - errorMessage ??= - 'Failed to start ssh tunnel and / or forward local port $localPort'; - return SSHNPFailed(errorMessage); - } - // All good - write the ssh command to stdout - return SSHNPSuccess.base( - localPort: localPort, - remoteUsername: remoteUsername, - host: 'localhost', - privateKeyFileName: publicKeyFileName.replaceAll('.pub', ''), - localSshOptions: (addForwardsToTunnel) ? null : localSshOptions, - sshProcess: process, - sshClient: client, - ); - } catch (e, s) { - return SSHNPFailed('SSH Client failure : $e', e, s); - } - } - - Future<(bool, String?, SSHClient?)> directSshViaSSHClient() async { - late final SSHSocket socket; - try { - socket = await SSHSocket.connect(host, _sshrvdPort); - } catch (e) { - return (false, 'Failed to open socket to $host:$port : $e', null); - } - - late final SSHClient client; - try { - client = SSHClient( - socket, - username: remoteUsername!, - identities: [ - // A single private key file may contain multiple keys. - ...SSHKeyPair.fromPem(ephemeralPrivateKey) - ], - keepAliveInterval: Duration(seconds: 15), - ); - } catch (e) { - return ( - false, - 'Failed to create SSHClient for $username@$host:$port : $e', - null - ); - } - - try { - await client.authenticated; - } catch (e) { - return ( - false, - 'Failed to authenticate as $username@$host:$port : $e', - null - ); - } - - int counter = 0; - - Future startForwarding( - {required int fLocalPort, - required String fRemoteHost, - required int fRemotePort}) async { - logger.info('Starting port forwarding' - ' from port $fLocalPort on localhost' - ' to $fRemoteHost:$fRemotePort on remote side'); - - /// Do the port forwarding for sshd - final serverSocket = await ServerSocket.bind('localhost', fLocalPort); - - serverSocket.listen((socket) async { - counter++; - final forward = await client.forwardLocal(fRemoteHost, fRemotePort); - unawaited( - forward.stream.cast>().pipe(socket).whenComplete( - () async { - counter--; - }, - ), - ); - unawaited(socket.pipe(forward.sink)); - }, onError: (Object error) { - counter = 0; - }, onDone: () { - counter = 0; - }); - } - - // Start local forwarding to the remote sshd - await startForwarding( - fLocalPort: localPort, - fRemoteHost: 'localhost', - fRemotePort: remoteSshdPort); - - if (addForwardsToTunnel) { - var optionsSplitBySpace = localSshOptions.join(' ').split(' '); - logger.info('addForwardsToTunnel is true;' - ' localSshOptions split by space is $optionsSplitBySpace'); - // parse the localSshOptions, extract all of the local port forwarding - // directives and act on all of them - var lsoIter = optionsSplitBySpace.iterator; - while (lsoIter.moveNext()) { - if (lsoIter.current == '-L') { - // we expect the args next - bool hasArgs = lsoIter.moveNext(); - if (!hasArgs) { - logger.warning('localSshOptions has -L with no args'); - continue; - } - String argString = lsoIter.current; - // We expect args like $localPort:$remoteHost:$remotePort - List args = argString.split(':'); - if (args.length != 3) { - logger.warning('localSshOptions has -L with bad args $argString'); - continue; - } - int? fLocalPort = int.tryParse(args[0]); - String fRemoteHost = args[1]; - int? fRemotePort = int.tryParse(args[2]); - if (fLocalPort == null || - fRemoteHost.isEmpty || - fRemotePort == null) { - logger.warning('localSshOptions has -L with bad args $argString'); - continue; - } - - // Start the forwarding - await startForwarding( - fLocalPort: fLocalPort, - fRemoteHost: fRemoteHost, - fRemotePort: fRemotePort); - } - } - } - - /// Set up timer to check to see if all connections are down - logger.info('ssh session will terminate after $idleTimeout seconds' - ' if it is not being used'); - Timer.periodic(Duration(seconds: idleTimeout), (timer) async { - if (counter == 0) { - timer.cancel(); - client.close(); - await client.done; - _doneCompleter.complete(); - logger.shout('$sessionId | no active connections' - ' - ssh session complete'); - } - }); - - return (true, null, client); - } - - Future<(bool, String?, Process?)> directSshViaExec() async { - // If using exec then we can assume we're on something unix-y - // So we can write the ephemeralPrivateKey to a tmp file, - // set its permissions appropriately, and remove it after we've - // executed the command - var tmpFileName = - path.normalize('$sshHomeDirectory/tmp/ephemeral_$sessionId'); - File tmpFile = File(tmpFileName); - await tmpFile.create(recursive: true); - await tmpFile.writeAsString(ephemeralPrivateKey, - mode: FileMode.write, flush: true); - await Process.run('chmod', ['go-rwx', tmpFileName]); - - String argsString = '$remoteUsername@$host' - ' -p $_sshrvdPort' - ' -i $tmpFileName' - ' -L $localPort:localhost:$remoteSshdPort' - ' -o LogLevel=VERBOSE' - ' -t -t' - ' -o StrictHostKeyChecking=accept-new' - ' -o IdentitiesOnly=yes' - ' -o BatchMode=yes' - ' -o ExitOnForwardFailure=yes' - ' -f' // fork after authentication - this is important - ; - if (addForwardsToTunnel) { - argsString += ' ${localSshOptions.join(' ')}'; - } - argsString += ' sleep 15'; - - List args = argsString.split(' '); - - logger.info('$sessionId | Executing /usr/bin/ssh ${args.join(' ')}'); - - // Because of the options we are using, we can wait for this process - // to complete, because it will exit with exitCode 0 once it has connected - // successfully - late int sshExitCode; - final soutBuf = StringBuffer(); - final serrBuf = StringBuffer(); - Process? process; - try { - process = await Process.start('/usr/bin/ssh', args); - process.stdout.transform(Utf8Decoder()).listen((String s) { - soutBuf.write(s); - logger.info('$sessionId | sshStdOut | $s'); - }, onError: (e) {}); - process.stderr.transform(Utf8Decoder()).listen((String s) { - serrBuf.write(s); - logger.info('$sessionId | sshStdErr | $s'); - }, onError: (e) {}); - sshExitCode = await process.exitCode.timeout(Duration(seconds: 10)); - // ignore: unused_catch_clause - } on TimeoutException catch (e) { - sshExitCode = 6464; - } - - await tmpFile.delete(); - - String? errorMessage; - if (sshExitCode != 0) { - if (sshExitCode == 6464) { - logger.shout( - '$sessionId | Command timed out: /usr/bin/ssh ${args.join(' ')}'); - errorMessage = 'Failed to establish connection - timed out'; - } else { - logger.shout('$sessionId | Exit code $sshExitCode from' - ' /usr/bin/ssh ${args.join(' ')}'); - errorMessage = - 'Failed to establish connection - exit code $sshExitCode'; - } - } - - return (sshExitCode == 0, errorMessage, process); - } - - /// Identical to [legacyStartReverseSsh] except for the request notification - Future startReverseSsh() async { - // Connect to rendezvous point using background process. - // sshnp (this program) can then exit without issue. - SSHRV sshrv = - sshrvGenerator(host, _sshrvdPort, localSshdPort: localSshdPort); - Future sshrvResult = sshrv.run(); - - // send request to the daemon via notification - await _notify( - AtKey() - ..key = 'ssh_request' - ..namespace = namespace - ..sharedBy = clientAtSign - ..sharedWith = sshnpdAtSign - ..metadata = (Metadata() - ..ttr = -1 - ..ttl = 10000), - signAndWrapAndJsonEncode(atClient, { - 'direct': false, - 'sessionId': sessionId, - 'host': host, - 'port': port, - 'username': username, - 'remoteForwardPort': localPort, - 'privateKey': sshPrivateKey - }), - sessionId: sessionId); - - bool acked = await waitForDaemonResponse(); - await cleanUpAfterReverseSsh(this); - if (!acked) { - return SSHNPFailed( - 'sshnp connection timeout: waiting for daemon response'); - } - - if (sshnpdAckErrors) { - return SSHNPFailed('sshnp failed: with sshnpd acknowledgement errors'); - } - - return SSHNPSuccess.base( - localPort: localPort, - remoteUsername: remoteUsername, - host: 'localhost', - privateKeyFileName: publicKeyFileName.replaceAll('.pub', ''), - localSshOptions: (addForwardsToTunnel) ? localSshOptions : null, - sshrvResult: sshrvResult, - ); - } - - Future legacyStartReverseSsh() async { - // Connect to rendezvous point using background process. - // sshnp (this program) can then exit without issue. - SSHRV sshrv = - sshrvGenerator(host, _sshrvdPort, localSshdPort: localSshdPort); - Future sshrvResult = sshrv.run(); - - // send request to the daemon via notification - await _notify( - AtKey() - ..key = 'sshd' - ..namespace = namespace - ..sharedBy = clientAtSign - ..sharedWith = sshnpdAtSign - ..metadata = (Metadata() - ..ttr = -1 - ..ttl = 10000), - '$localPort $port $username $host $sessionId', - sessionId: sessionId); - - bool acked = await waitForDaemonResponse(); - await cleanUpAfterReverseSsh(this); - if (!acked) { - return SSHNPFailed( - 'sshnp timed out: waiting for daemon response\nhint: make sure the device is online'); - } - - if (sshnpdAckErrors) { - return SSHNPFailed('sshnp failed: with sshnpd acknowledgement errors'); - } - - return SSHNPSuccess.base( - localPort: localPort, - remoteUsername: remoteUsername, - host: 'localhost', - privateKeyFileName: publicKeyFileName.replaceAll('.pub', ''), - localSshOptions: (addForwardsToTunnel) ? null : localSshOptions, - sshrvResult: sshrvResult, - ); - } - - /// Function which the response subscription (created in the [init] method - /// will call when it gets a response from the sshnpd - @visibleForTesting - handleSshnpdResponses(notification) async { - String notificationKey = notification.key - .replaceAll('${notification.to}:', '') - .replaceAll('.$device.sshnp${notification.from}', '') - // convert to lower case as the latest AtClient converts notification - // keys to lower case when received - .toLowerCase(); - logger.info('Received $notificationKey notification'); - - bool connected = false; - - if (notification.value == 'connected') { - connected = true; - } else if (notification.value.startsWith('{')) { - late final Map envelope; - late final Map daemonResponse; - try { - envelope = jsonDecode(notification.value!); - assertValidValue(envelope, 'signature', String); - assertValidValue(envelope, 'hashingAlgo', String); - assertValidValue(envelope, 'signingAlgo', String); - - daemonResponse = envelope['payload'] as Map; - assertValidValue(daemonResponse, 'sessionId', String); - assertValidValue(daemonResponse, 'ephemeralPrivateKey', String); - } catch (e) { - logger.warning( - 'Failed to extract parameters from notification value "${notification.value}" with error : $e'); - sshnpdAck = true; - sshnpdAckErrors = true; - return; - } - - try { - await verifyEnvelopeSignature(atClient, sshnpdAtSign, logger, envelope); - } catch (e) { - logger.shout('Failed to verify signature of msg from $sshnpdAtSign'); - logger.shout('Exception: $e'); - logger.shout('Notification value: ${notification.value}'); - sshnpdAck = true; - sshnpdAckErrors = true; - return; - } - - ephemeralPrivateKey = daemonResponse['ephemeralPrivateKey']; - connected = true; - } - - if (connected) { - logger.info('Session $sessionId connected successfully'); - sshnpdAck = true; - } else { - stderr.writeln('Remote sshnpd error: ${notification.value}'); - sshnpdAck = true; - sshnpdAckErrors = true; - } - } - - /// Look up the user name ... we expect a key to have been shared with us by - /// sshnpd. Let's say we are @human running sshnp, and @daemon is running - /// sshnpd, then we expect a key to have been shared whose ID is - /// @human:username.device.sshnp@daemon - /// Is not called if remoteUserName was set via constructor - Future fetchRemoteUserName() async { - AtKey userNameRecordID = - AtKey.fromString('$clientAtSign:username.$namespace$sshnpdAtSign'); - try { - remoteUsername = (await atClient.get(userNameRecordID)).value as String; - } catch (e, s) { - stderr.writeln("Device \"$device\" unknown, or username not shared"); - await cleanUpAfterReverseSsh(this); - throw SSHNPFailed( - "Device unknown, or username not shared\n" - "hint: make sure the device shares username or set remote username manually", - e, - s); - } - } - - Future sharePublicKeyWithSshnpdIfRequired() async { - if (publicKeyFileName.isEmpty) return; - - try { - String toSshPublicKey = await File(publicKeyFileName).readAsString(); - if (!toSshPublicKey.startsWith('ssh-')) { - throw ('$publicKeyFileName does not look like a public key file'); - } - AtKey sendOurPublicKeyToSshnpd = AtKey() - ..key = 'sshpublickey' - ..sharedBy = clientAtSign - ..sharedWith = sshnpdAtSign - ..metadata = (Metadata() - ..ttr = -1 - ..ttl = 10000); - await _notify(sendOurPublicKeyToSshnpd, toSshPublicKey); - } catch (e, s) { - stderr.writeln( - "Error opening or validating public key file or sending to remote atSign: $e"); - await cleanUpAfterReverseSsh(this); - throw SSHNPFailed( - 'Error opening or validating public key file or sending to remote atSign', - e, - s); - } - } - - Future sharePrivateKeyWithSshnpd() async { - AtKey sendOurPrivateKeyToSshnpd = AtKey() - ..key = 'privatekey' - ..sharedBy = clientAtSign - ..sharedWith = sshnpdAtSign - ..namespace = namespace - ..metadata = (Metadata() - ..ttr = -1 - ..ttl = 10000); - await _notify(sendOurPrivateKeyToSshnpd, sshPrivateKey); - } - - Future getHostAndPortFromSshrvd() async { - atClient.notificationService - .subscribe( - regex: '$sessionId.${SSHRVD.namespace}@', shouldDecrypt: true) - .listen((notification) async { - String ipPorts = notification.value.toString(); - List results = ipPorts.split(','); - host = results[0]; - port = int.parse(results[1]); - _sshrvdPort = int.parse(results[2]); - sshrvdAck = true; - }); - - AtKey ourSshrvdIdKey = AtKey() - ..key = '$device.${SSHRVD.namespace}' - ..sharedBy = clientAtSign // shared by us - ..sharedWith = host // shared with the sshrvd host - ..metadata = (Metadata() - // as we are sending a notification to the sshrvd namespace, - // we don't want to append our namespace - ..namespaceAware = false - ..ttr = -1 - ..ttl = 10000); - await _notify(ourSshrvdIdKey, sessionId); - - int counter = 0; - while (!sshrvdAck) { - await Future.delayed(Duration(milliseconds: 100)); - counter++; - if (counter == 100) { - await cleanUpAfterReverseSsh(this); - stderr.writeln('sshnp: connection timeout to sshrvd $host service'); - throw ('Connection timeout to sshrvd $host service\nhint: make sure host is valid and online'); - } - } - } - - Future> _getAtKeysRemote( - {String? regex, - String? sharedBy, - String? sharedWith, - bool showHiddenKeys = false}) async { - var builder = ScanVerbBuilder() - ..sharedWith = sharedWith - ..sharedBy = sharedBy - ..regex = regex - ..showHiddenKeys = showHiddenKeys - ..auth = true; - var scanResult = await atClient.getRemoteSecondary()?.executeVerb(builder); - scanResult = scanResult?.replaceFirst('data:', '') ?? ''; - var result = []; - if (scanResult.isNotEmpty) { - result = List.from(jsonDecode(scanResult)).map((key) { - try { - return AtKey.fromString(key); - } on InvalidSyntaxException { - logger.severe('$key is not a well-formed key'); - } on Exception catch (e) { - logger.severe( - 'Exception occurred: ${e.toString()}. Unable to form key $key'); - } - }).toList(); - } - result.removeWhere((element) => element == null); - return result.cast(); - } - - @override - Future<(Iterable, Iterable, Map)> - listDevices() async { - // get all the keys device_info.*.sshnpd - var scanRegex = 'device_info\\.$asciiMatcher\\.${DefaultArgs.namespace}'; - - var atKeys = - await _getAtKeysRemote(regex: scanRegex, sharedBy: sshnpdAtSign); - - var devices = {}; - var heartbeats = {}; - var info = {}; - - // Listen for heartbeat notifications - atClient.notificationService - .subscribe(regex: 'heartbeat\\.$asciiMatcher', shouldDecrypt: true) - .listen((notification) { - var deviceInfo = jsonDecode(notification.value ?? '{}'); - var devicename = deviceInfo['devicename']; - if (devicename != null) { - heartbeats.add(devicename); - } - }); - - // for each key, get the value - for (var entryKey in atKeys) { - var atValue = await atClient.get( - entryKey, - getRequestOptions: GetRequestOptions()..bypassCache = true, - ); - var deviceInfo = jsonDecode(atValue.value) ?? {}; - - if (deviceInfo['devicename'] == null) { - continue; - } - - var devicename = deviceInfo['devicename'] as String; - info[devicename] = deviceInfo; - - var metaData = Metadata() - ..isPublic = false - ..isEncrypted = true - ..ttr = -1 - ..namespaceAware = true; - - var pingKey = AtKey() - ..key = "ping.$devicename" - ..sharedBy = clientAtSign - ..sharedWith = entryKey.sharedBy - ..namespace = DefaultArgs.namespace - ..metadata = metaData; - - unawaited(_notify(pingKey, 'ping')); - - // Add the device to the base list - devices.add(devicename); - } - - // wait for 10 seconds in case any are being slow - await Future.delayed(const Duration(seconds: 5)); - - // The intersection is in place on the off chance that some random device - // sends a heartbeat notification, but is not on the list of devices - return ( - devices.intersection(heartbeats), - devices.difference(heartbeats), - info, - ); - } - - /// This function sends a notification given an atKey and value - Future _notify(AtKey atKey, String value, - {String sessionId = ""}) async { - await atClient.notificationService - .notify(NotificationParams.forUpdate(atKey, value: value), - onSuccess: (notification) { - logger.info('SUCCESS:$notification for: $sessionId with value: $value'); - }, onError: (notification) { - logger.info('ERROR:$notification'); - }); - } - - Future waitForDaemonResponse() async { - int counter = 0; - // Timer to timeout after 10 Secs or after the Ack of connected/Errors - while (!sshnpdAck) { - await Future.delayed(Duration(milliseconds: 100)); - counter++; - if (counter == 100) { - return false; - } - } - return true; - } -} diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart new file mode 100644 index 000000000..92b782b44 --- /dev/null +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart @@ -0,0 +1,222 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:at_client/at_client.dart'; +import 'package:dartssh2/dartssh2.dart'; +import 'package:noports_core/src/common/utils.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart'; +import 'package:noports_core/sshnp.dart'; + +class SSHNPForwardDartImpl extends SSHNPImpl with SSHNPForwardDirection { + SSHNPForwardDartImpl({ + required AtClient atClient, + required SSHNPParams params, + }) : super(atClient: atClient, params: params); + + @override + Future run() async { + logger.info( + 'Requesting daemon to set up socket tunnel for direct ssh session'); +// send request to the daemon via notification + await notify( + AtKey() + ..key = 'ssh_request' + ..namespace = namespace + ..sharedBy = clientAtSign + ..sharedWith = sshnpdAtSign + ..metadata = (Metadata() + ..ttr = -1 + ..ttl = 10000), + signAndWrapAndJsonEncode(atClient, { + 'direct': true, + 'sessionId': sessionId, + 'host': host, + 'port': port + }), + sessionId: sessionId); + + bool acked = await waitForDaemonResponse(); + if (!acked) { + var error = SSHNPError( + 'sshnp timed out: waiting for daemon response\nhint: make sure the device is online'); + doneCompleter.completeError(error); + return error; + } + + if (sshnpdAckErrors) { + var error = + SSHNPError('sshnp failed: with sshnpd acknowledgement errors'); + doneCompleter.completeError(error); + return error; + } + // 1) Execute an ssh command setting up local port forwarding. + // Note that this is very similar to what the daemon does when we + // ask for a reverse ssh + logger.info( + 'Starting direct ssh session for ${params.username} to $host on port $sshrvdPort with forwardLocal of $localPort'); + + try { + late final SSHClient client; + + late final SSHSocket socket; + try { + socket = await SSHSocket.connect(host, sshrvdPort); + } catch (e, s) { + var error = SSHNPError( + 'Failed to open socket to $host:$port : $e', + error: e, + stackTrace: s, + ); + doneCompleter.completeError(error); + return error; + } + + try { + client = SSHClient( + socket, + username: remoteUsername, + identities: [ + // A single private key file may contain multiple keys. + ...SSHKeyPair.fromPem(ephemeralPrivateKey) + ], + keepAliveInterval: Duration(seconds: 15), + ); + } catch (e, s) { + var error = SSHNPError( + 'Failed to create SSHClient for ${params.username}@$host:$port : $e', + error: e, + stackTrace: s, + ); + doneCompleter.completeError(error); + return error; + } + + try { + await client.authenticated; + } catch (e, s) { + var error = SSHNPError( + 'Failed to authenticate as ${params.username}@$host:$port : $e', + error: e, + stackTrace: s, + ); + doneCompleter.completeError(error); + return error; + } + + int counter = 0; + + Future startForwarding( + {required int fLocalPort, + required String fRemoteHost, + required int fRemotePort}) async { + logger.info('Starting port forwarding' + ' from port $fLocalPort on localhost' + ' to $fRemoteHost:$fRemotePort on remote side'); + + /// Do the port forwarding for sshd + final serverSocket = await ServerSocket.bind('localhost', fLocalPort); + + serverSocket.listen((socket) async { + counter++; + final forward = await client.forwardLocal(fRemoteHost, fRemotePort); + unawaited( + forward.stream.cast>().pipe(socket).whenComplete( + () async { + counter--; + }, + ), + ); + unawaited(socket.pipe(forward.sink)); + }, onError: (Object error) { + counter = 0; + }, onDone: () { + counter = 0; + }); + } + + // Start local forwarding to the remote sshd + await startForwarding( + fLocalPort: localPort, + fRemoteHost: 'localhost', + fRemotePort: params.remoteSshdPort); + + if (params.addForwardsToTunnel) { + var optionsSplitBySpace = params.localSshOptions.join(' ').split(' '); + logger.info('addForwardsToTunnel is true;' + ' localSshOptions split by space is $optionsSplitBySpace'); + // parse the localSshOptions, extract all of the local port forwarding + // directives and act on all of them + var lsoIter = optionsSplitBySpace.iterator; + while (lsoIter.moveNext()) { + if (lsoIter.current == '-L') { + // we expect the args next + bool hasArgs = lsoIter.moveNext(); + if (!hasArgs) { + logger.warning('localSshOptions has -L with no args'); + continue; + } + String argString = lsoIter.current; + // We expect args like $localPort:$remoteHost:$remotePort + List args = argString.split(':'); + if (args.length != 3) { + logger.warning('localSshOptions has -L with bad args $argString'); + continue; + } + int? fLocalPort = int.tryParse(args[0]); + String fRemoteHost = args[1]; + int? fRemotePort = int.tryParse(args[2]); + if (fLocalPort == null || + fRemoteHost.isEmpty || + fRemotePort == null) { + logger.warning('localSshOptions has -L with bad args $argString'); + continue; + } + + // Start the forwarding + await startForwarding( + fLocalPort: fLocalPort, + fRemoteHost: fRemoteHost, + fRemotePort: fRemotePort); + } + } + } + + /// Set up timer to check to see if all connections are down + logger + .info('ssh session will terminate after ${params.idleTimeout} seconds' + ' if it is not being used'); + Timer.periodic(Duration(seconds: params.idleTimeout), (timer) async { + if (counter == 0) { + timer.cancel(); + client.close(); + await client.done; + doneCompleter.complete(); + logger.shout('$sessionId | no active connections' + ' - ssh session complete'); + } + }); + + // All good - write the ssh command to stdout + return SSHNPSuccess.base( + localPort: localPort, + remoteUsername: remoteUsername, + host: 'localhost', + privateKeyFileName: publicKeyFileName.replaceAll('.pub', ''), + localSshOptions: + (params.addForwardsToTunnel) ? null : params.localSshOptions, + sshClient: client, + ); + } on SSHNPError catch (e, s) { + doneCompleter.completeError(e, s); + return e; + } catch (e, s) { + doneCompleter.completeError(e, s); + return SSHNPError( + 'SSH Client failure : $e', + error: e, + stackTrace: s, + ); + } + } +} diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart new file mode 100644 index 000000000..f77740456 --- /dev/null +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart @@ -0,0 +1,154 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:at_client/at_client.dart' hide StringBuffer; +import 'package:noports_core/src/common/utils.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart'; +import 'package:noports_core/sshnp.dart'; +import 'package:path/path.dart' as path; + +class SSHNPForwardExecImpl extends SSHNPImpl with SSHNPForwardDirection { + SSHNPForwardExecImpl({ + required AtClient atClient, + required SSHNPParams params, + }) : super(atClient: atClient, params: params); + + @override + Future run() async { + logger.info( + 'Requesting daemon to set up socket tunnel for direct ssh session'); + // send request to the daemon via notification + await notify( + AtKey() + ..key = 'ssh_request' + ..namespace = namespace + ..sharedBy = clientAtSign + ..sharedWith = sshnpdAtSign + ..metadata = (Metadata() + ..ttr = -1 + ..ttl = 10000), + signAndWrapAndJsonEncode(atClient, { + 'direct': true, + 'sessionId': sessionId, + 'host': host, + 'port': port + }), + sessionId: sessionId); + + bool acked = await waitForDaemonResponse(); + if (!acked) { + return SSHNPError( + 'sshnp timed out: waiting for daemon response\nhint: make sure the device is online'); + } + + if (sshnpdAckErrors) { + return SSHNPError('sshnp failed: with sshnpd acknowledgement errors'); + } + // 1) Execute an ssh command setting up local port forwarding. + // Note that this is very similar to what the daemon does when we + // ask for a reverse ssh + logger.info( + 'Starting direct ssh session for ${params.username} to $host on port $sshrvdPort with forwardLocal of $localPort'); + + try { + String? errorMessage; + Process? process; + + // If using exec then we can assume we're on something unix-y + // So we can write the ephemeralPrivateKey to a tmp file, + // set its permissions appropriately, and remove it after we've + // executed the command + var tmpFileName = + path.normalize('$sshHomeDirectory/tmp/ephemeral_$sessionId'); + File tmpFile = File(tmpFileName); + await tmpFile.create(recursive: true); + await tmpFile.writeAsString(ephemeralPrivateKey, + mode: FileMode.write, flush: true); + await Process.run('chmod', ['go-rwx', tmpFileName]); + + String argsString = '$remoteUsername@$host' + ' -p $sshrvdPort' + ' -i $tmpFileName' + ' -L $localPort:localhost:${params.remoteSshdPort}' + ' -o LogLevel=VERBOSE' + ' -t -t' + ' -o StrictHostKeyChecking=accept-new' + ' -o IdentitiesOnly=yes' + ' -o BatchMode=yes' + ' -o ExitOnForwardFailure=yes' + ' -f' // fork after authentication - this is important + ; + if (params.addForwardsToTunnel) { + argsString += ' ${params.localSshOptions.join(' ')}'; + } + argsString += ' sleep 15'; + + List args = argsString.split(' '); + + logger.info('$sessionId | Executing /usr/bin/ssh ${args.join(' ')}'); + + // Because of the options we are using, we can wait for this process + // to complete, because it will exit with exitCode 0 once it has connected + // successfully + late int sshExitCode; + final soutBuf = StringBuffer(); + final serrBuf = StringBuffer(); + try { + process = await Process.start('/usr/bin/ssh', args); + process.stdout.transform(Utf8Decoder()).listen((String s) { + soutBuf.write(s); + logger.info('$sessionId | sshStdOut | $s'); + }, onError: (e) {}); + process.stderr.transform(Utf8Decoder()).listen((String s) { + serrBuf.write(s); + logger.info('$sessionId | sshStdErr | $s'); + }, onError: (e) {}); + sshExitCode = await process.exitCode.timeout(Duration(seconds: 10)); + // ignore: unused_catch_clause + } on TimeoutException catch (e) { + sshExitCode = 6464; + } + + await tmpFile.delete(); + + if (sshExitCode != 0) { + if (sshExitCode == 6464) { + logger.shout( + '$sessionId | Command timed out: /usr/bin/ssh ${args.join(' ')}'); + errorMessage = 'Failed to establish connection - timed out'; + } else { + logger.shout('$sessionId | Exit code $sshExitCode from' + ' /usr/bin/ssh ${args.join(' ')}'); + errorMessage = + 'Failed to establish connection - exit code $sshExitCode'; + } + throw SSHNPError(errorMessage); + } + + doneCompleter.complete(); + + // All good - write the ssh command to stdout + return SSHNPSuccess.base( + localPort: localPort, + remoteUsername: remoteUsername, + host: 'localhost', + privateKeyFileName: publicKeyFileName.replaceAll('.pub', ''), + localSshOptions: + (params.addForwardsToTunnel) ? null : params.localSshOptions, + sshProcess: process, + ); + } on SSHNPError catch (e) { + doneCompleter.completeError(e, e.stackTrace); + return e; + } catch (e, s) { + doneCompleter.completeError(e, s); + return SSHNPError( + 'SSH Client failure : $e', + error: e, + stackTrace: s, + ); + } + } +} diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart new file mode 100644 index 000000000..d14ad5846 --- /dev/null +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart @@ -0,0 +1,489 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:at_client/at_client.dart'; +import 'package:at_commons/at_builders.dart'; +import 'package:at_utils/at_logger.dart'; +import 'package:logging/logging.dart'; +import 'package:meta/meta.dart'; +import 'package:noports_core/sshnp.dart'; +import 'package:noports_core/sshrv.dart'; +import 'package:noports_core/sshrvd.dart'; +import 'package:noports_core/utils.dart'; +import 'package:uuid/uuid.dart'; + +// If you've never seen an abstract implementation before, here it is :P +@protected +abstract class SSHNPImpl implements SSHNP { + final AtSignLogger logger = AtSignLogger(' sshnp '); + + // ==================================================================== + // Final instance variables, injected via constructor + // ==================================================================== + + @override + final AtClient atClient; + @override + final SSHNPParams params; + + final String sessionId; + + /// Function used to generate a [SSHRV] instance ([SSHRV.localbinary] by default) + final SSHRVGenerator sshrvGenerator; + + final String sshHomeDirectory; + + // ==================================================================== + // Final instance variables, derived during initialization + // ==================================================================== + + late final String remoteUsername; + + late final String publicKeyFileName; + + // ==================================================================== + // Volatile instance variables, injected via constructor + // but possibly modified later on + // ==================================================================== + + /// Host that we will send to sshnpd for it to connect to, + /// or the atSign of the sshrvd. + /// If using sshrvd then we will fetch the _actual_ host to use from sshrvd. + String host; + + /// Port that we will send to sshnpd for it to connect to. + /// Required if we are not using sshrvd. + /// If using sshrvd then initial port value will be ignored and instead we + /// will fetch the port from sshrvd. + int port; + + /// Port to which sshnpd will forwardRemote its [SSHClient]. If localPort + /// is set to '0' then + int localPort; + + /// When using sshrvd, this is fetched from sshrvd during [init] + /// This is only set when using sshrvd + /// (i.e. after [getHostAndPortFromSshrvd] has been called) + late int sshrvdPort; + + // ==================================================================== + // Status indicators (Available in the public API) + // ==================================================================== + + @protected + final Completer doneCompleter = Completer(); + @override + Future get done => doneCompleter.future; + + bool _initializeStarted = false; + @protected + bool get initializeStarted => _initializeStarted; + @protected + final Completer initializedCompleter = Completer(); + @override + Future get initialized => initializedCompleter.future; + + // ==================================================================== + // Internal state variables + // ==================================================================== + + /// true once we have received any response (success or error) from sshnpd + @visibleForTesting + bool sshnpdAck = false; + + /// true once we have received an error response from sshnpd + @protected + bool sshnpdAckErrors = false; + + /// true once we have received a response from sshrvd + @visibleForTesting + bool sshrvdAck = false; + + @protected + late String ephemeralPrivateKey; + + // ==================================================================== + // Getters for derived values + // ==================================================================== + + String get clientAtSign => atClient.getCurrentAtSign()!; + String get sshnpdAtSign => params.sshnpdAtSign; + + static String getNamespace(String device) => '$device.sshnp'; + String get namespace => getNamespace(params.device); + + // ==================================================================== + // Constructor and Initialization + // ==================================================================== + + SSHNPImpl({ + required this.atClient, + required this.params, + SSHRVGenerator? sshrvGenerator, + bool shouldInitialize = true, + }) : sessionId = Uuid().v4(), + host = params.host, + port = params.port, + localPort = params.localPort, + sshrvGenerator = sshrvGenerator ?? DefaultArgs.sshrvGenerator, + sshHomeDirectory = getDefaultSshDirectory(params.homeDirectory) { + /// Set the logger level to shout + logger.hierarchicalLoggingEnabled = true; + logger.logger.level = Level.SHOUT; + + /// Set the namespace to the device's namespace + AtClientPreference preference = + atClient.getPreferences() ?? AtClientPreference(); + preference.namespace = '${params.device}.sshnp'; + atClient.setPreferences(preference); + + /// Also call init + if (shouldInitialize) init(); + } + + @override + Future init() async { + if (_initializeStarted) { + return; + } else { + _initializeStarted = true; + } + + try { + if (!(await atSignIsActivated(atClient, sshnpdAtSign))) { + throw ('Device address $sshnpdAtSign is not activated.'); + } + } catch (e, s) { + throw SSHNPError(e, stackTrace: s); + } + + // Start listening for response notifications from sshnpd + logger.info('Subscribing to notifications on $sessionId.$namespace@'); + atClient.notificationService + .subscribe(regex: '$sessionId.$namespace@', shouldDecrypt: true) + .listen(handleSshnpdResponses); + + // Check that the public key file exists + if (publicKeyFileName.isNotEmpty && !File(publicKeyFileName).existsSync()) { + throw ('Unable to find ssh public key file : $publicKeyFileName'); + } + + // Check that the private key file exists + if (publicKeyFileName.isNotEmpty && + !File(publicKeyFileName.replaceAll('.pub', '')).existsSync()) { + throw ('Unable to find matching ssh private key for public key : $publicKeyFileName'); + } + + remoteUsername = params.remoteUsername ?? await fetchRemoteUserName(); + + // find a spare local port + if (localPort == 0) { + try { + ServerSocket serverSocket = + await ServerSocket.bind(InternetAddress.loopbackIPv4, 0); + localPort = serverSocket.port; + await serverSocket.close(); + } catch (e, s) { + throw SSHNPError('Unable to find a spare local port', + error: e, stackTrace: s); + } + } + + await sharePublicKeyWithSshnpdIfRequired(); + + // If host has an @ then contact the sshrvd service for some ports + if (host.startsWith('@')) { + await getHostAndPortFromSshrvd(); + } + + // N.B. Don't complete initialization here, subclasses will do that + // This is in case they need to implement further initialization steps + } + + @visibleForTesting + void handleSshnpdResponses(AtNotification notification) async { + String notificationKey = notification.key + .replaceAll('${notification.to}:', '') + .replaceAll('.$namespace${notification.from}', '') + // convert to lower case as the latest AtClient converts notification + // keys to lower case when received + .toLowerCase(); + logger.info('Received $notificationKey notification'); + + bool connected = false; + + if (notification.value == 'connected') { + connected = true; + } else if (notification.value?.startsWith('{') ?? false) { + late final Map envelope; + late final Map daemonResponse; + try { + envelope = jsonDecode(notification.value!); + assertValidValue(envelope, 'signature', String); + assertValidValue(envelope, 'hashingAlgo', String); + assertValidValue(envelope, 'signingAlgo', String); + + daemonResponse = envelope['payload'] as Map; + assertValidValue(daemonResponse, 'sessionId', String); + assertValidValue(daemonResponse, 'ephemeralPrivateKey', String); + } catch (e) { + logger.warning( + 'Failed to extract parameters from notification value "${notification.value}" with error : $e'); + sshnpdAck = true; + sshnpdAckErrors = true; + return; + } + + try { + await verifyEnvelopeSignature(atClient, sshnpdAtSign, logger, envelope); + } catch (e) { + logger.shout('Failed to verify signature of msg from $sshnpdAtSign'); + logger.shout('Exception: $e'); + logger.shout('Notification value: ${notification.value}'); + sshnpdAck = true; + sshnpdAckErrors = true; + return; + } + + ephemeralPrivateKey = daemonResponse['ephemeralPrivateKey']; + connected = true; + } + + if (connected) { + logger.info('Session $sessionId connected successfully'); + sshnpdAck = true; + } else { + stderr.writeln('Remote sshnpd error: ${notification.value}'); + sshnpdAck = true; + sshnpdAckErrors = true; + } + } + + // ==================================================================== + // Internal methods + // ==================================================================== + + @protected + Future notify(AtKey atKey, String value, + {String sessionId = ""}) async { + await atClient.notificationService + .notify(NotificationParams.forUpdate(atKey, value: value), + onSuccess: (notification) { + logger.info('SUCCESS:$notification for: $sessionId with value: $value'); + }, onError: (notification) { + logger.info('ERROR:$notification'); + }); + } + + /// Look up the user name ... we expect a key to have been shared with us by + /// sshnpd. Let's say we are @human running sshnp, and @daemon is running + /// sshnpd, then we expect a key to have been shared whose ID is + /// @human:username.device.sshnp@daemon + /// Is not called if remoteUserName was set via constructor + @protected + Future fetchRemoteUserName() async { + AtKey userNameRecordID = + AtKey.fromString('$clientAtSign:username.$namespace$sshnpdAtSign'); + try { + return (await atClient.get(userNameRecordID)).value as String; + } catch (e, s) { + await cleanUp(); + throw SSHNPError( + "Device unknown, or username not shared\n" + "hint: make sure the device shares username or set remote username manually", + error: e, + stackTrace: s, + ); + } + } + + @protected + Future getHostAndPortFromSshrvd() async { + atClient.notificationService + .subscribe( + regex: '$sessionId.${SSHRVD.namespace}@', shouldDecrypt: true) + .listen((notification) async { + String ipPorts = notification.value.toString(); + List results = ipPorts.split(','); + host = results[0]; + port = int.parse(results[1]); + sshrvdPort = int.parse(results[2]); + sshrvdAck = true; + }); + + AtKey ourSshrvdIdKey = AtKey() + ..key = '${params.device}.${SSHRVD.namespace}' + ..sharedBy = clientAtSign // shared by us + ..sharedWith = host // shared with the sshrvd host + ..metadata = (Metadata() + // as we are sending a notification to the sshrvd namespace, + // we don't want to append our namespace + ..namespaceAware = false + ..ttr = -1 + ..ttl = 10000); + await notify(ourSshrvdIdKey, sessionId); + + int counter = 0; + while (!sshrvdAck) { + await Future.delayed(Duration(milliseconds: 100)); + counter++; + if (counter == 100) { + await cleanUp(); + stderr.writeln('sshnp: connection timeout to sshrvd $host service'); + throw ('Connection timeout to sshrvd $host service\nhint: make sure host is valid and online'); + } + } + } + + Future sharePublicKeyWithSshnpdIfRequired() async { + if (publicKeyFileName.isEmpty) return; + + try { + String toSshPublicKey = await File(publicKeyFileName).readAsString(); + if (!toSshPublicKey.startsWith('ssh-')) { + throw ('$publicKeyFileName does not look like a public key file'); + } + AtKey sendOurPublicKeyToSshnpd = AtKey() + ..key = 'sshpublickey' + ..sharedBy = clientAtSign + ..sharedWith = sshnpdAtSign + ..metadata = (Metadata() + ..ttr = -1 + ..ttl = 10000); + await notify(sendOurPublicKeyToSshnpd, toSshPublicKey); + } catch (e, s) { + stderr.writeln( + "Error opening or validating public key file or sending to remote atSign: $e"); + await cleanUpAfterReverseSsh(this); + throw SSHNPError( + 'Error opening or validating public key file or sending to remote atSign', + error: e, + stackTrace: s, + ); + } + } + + @protected + Future waitForDaemonResponse() async { + int counter = 0; + // Timer to timeout after 10 Secs or after the Ack of connected/Errors + while (!sshnpdAck) { + await Future.delayed(Duration(milliseconds: 100)); + counter++; + if (counter == 100) { + return false; + } + } + return true; + } + + Future> _getAtKeysRemote( + {String? regex, + String? sharedBy, + String? sharedWith, + bool showHiddenKeys = false}) async { + var builder = ScanVerbBuilder() + ..sharedWith = sharedWith + ..sharedBy = sharedBy + ..regex = regex + ..showHiddenKeys = showHiddenKeys + ..auth = true; + var scanResult = await atClient.getRemoteSecondary()?.executeVerb(builder); + scanResult = scanResult?.replaceFirst('data:', '') ?? ''; + var result = []; + if (scanResult.isNotEmpty) { + result = List.from(jsonDecode(scanResult)).map((key) { + try { + return AtKey.fromString(key); + } on InvalidSyntaxException { + logger.severe('$key is not a well-formed key'); + } on Exception catch (e) { + logger.severe( + 'Exception occurred: ${e.toString()}. Unable to form key $key'); + } + }).toList(); + } + result.removeWhere((element) => element == null); + return result.cast(); + } + + // ==================================================================== + // Public API + // ==================================================================== + + @override + Future<(Iterable, Iterable, Map)> + listDevices() async { + // get all the keys device_info.*.sshnpd + var scanRegex = 'device_info\\.$asciiMatcher\\.${DefaultArgs.namespace}'; + + var atKeys = + await _getAtKeysRemote(regex: scanRegex, sharedBy: sshnpdAtSign); + + var devices = {}; + var heartbeats = {}; + var info = {}; + + // Listen for heartbeat notifications + atClient.notificationService + .subscribe(regex: 'heartbeat\\.$asciiMatcher', shouldDecrypt: true) + .listen((notification) { + var deviceInfo = jsonDecode(notification.value ?? '{}'); + var devicename = deviceInfo['devicename']; + if (devicename != null) { + heartbeats.add(devicename); + } + }); + + // for each key, get the value + for (var entryKey in atKeys) { + var atValue = await atClient.get( + entryKey, + getRequestOptions: GetRequestOptions()..bypassCache = true, + ); + var deviceInfo = jsonDecode(atValue.value) ?? {}; + + if (deviceInfo['devicename'] == null) { + continue; + } + + var devicename = deviceInfo['devicename'] as String; + info[devicename] = deviceInfo; + + var metaData = Metadata() + ..isPublic = false + ..isEncrypted = true + ..ttr = -1 + ..namespaceAware = true; + + var pingKey = AtKey() + ..key = "ping.$devicename" + ..sharedBy = clientAtSign + ..sharedWith = entryKey.sharedBy + ..namespace = DefaultArgs.namespace + ..metadata = metaData; + + unawaited(notify(pingKey, 'ping')); + + // Add the device to the base list + devices.add(devicename); + } + + // wait for 10 seconds in case any are being slow + await Future.delayed(const Duration(seconds: 5)); + + // The intersection is in place on the off chance that some random device + // sends a heartbeat notification, but is not on the list of devices + return ( + devices.intersection(heartbeats), + devices.difference(heartbeats), + info, + ); + } + + @override + FutureOr cleanUp() { + // This is an intentional no-op to allow overrides to safely call super.cleanUp() + } +} diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart new file mode 100644 index 000000000..ccfc2b95f --- /dev/null +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart @@ -0,0 +1,61 @@ +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl.dart'; +import 'package:noports_core/src/sshnp/sshnp_result.dart'; +import 'package:noports_core/utils.dart'; + +mixin SSHNPReverseDirection on SSHNPImpl { + /// Set by [generateSshKeys] during [init], if we're not doing direct ssh. + /// sshnp generates a new keypair for each ssh session, using ed25519 by + /// default but rsa if the [rsa] flag is set to true. sshnp will write + /// [sshPublicKey] to ~/.ssh/authorized_keys + late final String sshPublicKey; + + /// Set by [generateSshKeys] during [init]. + /// sshnp generates a new keypair for each ssh session, using ed25519 by + /// default but rsa if the [rsa] flag is set to true. sshnp will send the + /// [sshPrivateKey] to sshnpd + late final String sshPrivateKey; + + @override + Future init() async { + await super.init(); + try { + var (String ephemeralPublicKey, String ephemeralPrivateKey) = + await generateSshKeys( + rsa: params.rsa, + sessionId: sessionId, + sshHomeDirectory: sshHomeDirectory, + ); + sshPublicKey = ephemeralPublicKey; + sshPrivateKey = ephemeralPrivateKey; + } catch (e, s) { + throw SSHNPError( + 'Failed to generate ephemeral keypair', + error: e, + stackTrace: s, + ); + } + + try { + await addEphemeralKeyToAuthorizedKeys( + sshPublicKey: sshPublicKey, + localSshdPort: params.localSshdPort, + sessionId: sessionId); + } catch (e, s) { + throw SSHNPError( + 'Failed to add ephemeral key to authorized_keys', + error: e, + stackTrace: s, + ); + } + + initializedCompleter.complete(); + } + + @override + Future cleanUp() async { + await cleanUpAfterReverseSsh(this); + super.cleanUp(); + } +} + +mixin SSHNPForwardDirection on SSHNPImpl {} diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart new file mode 100644 index 000000000..27b8cab9f --- /dev/null +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart @@ -0,0 +1,95 @@ +import 'dart:async'; + +import 'package:at_client/at_client.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart'; +import 'package:noports_core/sshnp.dart'; +import 'package:noports_core/sshrv.dart'; + +class SSHNPLegacyImpl extends SSHNPImpl with SSHNPReverseDirection { + SSHNPLegacyImpl({ + required AtClient atClient, + required SSHNPParams params, + SSHRVGenerator? sshrvGenerator, + }) : super(atClient: atClient, params: params, sshrvGenerator: sshrvGenerator); + + @override + Future init() async { + await super.init(); + + // Share our private key with sshnpd + AtKey sendOurPrivateKeyToSshnpd = AtKey() + ..key = 'privatekey' + ..sharedBy = clientAtSign + ..sharedWith = sshnpdAtSign + ..namespace = namespace + ..metadata = (Metadata() + ..ttr = -1 + ..ttl = 10000); + await notify(sendOurPrivateKeyToSshnpd, sshPrivateKey); + } + + @override + Future run() async { + logger.info('Requesting legacy daemon to start reverse ssh session'); + + if (!initializedCompleter.isCompleted) { + // Call init in case it hasn't been called yet + unawaited(init()); + // Wait for init to complete + // N.B. must be called this way in case the init call above is not the first init call + await initialized; + } + + // Connect to rendezvous point using background process. + // sshnp (this program) can then exit without issue. + SSHRV sshrv = + sshrvGenerator(host, sshrvdPort, localSshdPort: params.localSshdPort); + Future sshrvResult = sshrv.run(); + + // send request to the daemon via notification + await notify( + AtKey() + ..key = 'sshd' + ..namespace = namespace + ..sharedBy = clientAtSign + ..sharedWith = sshnpdAtSign + ..metadata = (Metadata() + ..ttr = -1 + ..ttl = 10000), + '$localPort $port ${params.username} $host $sessionId', + sessionId: sessionId, + ); + + bool acked = await waitForDaemonResponse(); + await cleanUp(); + if (!acked) { + var error = SSHNPError( + 'sshnp timed out: waiting for daemon response\nhint: make sure the device is online', + stackTrace: StackTrace.current, + ); + doneCompleter.completeError(error); + return error; + } + + if (sshnpdAckErrors) { + var error = SSHNPError( + 'sshnp failed: with sshnpd acknowledgement errors', + stackTrace: StackTrace.current, + ); + doneCompleter.completeError(error); + return error; + } + + doneCompleter.complete(); + return SSHNPSuccess.base( + localPort: localPort, + remoteUsername: remoteUsername, + host: 'localhost', + privateKeyFileName: publicKeyFileName.replaceAll('.pub', ''), + localSshOptions: + (params.addForwardsToTunnel) ? null : params.localSshOptions, + sshrvResult: sshrvResult, + ); + } +} diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart new file mode 100644 index 000000000..8978d1f26 --- /dev/null +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart @@ -0,0 +1,73 @@ +import 'package:at_client/at_client.dart'; +import 'package:noports_core/src/common/utils.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart'; +import 'package:noports_core/sshnp.dart'; +import 'package:noports_core/sshrv.dart'; + +class SSHNPReverseImpl extends SSHNPImpl with SSHNPReverseDirection { + SSHNPReverseImpl({ + required AtClient atClient, + required SSHNPParams params, + SSHRVGenerator? sshrvGenerator, + }) : super(atClient: atClient, params: params, sshrvGenerator: sshrvGenerator); + + @override + Future run() async { + logger.info('Requesting daemon to start reverse ssh session'); + + // Connect to rendezvous point using background process. + // sshnp (this program) can then exit without issue. + SSHRV sshrv = + sshrvGenerator(host, sshrvdPort, localSshdPort: params.localSshdPort); + Future sshrvResult = sshrv.run(); + + // send request to the daemon via notification + await notify( + AtKey() + ..key = 'ssh_request' + ..namespace = namespace + ..sharedBy = clientAtSign + ..sharedWith = sshnpdAtSign + ..metadata = (Metadata() + ..ttr = -1 + ..ttl = 10000), + signAndWrapAndJsonEncode(atClient, { + 'direct': false, + 'sessionId': sessionId, + 'host': host, + 'port': port, + 'username': params.username, + 'remoteForwardPort': localPort, + 'privateKey': sshPrivateKey + }), + sessionId: sessionId); + + bool acked = await waitForDaemonResponse(); + await cleanUp(); + if (!acked) { + var error = + SSHNPError('sshnp connection timeout: waiting for daemon response'); + doneCompleter.completeError(error); + return error; + } + + if (sshnpdAckErrors) { + var error = + SSHNPError('sshnp failed: with sshnpd acknowledgement errors'); + doneCompleter.completeError(error); + return error; + } + + doneCompleter.complete(); + return SSHNPSuccess.base( + localPort: localPort, + remoteUsername: remoteUsername, + host: 'localhost', + privateKeyFileName: publicKeyFileName.replaceAll('.pub', ''), + localSshOptions: + (params.addForwardsToTunnel) ? null : params.localSshOptions, + sshrvResult: sshrvResult, + ); + } +} diff --git a/packages/noports_core/lib/src/sshnp/sshnp_mixins.dart b/packages/noports_core/lib/src/sshnp/sshnp_mixins.dart new file mode 100644 index 000000000..6b865bc66 --- /dev/null +++ b/packages/noports_core/lib/src/sshnp/sshnp_mixins.dart @@ -0,0 +1,12 @@ +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl.dart'; +import 'package:noports_core/sshnp.dart'; + +mixin LegacyReverseSSH on SSHNPImpl { + +} + +mixin ReverseSSH on SSHNP { + +} + +mixin DirectSSH on SSHNP {} diff --git a/packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_arg.dart b/packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_arg.dart index 043513a7b..380b1b259 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_arg.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_arg.dart @@ -183,7 +183,7 @@ class SSHNPArg { SSHNPArg( name: 'ssh-client', help: 'What to use for outbound ssh connections', - defaultsTo: SupportedSshClient.hostSsh.cliArg, + defaultsTo: SupportedSshClient.exec.cliArg, mandatory: false, format: ArgFormat.option, type: ArgType.string, diff --git a/packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_params.dart b/packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_params.dart index 8325287c8..4f41fc655 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_params.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_params.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'package:args/args.dart'; -import 'package:at_utils/at_logger.dart'; import 'package:noports_core/src/common/utils.dart'; import 'package:noports_core/src/sshnp/sshnp_params/config_file_repository.dart'; import 'package:noports_core/src/sshnp/sshnp_params/sshnp_arg.dart'; @@ -13,9 +12,9 @@ class SSHNPParams { /// Since there are multiple sources for these values, we cannot validate /// that they will be provided. If any are null, then the caller must /// handle the error. - final String? clientAtSign; - final String? sshnpdAtSign; - final String? host; + final String clientAtSign; + final String sshnpdAtSign; + final String host; /// Optional Arguments final String device; @@ -129,19 +128,15 @@ class SSHNPParams { SSHNPParams.fromPartial(SSHNPPartialParams.fromJson(json)); factory SSHNPParams.fromPartial(SSHNPPartialParams partial) { - AtSignLogger logger = AtSignLogger(' SSHNPParams '); - - /// If any required params are null log severe, but do not throw - /// The caller must handle the error if any required params are null - partial.clientAtSign ?? (logger.severe('clientAtSign is null')); - partial.sshnpdAtSign ?? (logger.severe('sshnpdAtSign is null')); - partial.host ?? (logger.severe('host is null')); + partial.clientAtSign ?? (throw ArgumentError('clientAtSign is mandatory')); + partial.sshnpdAtSign ?? (throw ArgumentError('sshnpdAtSign is mandatory')); + partial.host ?? (throw ArgumentError('host is mandatory')); return SSHNPParams( profileName: partial.profileName, - clientAtSign: partial.clientAtSign, - sshnpdAtSign: partial.sshnpdAtSign, - host: partial.host, + clientAtSign: partial.clientAtSign!, + sshnpdAtSign: partial.sshnpdAtSign!, + host: partial.host!, device: partial.device ?? DefaultSSHNPArgs.device, port: partial.port ?? DefaultSSHNPArgs.port, localPort: partial.localPort ?? DefaultSSHNPArgs.localPort, diff --git a/packages/noports_core/lib/src/sshnp/sshnp_result.dart b/packages/noports_core/lib/src/sshnp/sshnp_result.dart index 20d38bb8b..90c337bbb 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_result.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_result.dart @@ -1,5 +1,3 @@ - - import 'dart:io'; import 'package:dartssh2/dartssh2.dart'; @@ -11,16 +9,30 @@ const _optionsWithPrivateKey = [ '-o IdentitiesOnly=yes' ]; -class SSHNPFailed implements SSHNPResult { - final String message; - final Object? exception; +class SSHNPError implements SSHNPResult, Exception { + final Object message; + final Object? error; final StackTrace? stackTrace; - SSHNPFailed(this.message, [this.exception, this.stackTrace]); + SSHNPError(this.message, {this.error, this.stackTrace}); @override String toString() { - return message; + return message.toString(); + } + + String toVerboseString() { + final sb = StringBuffer(); + sb.write(message); + if (error != null) { + sb.write('\n'); + sb.write('Error: $error'); + } + if (stackTrace != null) { + sb.write('\n'); + sb.write('Stack Trace: $stackTrace'); + } + return sb.toString(); } } diff --git a/packages/noports_core/lib/src/sshnp/utils.dart b/packages/noports_core/lib/src/sshnp/utils.dart index c165040ed..f2d1617f6 100644 --- a/packages/noports_core/lib/src/sshnp/utils.dart +++ b/packages/noports_core/lib/src/sshnp/utils.dart @@ -1,15 +1,22 @@ +import 'dart:async'; import 'dart:io'; import 'package:noports_core/src/common/utils.dart'; import 'package:at_utils/at_logger.dart'; import 'package:noports_core/src/sshnp/sshnp.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart'; + +Completer wrapInCompleter(Future future) { + final completer = Completer(); + unawaited( + future.then(completer.complete).catchError(completer.completeError), + ); + return completer; +} Future cleanUpAfterReverseSsh(SSHNP sshnp) async { - if (!sshnp.initialized) { - // never got started, nothing to clean up - return; - } - if (sshnp.direct) { - // did a direct ssh, not a reverse one - nothing to clean up + if (!wrapInCompleter(sshnp.initialized).isCompleted || + sshnp is! SSHNPReverseDirection) { + // nothing to clean up return; } @@ -21,8 +28,10 @@ Future cleanUpAfterReverseSsh(SSHNP sshnp) async { sshnp.logger.info('Tidying up files'); // Delete the generated RSA keys and remove the entry from ~/.ssh/authorized_keys await deleteFile('$sshHomeDirectory/${sshnp.sessionId}_sshnp', sshnp.logger); - await deleteFile('$sshHomeDirectory/${sshnp.sessionId}_sshnp.pub', sshnp.logger); - await removeEphemeralKeyFromAuthorizedKeys(sshnp.sessionId, sshnp.logger, sshHomeDirectory: sshHomeDirectory); + await deleteFile( + '$sshHomeDirectory/${sshnp.sessionId}_sshnp.pub', sshnp.logger); + await removeEphemeralKeyFromAuthorizedKeys(sshnp.sessionId, sshnp.logger, + sshHomeDirectory: sshHomeDirectory); } Future deleteFile(String fileName, AtSignLogger logger) async { diff --git a/packages/noports_core/lib/src/sshnpd/sshnpd_impl.dart b/packages/noports_core/lib/src/sshnpd/sshnpd_impl.dart index b778700ed..d6723cf76 100644 --- a/packages/noports_core/lib/src/sshnpd/sshnpd_impl.dart +++ b/packages/noports_core/lib/src/sshnpd/sshnpd_impl.dart @@ -15,7 +15,6 @@ import 'package:noports_core/sshnpd.dart'; import 'package:noports_core/version.dart'; import 'package:uuid/uuid.dart'; - @protected class SSHNPDImpl implements SSHNPD { @override @@ -101,7 +100,7 @@ class SSHNPDImpl implements SSHNPD { AtSignLogger.root_level = 'INFO'; } - if (atClient == null && atClientGenerator == null) { + if (atClient == null && atClientGenerator == null) { throw StateError('atClient and atClientGenerator are both null'); } @@ -183,7 +182,8 @@ class SSHNPDImpl implements SSHNPD { logger.info('Subscribing to $device\\.${DefaultArgs.namespace}@'); notificationService - .subscribe(regex: '$device\\.${DefaultArgs.namespace}@', shouldDecrypt: true) + .subscribe( + regex: '$device\\.${DefaultArgs.namespace}@', shouldDecrypt: true) .listen( _notificationHandler, onError: (e) => logger.severe('Notification Failed:$e'), @@ -500,9 +500,8 @@ class SSHNPDImpl implements SSHNPD { try { // Connect to rendezvous point using background process. // This program can then exit without causing an issue. - Process rv = await SSHRV - .localBinary(host, port, localSshdPort: localSshdPort) - .run(); + Process rv = + await SSHRV.exec(host, port, localSshdPort: localSshdPort).run(); logger.info('Started rv - pid is ${rv.pid}'); /// - Generate an ephemeral keypair and adds its public key to the @@ -564,7 +563,7 @@ class SSHNPDImpl implements SSHNPD { String? errorMessage; switch (sshClient) { - case SupportedSshClient.hostSsh: + case SupportedSshClient.exec: (success, errorMessage) = await reverseSshViaExec( host: host, port: port, @@ -574,7 +573,7 @@ class SSHNPDImpl implements SSHNPD { requestingAtsign: requestingAtsign, privateKey: privateKey); break; - case SupportedSshClient.pureDart: + case SupportedSshClient.dart: (success, errorMessage) = await reverseSshViaSSHClient( host: host, port: port, diff --git a/packages/noports_core/lib/src/sshnpd/sshnpd_params.dart b/packages/noports_core/lib/src/sshnpd/sshnpd_params.dart index 4c31eff2e..0e86dae2e 100644 --- a/packages/noports_core/lib/src/sshnpd/sshnpd_params.dart +++ b/packages/noports_core/lib/src/sshnpd/sshnpd_params.dart @@ -120,7 +120,7 @@ class SSHNPDParams { parser.addOption('ssh-client', mandatory: false, - defaultsTo: SupportedSshClient.hostSsh.cliArg, + defaultsTo: SupportedSshClient.exec.cliArg, allowed: SupportedSshClient.values.map((c) => c.cliArg).toList(), help: 'What to use for outbound ssh connections.'); diff --git a/packages/noports_core/lib/src/sshrv/sshrv.dart b/packages/noports_core/lib/src/sshrv/sshrv.dart index 1e7314495..11c7f0f15 100644 --- a/packages/noports_core/lib/src/sshrv/sshrv.dart +++ b/packages/noports_core/lib/src/sshrv/sshrv.dart @@ -20,24 +20,24 @@ abstract class SSHRV { Future run(); // Can't use factory functions since SSHRV contains a generic type - static SSHRV localBinary( + static SSHRV exec( String host, int streamingPort, { int localSshdPort = DefaultArgs.localSshdPort, }) { - return SSHRVImpl( + return SSHRVImplExec( host, streamingPort, localSshdPort: localSshdPort, ); } - static SSHRV pureDart( + static SSHRV dart( String host, int streamingPort, { int localSshdPort = 22, }) { - return SSHRVImplPureDart( + return SSHRVImplDart( host, streamingPort, localSshdPort: localSshdPort, diff --git a/packages/noports_core/lib/src/sshrv/sshrv_impl.dart b/packages/noports_core/lib/src/sshrv/sshrv_impl.dart index 722b4777a..72d219f75 100644 --- a/packages/noports_core/lib/src/sshrv/sshrv_impl.dart +++ b/packages/noports_core/lib/src/sshrv/sshrv_impl.dart @@ -8,7 +8,7 @@ import 'package:socket_connector/socket_connector.dart'; import 'package:noports_core/src/common/default_args.dart'; @visibleForTesting -class SSHRVImpl implements SSHRV { +class SSHRVImplExec implements SSHRV { @override final String host; @@ -18,7 +18,7 @@ class SSHRVImpl implements SSHRV { @override final int localSshdPort; - const SSHRVImpl( + const SSHRVImplExec( this.host, this.streamingPort, { this.localSshdPort = DefaultArgs.localSshdPort, @@ -43,7 +43,7 @@ class SSHRVImpl implements SSHRV { } @visibleForTesting -class SSHRVImplPureDart implements SSHRV { +class SSHRVImplDart implements SSHRV { @override final String host; @@ -53,7 +53,7 @@ class SSHRVImplPureDart implements SSHRV { @override final int localSshdPort; - const SSHRVImplPureDart( + const SSHRVImplDart( this.host, this.streamingPort, { this.localSshdPort = 22, diff --git a/packages/noports_core/test/sshnpd_test.dart b/packages/noports_core/test/sshnpd_test.dart index 83c361e38..8d6e22bfd 100644 --- a/packages/noports_core/test/sshnpd_test.dart +++ b/packages/noports_core/test/sshnpd_test.dart @@ -40,19 +40,19 @@ void main() { test('test --ssh-client arg', () { expect(SSHNPDParams.fromArgs('-a @bob -m @alice'.split(' ')).sshClient, - SupportedSshClient.hostSsh); + SupportedSshClient.exec); expect( SSHNPDParams.fromArgs( '-a @bob -m @alice --ssh-client pure-dart'.split(' ')) .sshClient, - SupportedSshClient.pureDart); + SupportedSshClient.dart); expect( SSHNPDParams.fromArgs( '-a @bob -m @alice --ssh-client /usr/bin/ssh'.split(' ')) .sshClient, - SupportedSshClient.hostSsh); + SupportedSshClient.exec); expect( () => SSHNPDParams.fromArgs( diff --git a/packages/sshnoports/bin/sshnp.dart b/packages/sshnoports/bin/sshnp.dart index 1768779bc..ee44fdcbc 100644 --- a/packages/sshnoports/bin/sshnp.dart +++ b/packages/sshnoports/bin/sshnp.dart @@ -17,32 +17,34 @@ void main(List args) async { late final SSHNP sshnp; late final SSHNPParams params; - try { - params = SSHNPParams.fromPartial(SSHNPPartialParams.fromArgs(args)); - } catch (error) { + try {} catch (error) { stderr.writeln(error.toString()); exit(1); } + usageCallback(e, s) { + printVersion(); + stdout.writeln(SSHNPPartialParams.parser.usage); + stderr.writeln('\n$e'); + } + try { + params = SSHNPParams.fromPartial(SSHNPPartialParams.fromArgs(args)); sshnp = await SSHNP.fromParams( params, atClientGenerator: (SSHNPParams params, String sessionId) => createAtClientCli( homeDirectory: params.homeDirectory, - atsign: params.clientAtSign!, + atsign: params.clientAtSign, namespace: '${params.device}.sshnp', pathExtension: sessionId, atKeysFilePath: params.atKeysFilePath, rootDomain: params.rootDomain, ), - usageCallback: (e, s) { - printVersion(); - stdout.writeln(SSHNPPartialParams.parser.usage); - stderr.writeln('\n$e'); - }, + usageCallback: usageCallback, ); - } on ArgumentError catch (_) { + } on ArgumentError catch (e, s) { + usageCallback(e, s); exit(1); } @@ -73,7 +75,7 @@ void main(List args) async { await sshnp.init(); SSHNPResult res = await sshnp.run(); - if (res is SSHNPFailed) { + if (res is SSHNPError) { stderr.write('$res\n'); exit(1); } diff --git a/packages/sshnoports/bin/sshrv.dart b/packages/sshnoports/bin/sshrv.dart index 271e40816..54caf639a 100644 --- a/packages/sshnoports/bin/sshrv.dart +++ b/packages/sshnoports/bin/sshrv.dart @@ -17,5 +17,5 @@ Future main(List args) async { localSshdPort = int.parse(args[2]); } - await SSHRV.pureDart(host, streamingPort, localSshdPort: localSshdPort).run(); + await SSHRV.dart(host, streamingPort, localSshdPort: localSshdPort).run(); } diff --git a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart index 8ef6345ee..fe9416533 100644 --- a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart +++ b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart @@ -46,13 +46,13 @@ class _ProfileRunActionState extends ConsumerState { sshnp = await SSHNP.fromParams( params, atClient: AtClientManager.getInstance().atClient, - sshrvGenerator: SSHRV.pureDart, + sshrvGenerator: SSHRV.dart, ); await sshnp!.init(); sshnpResult = await sshnp!.run(); - if (sshnpResult is SSHNPFailed) { + if (sshnpResult is SSHNPError) { throw sshnpResult!; } ref @@ -74,8 +74,9 @@ class _ProfileRunActionState extends ConsumerState { (sshnpResult as SSHNPSuccess).sshClient?.close(); // DirectSSHViaClient var sshrvResult = await (sshnpResult as SSHNPSuccess).sshrvResult; if (sshrvResult is Process) sshrvResult.kill(); // SSHRV via local binary - if (sshrvResult is SocketConnector) + if (sshrvResult is SocketConnector) { sshrvResult.close(); // SSHRV via pure dart + } } ref .read(backgroundSessionFamilyController(widget.params.profileName!) diff --git a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_terminal_action.dart b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_terminal_action.dart index a9d913741..77a108d1b 100644 --- a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_terminal_action.dart +++ b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_terminal_action.dart @@ -42,12 +42,12 @@ class _ProfileTerminalActionState extends ConsumerState { final sshnp = await SSHNP.fromParams( params, atClient: AtClientManager.getInstance().atClient, - sshrvGenerator: SSHRV.pureDart, + sshrvGenerator: SSHRV.dart, ); await sshnp.init(); final result = await sshnp.run(); - if (result is SSHNPFailed) { + if (result is SSHNPError) { throw result; } From 2c5981025b29634daede7a5add246e8bf7a1b5cc Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Thu, 28 Sep 2023 20:04:27 -0400 Subject: [PATCH 12/49] chore: unify connectionBean on SSHNPSuccess class --- .../sshnp_impl/sshnp_forward_dart_impl.dart | 4 ++-- .../sshnp_impl/sshnp_forward_exec_impl.dart | 4 ++-- .../sshnp/sshnp_impl/sshnp_legacy_impl.dart | 7 +++--- .../sshnp/sshnp_impl/sshnp_reverse_impl.dart | 7 +++--- .../lib/src/sshnp/sshnp_result.dart | 16 ++++--------- .../profile_actions/profile_run_action.dart | 23 ++++++++++++++----- 6 files changed, 33 insertions(+), 28 deletions(-) diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart index 92b782b44..8df9503d7 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart @@ -198,14 +198,14 @@ class SSHNPForwardDartImpl extends SSHNPImpl with SSHNPForwardDirection { }); // All good - write the ssh command to stdout - return SSHNPSuccess.base( + return SSHNPSuccess( localPort: localPort, remoteUsername: remoteUsername, host: 'localhost', privateKeyFileName: publicKeyFileName.replaceAll('.pub', ''), localSshOptions: (params.addForwardsToTunnel) ? null : params.localSshOptions, - sshClient: client, + connectionBean: client, ); } on SSHNPError catch (e, s) { doneCompleter.completeError(e, s); diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart index f77740456..51515cd41 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart @@ -130,14 +130,14 @@ class SSHNPForwardExecImpl extends SSHNPImpl with SSHNPForwardDirection { doneCompleter.complete(); // All good - write the ssh command to stdout - return SSHNPSuccess.base( + return SSHNPSuccess( localPort: localPort, remoteUsername: remoteUsername, host: 'localhost', privateKeyFileName: publicKeyFileName.replaceAll('.pub', ''), localSshOptions: (params.addForwardsToTunnel) ? null : params.localSshOptions, - sshProcess: process, + connectionBean: process, ); } on SSHNPError catch (e) { doneCompleter.completeError(e, e.stackTrace); diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart index 27b8cab9f..a86d71bb4 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart @@ -11,7 +11,8 @@ class SSHNPLegacyImpl extends SSHNPImpl with SSHNPReverseDirection { required AtClient atClient, required SSHNPParams params, SSHRVGenerator? sshrvGenerator, - }) : super(atClient: atClient, params: params, sshrvGenerator: sshrvGenerator); + }) : super( + atClient: atClient, params: params, sshrvGenerator: sshrvGenerator); @override Future init() async { @@ -82,14 +83,14 @@ class SSHNPLegacyImpl extends SSHNPImpl with SSHNPReverseDirection { } doneCompleter.complete(); - return SSHNPSuccess.base( + return SSHNPSuccess( localPort: localPort, remoteUsername: remoteUsername, host: 'localhost', privateKeyFileName: publicKeyFileName.replaceAll('.pub', ''), localSshOptions: (params.addForwardsToTunnel) ? null : params.localSshOptions, - sshrvResult: sshrvResult, + connectionBean: sshrvResult, ); } } diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart index 8978d1f26..fb8e817e7 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart @@ -10,7 +10,8 @@ class SSHNPReverseImpl extends SSHNPImpl with SSHNPReverseDirection { required AtClient atClient, required SSHNPParams params, SSHRVGenerator? sshrvGenerator, - }) : super(atClient: atClient, params: params, sshrvGenerator: sshrvGenerator); + }) : super( + atClient: atClient, params: params, sshrvGenerator: sshrvGenerator); @override Future run() async { @@ -60,14 +61,14 @@ class SSHNPReverseImpl extends SSHNPImpl with SSHNPReverseDirection { } doneCompleter.complete(); - return SSHNPSuccess.base( + return SSHNPSuccess( localPort: localPort, remoteUsername: remoteUsername, host: 'localhost', privateKeyFileName: publicKeyFileName.replaceAll('.pub', ''), localSshOptions: (params.addForwardsToTunnel) ? null : params.localSshOptions, - sshrvResult: sshrvResult, + connectionBean: sshrvResult, ); } } diff --git a/packages/noports_core/lib/src/sshnp/sshnp_result.dart b/packages/noports_core/lib/src/sshnp/sshnp_result.dart index 90c337bbb..31578d362 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_result.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_result.dart @@ -1,7 +1,3 @@ -import 'dart:io'; - -import 'package:dartssh2/dartssh2.dart'; - abstract class SSHNPResult {} const _optionsWithPrivateKey = [ @@ -36,7 +32,7 @@ class SSHNPError implements SSHNPResult, Exception { } } -class SSHNPSuccess implements SSHNPResult { +class SSHNPSuccess implements SSHNPResult { final String command = 'ssh'; final int localPort; @@ -46,19 +42,15 @@ class SSHNPSuccess implements SSHNPResult { final List sshOptions; - Future? sshrvResult; - Process? sshProcess; - SSHClient? sshClient; + ConnectionBean? connectionBean; - SSHNPSuccess.base({ + SSHNPSuccess({ required this.localPort, required this.remoteUsername, required this.host, List? localSshOptions, this.privateKeyFileName, - this.sshrvResult, - this.sshProcess, - this.sshClient, + this.connectionBean }) : sshOptions = [ if (shouldIncludePrivateKey(privateKeyFileName)) ..._optionsWithPrivateKey, diff --git a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart index fe9416533..5a5010430 100644 --- a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart +++ b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart @@ -70,12 +70,23 @@ class _ProfileRunActionState extends ConsumerState { Future onStop() async { if (sshnpResult is SSHNPSuccess) { - (sshnpResult as SSHNPSuccess).sshProcess?.kill(); // DirectSSHViaExec - (sshnpResult as SSHNPSuccess).sshClient?.close(); // DirectSSHViaClient - var sshrvResult = await (sshnpResult as SSHNPSuccess).sshrvResult; - if (sshrvResult is Process) sshrvResult.kill(); // SSHRV via local binary - if (sshrvResult is SocketConnector) { - sshrvResult.close(); // SSHRV via pure dart + var res = sshnpResult as SSHNPSuccess; + if(res.connectionBean is Process) { + res.connectionBean.kill(); + } + if(res.connectionBean is SocketConnector) { + res.connectionBean.close(); + } + + if (res.connectionBean is Future) { + await (res.connectionBean as Future).then((value) { + if (value is Process) { + value.kill(); + } + if (value is SocketConnector) { + value.close(); + } + }); } } ref From 3346a5817f5690b15f234d4ab5d78224d23e3d0b Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Thu, 28 Sep 2023 20:09:15 -0400 Subject: [PATCH 13/49] feat: add killConnectionBean to SSHNPSuccess --- .../lib/src/sshnp/sshnp_result.dart | 41 +++++++++++++++---- .../profile_actions/profile_run_action.dart | 19 +-------- .../widgets/profile_form/profile_form.dart | 4 +- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/packages/noports_core/lib/src/sshnp/sshnp_result.dart b/packages/noports_core/lib/src/sshnp/sshnp_result.dart index 31578d362..a8ecf55aa 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_result.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_result.dart @@ -1,3 +1,7 @@ +import 'dart:io'; + +import 'package:socket_connector/socket_connector.dart'; + abstract class SSHNPResult {} const _optionsWithPrivateKey = [ @@ -44,14 +48,14 @@ class SSHNPSuccess implements SSHNPResult { ConnectionBean? connectionBean; - SSHNPSuccess({ - required this.localPort, - required this.remoteUsername, - required this.host, - List? localSshOptions, - this.privateKeyFileName, - this.connectionBean - }) : sshOptions = [ + SSHNPSuccess( + {required this.localPort, + required this.remoteUsername, + required this.host, + List? localSshOptions, + this.privateKeyFileName, + this.connectionBean}) + : sshOptions = [ if (shouldIncludePrivateKey(privateKeyFileName)) ..._optionsWithPrivateKey, ...(localSshOptions ?? []) @@ -79,4 +83,25 @@ class SSHNPSuccess implements SSHNPResult { sb.write(args.join(' ')); return sb.toString(); } + + Future killConnectionBean() async { + if (connectionBean is Process) { + (connectionBean as Process).kill(); + } + + if (connectionBean is SocketConnector) { + (connectionBean as SocketConnector).close(); + } + + if (connectionBean is Future) { + await (connectionBean as Future).then((value) { + if (value is Process) { + value.kill(); + } + if (value is SocketConnector) { + value.close(); + } + }); + } + } } diff --git a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart index 5a5010430..dd1f0de81 100644 --- a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart +++ b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart @@ -70,24 +70,7 @@ class _ProfileRunActionState extends ConsumerState { Future onStop() async { if (sshnpResult is SSHNPSuccess) { - var res = sshnpResult as SSHNPSuccess; - if(res.connectionBean is Process) { - res.connectionBean.kill(); - } - if(res.connectionBean is SocketConnector) { - res.connectionBean.close(); - } - - if (res.connectionBean is Future) { - await (res.connectionBean as Future).then((value) { - if (value is Process) { - value.kill(); - } - if (value is SocketConnector) { - value.close(); - } - }); - } + await (sshnpResult as SSHNPSuccess).killConnectionBean(); } ref .read(backgroundSessionFamilyController(widget.params.profileName!) diff --git a/packages/sshnp_gui/lib/src/presentation/widgets/profile_form/profile_form.dart b/packages/sshnp_gui/lib/src/presentation/widgets/profile_form/profile_form.dart index b03c001f3..70102fe2d 100644 --- a/packages/sshnp_gui/lib/src/presentation/widgets/profile_form/profile_form.dart +++ b/packages/sshnp_gui/lib/src/presentation/widgets/profile_form/profile_form.dart @@ -102,7 +102,7 @@ class _ProfileFormState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ CustomTextFormField( - initialValue: oldConfig.sshnpdAtSign ?? '', + initialValue: oldConfig.sshnpdAtSign, labelText: strings.sshnpdAtSign, onChanged: (value) => newConfig = SSHNPPartialParams.merge( @@ -113,7 +113,7 @@ class _ProfileFormState extends ConsumerState { ), gapW8, CustomTextFormField( - initialValue: oldConfig.host ?? '', + initialValue: oldConfig.host, labelText: strings.host, onChanged: (value) => newConfig = SSHNPPartialParams.merge( From 29f33a1fb8aec65ed1e206b3d14ec5eb7215a0f4 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Thu, 28 Sep 2023 20:15:10 -0400 Subject: [PATCH 14/49] feat: increment package version of noports core --- packages/noports_core/CHANGELOG.md | 2 +- packages/noports_core/README.md | 14 +++++++++++--- packages/noports_core/pubspec.yaml | 2 +- packages/sshnoports/pubspec.yaml | 2 +- packages/sshnp_gui/pubspec.yaml | 2 +- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/noports_core/CHANGELOG.md b/packages/noports_core/CHANGELOG.md index 067b89500..b6bd2229a 100644 --- a/packages/noports_core/CHANGELOG.md +++ b/packages/noports_core/CHANGELOG.md @@ -1,3 +1,3 @@ -# 4.0.0-dev.1 +# 4.0.0 - Initial release based of the 4.0.0 pre-release code of sshnoports \ No newline at end of file diff --git a/packages/noports_core/README.md b/packages/noports_core/README.md index b85e77cff..04375e9d4 100644 --- a/packages/noports_core/README.md +++ b/packages/noports_core/README.md @@ -5,9 +5,17 @@ # No Ports Core -Abstractions of the original sshnoports core code used for building No Ports -enabled applications. Currently in development, more documentation to come with -the official release. +No Ports Core is the underlying library used to enable sshnoports and the rest of the No Ports suite. + +## Examples + +### CLI Example + +See the [sshnoports](https://github.com/atsign-foundation/sshnoports/tree/trunk/packages/sshnoports) project. + +### Flutter Example + +See the [sshnp_gui](https://github.com/atsign-foundation/sshnoports/tree/trunk/packages/sshnp_gui) project. ## Maintainers diff --git a/packages/noports_core/pubspec.yaml b/packages/noports_core/pubspec.yaml index bb2ced39d..77d323ac5 100644 --- a/packages/noports_core/pubspec.yaml +++ b/packages/noports_core/pubspec.yaml @@ -3,7 +3,7 @@ description: Core library code for sshnoports # NOTE: If you update the version number here, you # must also update it in version.dart -version: 4.0.0-dev.1 +version: 4.0.0 homepage: https://docs.atsign.com/ diff --git a/packages/sshnoports/pubspec.yaml b/packages/sshnoports/pubspec.yaml index e880640c9..24cf22fdd 100644 --- a/packages/sshnoports/pubspec.yaml +++ b/packages/sshnoports/pubspec.yaml @@ -9,7 +9,7 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: - noports_core: 4.0.0-dev.1 + noports_core: 4.0.0 at_onboarding_cli: 1.3.0 dependency_overrides: diff --git a/packages/sshnp_gui/pubspec.yaml b/packages/sshnp_gui/pubspec.yaml index 58b8856e8..6a3da490c 100644 --- a/packages/sshnp_gui/pubspec.yaml +++ b/packages/sshnp_gui/pubspec.yaml @@ -28,7 +28,7 @@ dependencies: go_router: ^9.0.3 intl: any macos_ui: ^2.0.0 - noports_core: ^4.0.0-dev.1 + noports_core: ^4.0.0 page_transition: ^2.0.9 path: ^1.8.3 path_provider: ^2.0.11 From 55a5ad2fad8bba27bc2d384f598e796c6bb130b5 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Thu, 28 Sep 2023 20:18:12 -0400 Subject: [PATCH 15/49] chore: run end2end tests against noports_core 4.0.0-dev.2 --- packages/sshnoports/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sshnoports/pubspec.yaml b/packages/sshnoports/pubspec.yaml index 24cf22fdd..128af908b 100644 --- a/packages/sshnoports/pubspec.yaml +++ b/packages/sshnoports/pubspec.yaml @@ -9,7 +9,7 @@ environment: sdk: ">=3.0.0 <4.0.0" dependencies: - noports_core: 4.0.0 + noports_core: 4.0.0-dev.2 at_onboarding_cli: 1.3.0 dependency_overrides: From 58ff1a2917e23b2e30468aa007d3804a9c0ad079 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Fri, 29 Sep 2023 08:51:35 -0400 Subject: [PATCH 16/49] ci: add better version tag checker --- .github/composite/verify_cli_tags/action.yaml | 30 +++++++++++++++++++ .../composite/verify_core_tags/action.yaml | 17 +++++++++++ .github/workflows/dockerhub_sshnpd.yml | 15 ++-------- .github/workflows/multibuild.yaml | 13 +------- .github/workflows/unit_tests.yaml | 26 ++++++++++++++++ packages/noports_core/lib/version.dart | 2 +- packages/sshnoports/lib/version.dart | 2 +- packages/sshnoports/pubspec.yaml | 4 +-- 8 files changed, 81 insertions(+), 28 deletions(-) create mode 100644 .github/composite/verify_cli_tags/action.yaml create mode 100644 .github/composite/verify_core_tags/action.yaml create mode 100644 .github/workflows/unit_tests.yaml diff --git a/.github/composite/verify_cli_tags/action.yaml b/.github/composite/verify_cli_tags/action.yaml new file mode 100644 index 000000000..8a49793a7 --- /dev/null +++ b/.github/composite/verify_cli_tags/action.yaml @@ -0,0 +1,30 @@ +name: verify_cli_tags +description: | + Ensures that the tag of sshnoports matches the tag of the composite. + +runs: + using: composite + steps: + - name: Ensure pubspec.yaml matches version.dart + shell: bash + working-directory: ./packages/sshnoports + run: | + DART_TAG="v$(egrep -o '^const String version = "(.*)";' lib/version.dart | cut -d'"' -f2)" + PUBSPEC_TAG="v$(egrep -o '^version: (.*)$' pubspec.yaml | cut -d':' -f2 | tr -d '[:space:]')" + if [ "$PUBSPEC_TAG" != "$DART_TAG" ]; then + echo "Tag $PUBSPEC_TAG does not match version in version.dart: $DART_TAG" + exit 1 + fi + - name: Ensure version.dart matches git ref (if current git ref is a version tag) + shell: bash + if: startsWith(github.ref, 'refs/tags/v') + working-directory: ./packages/sshnoports + run: | + # check version.dart + REF=${{ github.ref }} + TAG=${REF:10} + DART_TAG="v$(egrep -o '^const String version = "(.*)";' lib/version.dart | cut -d'"' -f2)" + if [ "$TAG" != "$DART_TAG" ]; then + echo "Tag $TAG does not match version in version.dart: $DART_TAG" + exit 1 + fi diff --git a/.github/composite/verify_core_tags/action.yaml b/.github/composite/verify_core_tags/action.yaml new file mode 100644 index 000000000..eaf2b5e5d --- /dev/null +++ b/.github/composite/verify_core_tags/action.yaml @@ -0,0 +1,17 @@ +name: verify_core_tags +description: | + Ensures that the tag of noports_core matches the tag of the composite. + +runs: + using: composite + steps: + - name: Ensure pubspec.yaml matches version.dart + shell: bash + working-directory: ./packages/noports_core + run: | + DART_TAG="v$(grep -Po '^const String version = "(.*)";' lib/version.dart | cut -d'"' -f2)" + PUBSPEC_TAG="v$(egrep -o '^version: (.*)$' pubspec.yaml | cut -d':' -f2 | tr -d '[:space:]')" + if [ "$PUBSPEC_TAG" != "$DART_TAG" ]; then + echo "Tag $PUBSPEC_TAG does not match version in version.dart: $DART_TAG" + exit 1 + fi diff --git a/.github/workflows/dockerhub_sshnpd.yml b/.github/workflows/dockerhub_sshnpd.yml index 1346e5dcf..5b650f759 100644 --- a/.github/workflows/dockerhub_sshnpd.yml +++ b/.github/workflows/dockerhub_sshnpd.yml @@ -23,18 +23,9 @@ jobs: - name: Checkout uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - - name: Ensure version matches tag - if: startsWith(github.ref, 'refs/tags/v') - working-directory: ./packages/sshnoports - run: | - # check version.dart - REF=${{ github.ref }} - TAG=${REF:10} - DART_TAG="v$(grep -Po '^const String version = "(.*)";' lib/version.dart | cut -d'"' -f2)" - if [ "$TAG" != "$DART_TAG" ]; then - echo "Tag $TAG does not match version in version.dart: $DART_TAG" - exit 1 - fi + # verify tags + - uses: ./.github/workflows/verify_cli_tags + - uses: ./.github/workflows/verify_core_tags - name: Set up QEMU uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 diff --git a/.github/workflows/multibuild.yaml b/.github/workflows/multibuild.yaml index 441a392f5..4fba2aae2 100644 --- a/.github/workflows/multibuild.yaml +++ b/.github/workflows/multibuild.yaml @@ -24,18 +24,7 @@ jobs: steps: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - - name: Ensure version matches tag - if: startsWith(github.ref, 'refs/tags/v') - working-directory: ./packages/sshnoports - run: | - # check version.dart - REF=${{ github.ref }} - TAG=${REF:10} - DART_TAG="v$(egrep -o '^const String version = "(.*)";' lib/version.dart | cut -d'"' -f2)" - if [ "$TAG" != "$DART_TAG" ]; then - echo "Tag $TAG does not match version in version.dart: $DART_TAG" - exit 1 - fi + - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 # v1.5.1 - run: mkdir sshnp diff --git a/.github/workflows/unit_tests.yaml b/.github/workflows/unit_tests.yaml new file mode 100644 index 000000000..429b76add --- /dev/null +++ b/.github/workflows/unit_tests.yaml @@ -0,0 +1,26 @@ +name: unit_tests + +permissions: + contents: read + +on: + workflow_dispatch: + push: + branches: + - trunk + + pull_request: + branches: + - trunk + +jobs: + cli_tags: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + - uses: ./.github/composite/verify_cli_tags + core_tags: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + - uses: ./.github/composite/verify_core_tags diff --git a/packages/noports_core/lib/version.dart b/packages/noports_core/lib/version.dart index 5216dc737..4cbfe8270 100644 --- a/packages/noports_core/lib/version.dart +++ b/packages/noports_core/lib/version.dart @@ -1,2 +1,2 @@ // Note: if you update this version also update pubspec.yaml -const String version = "4.0.0-dev.1"; +const String version = "4.0.0"; diff --git a/packages/sshnoports/lib/version.dart b/packages/sshnoports/lib/version.dart index 85d0da462..75dcb4575 100644 --- a/packages/sshnoports/lib/version.dart +++ b/packages/sshnoports/lib/version.dart @@ -1,7 +1,7 @@ import 'dart:io'; // Note: if you update this version also update pubspec.yaml -const String version = "4.0.0-rc.6"; +const String version = "4.0.0-rc.7"; /// Print version number void printVersion() { diff --git a/packages/sshnoports/pubspec.yaml b/packages/sshnoports/pubspec.yaml index 128af908b..94ed1cdc1 100644 --- a/packages/sshnoports/pubspec.yaml +++ b/packages/sshnoports/pubspec.yaml @@ -3,13 +3,13 @@ publish_to: none # NOTE: If you update the version number here, you # must also update it in version.dart -version: 4.0.0-rc.6 +version: 4.0.0-rc.7 environment: sdk: ">=3.0.0 <4.0.0" dependencies: - noports_core: 4.0.0-dev.2 + noports_core: 4.0.0 at_onboarding_cli: 1.3.0 dependency_overrides: From e2a5081e1ea20052615d24c7e0816d435a5b80f8 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Fri, 29 Sep 2023 15:14:11 -0400 Subject: [PATCH 17/49] chore: use local build for end2end tests where appropriate, use hosted build for packaging --- packages/sshnoports/pubspec.lock | 2 +- packages/sshnp_gui/pubspec.lock | 2 +- tests/end2end_tests/image/Dockerfile | 8 +++++++- tools/package-macos-arm64.sh | 8 +++++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/sshnoports/pubspec.lock b/packages/sshnoports/pubspec.lock index 9cc331377..c137bb51e 100644 --- a/packages/sshnoports/pubspec.lock +++ b/packages/sshnoports/pubspec.lock @@ -447,7 +447,7 @@ packages: path: "../noports_core" relative: true source: path - version: "4.0.0-dev.1" + version: "4.0.0" package_config: dependency: transitive description: diff --git a/packages/sshnp_gui/pubspec.lock b/packages/sshnp_gui/pubspec.lock index b49ed54c0..d12df9205 100644 --- a/packages/sshnp_gui/pubspec.lock +++ b/packages/sshnp_gui/pubspec.lock @@ -763,7 +763,7 @@ packages: path: "../noports_core" relative: true source: path - version: "4.0.0-dev.1" + version: "4.0.0" package_config: dependency: transitive description: diff --git a/tests/end2end_tests/image/Dockerfile b/tests/end2end_tests/image/Dockerfile index ce9e00205..0f488af0e 100644 --- a/tests/end2end_tests/image/Dockerfile +++ b/tests/end2end_tests/image/Dockerfile @@ -34,12 +34,15 @@ ENV OUTPUT_DIR=/app/output ARG branch=trunk +# Builds using the noports_core package available on the branch specified RUN set -eux ; \ mkdir -p ${REPO_DIR} ${OUTPUT_DIR} ; \ apt-get update ; \ apt-get install -y git ; \ cd ${REPO_DIR} ; \ git clone -b ${branch} --single-branch ${URL} . ; \ + dart pub get; \ + dart run melos bootstrap; \ cd packages/sshnoports ; \ dart pub get ; \ dart compile exe ${REPO_DIR}/packages/sshnoports/bin/sshnp.dart -o ${OUTPUT_DIR}/sshnp ; \ @@ -73,7 +76,10 @@ RUN mkdir -p ${REPO_DIR} ${OUTPUT_DIR} ; COPY . ${REPO_DIR} RUN set -eux ; \ - cd ${REPO_DIR}/packages/sshnoports ; \ + cd ${REPO_DIR}; \ + dart pub get; \ + dart run melos bootstrap; \ + cd packages/sshnoports ; \ dart pub get ; \ dart compile exe ${REPO_DIR}/packages/sshnoports/bin/sshnp.dart -o ${OUTPUT_DIR}/sshnp ; \ dart compile exe ${REPO_DIR}/packages/sshnoports/bin/sshnpd.dart -o ${OUTPUT_DIR}/sshnpd ; \ diff --git a/tools/package-macos-arm64.sh b/tools/package-macos-arm64.sh index 3ffa8e749..5fba80731 100755 --- a/tools/package-macos-arm64.sh +++ b/tools/package-macos-arm64.sh @@ -21,7 +21,13 @@ else DART=$(which dart) fi -eval "$DART pub get -C $SRC_DIR" +restore_backup() { + mv "$SRC_DIR/pubspec_overrides.back.yaml" "$SRC_DIR/pubspec_overrides.yaml" +} + +mv "$SRC_DIR/pubspec_overrides.yaml" "$SRC_DIR/pubspec_overrides.back.yaml" +eval "$DART pub get -C $SRC_DIR" || restore_backup +restore_backup OUTPUT_DIR_PATH="$ROOT_DIRECTORY/build/macos-arm64" OUTPUT_DIR="$OUTPUT_DIR_PATH/sshnp" From 6407b661ae6bd8e05e2482c63d9fd85d2759864e Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Fri, 29 Sep 2023 15:18:54 -0400 Subject: [PATCH 18/49] chore: remove flutter constraints from melos workspace, so it can be run in a dart container --- pubspec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 2d502d539..85b94aa65 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,6 @@ name: sshnoports_workspace environment: sdk: ">=2.12.0 <4.0.0" - flutter: ">=1.20.0" dev_dependencies: melos: ^3.1.1 From 4daa177f78f3c6b7ba8c79733c9c268aa0c3ebe0 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Fri, 29 Sep 2023 15:23:03 -0400 Subject: [PATCH 19/49] fix(end2end tests): only bootstrap what is needed --- tests/end2end_tests/image/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/end2end_tests/image/Dockerfile b/tests/end2end_tests/image/Dockerfile index 0f488af0e..a94e3b392 100644 --- a/tests/end2end_tests/image/Dockerfile +++ b/tests/end2end_tests/image/Dockerfile @@ -42,7 +42,7 @@ RUN set -eux ; \ cd ${REPO_DIR} ; \ git clone -b ${branch} --single-branch ${URL} . ; \ dart pub get; \ - dart run melos bootstrap; \ + dart run melos bootstrap --scope="noports_core" --scope="sshnoports"; \ cd packages/sshnoports ; \ dart pub get ; \ dart compile exe ${REPO_DIR}/packages/sshnoports/bin/sshnp.dart -o ${OUTPUT_DIR}/sshnp ; \ @@ -78,7 +78,7 @@ COPY . ${REPO_DIR} RUN set -eux ; \ cd ${REPO_DIR}; \ dart pub get; \ - dart run melos bootstrap; \ + dart run melos bootstrap --scope="noports_core" --scope="sshnoports"; \ cd packages/sshnoports ; \ dart pub get ; \ dart compile exe ${REPO_DIR}/packages/sshnoports/bin/sshnp.dart -o ${OUTPUT_DIR}/sshnp ; \ From 2548c1c03864ef18d18dfbf16e74d2a314d544c6 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Fri, 29 Sep 2023 15:32:14 -0400 Subject: [PATCH 20/49] fix: add back initialization of publicKeyFileName --- .../lib/src/sshnp/sshnp_impl/sshnp_impl.dart | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart index d14ad5846..43aba9f5a 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart @@ -12,6 +12,7 @@ import 'package:noports_core/sshrv.dart'; import 'package:noports_core/sshrvd.dart'; import 'package:noports_core/utils.dart'; import 'package:uuid/uuid.dart'; +import 'package:path/path.dart' as path; // If you've never seen an abstract implementation before, here it is :P @protected @@ -138,6 +139,20 @@ abstract class SSHNPImpl implements SSHNP { preference.namespace = '${params.device}.sshnp'; atClient.setPreferences(preference); + // previously, the default value for sendSshPublicKey was 'false' instead of '' + // immediately set it to '' to avoid the program from attempting to + // search for a public key file called 'false' + if (params.sendSshPublicKey == 'false' || params.sendSshPublicKey.isEmpty) { + publicKeyFileName = ''; + } else if (path.normalize(params.sendSshPublicKey).contains('/') || + path.normalize(params.sendSshPublicKey).contains(r'\')) { + publicKeyFileName = + path.normalize(path.absolute(params.sendSshPublicKey)); + } else { + publicKeyFileName = + path.normalize('$sshHomeDirectory/${params.sendSshPublicKey}'); + } + /// Also call init if (shouldInitialize) init(); } From 629b6a8d564c54422f27880ecbf471154d1a90cd Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Fri, 29 Sep 2023 15:56:43 -0400 Subject: [PATCH 21/49] fix: ensure sshrvPort is always set when it should be --- .../lib/src/sshnp/sshnp_impl/sshnp_impl.dart | 5 ++++- .../lib/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart | 13 ++++++++++++- .../src/sshnp/sshnp_impl/sshnp_legacy_impl.dart | 13 ++++++++----- .../src/sshnp/sshnp_impl/sshnp_reverse_impl.dart | 14 ++++++++------ 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart index 43aba9f5a..c66d4e879 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart @@ -66,7 +66,7 @@ abstract class SSHNPImpl implements SSHNP { /// When using sshrvd, this is fetched from sshrvd during [init] /// This is only set when using sshrvd /// (i.e. after [getHostAndPortFromSshrvd] has been called) - late int sshrvdPort; + int? sshrvdPort; // ==================================================================== // Status indicators (Available in the public API) @@ -139,6 +139,7 @@ abstract class SSHNPImpl implements SSHNP { preference.namespace = '${params.device}.sshnp'; atClient.setPreferences(preference); + // Set the file name for the public key based on the value of sendSshPublicKey // previously, the default value for sendSshPublicKey was 'false' instead of '' // immediately set it to '' to avoid the program from attempting to // search for a public key file called 'false' @@ -324,6 +325,8 @@ abstract class SSHNPImpl implements SSHNP { host = results[0]; port = int.parse(results[1]); sshrvdPort = int.parse(results[2]); + logger.info('Received host and port from sshrvd: $host:$port'); + logger.info('Set sshrvdPort to: $sshrvdPort'); sshrvdAck = true; }); diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart index ccfc2b95f..fa9349409 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart @@ -56,6 +56,17 @@ mixin SSHNPReverseDirection on SSHNPImpl { await cleanUpAfterReverseSsh(this); super.cleanUp(); } + + bool get usingSshrv => sshrvdPort != null; } -mixin SSHNPForwardDirection on SSHNPImpl {} +mixin SSHNPForwardDirection on SSHNPImpl { + // Direct ssh is only ever done with a sshrvd host + // So we should expect that sshrvdPort is never null + // Hence overriding the getter and setter to make it non-nullable + late int _sshrvdPort; + @override + int get sshrvdPort => _sshrvdPort; + @override + set sshrvdPort(int? port) => _sshrvdPort = port!; +} diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart index a86d71bb4..1c7fbaf28 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart @@ -42,11 +42,14 @@ class SSHNPLegacyImpl extends SSHNPImpl with SSHNPReverseDirection { await initialized; } - // Connect to rendezvous point using background process. - // sshnp (this program) can then exit without issue. - SSHRV sshrv = - sshrvGenerator(host, sshrvdPort, localSshdPort: params.localSshdPort); - Future sshrvResult = sshrv.run(); + Future? sshrvResult; + if(usingSshrv) { + // Connect to rendezvous point using background process. + // sshnp (this program) can then exit without issue. + SSHRV sshrv = + sshrvGenerator(host, sshrvdPort!, localSshdPort: params.localSshdPort); + sshrvResult = sshrv.run(); + } // send request to the daemon via notification await notify( diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart index fb8e817e7..32777a643 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart @@ -17,12 +17,14 @@ class SSHNPReverseImpl extends SSHNPImpl with SSHNPReverseDirection { Future run() async { logger.info('Requesting daemon to start reverse ssh session'); - // Connect to rendezvous point using background process. - // sshnp (this program) can then exit without issue. - SSHRV sshrv = - sshrvGenerator(host, sshrvdPort, localSshdPort: params.localSshdPort); - Future sshrvResult = sshrv.run(); - + Future? sshrvResult; + if (usingSshrv) { + // Connect to rendezvous point using background process. + // sshnp (this program) can then exit without issue. + SSHRV sshrv = sshrvGenerator(host, sshrvdPort!, + localSshdPort: params.localSshdPort); + sshrvResult = sshrv.run(); + } // send request to the daemon via notification await notify( AtKey() From db66f652c4fba7796366742d44e6b1285e518551 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Fri, 29 Sep 2023 15:56:50 -0400 Subject: [PATCH 22/49] chore: remove unused code --- .../noports_core/lib/src/sshnp/sshnp_mixins.dart | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 packages/noports_core/lib/src/sshnp/sshnp_mixins.dart diff --git a/packages/noports_core/lib/src/sshnp/sshnp_mixins.dart b/packages/noports_core/lib/src/sshnp/sshnp_mixins.dart deleted file mode 100644 index 6b865bc66..000000000 --- a/packages/noports_core/lib/src/sshnp/sshnp_mixins.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl.dart'; -import 'package:noports_core/sshnp.dart'; - -mixin LegacyReverseSSH on SSHNPImpl { - -} - -mixin ReverseSSH on SSHNP { - -} - -mixin DirectSSH on SSHNP {} From 9a66a8d6719f62d3d6d678e34b78caf540b7c6b1 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 14:34:35 -0400 Subject: [PATCH 23/49] chore: checkin changes --- .../noports_core/lib/config_repository.dart | 5 - .../lib/src/common/default_args.dart | 1 + .../noports_core/lib/src/sshnp/sshnp.dart | 55 +++++- .../lib/src/sshnp/sshnp_impl/sshnp_impl.dart | 45 ++++- .../sshnp/sshnp_impl/sshnp_impl_mixin.dart | 4 + .../lib/src/sshnp/sshnp_params/sshnp_arg.dart | 14 ++ .../src/sshnp/sshnp_params/sshnp_params.dart | 4 +- packages/noports_core/lib/sshnp_params.dart | 6 + packages/sshnoports/bin/sshnp.dart | 157 +++++++++++------- .../src/controllers/config_controller.dart | 2 +- .../home_screen_action_callbacks.dart | 2 +- .../profile_action_callbacks.dart | 2 +- 12 files changed, 212 insertions(+), 85 deletions(-) delete mode 100644 packages/noports_core/lib/config_repository.dart create mode 100644 packages/noports_core/lib/sshnp_params.dart diff --git a/packages/noports_core/lib/config_repository.dart b/packages/noports_core/lib/config_repository.dart deleted file mode 100644 index 5be18936d..000000000 --- a/packages/noports_core/lib/config_repository.dart +++ /dev/null @@ -1,5 +0,0 @@ -library sshnp_core_config_repository; - -export 'src/sshnp/sshnp_params/config_file_repository.dart'; -export 'src/sshnp/sshnp_params/config_key_repository.dart'; -export 'src/sshnp/sshnp_params/sshnp_params.dart'; \ No newline at end of file diff --git a/packages/noports_core/lib/src/common/default_args.dart b/packages/noports_core/lib/src/common/default_args.dart index b957d29a1..a0b6f5958 100644 --- a/packages/noports_core/lib/src/common/default_args.dart +++ b/packages/noports_core/lib/src/common/default_args.dart @@ -15,6 +15,7 @@ class DefaultArgs { /// value in seconds after which idle ssh tunnels will be closed static const idleTimeout = 15; + static const help = false; } class DefaultSSHNPArgs { diff --git a/packages/noports_core/lib/src/sshnp/sshnp.dart b/packages/noports_core/lib/src/sshnp/sshnp.dart index bb3bd6320..16caf63e8 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp.dart @@ -21,7 +21,6 @@ abstract class SSHNP { SSHNPParams params, { AtClient? atClient, AtClientGenerator? atClientGenerator, - UsageCallback? usageCallback, SSHRVGenerator? sshrvGenerator, }) async { atClient ??= await atClientGenerator?.call( @@ -33,27 +32,73 @@ abstract class SSHNP { } if (params.legacyDaemon) { - return SSHNPLegacyImpl( + return SSHNP.legacy( atClient: atClient, params: params, sshrvGenerator: sshrvGenerator); } if (!params.host.startsWith('@')) { - return SSHNPReverseImpl( + return SSHNP.reverse( atClient: atClient, params: params, sshrvGenerator: sshrvGenerator); } switch (SupportedSshClient.fromCliArg(params.sshClient)) { case SupportedSshClient.exec: - return SSHNPForwardExecImpl(atClient: atClient, params: params); + return SSHNP.forwardExec(atClient: atClient, params: params); case SupportedSshClient.dart: - return SSHNPForwardDartImpl(atClient: atClient, params: params); + return SSHNP.forwardDart(atClient: atClient, params: params); default: throw ArgumentError('Unsupported ssh client: ${params.sshClient}'); } } + /// Creates an SSHNP instance that is configured to communicate with legacy >= 3.0.0 <4.0.0 daemons + factory SSHNP.legacy({ + required AtClient atClient, + required SSHNPParams params, + SSHRVGenerator? sshrvGenerator, + }) => + SSHNPLegacyImpl( + atClient: atClient, + params: params, + sshrvGenerator: sshrvGenerator, + ); + + /// Creates an SSHNP instance that is configured to use reverse ssh tunneling + factory SSHNP.reverse({ + required AtClient atClient, + required SSHNPParams params, + SSHRVGenerator? sshrvGenerator, + }) => + SSHNPReverseImpl( + atClient: atClient, + params: params, + sshrvGenerator: sshrvGenerator, + ); + + /// Creates an SSHNP instance that is configured to use direct ssh tunneling by executing the ssh command + factory SSHNP.forwardExec({ + required AtClient atClient, + required SSHNPParams params, + }) => + SSHNPForwardExecImpl( + atClient: atClient, + params: params, + ); + + /// Creates an SSHNP instance that is configured to use direct ssh tunneling using a pure-dart SSHClient + factory SSHNP.forwardDart({ + required AtClient atClient, + required SSHNPParams params, + }) => + SSHNPForwardDartImpl( + atClient: atClient, + params: params, + ); + + /// The atClient to use for communicating with the atsign's secondary server AtClient get atClient; + /// The parameters used to configure this SSHNP instance SSHNPParams get params; /// Completes when the SSHNP instance is no longer doing anything diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart index c66d4e879..3a059c013 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart @@ -133,6 +133,10 @@ abstract class SSHNPImpl implements SSHNP { logger.hierarchicalLoggingEnabled = true; logger.logger.level = Level.SHOUT; + if (params.verbose) { + logger.logger.level = Level.INFO; + } + /// Set the namespace to the device's namespace AtClientPreference preference = atClient.getPreferences() ?? AtClientPreference(); @@ -144,14 +148,19 @@ abstract class SSHNPImpl implements SSHNP { // immediately set it to '' to avoid the program from attempting to // search for a public key file called 'false' if (params.sendSshPublicKey == 'false' || params.sendSshPublicKey.isEmpty) { + logger.warning('No ssh public key file will be sent to sshnpd'); publicKeyFileName = ''; } else if (path.normalize(params.sendSshPublicKey).contains('/') || path.normalize(params.sendSshPublicKey).contains(r'\')) { publicKeyFileName = path.normalize(path.absolute(params.sendSshPublicKey)); + logger.info( + 'Using absolute path for ssh public key file: $publicKeyFileName'); } else { publicKeyFileName = path.normalize('$sshHomeDirectory/${params.sendSshPublicKey}'); + logger.info( + 'Using default .ssh path for ssh public key file: $publicKeyFileName'); } /// Also call init @@ -160,7 +169,9 @@ abstract class SSHNPImpl implements SSHNP { @override Future init() async { + logger.info('Initializing SSHNPImpl'); if (_initializeStarted) { + logger.warning('Cancelling initialization: Already started'); return; } else { _initializeStarted = true; @@ -168,6 +179,7 @@ abstract class SSHNPImpl implements SSHNP { try { if (!(await atSignIsActivated(atClient, sshnpdAtSign))) { + logger.severe('Device address $sshnpdAtSign is not activated.'); throw ('Device address $sshnpdAtSign is not activated.'); } } catch (e, s) { @@ -182,12 +194,15 @@ abstract class SSHNPImpl implements SSHNP { // Check that the public key file exists if (publicKeyFileName.isNotEmpty && !File(publicKeyFileName).existsSync()) { + logger.info('Unable to find ssh public key file'); throw ('Unable to find ssh public key file : $publicKeyFileName'); } // Check that the private key file exists if (publicKeyFileName.isNotEmpty && !File(publicKeyFileName.replaceAll('.pub', '')).existsSync()) { + logger.info( + 'Unable to find matching ssh private key for public key: $publicKeyFileName'); throw ('Unable to find matching ssh private key for public key : $publicKeyFileName'); } @@ -195,12 +210,14 @@ abstract class SSHNPImpl implements SSHNP { // find a spare local port if (localPort == 0) { + logger.info('Finding a spare local port'); try { ServerSocket serverSocket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0); localPort = serverSocket.port; await serverSocket.close(); } catch (e, s) { + logger.info('Unable to find a spare local port'); throw SSHNPError('Unable to find a spare local port', error: e, stackTrace: s); } @@ -210,9 +227,11 @@ abstract class SSHNPImpl implements SSHNP { // If host has an @ then contact the sshrvd service for some ports if (host.startsWith('@')) { + logger.info('Host is an atSign, fetching host and port from sshrvd'); await getHostAndPortFromSshrvd(); } + logger.finer('Base initialization complete'); // N.B. Don't complete initialization here, subclasses will do that // This is in case they need to implement further initialization steps } @@ -270,7 +289,6 @@ abstract class SSHNPImpl implements SSHNP { logger.info('Session $sessionId connected successfully'); sshnpdAck = true; } else { - stderr.writeln('Remote sshnpd error: ${notification.value}'); sshnpdAck = true; sshnpdAckErrors = true; } @@ -285,8 +303,9 @@ abstract class SSHNPImpl implements SSHNP { {String sessionId = ""}) async { await atClient.notificationService .notify(NotificationParams.forUpdate(atKey, value: value), - onSuccess: (notification) { - logger.info('SUCCESS:$notification for: $sessionId with value: $value'); + onSuccess: (NotificationResult notification) { + logger.info( + 'SUCCESS:$notification for: $sessionId with key: ${atKey.toString()}'); }, onError: (notification) { logger.info('ERROR:$notification'); }); @@ -299,6 +318,7 @@ abstract class SSHNPImpl implements SSHNP { /// Is not called if remoteUserName was set via constructor @protected Future fetchRemoteUserName() async { + logger.info('Fetching remote username from sshnpd'); AtKey userNameRecordID = AtKey.fromString('$clientAtSign:username.$namespace$sshnpdAtSign'); try { @@ -329,7 +349,7 @@ abstract class SSHNPImpl implements SSHNP { logger.info('Set sshrvdPort to: $sshrvdPort'); sshrvdAck = true; }); - + logger.info('Started listening for sshrvd response'); AtKey ourSshrvdIdKey = AtKey() ..key = '${params.device}.${SSHRVD.namespace}' ..sharedBy = clientAtSign // shared by us @@ -340,26 +360,33 @@ abstract class SSHNPImpl implements SSHNP { ..namespaceAware = false ..ttr = -1 ..ttl = 10000); + logger.info('Sending notification to sshrvd: $ourSshrvdIdKey'); await notify(ourSshrvdIdKey, sessionId); + logger.info('Waiting for sshrvd response'); int counter = 0; while (!sshrvdAck) { await Future.delayed(Duration(milliseconds: 100)); counter++; if (counter == 100) { await cleanUp(); - stderr.writeln('sshnp: connection timeout to sshrvd $host service'); throw ('Connection timeout to sshrvd $host service\nhint: make sure host is valid and online'); } } } Future sharePublicKeyWithSshnpdIfRequired() async { - if (publicKeyFileName.isEmpty) return; + if (publicKeyFileName.isEmpty) { + logger.info('Skipped sharing public key with sshnpd: none provided'); + return; + } + logger.info('Sharing public key with sshnpd: $publicKeyFileName'); try { String toSshPublicKey = await File(publicKeyFileName).readAsString(); if (!toSshPublicKey.startsWith('ssh-')) { + logger + .severe('$publicKeyFileName does not look like a public key file'); throw ('$publicKeyFileName does not look like a public key file'); } AtKey sendOurPublicKeyToSshnpd = AtKey() @@ -371,9 +398,7 @@ abstract class SSHNPImpl implements SSHNP { ..ttl = 10000); await notify(sendOurPublicKeyToSshnpd, toSshPublicKey); } catch (e, s) { - stderr.writeln( - "Error opening or validating public key file or sending to remote atSign: $e"); - await cleanUpAfterReverseSsh(this); + await cleanUp(); throw SSHNPError( 'Error opening or validating public key file or sending to remote atSign', error: e, @@ -384,6 +409,7 @@ abstract class SSHNPImpl implements SSHNP { @protected Future waitForDaemonResponse() async { + logger.finer('Waiting for daemon response'); int counter = 0; // Timer to timeout after 10 Secs or after the Ack of connected/Errors while (!sshnpdAck) { @@ -502,6 +528,7 @@ abstract class SSHNPImpl implements SSHNP { @override FutureOr cleanUp() { + logger.info('Cleaning up SSHNPImpl'); // This is an intentional no-op to allow overrides to safely call super.cleanUp() } } diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart index fa9349409..467314ab4 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart @@ -18,6 +18,7 @@ mixin SSHNPReverseDirection on SSHNPImpl { @override Future init() async { await super.init(); + logger.info('Generating ephemeral keypair'); try { var (String ephemeralPublicKey, String ephemeralPrivateKey) = await generateSshKeys( @@ -28,6 +29,7 @@ mixin SSHNPReverseDirection on SSHNPImpl { sshPublicKey = ephemeralPublicKey; sshPrivateKey = ephemeralPrivateKey; } catch (e, s) { + logger.info('Failed to generate ephemeral keypair'); throw SSHNPError( 'Failed to generate ephemeral keypair', error: e, @@ -35,7 +37,9 @@ mixin SSHNPReverseDirection on SSHNPImpl { ); } + try { + logger.info('Adding ephemeral key to authorized_keys'); await addEphemeralKeyToAuthorizedKeys( sshPublicKey: sshPublicKey, localSshdPort: params.localSshdPort, diff --git a/packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_arg.dart b/packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_arg.dart index 380b1b259..cf4bdd9bb 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_arg.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_arg.dart @@ -44,6 +44,8 @@ class SSHNPArg { String get bashName => name.replaceAll('-', '_').toUpperCase(); + List get aliasList => ['--$name', ...aliases?.map((e) => '--$e') ?? [], '-$abbr']; + factory SSHNPArg.noArg() { return SSHNPArg(name: ''); } @@ -63,6 +65,13 @@ class SSHNPArg { } static List args = [ + const SSHNPArg( + name: 'help', + help: 'Print this usage information', + defaultsTo: DefaultArgs.help, + format: ArgFormat.flag, + commandLineOnly: true, + ), const SSHNPArg( name: 'key-file', abbr: 'k', @@ -114,6 +123,7 @@ class SSHNPArg { help: 'Public key file from ~/.ssh to be appended to authorized_hosts on the remote device', defaultsTo: DefaultSSHNPArgs.sendSshPublicKey, + commandLineOnly: true, ), const SSHNPArg( name: 'local-ssh-options', @@ -221,10 +231,14 @@ class SSHNPArg { static ArgParser createArgParser({ bool isCommandLine = true, bool withDefaults = true, + Iterable? includeList, }) { var parser = ArgParser(); // Basic arguments for (SSHNPArg arg in SSHNPArg.args) { + if (includeList != null && !includeList.contains(arg.name)) { + continue; + } if (arg.commandLineOnly && !isCommandLine) { continue; } diff --git a/packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_params.dart b/packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_params.dart index 4f41fc655..6406277f8 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_params.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_params/sshnp_params.dart @@ -128,8 +128,8 @@ class SSHNPParams { SSHNPParams.fromPartial(SSHNPPartialParams.fromJson(json)); factory SSHNPParams.fromPartial(SSHNPPartialParams partial) { - partial.clientAtSign ?? (throw ArgumentError('clientAtSign is mandatory')); - partial.sshnpdAtSign ?? (throw ArgumentError('sshnpdAtSign is mandatory')); + partial.clientAtSign ?? (throw ArgumentError('from is mandatory')); + partial.sshnpdAtSign ?? (throw ArgumentError('to is mandatory')); partial.host ?? (throw ArgumentError('host is mandatory')); return SSHNPParams( diff --git a/packages/noports_core/lib/sshnp_params.dart b/packages/noports_core/lib/sshnp_params.dart new file mode 100644 index 000000000..b7b776350 --- /dev/null +++ b/packages/noports_core/lib/sshnp_params.dart @@ -0,0 +1,6 @@ +library noports_core_sshnp_params; + +export 'src/sshnp/sshnp_params/config_file_repository.dart'; +export 'src/sshnp/sshnp_params/config_key_repository.dart'; +export 'src/sshnp/sshnp_params/sshnp_params.dart'; +export 'src/sshnp/sshnp_params/sshnp_arg.dart'; \ No newline at end of file diff --git a/packages/sshnoports/bin/sshnp.dart b/packages/sshnoports/bin/sshnp.dart index ee44fdcbc..ad6520ed3 100644 --- a/packages/sshnoports/bin/sshnp.dart +++ b/packages/sshnoports/bin/sshnp.dart @@ -7,100 +7,135 @@ import 'package:at_utils/at_logger.dart'; // local packages import 'package:noports_core/sshnp.dart'; -import 'package:noports_core/utils.dart'; +import 'package:noports_core/sshnp_params.dart' show SSHNPArg; import 'package:sshnoports/create_at_client_cli.dart'; import 'package:sshnoports/version.dart'; void main(List args) async { AtSignLogger.root_level = 'SHOUT'; AtSignLogger.defaultLoggingHandler = AtSignLogger.stdErrLoggingHandler; - late final SSHNP sshnp; + late final SSHNPParams params; + SSHNP? sshnp; - try {} catch (error) { - stderr.writeln(error.toString()); - exit(1); - } + // Manually check if the verbose flag is set + Set verboseSet = SSHNPArg.fromName('verbose').aliasList.toSet(); + final bool verbose = args.toSet().intersection(verboseSet).isNotEmpty; + + // Manually check if the help flag is set + Set helpSet = SSHNPArg.fromName('help').aliasList.toSet(); + final bool help = args.toSet().intersection(helpSet).isNotEmpty; - usageCallback(e, s) { + if (help) { printVersion(); stdout.writeln(SSHNPPartialParams.parser.usage); - stderr.writeln('\n$e'); - } - - try { - params = SSHNPParams.fromPartial(SSHNPPartialParams.fromArgs(args)); - sshnp = await SSHNP.fromParams( - params, - atClientGenerator: (SSHNPParams params, String sessionId) => - createAtClientCli( - homeDirectory: params.homeDirectory, - atsign: params.clientAtSign, - namespace: '${params.device}.sshnp', - pathExtension: sessionId, - atKeysFilePath: params.atKeysFilePath, - rootDomain: params.rootDomain, - ), - usageCallback: usageCallback, - ); - } on ArgumentError catch (e, s) { - usageCallback(e, s); - exit(1); + exit(0); } ProcessSignal.sigint.watch().listen((signal) async { - await cleanUpAfterReverseSsh(sshnp); + await sshnp?.cleanUp(); exit(1); }); await runZonedGuarded(() async { - if (params.listDevices) { - stdout.writeln('Searching for devices...'); - var (active, off, info) = await sshnp.listDevices(); - if (active.isEmpty && off.isEmpty) { - stdout.writeln('[X] No devices found\n'); - stdout.writeln( - 'Note: only devices with sshnpd version 3.4.0 or higher are supported by this command.'); - stdout.writeln( - 'Please update your devices to sshnpd version >= 3.4.0 and try again.'); + try { + params = SSHNPParams.fromPartial(SSHNPPartialParams.fromArgs(args)); + sshnp = await SSHNP + .fromParams( + params, + atClientGenerator: (SSHNPParams params, String sessionId) => + createAtClientCli( + homeDirectory: params.homeDirectory, + atsign: params.clientAtSign, + namespace: '${params.device}.sshnp', + pathExtension: sessionId, + atKeysFilePath: params.atKeysFilePath, + rootDomain: params.rootDomain, + ), + ) + .catchError((e) { + if (e.stackTrace != null) { + Error.throwWithStackTrace(e, e.stackTrace!); + } + throw e; + }); + + if (params.listDevices) { + stdout.writeln('Searching for devices...'); + var (active, off, info) = await sshnp!.listDevices(); + printDevices(active, off, info); exit(0); } - stdout.writeln('Active Devices:'); - _printDevices(active, info); - stdout.writeln('Inactive Devices:'); - _printDevices(off, info); - exit(0); - } + await sshnp!.initialized; + + SSHNPResult res = await sshnp!.run(); - await sshnp.init(); - SSHNPResult res = await sshnp.run(); - if (res is SSHNPError) { - stderr.write('$res\n'); + if (res is SSHNPError) { + if (res.stackTrace != null) { + Error.throwWithStackTrace(res, res.stackTrace!); + } + throw res; + } + if (res is SSHNPSuccess) { + stdout.write('$res\n'); + await sshnp!.done; + exit(0); + } + } on ArgumentError catch (error, stackTrace) { + usageCallback(error, stackTrace); + exit(1); + } on SSHNPError catch (error, stackTrace) { + stderr.writeln(error.toString()); + if (verbose) { + stderr.writeln('\nStack Trace: ${stackTrace.toString()}'); + } + + await sshnp?.cleanUp(); exit(1); - } - if (res is SSHNPSuccess) { - stdout.write('$res\n'); - await sshnp.done; - exit(0); } }, (Object error, StackTrace stackTrace) async { - stderr.writeln(error.toString()); - - if (params.verbose) { + if (error is ArgumentError) return; + if (error is SSHNPError) return; + stderr.writeln('Unknown error: ${error.toString()}'); + if (verbose) { stderr.writeln('\nStack Trace: ${stackTrace.toString()}'); } - await cleanUpAfterReverseSsh(sshnp); - - await stderr.flush().timeout(Duration(milliseconds: 100)); + await sshnp?.cleanUp(); exit(1); }); } -void _printDevices(Iterable devices, Map info) { +void usageCallback(Object e, StackTrace s) { + printVersion(); + stdout.writeln(SSHNPPartialParams.parser.usage); + stderr.writeln('\n$e'); +} + +void printDevices( + Iterable active, + Iterable off, + Map info, +) { + if (active.isEmpty && off.isEmpty) { + stdout.writeln('[X] No devices found\n'); + stdout.writeln( + 'Note: only devices with sshnpd version 3.4.0 or higher are supported by this command.'); + stdout.writeln( + 'Please update your devices to sshnpd version >= 3.4.0 and try again.'); + exit(0); + } + + stdout.writeln('Active Devices:'); + printDeviceList(active, info); + stdout.writeln('Inactive Devices:'); + printDeviceList(off, info); +} + +void printDeviceList(Iterable devices, Map info) { if (devices.isEmpty) { - stdout.writeln(' [X] No devices found'); + stdout.writeln(' No devices found'); return; } for (var device in devices) { diff --git a/packages/sshnp_gui/lib/src/controllers/config_controller.dart b/packages/sshnp_gui/lib/src/controllers/config_controller.dart index 9623d0b01..abb6838e2 100644 --- a/packages/sshnp_gui/lib/src/controllers/config_controller.dart +++ b/packages/sshnp_gui/lib/src/controllers/config_controller.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:at_client_mobile/at_client_mobile.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:noports_core/config_repository.dart'; +import 'package:noports_core/sshnp_params.dart'; import 'package:sshnp_gui/src/presentation/widgets/utility/custom_snack_bar.dart'; enum ConfigFileWriteState { create, update } diff --git a/packages/sshnp_gui/lib/src/presentation/widgets/home_screen_actions/home_screen_action_callbacks.dart b/packages/sshnp_gui/lib/src/presentation/widgets/home_screen_actions/home_screen_action_callbacks.dart index c296efbde..139abba59 100644 --- a/packages/sshnp_gui/lib/src/presentation/widgets/home_screen_actions/home_screen_action_callbacks.dart +++ b/packages/sshnp_gui/lib/src/presentation/widgets/home_screen_actions/home_screen_action_callbacks.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:noports_core/config_repository.dart'; +import 'package:noports_core/sshnp_params.dart'; import 'package:sshnp_gui/src/controllers/config_controller.dart'; import 'package:sshnp_gui/src/presentation/widgets/home_screen_actions/home_screen_import_dialog.dart'; import 'package:sshnp_gui/src/presentation/widgets/utility/custom_snack_bar.dart'; diff --git a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_action_callbacks.dart b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_action_callbacks.dart index 5b8c101d7..e81bec965 100644 --- a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_action_callbacks.dart +++ b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_action_callbacks.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:noports_core/utils.dart'; -import 'package:noports_core/config_repository.dart'; +import 'package:noports_core/sshnp_params.dart'; import 'package:sshnp_gui/src/controllers/config_controller.dart'; import 'package:sshnp_gui/src/controllers/navigation_controller.dart'; import 'package:sshnp_gui/src/presentation/widgets/profile_actions/profile_delete_dialog.dart'; From 77dfdbad972fc2a01611980b530e9d75b1131893 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 14:42:28 -0400 Subject: [PATCH 24/49] refactor: separate forward and reverse mixins to separate file --- .../sshnp_impl/sshnp_forward_dart_impl.dart | 41 ++----------- .../sshnp_impl/sshnp_forward_direction.dart | 58 +++++++++++++++++++ .../sshnp_impl/sshnp_forward_exec_impl.dart | 36 ++---------- .../sshnp/sshnp_impl/sshnp_legacy_impl.dart | 2 +- ...ixin.dart => sshnp_reverse_direction.dart} | 16 +---- .../sshnp/sshnp_impl/sshnp_reverse_impl.dart | 2 +- .../noports_core/lib/src/sshnp/utils.dart | 2 +- 7 files changed, 72 insertions(+), 85 deletions(-) create mode 100644 packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_direction.dart rename packages/noports_core/lib/src/sshnp/sshnp_impl/{sshnp_impl_mixin.dart => sshnp_reverse_direction.dart} (84%) diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart index 8df9503d7..3cdbf8855 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart @@ -3,9 +3,9 @@ import 'dart:io'; import 'package:at_client/at_client.dart'; import 'package:dartssh2/dartssh2.dart'; -import 'package:noports_core/src/common/utils.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_forward_direction.dart'; import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl.dart'; -import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart'; + import 'package:noports_core/sshnp.dart'; class SSHNPForwardDartImpl extends SSHNPImpl with SSHNPForwardDirection { @@ -16,46 +16,13 @@ class SSHNPForwardDartImpl extends SSHNPImpl with SSHNPForwardDirection { @override Future run() async { - logger.info( - 'Requesting daemon to set up socket tunnel for direct ssh session'); -// send request to the daemon via notification - await notify( - AtKey() - ..key = 'ssh_request' - ..namespace = namespace - ..sharedBy = clientAtSign - ..sharedWith = sshnpdAtSign - ..metadata = (Metadata() - ..ttr = -1 - ..ttl = 10000), - signAndWrapAndJsonEncode(atClient, { - 'direct': true, - 'sessionId': sessionId, - 'host': host, - 'port': port - }), - sessionId: sessionId); - - bool acked = await waitForDaemonResponse(); - if (!acked) { - var error = SSHNPError( - 'sshnp timed out: waiting for daemon response\nhint: make sure the device is online'); - doneCompleter.completeError(error); + var error = await requestSocketTunnelFromDaemon(); + if (error != null) { return error; } - if (sshnpdAckErrors) { - var error = - SSHNPError('sshnp failed: with sshnpd acknowledgement errors'); - doneCompleter.completeError(error); - return error; - } - // 1) Execute an ssh command setting up local port forwarding. - // Note that this is very similar to what the daemon does when we - // ask for a reverse ssh logger.info( 'Starting direct ssh session for ${params.username} to $host on port $sshrvdPort with forwardLocal of $localPort'); - try { late final SSHClient client; diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_direction.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_direction.dart new file mode 100644 index 000000000..aa33d468a --- /dev/null +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_direction.dart @@ -0,0 +1,58 @@ +import 'dart:async'; + +import 'package:at_client/at_client.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl.dart'; +import 'package:noports_core/src/sshnp/sshnp_result.dart'; +import 'package:noports_core/utils.dart'; + + + +mixin SSHNPForwardDirection on SSHNPImpl { + // Direct ssh is only ever done with a sshrvd host + // So we should expect that sshrvdPort is never null + // Hence overriding the getter and setter to make it non-nullable + late int _sshrvdPort; + @override + int get sshrvdPort => _sshrvdPort; + @override + set sshrvdPort(int? port) => _sshrvdPort = port!; + + Future requestSocketTunnelFromDaemon() async { + logger.info( + 'Requesting daemon to set up socket tunnel for direct ssh session'); +// send request to the daemon via notification + await notify( + AtKey() + ..key = 'ssh_request' + ..namespace = namespace + ..sharedBy = clientAtSign + ..sharedWith = sshnpdAtSign + ..metadata = (Metadata() + ..ttr = -1 + ..ttl = 10000), + signAndWrapAndJsonEncode(atClient, { + 'direct': true, + 'sessionId': sessionId, + 'host': host, + 'port': port + }), + sessionId: sessionId); + + bool acked = await waitForDaemonResponse(); + if (!acked) { + var error = SSHNPError( + 'sshnp timed out: waiting for daemon response\nhint: make sure the device is online'); + doneCompleter.completeError(error); + return error; + } + + if (sshnpdAckErrors) { + var error = + SSHNPError('sshnp failed: with sshnpd acknowledgement errors'); + doneCompleter.completeError(error); + return error; + } + + return null; + } +} diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart index 51515cd41..310374d64 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart @@ -3,9 +3,8 @@ import 'dart:convert'; import 'dart:io'; import 'package:at_client/at_client.dart' hide StringBuffer; -import 'package:noports_core/src/common/utils.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_forward_direction.dart'; import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl.dart'; -import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart'; import 'package:noports_core/sshnp.dart'; import 'package:path/path.dart' as path; @@ -17,38 +16,11 @@ class SSHNPForwardExecImpl extends SSHNPImpl with SSHNPForwardDirection { @override Future run() async { - logger.info( - 'Requesting daemon to set up socket tunnel for direct ssh session'); - // send request to the daemon via notification - await notify( - AtKey() - ..key = 'ssh_request' - ..namespace = namespace - ..sharedBy = clientAtSign - ..sharedWith = sshnpdAtSign - ..metadata = (Metadata() - ..ttr = -1 - ..ttl = 10000), - signAndWrapAndJsonEncode(atClient, { - 'direct': true, - 'sessionId': sessionId, - 'host': host, - 'port': port - }), - sessionId: sessionId); - - bool acked = await waitForDaemonResponse(); - if (!acked) { - return SSHNPError( - 'sshnp timed out: waiting for daemon response\nhint: make sure the device is online'); + var error = await requestSocketTunnelFromDaemon(); + if (error != null) { + return error; } - if (sshnpdAckErrors) { - return SSHNPError('sshnp failed: with sshnpd acknowledgement errors'); - } - // 1) Execute an ssh command setting up local port forwarding. - // Note that this is very similar to what the daemon does when we - // ask for a reverse ssh logger.info( 'Starting direct ssh session for ${params.username} to $host on port $sshrvdPort with forwardLocal of $localPort'); diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart index 1c7fbaf28..378f8d709 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:at_client/at_client.dart'; import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl.dart'; -import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_reverse_direction.dart'; import 'package:noports_core/sshnp.dart'; import 'package:noports_core/sshrv.dart'; diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_direction.dart similarity index 84% rename from packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart rename to packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_direction.dart index 467314ab4..9ba4ddf78 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_direction.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl.dart'; import 'package:noports_core/src/sshnp/sshnp_result.dart'; import 'package:noports_core/utils.dart'; @@ -37,7 +39,6 @@ mixin SSHNPReverseDirection on SSHNPImpl { ); } - try { logger.info('Adding ephemeral key to authorized_keys'); await addEphemeralKeyToAuthorizedKeys( @@ -62,15 +63,4 @@ mixin SSHNPReverseDirection on SSHNPImpl { } bool get usingSshrv => sshrvdPort != null; -} - -mixin SSHNPForwardDirection on SSHNPImpl { - // Direct ssh is only ever done with a sshrvd host - // So we should expect that sshrvdPort is never null - // Hence overriding the getter and setter to make it non-nullable - late int _sshrvdPort; - @override - int get sshrvdPort => _sshrvdPort; - @override - set sshrvdPort(int? port) => _sshrvdPort = port!; -} +} \ No newline at end of file diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart index 32777a643..ef20591bd 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart @@ -1,7 +1,7 @@ import 'package:at_client/at_client.dart'; import 'package:noports_core/src/common/utils.dart'; import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl.dart'; -import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_reverse_direction.dart'; import 'package:noports_core/sshnp.dart'; import 'package:noports_core/sshrv.dart'; diff --git a/packages/noports_core/lib/src/sshnp/utils.dart b/packages/noports_core/lib/src/sshnp/utils.dart index f2d1617f6..a32d0f293 100644 --- a/packages/noports_core/lib/src/sshnp/utils.dart +++ b/packages/noports_core/lib/src/sshnp/utils.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:noports_core/src/common/utils.dart'; import 'package:at_utils/at_logger.dart'; import 'package:noports_core/src/sshnp/sshnp.dart'; -import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl_mixin.dart'; +import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_reverse_direction.dart'; Completer wrapInCompleter(Future future) { final completer = Completer(); From e5fb53b8c3ea432af4b9c2af961ae2eed93d628f Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 14:43:35 -0400 Subject: [PATCH 25/49] style: remove whitespace --- .../lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart | 1 - .../lib/src/sshnp/sshnp_impl/sshnp_forward_direction.dart | 2 -- 2 files changed, 3 deletions(-) diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart index 3cdbf8855..01736714c 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart @@ -5,7 +5,6 @@ import 'package:at_client/at_client.dart'; import 'package:dartssh2/dartssh2.dart'; import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_forward_direction.dart'; import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl.dart'; - import 'package:noports_core/sshnp.dart'; class SSHNPForwardDartImpl extends SSHNPImpl with SSHNPForwardDirection { diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_direction.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_direction.dart index aa33d468a..c22ab97f1 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_direction.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_direction.dart @@ -5,8 +5,6 @@ import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl.dart'; import 'package:noports_core/src/sshnp/sshnp_result.dart'; import 'package:noports_core/utils.dart'; - - mixin SSHNPForwardDirection on SSHNPImpl { // Direct ssh is only ever done with a sshrvd host // So we should expect that sshrvdPort is never null From 446cb3aaa40f0dca08136286bcf2f360dd367ac4 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 15:10:07 -0400 Subject: [PATCH 26/49] ci: set context to the root of the repo for all local docker builds --- .github/workflows/dockerhub_sshnpd.yml | 2 +- .github/workflows/multibuild.yaml | 3 -- .../workflows/update_python_requirements.yaml | 4 +-- .../sshnoports/templates/docker/Dockerfile | 9 +++--- .../templates/docker/Dockerfile.activate | 7 +++-- .../docker/docker-compose.local.yaml | 8 +++--- tests/end2end_tests/image/Dockerfile | 28 +++++++++---------- tools/manual-docker/blank/docker-compose.yaml | 4 +-- .../manual-docker/branch/docker-compose.yaml | 4 +-- tools/manual-docker/local/docker-compose.yaml | 2 +- .../manual-docker/release/docker-compose.yaml | 4 +-- tools/package-macos-arm64.sh | 8 ++++-- 12 files changed, 42 insertions(+), 41 deletions(-) diff --git a/.github/workflows/dockerhub_sshnpd.yml b/.github/workflows/dockerhub_sshnpd.yml index 5b650f759..18f907847 100644 --- a/.github/workflows/dockerhub_sshnpd.yml +++ b/.github/workflows/dockerhub_sshnpd.yml @@ -42,7 +42,7 @@ jobs: - name: Build and push uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 with: - context: ./packages/sshnoports + context: . file: ${{ matrix.dockerfile }} platforms: linux/amd64,linux/arm64,linux/arm/v7 push: true diff --git a/.github/workflows/multibuild.yaml b/.github/workflows/multibuild.yaml index 4fba2aae2..6ef731769 100644 --- a/.github/workflows/multibuild.yaml +++ b/.github/workflows/multibuild.yaml @@ -23,9 +23,6 @@ jobs: steps: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - - - - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 # v1.5.1 - run: mkdir sshnp - run: mkdir tarball diff --git a/.github/workflows/update_python_requirements.yaml b/.github/workflows/update_python_requirements.yaml index 1ed871ede..7f9850357 100644 --- a/.github/workflows/update_python_requirements.yaml +++ b/.github/workflows/update_python_requirements.yaml @@ -7,7 +7,7 @@ on: permissions: pull-requests: write - + jobs: bump_requirements: runs-on: ubuntu-latest @@ -32,7 +32,7 @@ jobs: uses: abatilo/actions-poetry@192395c0d10c082a7c62294ab5d9a9de40e48974 # v2.3.0 with: poetry-version: '1.6.1' - + - name: Bump Python dependencies if: ${{ github.actor == 'dependabot[bot]' }} run: | diff --git a/packages/sshnoports/templates/docker/Dockerfile b/packages/sshnoports/templates/docker/Dockerfile index 22ca329e5..4943db7cb 100644 --- a/packages/sshnoports/templates/docker/Dockerfile +++ b/packages/sshnoports/templates/docker/Dockerfile @@ -1,4 +1,5 @@ FROM dart:3.1.2@sha256:05f1c1035f0090f8fcc340630f09782215ae2dfc6c17941756d3a716b16f8ec5 AS buildimage +ENV PACKAGEDIR=packages/sshnoports ENV BINARYDIR=/usr/local/at SHELL ["/bin/bash", "-c"] WORKDIR /app @@ -6,10 +7,10 @@ COPY . . RUN \ set -eux ; \ mkdir -p ${BINARYDIR} ; \ - dart pub get ; \ - dart pub update ; \ - dart compile exe bin/sshnpd.dart -o ${BINARYDIR}/sshnpd ; \ - dart compile exe bin/sshrv.dart -o ${BINARYDIR}/sshrv + dart pub get -C ${PACKAGEDIR}; \ + dart pub update -C ${PACKAGEDIR}; \ + dart compile exe ${PACKAGEDIR}/bin/sshnpd.dart -o ${BINARYDIR}/sshnpd ; \ + dart compile exe ${PACKAGEDIR}/bin/sshrv.dart -o ${BINARYDIR}/sshrv # Second stage of build FROM debian-slim FROM debian:stable-20230919-slim@sha256:149e944a6f4855f9738baf4ddd79fc2f218e6440218223fa9017aebc1e45f1f5 diff --git a/packages/sshnoports/templates/docker/Dockerfile.activate b/packages/sshnoports/templates/docker/Dockerfile.activate index 3d068a8b6..11a4d58d1 100644 --- a/packages/sshnoports/templates/docker/Dockerfile.activate +++ b/packages/sshnoports/templates/docker/Dockerfile.activate @@ -1,4 +1,5 @@ FROM dart:3.1.2@sha256:05f1c1035f0090f8fcc340630f09782215ae2dfc6c17941756d3a716b16f8ec5 AS buildimage +ENV PACKAGEDIR=packages/sshnoports ENV BINARYDIR=/usr/local/at SHELL ["/bin/bash", "-c"] WORKDIR /app @@ -6,9 +7,9 @@ COPY . . RUN \ set -eux ; \ mkdir -p ${BINARYDIR} ; \ - dart pub get ; \ - dart pub update ; \ - dart compile exe bin/activate_cli.dart -o ${BINARYDIR}/at_activate + dart pub get -C ${PACKAGEDIR}; \ + dart pub update -C ${PACKAGEDIR}; \ + dart compile exe ${PACKAGEDIR}/bin/activate_cli.dart -o ${BINARYDIR}/at_activate # Second stage of build FROM debian-slim FROM debian:stable-20230919-slim@sha256:149e944a6f4855f9738baf4ddd79fc2f218e6440218223fa9017aebc1e45f1f5 diff --git a/packages/sshnoports/templates/docker/docker-compose.local.yaml b/packages/sshnoports/templates/docker/docker-compose.local.yaml index b5192ded8..3cf20ef63 100644 --- a/packages/sshnoports/templates/docker/docker-compose.local.yaml +++ b/packages/sshnoports/templates/docker/docker-compose.local.yaml @@ -2,15 +2,15 @@ version: "3.0" services: activate: build: - context: ../../ - dockerfile: ./templates/docker/Dockerfile.activate + context: ../../../../ + dockerfile: ./packages/sshnoports/templates/docker/Dockerfile.activate volumes: - ${HOME}/.atsign/keys:/atsign/.atsign/keys command: -a "${TO}" -c "${TO_CRAM}" sshnpd: build: - context: ../../ - dockerfile: ./templates/docker/Dockerfile + context: ../../../../ + dockerfile: ./packages/sshnoports/templates/docker/Dockerfile volumes: - ${HOME}/.atsign/keys:/atsign/.atsign/keys command: -a "${TO}" -m "${FROM}" -d "${DEVICE}" -s -u -v diff --git a/tests/end2end_tests/image/Dockerfile b/tests/end2end_tests/image/Dockerfile index a94e3b392..2cfa2b207 100644 --- a/tests/end2end_tests/image/Dockerfile +++ b/tests/end2end_tests/image/Dockerfile @@ -30,6 +30,7 @@ FROM dart:3.1.2@sha256:05f1c1035f0090f8fcc340630f09782215ae2dfc6c17941756d3a716b ENV URL=https://github.com/atsign-foundation/sshnoports.git ENV REPO_DIR=/app/repo +ENV PACKAGE_DIR=${REPO_DIR}/packages/sshnoports ENV OUTPUT_DIR=/app/output ARG branch=trunk @@ -43,13 +44,12 @@ RUN set -eux ; \ git clone -b ${branch} --single-branch ${URL} . ; \ dart pub get; \ dart run melos bootstrap --scope="noports_core" --scope="sshnoports"; \ - cd packages/sshnoports ; \ - dart pub get ; \ - dart compile exe ${REPO_DIR}/packages/sshnoports/bin/sshnp.dart -o ${OUTPUT_DIR}/sshnp ; \ - dart compile exe ${REPO_DIR}/packages/sshnoports/bin/sshnpd.dart -o ${OUTPUT_DIR}/sshnpd ; \ - dart compile exe ${REPO_DIR}/packages/sshnoports/bin/sshrv.dart -o ${OUTPUT_DIR}/sshrv ; \ - dart compile exe ${REPO_DIR}/packages/sshnoports/bin/sshrvd.dart -o ${OUTPUT_DIR}/sshrvd ; \ - dart compile exe ${REPO_DIR}/packages/sshnoports/bin/activate_cli.dart -o ${OUTPUT_DIR}/at_activate ; + dart pub get -C ${PACKAGE_DIR}; \ + dart compile exe ${PACKAGE_DIR}/bin/sshnp.dart -o ${OUTPUT_DIR}/sshnp ; \ + dart compile exe ${PACKAGE_DIR}/bin/sshnpd.dart -o ${OUTPUT_DIR}/sshnpd ; \ + dart compile exe ${PACKAGE_DIR}/bin/sshrv.dart -o ${OUTPUT_DIR}/sshrv ; \ + dart compile exe ${PACKAGE_DIR}/bin/sshrvd.dart -o ${OUTPUT_DIR}/sshrvd ; \ + dart compile exe ${PACKAGE_DIR}/bin/activate_cli.dart -o ${OUTPUT_DIR}/at_activate ; # RUNTIME BRANCH FROM base AS runtime-branch @@ -68,6 +68,7 @@ ENTRYPOINT cp -r /mount/. ${HOMEDIR} && sudo service ssh start && sh ${HOMEDIR}/ FROM dart:3.1.2@sha256:05f1c1035f0090f8fcc340630f09782215ae2dfc6c17941756d3a716b16f8ec5 AS build-local ENV REPO_DIR=/app/repo +ENV PACKAGE_DIR=${REPO_DIR}/packages/sshnoports ENV OUTPUT_DIR=/app/output RUN mkdir -p ${REPO_DIR} ${OUTPUT_DIR} ; @@ -79,13 +80,12 @@ RUN set -eux ; \ cd ${REPO_DIR}; \ dart pub get; \ dart run melos bootstrap --scope="noports_core" --scope="sshnoports"; \ - cd packages/sshnoports ; \ - dart pub get ; \ - dart compile exe ${REPO_DIR}/packages/sshnoports/bin/sshnp.dart -o ${OUTPUT_DIR}/sshnp ; \ - dart compile exe ${REPO_DIR}/packages/sshnoports/bin/sshnpd.dart -o ${OUTPUT_DIR}/sshnpd ; \ - dart compile exe ${REPO_DIR}/packages/sshnoports/bin/sshrv.dart -o ${OUTPUT_DIR}/sshrv ; \ - dart compile exe ${REPO_DIR}/packages/sshnoports/bin/sshrvd.dart -o ${OUTPUT_DIR}/sshrvd ; \ - dart compile exe ${REPO_DIR}/packages/sshnoports/bin/activate_cli.dart -o ${OUTPUT_DIR}/at_activate ; + dart pub get -C ${PACKAGE_DIR}; \ + dart compile exe ${PACKAGE_DIR}/bin/sshnp.dart -o ${OUTPUT_DIR}/sshnp ; \ + dart compile exe ${PACKAGE_DIR}/bin/sshnpd.dart -o ${OUTPUT_DIR}/sshnpd ; \ + dart compile exe ${PACKAGE_DIR}/bin/sshrv.dart -o ${OUTPUT_DIR}/sshrv ; \ + dart compile exe ${PACKAGE_DIR}/bin/sshrvd.dart -o ${OUTPUT_DIR}/sshrvd ; \ + dart compile exe ${PACKAGE_DIR}/bin/activate_cli.dart -o ${OUTPUT_DIR}/at_activate ; # RUNTIME LOCAL FROM base AS runtime-local diff --git a/tools/manual-docker/blank/docker-compose.yaml b/tools/manual-docker/blank/docker-compose.yaml index caa2ca3a9..2b90a9a31 100644 --- a/tools/manual-docker/blank/docker-compose.yaml +++ b/tools/manual-docker/blank/docker-compose.yaml @@ -4,8 +4,8 @@ version: '3.8' services: image-manual-blank: build: - context: ../../../tests/end2end_tests/image/ - dockerfile: ./Dockerfile + context: ../../../ + dockerfile: ./tests/end2end_tests/image/Dockerfile target: manual-blank image: atsigncompany/sshnp-e2e-manual:blank deploy: diff --git a/tools/manual-docker/branch/docker-compose.yaml b/tools/manual-docker/branch/docker-compose.yaml index 5186fc782..13c3953ce 100644 --- a/tools/manual-docker/branch/docker-compose.yaml +++ b/tools/manual-docker/branch/docker-compose.yaml @@ -4,8 +4,8 @@ version: '3.8' services: image-manual-branch: build: - context: ../../../tests/end2end_tests/image/ - dockerfile: ./Dockerfile + context: ../../../ + dockerfile: ./tests/end2end_tests/image/Dockerfile target: manual-branch args: - branch=trunk diff --git a/tools/manual-docker/local/docker-compose.yaml b/tools/manual-docker/local/docker-compose.yaml index 2e8dbb4f5..36030b4ca 100644 --- a/tools/manual-docker/local/docker-compose.yaml +++ b/tools/manual-docker/local/docker-compose.yaml @@ -5,7 +5,7 @@ services: image-manual-local: build: context: ../../../ - dockerfile: tests/end2end_tests/image/Dockerfile + dockerfile: ./tests/end2end_tests/image/Dockerfile target: manual-local image: atsigncompany/sshnp-e2e-manual:local deploy: diff --git a/tools/manual-docker/release/docker-compose.yaml b/tools/manual-docker/release/docker-compose.yaml index ee18fbbe4..a333f07cc 100644 --- a/tools/manual-docker/release/docker-compose.yaml +++ b/tools/manual-docker/release/docker-compose.yaml @@ -4,8 +4,8 @@ version: '3.8' services: image-manual-release: build: - context: ../../../tests/end2end_tests/image/ - dockerfile: ./Dockerfile + context: ../../../ + dockerfile: ./tests/end2end_tests/image/Dockerfile target: manual-release image: atsigncompany/sshnp-e2e-manual:release deploy: diff --git a/tools/package-macos-arm64.sh b/tools/package-macos-arm64.sh index 5fba80731..27d9fafbb 100755 --- a/tools/package-macos-arm64.sh +++ b/tools/package-macos-arm64.sh @@ -21,13 +21,13 @@ else DART=$(which dart) fi -restore_backup() { +restore_backup_and_exit() { mv "$SRC_DIR/pubspec_overrides.back.yaml" "$SRC_DIR/pubspec_overrides.yaml" + exit "$1" } mv "$SRC_DIR/pubspec_overrides.yaml" "$SRC_DIR/pubspec_overrides.back.yaml" -eval "$DART pub get -C $SRC_DIR" || restore_backup -restore_backup +eval "$DART pub get -C $SRC_DIR" || restore_backup_and_exit 1 OUTPUT_DIR_PATH="$ROOT_DIRECTORY/build/macos-arm64" OUTPUT_DIR="$OUTPUT_DIR_PATH/sshnp" @@ -43,3 +43,5 @@ eval "$DART compile exe -o $OUTPUT_DIR/at_activate $SRC_DIR/bin/activate_cli.dar cp -r "$SRC_DIR/templates" "$OUTPUT_DIR/templates"; cp "$SRC_DIR"/LICENSE "$OUTPUT_DIR/"; + +restore_backup_and_exit 0 From aa4fe66f4d55ff4948f6e8956ecf7519b9335772 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 15:15:58 -0400 Subject: [PATCH 27/49] fix: path to startup file --- packages/sshnoports/templates/docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sshnoports/templates/docker/Dockerfile b/packages/sshnoports/templates/docker/Dockerfile index 4943db7cb..e7df0adaf 100644 --- a/packages/sshnoports/templates/docker/Dockerfile +++ b/packages/sshnoports/templates/docker/Dockerfile @@ -20,7 +20,7 @@ ENV BINARYDIR=/usr/local/at ENV USER_ID=1024 ENV GROUP_ID=1024 -COPY --from=buildimage /app/templates/docker/.startup.sh ${HOMEDIR}/ +COPY --from=buildimage /app/packages/sshnoports/templates/docker/.startup.sh ${HOMEDIR}/ RUN \ set -eux ; \ apt-get update ; \ From 608a1a19501934697918c3a6b0413732796b568f Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 16:52:30 -0400 Subject: [PATCH 28/49] chore: cleanup e2e code paths --- .../lib/src/common/default_args.dart | 2 +- .../sshnp_impl/sshnp_forward_dart_impl.dart | 30 +++---- .../sshnp_impl/sshnp_forward_direction.dart | 15 +++- .../sshnp_impl/sshnp_forward_exec_impl.dart | 6 +- .../lib/src/sshnp/sshnp_impl/sshnp_impl.dart | 12 +++ .../sshnp/sshnp_impl/sshnp_legacy_impl.dart | 24 +++--- .../sshnp_impl/sshnp_reverse_direction.dart | 2 - .../sshnp/sshnp_impl/sshnp_reverse_impl.dart | 14 +++- .../lib/src/sshnp/sshnp_result.dart | 79 ++++++++++++------- packages/sshnoports/bin/sshnp.dart | 4 +- .../sshnoports/lib/create_at_client_cli.dart | 5 +- packages/sshnoports/pubspec.lock | 16 ++-- .../profile_actions/profile_run_action.dart | 4 +- .../profile_terminal_action.dart | 2 +- 14 files changed, 128 insertions(+), 87 deletions(-) diff --git a/packages/noports_core/lib/src/common/default_args.dart b/packages/noports_core/lib/src/common/default_args.dart index a0b6f5958..3457e9b26 100644 --- a/packages/noports_core/lib/src/common/default_args.dart +++ b/packages/noports_core/lib/src/common/default_args.dart @@ -24,7 +24,7 @@ class DefaultSSHNPArgs { static const localPort = 0; static const sendSshPublicKey = ''; static const localSshOptions = []; - static const legacyDaemon = true; + static const legacyDaemon = false; static const listDevices = false; static const sshClient = SupportedSshClient.exec; } diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart index 01736714c..438eb0f00 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart @@ -15,6 +15,8 @@ class SSHNPForwardDartImpl extends SSHNPImpl with SSHNPForwardDirection { @override Future run() async { + await startAndWaitForInit(); + var error = await requestSocketTunnelFromDaemon(); if (error != null) { return error; @@ -77,7 +79,7 @@ class SSHNPForwardDartImpl extends SSHNPImpl with SSHNPForwardDirection { required String fRemoteHost, required int fRemotePort}) async { logger.info('Starting port forwarding' - ' from port $fLocalPort on localhost' + ' from localhost:$fLocalPort on local side' ' to $fRemoteHost:$fRemotePort on remote side'); /// Do the port forwarding for sshd @@ -141,9 +143,10 @@ class SSHNPForwardDartImpl extends SSHNPImpl with SSHNPForwardDirection { // Start the forwarding await startForwarding( - fLocalPort: fLocalPort, - fRemoteHost: fRemoteHost, - fRemotePort: fRemotePort); + fLocalPort: fLocalPort, + fRemoteHost: fRemoteHost, + fRemotePort: fRemotePort, + ); } } } @@ -153,26 +156,17 @@ class SSHNPForwardDartImpl extends SSHNPImpl with SSHNPForwardDirection { .info('ssh session will terminate after ${params.idleTimeout} seconds' ' if it is not being used'); Timer.periodic(Duration(seconds: params.idleTimeout), (timer) async { - if (counter == 0) { + if (counter == 0 || client.isClosed) { timer.cancel(); - client.close(); + if (!client.isClosed) client.close(); await client.done; doneCompleter.complete(); - logger.shout('$sessionId | no active connections' - ' - ssh session complete'); + logger.shout( + '$sessionId | no active connections - ssh session complete'); } }); - // All good - write the ssh command to stdout - return SSHNPSuccess( - localPort: localPort, - remoteUsername: remoteUsername, - host: 'localhost', - privateKeyFileName: publicKeyFileName.replaceAll('.pub', ''), - localSshOptions: - (params.addForwardsToTunnel) ? null : params.localSshOptions, - connectionBean: client, - ); + return SSHNPNoOpSuccess(connectionBean: client); } on SSHNPError catch (e, s) { doneCompleter.completeError(e, s); return e; diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_direction.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_direction.dart index c22ab97f1..e55766af9 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_direction.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_direction.dart @@ -15,6 +15,12 @@ mixin SSHNPForwardDirection on SSHNPImpl { @override set sshrvdPort(int? port) => _sshrvdPort = port!; + @override + Future init() async { + await super.init(); + initializedCompleter.complete(); + } + Future requestSocketTunnelFromDaemon() async { logger.info( 'Requesting daemon to set up socket tunnel for direct ssh session'); @@ -22,7 +28,7 @@ mixin SSHNPForwardDirection on SSHNPImpl { await notify( AtKey() ..key = 'ssh_request' - ..namespace = namespace + ..namespace = this.namespace ..sharedBy = clientAtSign ..sharedWith = sshnpdAtSign ..metadata = (Metadata() @@ -39,14 +45,15 @@ mixin SSHNPForwardDirection on SSHNPImpl { bool acked = await waitForDaemonResponse(); if (!acked) { var error = SSHNPError( - 'sshnp timed out: waiting for daemon response\nhint: make sure the device is online'); + 'sshnp timed out: waiting for daemon response\nhint: make sure the device is online', + stackTrace: StackTrace.current); doneCompleter.completeError(error); return error; } if (sshnpdAckErrors) { - var error = - SSHNPError('sshnp failed: with sshnpd acknowledgement errors'); + var error = SSHNPError('sshnp failed: with sshnpd acknowledgement errors', + stackTrace: StackTrace.current); doneCompleter.completeError(error); return error; } diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart index 310374d64..398294918 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart @@ -16,6 +16,8 @@ class SSHNPForwardExecImpl extends SSHNPImpl with SSHNPForwardDirection { @override Future run() async { + await startAndWaitForInit(); + var error = await requestSocketTunnelFromDaemon(); if (error != null) { return error; @@ -100,9 +102,7 @@ class SSHNPForwardExecImpl extends SSHNPImpl with SSHNPForwardDirection { } doneCompleter.complete(); - - // All good - write the ssh command to stdout - return SSHNPSuccess( + return SSHNPCommand( localPort: localPort, remoteUsername: remoteUsername, host: 'localhost', diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart index 3a059c013..9c71194db 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart @@ -298,6 +298,16 @@ abstract class SSHNPImpl implements SSHNP { // Internal methods // ==================================================================== + Future startAndWaitForInit() async { + if (!initializedCompleter.isCompleted) { + // Call init in case it hasn't been called yet + unawaited(init()); + // Wait for init to complete + // N.B. must be called this way in case the init call above is not the first init call + await initialized; + } + } + @protected Future notify(AtKey atKey, String value, {String sessionId = ""}) async { @@ -366,9 +376,11 @@ abstract class SSHNPImpl implements SSHNP { logger.info('Waiting for sshrvd response'); int counter = 0; while (!sshrvdAck) { + logger.info('Waiting for sshrvd response: $counter'); await Future.delayed(Duration(milliseconds: 100)); counter++; if (counter == 100) { + logger.warning('Timed out waiting for sshrvd response'); await cleanUp(); throw ('Connection timeout to sshrvd $host service\nhint: make sure host is valid and online'); } diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart index 378f8d709..4176dbabb 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart @@ -23,31 +23,27 @@ class SSHNPLegacyImpl extends SSHNPImpl with SSHNPReverseDirection { ..key = 'privatekey' ..sharedBy = clientAtSign ..sharedWith = sshnpdAtSign - ..namespace = namespace + ..namespace = this.namespace ..metadata = (Metadata() ..ttr = -1 ..ttl = 10000); await notify(sendOurPrivateKeyToSshnpd, sshPrivateKey); + + initializedCompleter.complete(); } @override Future run() async { - logger.info('Requesting legacy daemon to start reverse ssh session'); + await startAndWaitForInit(); - if (!initializedCompleter.isCompleted) { - // Call init in case it hasn't been called yet - unawaited(init()); - // Wait for init to complete - // N.B. must be called this way in case the init call above is not the first init call - await initialized; - } + logger.info('Requesting legacy daemon to start reverse ssh session'); Future? sshrvResult; - if(usingSshrv) { + if (usingSshrv) { // Connect to rendezvous point using background process. // sshnp (this program) can then exit without issue. - SSHRV sshrv = - sshrvGenerator(host, sshrvdPort!, localSshdPort: params.localSshdPort); + SSHRV sshrv = sshrvGenerator(host, sshrvdPort!, + localSshdPort: params.localSshdPort); sshrvResult = sshrv.run(); } @@ -55,7 +51,7 @@ class SSHNPLegacyImpl extends SSHNPImpl with SSHNPReverseDirection { await notify( AtKey() ..key = 'sshd' - ..namespace = namespace + ..namespace = this.namespace ..sharedBy = clientAtSign ..sharedWith = sshnpdAtSign ..metadata = (Metadata() @@ -86,7 +82,7 @@ class SSHNPLegacyImpl extends SSHNPImpl with SSHNPReverseDirection { } doneCompleter.complete(); - return SSHNPSuccess( + return SSHNPCommand( localPort: localPort, remoteUsername: remoteUsername, host: 'localhost', diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_direction.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_direction.dart index 9ba4ddf78..9f2e14e16 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_direction.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_direction.dart @@ -52,8 +52,6 @@ mixin SSHNPReverseDirection on SSHNPImpl { stackTrace: s, ); } - - initializedCompleter.complete(); } @override diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart index ef20591bd..efc425dc7 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:at_client/at_client.dart'; import 'package:noports_core/src/common/utils.dart'; import 'package:noports_core/src/sshnp/sshnp_impl/sshnp_impl.dart'; @@ -13,8 +15,16 @@ class SSHNPReverseImpl extends SSHNPImpl with SSHNPReverseDirection { }) : super( atClient: atClient, params: params, sshrvGenerator: sshrvGenerator); + @override + Future init() async { + await super.init(); + initializedCompleter.complete(); + } + @override Future run() async { + await startAndWaitForInit(); + logger.info('Requesting daemon to start reverse ssh session'); Future? sshrvResult; @@ -29,7 +39,7 @@ class SSHNPReverseImpl extends SSHNPImpl with SSHNPReverseDirection { await notify( AtKey() ..key = 'ssh_request' - ..namespace = namespace + ..namespace = this.namespace ..sharedBy = clientAtSign ..sharedWith = sshnpdAtSign ..metadata = (Metadata() @@ -63,7 +73,7 @@ class SSHNPReverseImpl extends SSHNPImpl with SSHNPReverseDirection { } doneCompleter.complete(); - return SSHNPSuccess( + return SSHNPCommand( localPort: localPort, remoteUsername: remoteUsername, host: 'localhost', diff --git a/packages/noports_core/lib/src/sshnp/sshnp_result.dart b/packages/noports_core/lib/src/sshnp/sshnp_result.dart index a8ecf55aa..f456beb1b 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_result.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_result.dart @@ -1,15 +1,52 @@ import 'dart:io'; +import 'package:meta/meta.dart'; import 'package:socket_connector/socket_connector.dart'; abstract class SSHNPResult {} +class SSHNPSuccess implements SSHNPResult {} + +class SSHNPFailure implements SSHNPResult {} + +mixin SSHNPConnectionBean on SSHNPResult { + Bean? _connectionBean; + + @protected + set connectionBean(Bean? connectionBean) { + _connectionBean = connectionBean; + } + + Bean? get connectionBean => _connectionBean; + + Future killConnectionBean() async { + if (_connectionBean is Process) { + (_connectionBean as Process).kill(); + } + + if (_connectionBean is SocketConnector) { + (_connectionBean as SocketConnector).close(); + } + + if (_connectionBean is Future) { + await (_connectionBean as Future).then((value) { + if (value is Process) { + value.kill(); + } + if (value is SocketConnector) { + value.close(); + } + }); + } + } +} + const _optionsWithPrivateKey = [ '-o StrictHostKeyChecking=accept-new', '-o IdentitiesOnly=yes' ]; -class SSHNPError implements SSHNPResult, Exception { +class SSHNPError implements SSHNPFailure, Exception { final Object message; final Object? error; final StackTrace? stackTrace; @@ -36,9 +73,8 @@ class SSHNPError implements SSHNPResult, Exception { } } -class SSHNPSuccess implements SSHNPResult { - final String command = 'ssh'; - +class SSHNPCommand extends SSHNPSuccess with SSHNPConnectionBean { + final String command; final int localPort; final String? remoteUsername; final String host; @@ -46,20 +82,21 @@ class SSHNPSuccess implements SSHNPResult { final List sshOptions; - ConnectionBean? connectionBean; - - SSHNPSuccess( + SSHNPCommand( {required this.localPort, required this.remoteUsername, required this.host, + this.command = 'ssh', List? localSshOptions, this.privateKeyFileName, - this.connectionBean}) + Bean? connectionBean}) : sshOptions = [ if (shouldIncludePrivateKey(privateKeyFileName)) ..._optionsWithPrivateKey, ...(localSshOptions ?? []) - ]; + ] { + this.connectionBean = connectionBean; + } static bool shouldIncludePrivateKey(String? privateKeyFileName) => privateKeyFileName != null && privateKeyFileName.isNotEmpty; @@ -83,25 +120,11 @@ class SSHNPSuccess implements SSHNPResult { sb.write(args.join(' ')); return sb.toString(); } +} - Future killConnectionBean() async { - if (connectionBean is Process) { - (connectionBean as Process).kill(); - } - - if (connectionBean is SocketConnector) { - (connectionBean as SocketConnector).close(); - } - - if (connectionBean is Future) { - await (connectionBean as Future).then((value) { - if (value is Process) { - value.kill(); - } - if (value is SocketConnector) { - value.close(); - } - }); - } +class SSHNPNoOpSuccess extends SSHNPSuccess + with SSHNPConnectionBean { + SSHNPNoOpSuccess({Bean? connectionBean}) { + this.connectionBean = connectionBean; } } diff --git a/packages/sshnoports/bin/sshnp.dart b/packages/sshnoports/bin/sshnp.dart index ad6520ed3..c769c7b15 100644 --- a/packages/sshnoports/bin/sshnp.dart +++ b/packages/sshnoports/bin/sshnp.dart @@ -57,7 +57,7 @@ void main(List args) async { if (e.stackTrace != null) { Error.throwWithStackTrace(e, e.stackTrace!); } - throw e; + throw e; }); if (params.listDevices) { @@ -77,7 +77,7 @@ void main(List args) async { } throw res; } - if (res is SSHNPSuccess) { + if (res is SSHNPCommand) { stdout.write('$res\n'); await sshnp!.done; exit(0); diff --git a/packages/sshnoports/lib/create_at_client_cli.dart b/packages/sshnoports/lib/create_at_client_cli.dart index d024a428e..38dce2240 100644 --- a/packages/sshnoports/lib/create_at_client_cli.dart +++ b/packages/sshnoports/lib/create_at_client_cli.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:at_client/at_client.dart'; import 'package:at_onboarding_cli/at_onboarding_cli.dart'; +import 'package:noports_core/utils.dart'; import 'package:version/version.dart'; import 'package:path/path.dart' as path; import 'service_factories.dart'; @@ -11,8 +12,8 @@ Future createAtClientCli({ required String atKeysFilePath, String? pathExtension, String subDirectory = '.sshnp', - String namespace = 'sshnp', - String rootDomain = 'root.atsign.org', + String namespace = DefaultArgs.namespace, + String rootDomain = DefaultArgs.rootDomain, }) async { // Now on to the atPlatform startup //onboarding preference builder can be used to set onboardingService parameters diff --git a/packages/sshnoports/pubspec.lock b/packages/sshnoports/pubspec.lock index c137bb51e..1399626eb 100644 --- a/packages/sshnoports/pubspec.lock +++ b/packages/sshnoports/pubspec.lock @@ -189,10 +189,10 @@ packages: dependency: transitive description: name: coverage - sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" + sha256: "595a29b55ce82d53398e1bcc2cba525d7bd7c59faeb2d2540e9d42c390cfeeeb" url: "https://pub.dev" source: hosted - version: "1.6.3" + version: "1.6.4" cron: dependency: transitive description: @@ -628,10 +628,10 @@ packages: dependency: "direct dev" description: name: test - sha256: "9b0dd8e36af4a5b1569029949d50a52cb2a2a2fdaa20cebb96e6603b9ae241f9" + sha256: a20ddc0723556dc6dd56094e58ec1529196d5d7774156604cb14e8445a5a82ff url: "https://pub.dev" source: hosted - version: "1.24.6" + version: "1.24.7" test_api: dependency: transitive description: @@ -644,10 +644,10 @@ packages: dependency: transitive description: name: test_core - sha256: "4bef837e56375537055fdbbbf6dd458b1859881f4c7e6da936158f77d61ab265" + sha256: "96382d0bc826e260b077bb496259e58bc82e90b603ab16cd5ae95dfe1dfcba8b" url: "https://pub.dev" source: hosted - version: "0.5.6" + version: "0.5.7" tuple: dependency: transitive description: @@ -684,10 +684,10 @@ packages: dependency: transitive description: name: vm_service - sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 + sha256: a13d5503b4facefc515c8c587ce3cf69577a7b064a9f1220e005449cf1f64aad url: "https://pub.dev" source: hosted - version: "11.10.0" + version: "12.0.0" watcher: dependency: transitive description: diff --git a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart index dd1f0de81..37999705f 100644 --- a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart +++ b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart @@ -69,8 +69,8 @@ class _ProfileRunActionState extends ConsumerState { } Future onStop() async { - if (sshnpResult is SSHNPSuccess) { - await (sshnpResult as SSHNPSuccess).killConnectionBean(); + if (sshnpResult is SSHNPCommand) { + await (sshnpResult as SSHNPCommand).killConnectionBean(); } ref .read(backgroundSessionFamilyController(widget.params.profileName!) diff --git a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_terminal_action.dart b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_terminal_action.dart index 77a108d1b..9e4ae3b0d 100644 --- a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_terminal_action.dart +++ b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_terminal_action.dart @@ -59,7 +59,7 @@ class _ProfileTerminalActionState extends ConsumerState { final sessionController = ref.watch(terminalSessionFamilyController(sessionId).notifier); - if (result is SSHNPSuccess) { + if (result is SSHNPCommand) { /// Set the command for the new session sessionController.setProcess( command: result.command, args: result.args); From a0bbbeb92223b2ccae3be3cebb78395f54ccdb32 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 16:57:55 -0400 Subject: [PATCH 29/49] chore: cleanup success message for pure-dart --- .../src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart | 11 +++++++---- packages/noports_core/lib/src/sshnp/sshnp_result.dart | 8 +++++++- packages/sshnoports/bin/sshnp.dart | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart index 438eb0f00..a8f2a2607 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart @@ -152,9 +152,10 @@ class SSHNPForwardDartImpl extends SSHNPImpl with SSHNPForwardDirection { } /// Set up timer to check to see if all connections are down - logger - .info('ssh session will terminate after ${params.idleTimeout} seconds' - ' if it is not being used'); + String terminateMessage = + 'ssh session will terminate after ${params.idleTimeout} seconds' + ' if it is not being used'; + logger.info(terminateMessage); Timer.periodic(Duration(seconds: params.idleTimeout), (timer) async { if (counter == 0 || client.isClosed) { timer.cancel(); @@ -166,7 +167,9 @@ class SSHNPForwardDartImpl extends SSHNPImpl with SSHNPForwardDirection { } }); - return SSHNPNoOpSuccess(connectionBean: client); + return SSHNPNoOpSuccess( + message: 'Connection established:\n$terminateMessage', + connectionBean: client); } on SSHNPError catch (e, s) { doneCompleter.completeError(e, s); return e; diff --git a/packages/noports_core/lib/src/sshnp/sshnp_result.dart b/packages/noports_core/lib/src/sshnp/sshnp_result.dart index f456beb1b..eca264afe 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_result.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_result.dart @@ -124,7 +124,13 @@ class SSHNPCommand extends SSHNPSuccess with SSHNPConnectionBean { class SSHNPNoOpSuccess extends SSHNPSuccess with SSHNPConnectionBean { - SSHNPNoOpSuccess({Bean? connectionBean}) { + String? message; + SSHNPNoOpSuccess({this.message, Bean? connectionBean}) { this.connectionBean = connectionBean; } + + @override + String toString() { + return message ?? 'Connection Established'; + } } diff --git a/packages/sshnoports/bin/sshnp.dart b/packages/sshnoports/bin/sshnp.dart index c769c7b15..2957c875b 100644 --- a/packages/sshnoports/bin/sshnp.dart +++ b/packages/sshnoports/bin/sshnp.dart @@ -77,7 +77,7 @@ void main(List args) async { } throw res; } - if (res is SSHNPCommand) { + if (res is SSHNPCommand || res is SSHNPNoOpSuccess) { stdout.write('$res\n'); await sshnp!.done; exit(0); From 9c21e07d1df6b75a7e77d298c0ea97687adee4d0 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 17:25:54 -0400 Subject: [PATCH 30/49] chore: fix legacy daemon support in e2e tests --- .github/composite/setup_entrypoints/action.yaml | 9 ++++++++- .github/workflows/end2end_tests.yaml | 15 ++++++++++++--- .../end2end_tests/entrypoints/sshnp_entrypoint.sh | 3 ++- .../entrypoints/sshnp_installer_entrypoint.sh | 5 ++--- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/.github/composite/setup_entrypoints/action.yaml b/.github/composite/setup_entrypoints/action.yaml index 994874ab4..43d162d09 100644 --- a/.github/composite/setup_entrypoints/action.yaml +++ b/.github/composite/setup_entrypoints/action.yaml @@ -21,6 +21,9 @@ inputs: devicename: description: Unique sshnp devicename required: true + legacy_daemon: + description: Legacy daemon + required: false runs: using: composite @@ -37,7 +40,11 @@ runs: entrypoint_filename="sshnp_entrypoint.sh" ;; esac - echo "entrypoint_filename: $entrypoint_filename" + ARGS="" + if [ "${{ inputs.legacy_daemon }}" = "true" ]; then + ARGS="$ARGS --legacy-daemon" + fi + sed -i "s/ARGS=.*/ARGS=\"$ARGS\"/" "$entrypoint_filename" ./setup-sshnp-entrypoint.sh ${{ inputs.devicename }} ${{ inputs.sshnp_atsign }} ${{ inputs.sshnpd_atsign }} ${{ inputs.sshrvd_atsign }} "$entrypoint_filename" - name: Setup NPD entrypoint diff --git a/.github/workflows/end2end_tests.yaml b/.github/workflows/end2end_tests.yaml index aa7b4afd0..a45882a81 100644 --- a/.github/workflows/end2end_tests.yaml +++ b/.github/workflows/end2end_tests.yaml @@ -206,6 +206,11 @@ jobs: # Don't run these against themselves, pointless to test - np: local npd: local + + include: + - np: local + npd: installer + legacy_daemon: true steps: - name: Show Matrix Values run: | @@ -244,6 +249,7 @@ jobs: sshnpd_atsign: ${{ env.SSHNPD_ATSIGN }} sshrvd_atsign: ${{ env[env.PROD_RVD_ATSIGN] }} devicename: ${{ env.DEVICENAME }} + legacy_daemon: ${{ matrix.legacy_daemon || false }} - name: Ensure entrypoints exist working-directory: tests/end2end_tests/contexts @@ -368,12 +374,14 @@ jobs: matrix: include: - np: local - npd: latest - - np: latest + npd: v3.4.2 + legacy_daemon: true + - np: v3.4.2 npd: local - np: local npd: v3.3.0 + legacy_daemon: true - np: v3.3.0 npd: local @@ -415,6 +423,7 @@ jobs: sshnpd_atsign: ${{ env.SSHNPD_ATSIGN }} sshrvd_atsign: ${{ env[env.PROD_RVD_ATSIGN] }} devicename: ${{ env.DEVICENAME }} + legacy_daemon: ${{ matrix.legacy_daemon || false }} - name: Ensure entrypoints exist working-directory: tests/end2end_tests/contexts @@ -610,4 +619,4 @@ jobs: continue-on-error: true # failing this step does not fail the entire job working-directory: tests/end2end_tests/tests run: | - docker compose down \ No newline at end of file + docker compose down diff --git a/tests/end2end_tests/entrypoints/sshnp_entrypoint.sh b/tests/end2end_tests/entrypoints/sshnp_entrypoint.sh index ad3bdbb9c..c49902105 100644 --- a/tests/end2end_tests/entrypoints/sshnp_entrypoint.sh +++ b/tests/end2end_tests/entrypoints/sshnp_entrypoint.sh @@ -1,6 +1,7 @@ #!/bin/bash echo "SSHNP START ENTRY" -SSHNP_COMMAND="$HOME/.local/bin/sshnp -f @sshnpatsign -t @sshnpdatsign -d deviceName -h @sshrvdatsign -s id_ed25519.pub -v > sshnp.log" +ARGS="" +SSHNP_COMMAND="$HOME/.local/bin/sshnp -f @sshnpatsign -t @sshnpdatsign -d deviceName -h @sshrvdatsign -s id_ed25519.pub -v $ARGS > sshnp.log" run_test() { diff --git a/tests/end2end_tests/entrypoints/sshnp_installer_entrypoint.sh b/tests/end2end_tests/entrypoints/sshnp_installer_entrypoint.sh index 97ea189b8..55b3ab918 100644 --- a/tests/end2end_tests/entrypoints/sshnp_installer_entrypoint.sh +++ b/tests/end2end_tests/entrypoints/sshnp_installer_entrypoint.sh @@ -1,7 +1,6 @@ #!/bin/bash -sleep WAITING_TIME # time for sshnpd to share device name - -SSHNP_COMMAND="$HOME/.local/bin/sshnp -f @sshnpatsign -t @sshnpdatsign -d deviceName -h @sshrvdatsign -s id_ed25519.pub -v > sshnp.log" +ARGS="" +SSHNP_COMMAND="$HOME/.local/bin/sshnp -f @sshnpatsign -t @sshnpdatsign -d deviceName -h @sshrvdatsign -s id_ed25519.pub -v $ARGS > sshnp.log" echo "Running: $SSHNP_COMMAND" eval "$SSHNP_COMMAND" cat sshnp.log From 5c92c4fe4f287fbeb8e0d8282c2a95ed607e5f95 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 17:33:40 -0400 Subject: [PATCH 31/49] chore: revert end2end test regression --- .../composite/setup_entrypoints/action.yaml | 11 +++-- .github/workflows/end2end_tests.yaml | 13 ++++-- .../contexts/_init_/setup-sshnp-entrypoint.sh | 8 ++-- .../alternate_port_test/sshnp_entrypoint.sh | 28 ------------- .../entrypoints/sshnp_entrypoint.sh | 40 ------------------- .../entrypoints/sshnp_installer_entrypoint.sh | 25 ------------ .../entrypoints/sshnpd_entrypoint.sh | 5 --- .../sshnpd_installer_entrypoint.sh | 4 -- .../entrypoints/sshrvd_entrypoint.sh | 2 - 9 files changed, 20 insertions(+), 116 deletions(-) delete mode 100644 tests/end2end_tests/entrypoints/alternate_port_test/sshnp_entrypoint.sh delete mode 100644 tests/end2end_tests/entrypoints/sshnp_entrypoint.sh delete mode 100644 tests/end2end_tests/entrypoints/sshnp_installer_entrypoint.sh delete mode 100644 tests/end2end_tests/entrypoints/sshnpd_entrypoint.sh delete mode 100644 tests/end2end_tests/entrypoints/sshnpd_installer_entrypoint.sh delete mode 100644 tests/end2end_tests/entrypoints/sshrvd_entrypoint.sh diff --git a/.github/composite/setup_entrypoints/action.yaml b/.github/composite/setup_entrypoints/action.yaml index 43d162d09..4a53e9775 100644 --- a/.github/composite/setup_entrypoints/action.yaml +++ b/.github/composite/setup_entrypoints/action.yaml @@ -24,6 +24,9 @@ inputs: legacy_daemon: description: Legacy daemon required: false + alternate_port: + description: Alternate port + required: false runs: using: composite @@ -40,12 +43,14 @@ runs: entrypoint_filename="sshnp_entrypoint.sh" ;; esac - ARGS="" + ARGS="-v" if [ "${{ inputs.legacy_daemon }}" = "true" ]; then ARGS="$ARGS --legacy-daemon" fi - sed -i "s/ARGS=.*/ARGS=\"$ARGS\"/" "$entrypoint_filename" - ./setup-sshnp-entrypoint.sh ${{ inputs.devicename }} ${{ inputs.sshnp_atsign }} ${{ inputs.sshnpd_atsign }} ${{ inputs.sshrvd_atsign }} "$entrypoint_filename" + if [ "${{ inputs.alternate_port }}" = "true" ]; then + ARGS="$ARGS -P 55" + fi + ./setup-sshnp-entrypoint.sh ${{ inputs.devicename }} ${{ inputs.sshnp_atsign }} ${{ inputs.sshnpd_atsign }} ${{ inputs.sshrvd_atsign }} "$entrypoint_filename" "$ARGS" - name: Setup NPD entrypoint shell: bash diff --git a/.github/workflows/end2end_tests.yaml b/.github/workflows/end2end_tests.yaml index a45882a81..fa441ce83 100644 --- a/.github/workflows/end2end_tests.yaml +++ b/.github/workflows/end2end_tests.yaml @@ -554,10 +554,15 @@ jobs: echo "${{ secrets[env.SSHNPD_ATKEYS] }}" > sshnpd/.atsign/keys/${{ env.SSHNPD_ATSIGN }}_key.atKeys - name: Set up entrypoints - working-directory: tests/end2end_tests/contexts/_init_ - run: | - ./setup-sshnp-entrypoint.sh ${{ env.DEVICENAME }} ${{ env.SSHNP_ATSIGN }} ${{ env.SSHNPD_ATSIGN }} ${{ env[env.PROD_RVD_ATSIGN] }} alternate_port_test/sshnp_entrypoint.sh - ./setup-sshnpd-entrypoint.sh ${{ env.DEVICENAME }} ${{ env.SSHNP_ATSIGN }} ${{ env.SSHNPD_ATSIGN }} sshnpd_entrypoint.sh + uses: ./.github/composite/setup_entrypoints + with: + sshnp: "local" + sshnp_atsign: ${{ env.SSHNP_ATSIGN }} + sshnpd: "local" + sshnpd_atsign: ${{ env.SSHNPD_ATSIGN }} + sshrvd_atsign: ${{ env[env.PROD_RVD_ATSIGN] }} + devicename: ${{ env.DEVICENAME }} + legacy_daemon: false - name: Ensure entrypoints exist working-directory: tests/end2end_tests/contexts diff --git a/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh b/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh index b63bb48de..7cb753c98 100755 --- a/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh +++ b/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh @@ -1,14 +1,11 @@ #!/bin/bash -# this script copies the template sshnp entrypoint to ../sshnp/entrypoint.sh -# then also replaces the device name, sshnp atSign, sshnpd atSign, and sshrvd atSign with the provided arguments -# example usage: ./setup-sshnp-entrypoint.sh e2e @alice @alice @alice - device=$1 # e.g. e2e sshnp=$2 # e.g. @alice sshnpd=$3 # e.g. @alice sshrvd=$4 # e.g. @alice template_name=$5 # e.g. sshnp_entrypoint.sh +args=$6 # e.g. -v cp ../../entrypoints/"$template_name" ../sshnp/entrypoint.sh # copy template to the mounted folder @@ -23,4 +20,5 @@ fi eval "$prefix" "s/@sshnpatsign/${sshnp}/g" ../sshnp/entrypoint.sh eval "$prefix" "s/@sshnpdatsign/${sshnpd}/g" ../sshnp/entrypoint.sh eval "$prefix" "s/@sshrvdatsign/${sshrvd}/g" ../sshnp/entrypoint.sh -eval "$prefix" "s/deviceName/${device}/g" ../sshnp/entrypoint.sh \ No newline at end of file +eval "$prefix" "s/deviceName/${device}/g" ../sshnp/entrypoint.sh +eval "$prefix" "s/args/${args}/g" ../sshnp/entrypoint.sh \ No newline at end of file diff --git a/tests/end2end_tests/entrypoints/alternate_port_test/sshnp_entrypoint.sh b/tests/end2end_tests/entrypoints/alternate_port_test/sshnp_entrypoint.sh deleted file mode 100644 index 269fbb109..000000000 --- a/tests/end2end_tests/entrypoints/alternate_port_test/sshnp_entrypoint.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -echo "SSHNP START ENTRY" -echo "Port 55" | sudo tee -a /etc/ssh/sshd_config -sudo service ssh restart -SSHNP_COMMAND="$HOME/.local/bin/sshnp -f @sshnpatsign -t @sshnpdatsign -d deviceName -h @sshrvdatsign -s id_ed25519.pub -P 55 -v > logs.txt" -echo "Running: $SSHNP_COMMAND" -eval "$SSHNP_COMMAND" -cat logs.txt -tail -n 5 logs.txt | grep "ssh -p" > sshcommand.txt - -if [ ! -s sshcommand.txt ]; then - # try again - echo "Running: $SSHNP_COMMAND" - eval "$SSHNP_COMMAND" - cat logs.txt - tail -n 5 logs.txt | grep "ssh -p" > sshcommand.txt - if [ ! -s sshcommand.txt ]; then - echo "could not find 'ssh -p' command in logs.txt" - echo "last 5 lines of logs.txt:" - tail -n 5 logs.txt || echo - exit 1 - fi -fi -echo "$(sed '1!d' sshcommand.txt) -o StrictHostKeyChecking=no " > sshcommand.txt ; -echo "ssh -p command: $(cat sshcommand.txt)" -echo "sh test.sh " | eval "$(cat sshcommand.txt)" -sleep 2 # time for ssh connection to properly exit - diff --git a/tests/end2end_tests/entrypoints/sshnp_entrypoint.sh b/tests/end2end_tests/entrypoints/sshnp_entrypoint.sh deleted file mode 100644 index c49902105..000000000 --- a/tests/end2end_tests/entrypoints/sshnp_entrypoint.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash -echo "SSHNP START ENTRY" -ARGS="" -SSHNP_COMMAND="$HOME/.local/bin/sshnp -f @sshnpatsign -t @sshnpdatsign -d deviceName -h @sshrvdatsign -s id_ed25519.pub -v $ARGS > sshnp.log" - -run_test() -{ - echo "Running: $SSHNP_COMMAND" - eval "$SSHNP_COMMAND" - cat sshnp.log - tail -n 20 sshnp.log | grep "ssh -p" > sshcommand.txt - - # if sshcommand is empty, exit code 1 - if [ ! -s sshcommand.txt ]; then - echo "sshcommand.txt is empty" - return 1 - fi - - sed '1!d' sshcommand.txt - echo "ssh -p command: $(cat sshcommand.txt)" - echo "./test.sh " | eval "$(cat sshcommand.txt)" - sleep 2 # time for ssh connection to properly exit - return 0 -} - -main() -{ - # run test 3 times, while run_test is not successful - for i in {1..3} - do - run_test - if [ $? -eq 0 ]; then - exit 0 - fi - sleep 5 - done - exit 1 -} - -main diff --git a/tests/end2end_tests/entrypoints/sshnp_installer_entrypoint.sh b/tests/end2end_tests/entrypoints/sshnp_installer_entrypoint.sh deleted file mode 100644 index 55b3ab918..000000000 --- a/tests/end2end_tests/entrypoints/sshnp_installer_entrypoint.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -ARGS="" -SSHNP_COMMAND="$HOME/.local/bin/sshnp -f @sshnpatsign -t @sshnpdatsign -d deviceName -h @sshrvdatsign -s id_ed25519.pub -v $ARGS > sshnp.log" -echo "Running: $SSHNP_COMMAND" -eval "$SSHNP_COMMAND" -cat sshnp.log -tail -n 5 sshnp.log | grep "ssh -p" > sshcommand.txt - -if [ ! -s sshcommand.txt ]; then - # try again - echo "Running: $SSHNP_COMMAND" - eval "$SSHNP_COMMAND" - cat sshnp.log - tail -n 5 sshnp.log | grep "ssh -p" > sshcommand.txt - if [ ! -s sshcommand.txt ]; then - echo "could not find 'ssh -p' command in sshnp.log" - echo "last 5 lines of sshnp.log:" - tail -n 5 sshnp.log || echo - exit 1 - fi -fi -echo "$(sed '1!d' sshcommand.txt) -o StrictHostKeyChecking=no " > sshcommand.txt ; -echo "ssh -p command: $(cat sshcommand.txt)" -echo "sh test.sh " | eval "$(cat sshcommand.txt)" -sleep 2 # time for ssh connection to properly exit diff --git a/tests/end2end_tests/entrypoints/sshnpd_entrypoint.sh b/tests/end2end_tests/entrypoints/sshnpd_entrypoint.sh deleted file mode 100644 index 16341ec0d..000000000 --- a/tests/end2end_tests/entrypoints/sshnpd_entrypoint.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -echo "SSHNPD START ENTRY" -SSHNPD_COMMAND="$HOME/.local/bin/sshnpd -a @sshnpdatsign -m @sshnpatsign -d deviceName -s -u -v 2>&1 | tee -a sshnpd.log" -echo "Running: $SSHNPD_COMMAND" -eval "$SSHNPD_COMMAND" diff --git a/tests/end2end_tests/entrypoints/sshnpd_installer_entrypoint.sh b/tests/end2end_tests/entrypoints/sshnpd_installer_entrypoint.sh deleted file mode 100644 index 63bce9eb1..000000000 --- a/tests/end2end_tests/entrypoints/sshnpd_installer_entrypoint.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -SSHNPD_COMMAND="$HOME/.local/bin/sshnpd@sshnpatsign 2>&1 | tee -a sshnpd.log" -echo "Running: $SSHNPD_COMMAND" -eval "$SSHNPD_COMMAND" \ No newline at end of file diff --git a/tests/end2end_tests/entrypoints/sshrvd_entrypoint.sh b/tests/end2end_tests/entrypoints/sshrvd_entrypoint.sh deleted file mode 100644 index c41a11bc8..000000000 --- a/tests/end2end_tests/entrypoints/sshrvd_entrypoint.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -"$HOME"/.local/bin/sshrvd -a @sshrvdatsign -i "$(hostname -i)" -v -s 2>&1 | tee -a sshrvd.log From d57fc7e7b9120fa5f1c08208d53ad479cf0517f7 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 17:34:34 -0400 Subject: [PATCH 32/49] Revert "chore: revert end2end test regression" This reverts commit 5c92c4fe4f287fbeb8e0d8282c2a95ed607e5f95. --- .../composite/setup_entrypoints/action.yaml | 11 ++--- .github/workflows/end2end_tests.yaml | 13 ++---- .../contexts/_init_/setup-sshnp-entrypoint.sh | 8 ++-- .../alternate_port_test/sshnp_entrypoint.sh | 28 +++++++++++++ .../entrypoints/sshnp_entrypoint.sh | 40 +++++++++++++++++++ .../entrypoints/sshnp_installer_entrypoint.sh | 25 ++++++++++++ .../entrypoints/sshnpd_entrypoint.sh | 5 +++ .../sshnpd_installer_entrypoint.sh | 4 ++ .../entrypoints/sshrvd_entrypoint.sh | 2 + 9 files changed, 116 insertions(+), 20 deletions(-) create mode 100644 tests/end2end_tests/entrypoints/alternate_port_test/sshnp_entrypoint.sh create mode 100644 tests/end2end_tests/entrypoints/sshnp_entrypoint.sh create mode 100644 tests/end2end_tests/entrypoints/sshnp_installer_entrypoint.sh create mode 100644 tests/end2end_tests/entrypoints/sshnpd_entrypoint.sh create mode 100644 tests/end2end_tests/entrypoints/sshnpd_installer_entrypoint.sh create mode 100644 tests/end2end_tests/entrypoints/sshrvd_entrypoint.sh diff --git a/.github/composite/setup_entrypoints/action.yaml b/.github/composite/setup_entrypoints/action.yaml index 4a53e9775..43d162d09 100644 --- a/.github/composite/setup_entrypoints/action.yaml +++ b/.github/composite/setup_entrypoints/action.yaml @@ -24,9 +24,6 @@ inputs: legacy_daemon: description: Legacy daemon required: false - alternate_port: - description: Alternate port - required: false runs: using: composite @@ -43,14 +40,12 @@ runs: entrypoint_filename="sshnp_entrypoint.sh" ;; esac - ARGS="-v" + ARGS="" if [ "${{ inputs.legacy_daemon }}" = "true" ]; then ARGS="$ARGS --legacy-daemon" fi - if [ "${{ inputs.alternate_port }}" = "true" ]; then - ARGS="$ARGS -P 55" - fi - ./setup-sshnp-entrypoint.sh ${{ inputs.devicename }} ${{ inputs.sshnp_atsign }} ${{ inputs.sshnpd_atsign }} ${{ inputs.sshrvd_atsign }} "$entrypoint_filename" "$ARGS" + sed -i "s/ARGS=.*/ARGS=\"$ARGS\"/" "$entrypoint_filename" + ./setup-sshnp-entrypoint.sh ${{ inputs.devicename }} ${{ inputs.sshnp_atsign }} ${{ inputs.sshnpd_atsign }} ${{ inputs.sshrvd_atsign }} "$entrypoint_filename" - name: Setup NPD entrypoint shell: bash diff --git a/.github/workflows/end2end_tests.yaml b/.github/workflows/end2end_tests.yaml index fa441ce83..a45882a81 100644 --- a/.github/workflows/end2end_tests.yaml +++ b/.github/workflows/end2end_tests.yaml @@ -554,15 +554,10 @@ jobs: echo "${{ secrets[env.SSHNPD_ATKEYS] }}" > sshnpd/.atsign/keys/${{ env.SSHNPD_ATSIGN }}_key.atKeys - name: Set up entrypoints - uses: ./.github/composite/setup_entrypoints - with: - sshnp: "local" - sshnp_atsign: ${{ env.SSHNP_ATSIGN }} - sshnpd: "local" - sshnpd_atsign: ${{ env.SSHNPD_ATSIGN }} - sshrvd_atsign: ${{ env[env.PROD_RVD_ATSIGN] }} - devicename: ${{ env.DEVICENAME }} - legacy_daemon: false + working-directory: tests/end2end_tests/contexts/_init_ + run: | + ./setup-sshnp-entrypoint.sh ${{ env.DEVICENAME }} ${{ env.SSHNP_ATSIGN }} ${{ env.SSHNPD_ATSIGN }} ${{ env[env.PROD_RVD_ATSIGN] }} alternate_port_test/sshnp_entrypoint.sh + ./setup-sshnpd-entrypoint.sh ${{ env.DEVICENAME }} ${{ env.SSHNP_ATSIGN }} ${{ env.SSHNPD_ATSIGN }} sshnpd_entrypoint.sh - name: Ensure entrypoints exist working-directory: tests/end2end_tests/contexts diff --git a/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh b/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh index 7cb753c98..b63bb48de 100755 --- a/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh +++ b/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh @@ -1,11 +1,14 @@ #!/bin/bash +# this script copies the template sshnp entrypoint to ../sshnp/entrypoint.sh +# then also replaces the device name, sshnp atSign, sshnpd atSign, and sshrvd atSign with the provided arguments +# example usage: ./setup-sshnp-entrypoint.sh e2e @alice @alice @alice + device=$1 # e.g. e2e sshnp=$2 # e.g. @alice sshnpd=$3 # e.g. @alice sshrvd=$4 # e.g. @alice template_name=$5 # e.g. sshnp_entrypoint.sh -args=$6 # e.g. -v cp ../../entrypoints/"$template_name" ../sshnp/entrypoint.sh # copy template to the mounted folder @@ -20,5 +23,4 @@ fi eval "$prefix" "s/@sshnpatsign/${sshnp}/g" ../sshnp/entrypoint.sh eval "$prefix" "s/@sshnpdatsign/${sshnpd}/g" ../sshnp/entrypoint.sh eval "$prefix" "s/@sshrvdatsign/${sshrvd}/g" ../sshnp/entrypoint.sh -eval "$prefix" "s/deviceName/${device}/g" ../sshnp/entrypoint.sh -eval "$prefix" "s/args/${args}/g" ../sshnp/entrypoint.sh \ No newline at end of file +eval "$prefix" "s/deviceName/${device}/g" ../sshnp/entrypoint.sh \ No newline at end of file diff --git a/tests/end2end_tests/entrypoints/alternate_port_test/sshnp_entrypoint.sh b/tests/end2end_tests/entrypoints/alternate_port_test/sshnp_entrypoint.sh new file mode 100644 index 000000000..269fbb109 --- /dev/null +++ b/tests/end2end_tests/entrypoints/alternate_port_test/sshnp_entrypoint.sh @@ -0,0 +1,28 @@ +#!/bin/bash +echo "SSHNP START ENTRY" +echo "Port 55" | sudo tee -a /etc/ssh/sshd_config +sudo service ssh restart +SSHNP_COMMAND="$HOME/.local/bin/sshnp -f @sshnpatsign -t @sshnpdatsign -d deviceName -h @sshrvdatsign -s id_ed25519.pub -P 55 -v > logs.txt" +echo "Running: $SSHNP_COMMAND" +eval "$SSHNP_COMMAND" +cat logs.txt +tail -n 5 logs.txt | grep "ssh -p" > sshcommand.txt + +if [ ! -s sshcommand.txt ]; then + # try again + echo "Running: $SSHNP_COMMAND" + eval "$SSHNP_COMMAND" + cat logs.txt + tail -n 5 logs.txt | grep "ssh -p" > sshcommand.txt + if [ ! -s sshcommand.txt ]; then + echo "could not find 'ssh -p' command in logs.txt" + echo "last 5 lines of logs.txt:" + tail -n 5 logs.txt || echo + exit 1 + fi +fi +echo "$(sed '1!d' sshcommand.txt) -o StrictHostKeyChecking=no " > sshcommand.txt ; +echo "ssh -p command: $(cat sshcommand.txt)" +echo "sh test.sh " | eval "$(cat sshcommand.txt)" +sleep 2 # time for ssh connection to properly exit + diff --git a/tests/end2end_tests/entrypoints/sshnp_entrypoint.sh b/tests/end2end_tests/entrypoints/sshnp_entrypoint.sh new file mode 100644 index 000000000..c49902105 --- /dev/null +++ b/tests/end2end_tests/entrypoints/sshnp_entrypoint.sh @@ -0,0 +1,40 @@ +#!/bin/bash +echo "SSHNP START ENTRY" +ARGS="" +SSHNP_COMMAND="$HOME/.local/bin/sshnp -f @sshnpatsign -t @sshnpdatsign -d deviceName -h @sshrvdatsign -s id_ed25519.pub -v $ARGS > sshnp.log" + +run_test() +{ + echo "Running: $SSHNP_COMMAND" + eval "$SSHNP_COMMAND" + cat sshnp.log + tail -n 20 sshnp.log | grep "ssh -p" > sshcommand.txt + + # if sshcommand is empty, exit code 1 + if [ ! -s sshcommand.txt ]; then + echo "sshcommand.txt is empty" + return 1 + fi + + sed '1!d' sshcommand.txt + echo "ssh -p command: $(cat sshcommand.txt)" + echo "./test.sh " | eval "$(cat sshcommand.txt)" + sleep 2 # time for ssh connection to properly exit + return 0 +} + +main() +{ + # run test 3 times, while run_test is not successful + for i in {1..3} + do + run_test + if [ $? -eq 0 ]; then + exit 0 + fi + sleep 5 + done + exit 1 +} + +main diff --git a/tests/end2end_tests/entrypoints/sshnp_installer_entrypoint.sh b/tests/end2end_tests/entrypoints/sshnp_installer_entrypoint.sh new file mode 100644 index 000000000..55b3ab918 --- /dev/null +++ b/tests/end2end_tests/entrypoints/sshnp_installer_entrypoint.sh @@ -0,0 +1,25 @@ +#!/bin/bash +ARGS="" +SSHNP_COMMAND="$HOME/.local/bin/sshnp -f @sshnpatsign -t @sshnpdatsign -d deviceName -h @sshrvdatsign -s id_ed25519.pub -v $ARGS > sshnp.log" +echo "Running: $SSHNP_COMMAND" +eval "$SSHNP_COMMAND" +cat sshnp.log +tail -n 5 sshnp.log | grep "ssh -p" > sshcommand.txt + +if [ ! -s sshcommand.txt ]; then + # try again + echo "Running: $SSHNP_COMMAND" + eval "$SSHNP_COMMAND" + cat sshnp.log + tail -n 5 sshnp.log | grep "ssh -p" > sshcommand.txt + if [ ! -s sshcommand.txt ]; then + echo "could not find 'ssh -p' command in sshnp.log" + echo "last 5 lines of sshnp.log:" + tail -n 5 sshnp.log || echo + exit 1 + fi +fi +echo "$(sed '1!d' sshcommand.txt) -o StrictHostKeyChecking=no " > sshcommand.txt ; +echo "ssh -p command: $(cat sshcommand.txt)" +echo "sh test.sh " | eval "$(cat sshcommand.txt)" +sleep 2 # time for ssh connection to properly exit diff --git a/tests/end2end_tests/entrypoints/sshnpd_entrypoint.sh b/tests/end2end_tests/entrypoints/sshnpd_entrypoint.sh new file mode 100644 index 000000000..16341ec0d --- /dev/null +++ b/tests/end2end_tests/entrypoints/sshnpd_entrypoint.sh @@ -0,0 +1,5 @@ +#!/bin/bash +echo "SSHNPD START ENTRY" +SSHNPD_COMMAND="$HOME/.local/bin/sshnpd -a @sshnpdatsign -m @sshnpatsign -d deviceName -s -u -v 2>&1 | tee -a sshnpd.log" +echo "Running: $SSHNPD_COMMAND" +eval "$SSHNPD_COMMAND" diff --git a/tests/end2end_tests/entrypoints/sshnpd_installer_entrypoint.sh b/tests/end2end_tests/entrypoints/sshnpd_installer_entrypoint.sh new file mode 100644 index 000000000..63bce9eb1 --- /dev/null +++ b/tests/end2end_tests/entrypoints/sshnpd_installer_entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/bash +SSHNPD_COMMAND="$HOME/.local/bin/sshnpd@sshnpatsign 2>&1 | tee -a sshnpd.log" +echo "Running: $SSHNPD_COMMAND" +eval "$SSHNPD_COMMAND" \ No newline at end of file diff --git a/tests/end2end_tests/entrypoints/sshrvd_entrypoint.sh b/tests/end2end_tests/entrypoints/sshrvd_entrypoint.sh new file mode 100644 index 000000000..c41a11bc8 --- /dev/null +++ b/tests/end2end_tests/entrypoints/sshrvd_entrypoint.sh @@ -0,0 +1,2 @@ +#!/bin/bash +"$HOME"/.local/bin/sshrvd -a @sshrvdatsign -i "$(hostname -i)" -v -s 2>&1 | tee -a sshrvd.log From 01fb834cbabd266abe2bbb26afeb4e36f9a466d0 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 17:37:14 -0400 Subject: [PATCH 33/49] ci: use legacy daemon --- .github/composite/setup_entrypoints/action.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/composite/setup_entrypoints/action.yaml b/.github/composite/setup_entrypoints/action.yaml index 43d162d09..143724671 100644 --- a/.github/composite/setup_entrypoints/action.yaml +++ b/.github/composite/setup_entrypoints/action.yaml @@ -44,8 +44,7 @@ runs: if [ "${{ inputs.legacy_daemon }}" = "true" ]; then ARGS="$ARGS --legacy-daemon" fi - sed -i "s/ARGS=.*/ARGS=\"$ARGS\"/" "$entrypoint_filename" - ./setup-sshnp-entrypoint.sh ${{ inputs.devicename }} ${{ inputs.sshnp_atsign }} ${{ inputs.sshnpd_atsign }} ${{ inputs.sshrvd_atsign }} "$entrypoint_filename" + ./setup-sshnp-entrypoint.sh ${{ inputs.devicename }} ${{ inputs.sshnp_atsign }} ${{ inputs.sshnpd_atsign }} ${{ inputs.sshrvd_atsign }} "$entrypoint_filename" "$ARGS" - name: Setup NPD entrypoint shell: bash From 396c94e769225df24aa6b9656033dbd8b8a3cc32 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 17:37:30 -0400 Subject: [PATCH 34/49] ci: replace args in entrypoint file --- tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh b/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh index b63bb48de..dbfdf513d 100755 --- a/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh +++ b/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh @@ -9,6 +9,7 @@ sshnp=$2 # e.g. @alice sshnpd=$3 # e.g. @alice sshrvd=$4 # e.g. @alice template_name=$5 # e.g. sshnp_entrypoint.sh +args=$6 # e.g. "arg1 arg2 arg3" cp ../../entrypoints/"$template_name" ../sshnp/entrypoint.sh # copy template to the mounted folder @@ -23,4 +24,5 @@ fi eval "$prefix" "s/@sshnpatsign/${sshnp}/g" ../sshnp/entrypoint.sh eval "$prefix" "s/@sshnpdatsign/${sshnpd}/g" ../sshnp/entrypoint.sh eval "$prefix" "s/@sshrvdatsign/${sshrvd}/g" ../sshnp/entrypoint.sh -eval "$prefix" "s/deviceName/${device}/g" ../sshnp/entrypoint.sh \ No newline at end of file +eval "$prefix" "s/deviceName/${device}/g" ../sshnp/entrypoint.sh +eval "$prefix" "s/args/${args}/g" ../sshnp/entrypoint.sh \ No newline at end of file From 0e9a75ddc05f6b26d230adbbe71984e54de5177b Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 17:38:23 -0400 Subject: [PATCH 35/49] chore: put placeholder for args in place --- tests/end2end_tests/entrypoints/sshnp_entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end2end_tests/entrypoints/sshnp_entrypoint.sh b/tests/end2end_tests/entrypoints/sshnp_entrypoint.sh index c49902105..68aa7bbc5 100644 --- a/tests/end2end_tests/entrypoints/sshnp_entrypoint.sh +++ b/tests/end2end_tests/entrypoints/sshnp_entrypoint.sh @@ -1,6 +1,6 @@ #!/bin/bash echo "SSHNP START ENTRY" -ARGS="" +ARGS="args" SSHNP_COMMAND="$HOME/.local/bin/sshnp -f @sshnpatsign -t @sshnpdatsign -d deviceName -h @sshrvdatsign -s id_ed25519.pub -v $ARGS > sshnp.log" run_test() From 9559a5a1ba89dd9a4e1eb914c77fe72d572288df Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 17:43:13 -0400 Subject: [PATCH 36/49] ci: pass through only legacy daemon --- .github/composite/setup_entrypoints/action.yaml | 6 +----- .../contexts/_init_/setup-sshnp-entrypoint.sh | 10 ++++++++-- tests/end2end_tests/entrypoints/sshnp_entrypoint.sh | 3 +-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/composite/setup_entrypoints/action.yaml b/.github/composite/setup_entrypoints/action.yaml index 143724671..4ed0c0c93 100644 --- a/.github/composite/setup_entrypoints/action.yaml +++ b/.github/composite/setup_entrypoints/action.yaml @@ -40,11 +40,7 @@ runs: entrypoint_filename="sshnp_entrypoint.sh" ;; esac - ARGS="" - if [ "${{ inputs.legacy_daemon }}" = "true" ]; then - ARGS="$ARGS --legacy-daemon" - fi - ./setup-sshnp-entrypoint.sh ${{ inputs.devicename }} ${{ inputs.sshnp_atsign }} ${{ inputs.sshnpd_atsign }} ${{ inputs.sshrvd_atsign }} "$entrypoint_filename" "$ARGS" + ./setup-sshnp-entrypoint.sh ${{ inputs.devicename }} ${{ inputs.sshnp_atsign }} ${{ inputs.sshnpd_atsign }} ${{ inputs.sshrvd_atsign }} "$entrypoint_filename" ${{ inputs.legacy_daemon || false }} - name: Setup NPD entrypoint shell: bash diff --git a/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh b/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh index dbfdf513d..3f19b9d39 100755 --- a/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh +++ b/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh @@ -9,7 +9,7 @@ sshnp=$2 # e.g. @alice sshnpd=$3 # e.g. @alice sshrvd=$4 # e.g. @alice template_name=$5 # e.g. sshnp_entrypoint.sh -args=$6 # e.g. "arg1 arg2 arg3" +legacy=$6 # e.g. "arg1 arg2 arg3" cp ../../entrypoints/"$template_name" ../sshnp/entrypoint.sh # copy template to the mounted folder @@ -25,4 +25,10 @@ eval "$prefix" "s/@sshnpatsign/${sshnp}/g" ../sshnp/entrypoint.sh eval "$prefix" "s/@sshnpdatsign/${sshnpd}/g" ../sshnp/entrypoint.sh eval "$prefix" "s/@sshrvdatsign/${sshrvd}/g" ../sshnp/entrypoint.sh eval "$prefix" "s/deviceName/${device}/g" ../sshnp/entrypoint.sh -eval "$prefix" "s/args/${args}/g" ../sshnp/entrypoint.sh \ No newline at end of file +legacy_sub='' +if [ "$legacy" ]; then + legacy_sub="s/legacy/--legacy-daemon/g" +else + legacy_sub='s/legacy/--no-legacy-daemon/g' +fi +eval "$prefix" "$legacy_sub" ../sshnp/entrypoint.sh \ No newline at end of file diff --git a/tests/end2end_tests/entrypoints/sshnp_entrypoint.sh b/tests/end2end_tests/entrypoints/sshnp_entrypoint.sh index 68aa7bbc5..05384a880 100644 --- a/tests/end2end_tests/entrypoints/sshnp_entrypoint.sh +++ b/tests/end2end_tests/entrypoints/sshnp_entrypoint.sh @@ -1,7 +1,6 @@ #!/bin/bash echo "SSHNP START ENTRY" -ARGS="args" -SSHNP_COMMAND="$HOME/.local/bin/sshnp -f @sshnpatsign -t @sshnpdatsign -d deviceName -h @sshrvdatsign -s id_ed25519.pub -v $ARGS > sshnp.log" +SSHNP_COMMAND="$HOME/.local/bin/sshnp -f @sshnpatsign -t @sshnpdatsign -d deviceName -h @sshrvdatsign -s id_ed25519.pub -v legacy > sshnp.log" run_test() { From c3269795a3e39ba6c0bf83b73ae3da80d24a1b90 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 19:19:59 -0400 Subject: [PATCH 37/49] ci: ensure multibuild verifies tags --- .github/workflows/multibuild.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/multibuild.yaml b/.github/workflows/multibuild.yaml index 6ef731769..f721d7184 100644 --- a/.github/workflows/multibuild.yaml +++ b/.github/workflows/multibuild.yaml @@ -23,6 +23,8 @@ jobs: steps: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + - uses: ./.github/composite/verify_cli_tags + - uses: ./.github/composite/verify_core_tags - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 # v1.5.1 - run: mkdir sshnp - run: mkdir tarball From 060d9b5eb2a4287c8cb48bbb27258d899352ef22 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 19:21:29 -0400 Subject: [PATCH 38/49] ci: verify tags for both multibuilds --- .github/workflows/multibuild.yaml | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/.github/workflows/multibuild.yaml b/.github/workflows/multibuild.yaml index f721d7184..77d034497 100644 --- a/.github/workflows/multibuild.yaml +++ b/.github/workflows/multibuild.yaml @@ -7,7 +7,14 @@ permissions: # added using https://github.com/step-security/secure-repo contents: read jobs: + verify_tags: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + - uses: ./.github/composite/verify_cli_tags + - uses: ./.github/composite/verify_core_tags x64_build: + needs: verify_tags runs-on: ${{ matrix.os }} defaults: run: @@ -23,8 +30,6 @@ jobs: steps: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - - uses: ./.github/composite/verify_cli_tags - - uses: ./.github/composite/verify_core_tags - uses: dart-lang/setup-dart@8a4b97ea2017cc079571daec46542f76189836b1 # v1.5.1 - run: mkdir sshnp - run: mkdir tarball @@ -44,6 +49,7 @@ jobs: if-no-files-found: error other_build: + needs: verify_tags runs-on: ubuntu-latest defaults: run: @@ -60,20 +66,6 @@ jobs: output-name: sshnp-linux-riscv64 steps: - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 - - - name: Ensure version matches tag - if: startsWith(github.ref, 'refs/tags/v') - working-directory: ./packages/sshnoports - run: | - # check version.dart - REF=${{ github.ref }} - TAG=${REF:10} - DART_TAG="v$(grep -Po '^const String version = "(.*)";' lib/version.dart | cut -d'"' -f2)" - if [ "$TAG" != "$DART_TAG" ]; then - echo "Tag $TAG does not match version in version.dart: $DART_TAG" - exit 1 - fi - - uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 - run: | From 9297b11a41f8aa1e0519a2fd7cfe0b6f5781a043 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 19:52:39 -0400 Subject: [PATCH 39/49] chore: more cleanup --- packages/noports_core/CHANGELOG.md | 2 +- .../noports_core/lib/src/sshnp/sshnp.dart | 33 ++++++++++++++++--- .../sshnp_impl/sshnp_forward_dart_impl.dart | 7 +++- .../sshnp_impl/sshnp_forward_exec_impl.dart | 7 +++- .../lib/src/sshnp/sshnp_impl/sshnp_impl.dart | 10 +++--- .../sshnp/sshnp_impl/sshnp_legacy_impl.dart | 7 +++- .../sshnp/sshnp_impl/sshnp_reverse_impl.dart | 7 +++- 7 files changed, 59 insertions(+), 14 deletions(-) diff --git a/packages/noports_core/CHANGELOG.md b/packages/noports_core/CHANGELOG.md index b6bd2229a..58b982233 100644 --- a/packages/noports_core/CHANGELOG.md +++ b/packages/noports_core/CHANGELOG.md @@ -1,3 +1,3 @@ # 4.0.0 -- Initial release based of the 4.0.0 pre-release code of sshnoports \ No newline at end of file +- Initial release based off of the 4.0.0 pre-release code of sshnoports \ No newline at end of file diff --git a/packages/noports_core/lib/src/sshnp/sshnp.dart b/packages/noports_core/lib/src/sshnp/sshnp.dart index 16caf63e8..fd17ba7c1 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp.dart @@ -22,6 +22,7 @@ abstract class SSHNP { AtClient? atClient, AtClientGenerator? atClientGenerator, SSHRVGenerator? sshrvGenerator, + bool? shouldInitialize, }) async { atClient ??= await atClientGenerator?.call( params, SSHNPImpl.getNamespace(params.device)); @@ -33,19 +34,35 @@ abstract class SSHNP { if (params.legacyDaemon) { return SSHNP.legacy( - atClient: atClient, params: params, sshrvGenerator: sshrvGenerator); + atClient: atClient, + params: params, + sshrvGenerator: sshrvGenerator, + shouldInitialize: shouldInitialize, + ); } if (!params.host.startsWith('@')) { return SSHNP.reverse( - atClient: atClient, params: params, sshrvGenerator: sshrvGenerator); + atClient: atClient, + params: params, + sshrvGenerator: sshrvGenerator, + shouldInitialize: shouldInitialize, + ); } switch (SupportedSshClient.fromCliArg(params.sshClient)) { case SupportedSshClient.exec: - return SSHNP.forwardExec(atClient: atClient, params: params); + return SSHNP.forwardExec( + atClient: atClient, + params: params, + shouldInitialize: shouldInitialize, + ); case SupportedSshClient.dart: - return SSHNP.forwardDart(atClient: atClient, params: params); + return SSHNP.forwardDart( + atClient: atClient, + params: params, + shouldInitialize: shouldInitialize, + ); default: throw ArgumentError('Unsupported ssh client: ${params.sshClient}'); } @@ -56,11 +73,13 @@ abstract class SSHNP { required AtClient atClient, required SSHNPParams params, SSHRVGenerator? sshrvGenerator, + bool? shouldInitialize, }) => SSHNPLegacyImpl( atClient: atClient, params: params, sshrvGenerator: sshrvGenerator, + shouldInitialize: shouldInitialize, ); /// Creates an SSHNP instance that is configured to use reverse ssh tunneling @@ -68,31 +87,37 @@ abstract class SSHNP { required AtClient atClient, required SSHNPParams params, SSHRVGenerator? sshrvGenerator, + bool? shouldInitialize, }) => SSHNPReverseImpl( atClient: atClient, params: params, sshrvGenerator: sshrvGenerator, + shouldInitialize: shouldInitialize, ); /// Creates an SSHNP instance that is configured to use direct ssh tunneling by executing the ssh command factory SSHNP.forwardExec({ required AtClient atClient, required SSHNPParams params, + bool? shouldInitialize, }) => SSHNPForwardExecImpl( atClient: atClient, params: params, + shouldInitialize: shouldInitialize, ); /// Creates an SSHNP instance that is configured to use direct ssh tunneling using a pure-dart SSHClient factory SSHNP.forwardDart({ required AtClient atClient, required SSHNPParams params, + bool? shouldInitialize, }) => SSHNPForwardDartImpl( atClient: atClient, params: params, + shouldInitialize: shouldInitialize, ); /// The atClient to use for communicating with the atsign's secondary server diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart index a8f2a2607..01707e29a 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart @@ -11,7 +11,12 @@ class SSHNPForwardDartImpl extends SSHNPImpl with SSHNPForwardDirection { SSHNPForwardDartImpl({ required AtClient atClient, required SSHNPParams params, - }) : super(atClient: atClient, params: params); + bool? shouldInitialize, + }) : super( + atClient: atClient, + params: params, + shouldInitialize: shouldInitialize, + ); @override Future run() async { diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart index 398294918..82147f7fe 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_exec_impl.dart @@ -12,7 +12,12 @@ class SSHNPForwardExecImpl extends SSHNPImpl with SSHNPForwardDirection { SSHNPForwardExecImpl({ required AtClient atClient, required SSHNPParams params, - }) : super(atClient: atClient, params: params); + bool? shouldInitialize, + }) : super( + atClient: atClient, + params: params, + shouldInitialize: shouldInitialize, + ); @override Future run() async { diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart index 9c71194db..13cb3279d 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart @@ -122,7 +122,7 @@ abstract class SSHNPImpl implements SSHNP { required this.atClient, required this.params, SSHRVGenerator? sshrvGenerator, - bool shouldInitialize = true, + bool? shouldInitialize = true, }) : sessionId = Uuid().v4(), host = params.host, port = params.port, @@ -164,7 +164,7 @@ abstract class SSHNPImpl implements SSHNP { } /// Also call init - if (shouldInitialize) init(); + if (shouldInitialize ?? true) init(); } @override @@ -302,10 +302,10 @@ abstract class SSHNPImpl implements SSHNP { if (!initializedCompleter.isCompleted) { // Call init in case it hasn't been called yet unawaited(init()); - // Wait for init to complete - // N.B. must be called this way in case the init call above is not the first init call - await initialized; } + // Wait for init to complete + // N.B. must be called this way in case the init call above is not the first init call + return await initialized; } @protected diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart index 4176dbabb..d1dceb0c7 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_legacy_impl.dart @@ -11,8 +11,13 @@ class SSHNPLegacyImpl extends SSHNPImpl with SSHNPReverseDirection { required AtClient atClient, required SSHNPParams params, SSHRVGenerator? sshrvGenerator, + bool? shouldInitialize, }) : super( - atClient: atClient, params: params, sshrvGenerator: sshrvGenerator); + atClient: atClient, + params: params, + sshrvGenerator: sshrvGenerator, + shouldInitialize: shouldInitialize, + ); @override Future init() async { diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart index efc425dc7..2df4a8ec6 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_reverse_impl.dart @@ -12,8 +12,13 @@ class SSHNPReverseImpl extends SSHNPImpl with SSHNPReverseDirection { required AtClient atClient, required SSHNPParams params, SSHRVGenerator? sshrvGenerator, + bool? shouldInitialize, }) : super( - atClient: atClient, params: params, sshrvGenerator: sshrvGenerator); + atClient: atClient, + params: params, + sshrvGenerator: sshrvGenerator, + shouldInitialize: shouldInitialize, + ); @override Future init() async { From 2a0634bed8a22e1733116399533cc3a57869896f Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 20:05:33 -0400 Subject: [PATCH 40/49] chore: update .gitignore rules for pubspec.lock files --- .gitignore | 4 ++-- examples/.gitignore | 1 + packages/noports_core/.gitignore | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 examples/.gitignore create mode 100644 packages/noports_core/.gitignore diff --git a/.gitignore b/.gitignore index 8e3ba5e87..b08064d0f 100644 --- a/.gitignore +++ b/.gitignore @@ -22,8 +22,8 @@ compose-dev.yaml *.hash *.atKeys -# Ignore pubspec.lock -pubspec.lock +# Ignore root pubspec.lock only +/pubspec.lock # Conventional directory for build output. build/ diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 000000000..34c62e31c --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1 @@ +pubspec.lock \ No newline at end of file diff --git a/packages/noports_core/.gitignore b/packages/noports_core/.gitignore new file mode 100644 index 000000000..34c62e31c --- /dev/null +++ b/packages/noports_core/.gitignore @@ -0,0 +1 @@ +pubspec.lock \ No newline at end of file From 0b971c22e83c64606579af83e4a4b539bdd44170 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 20:12:06 -0400 Subject: [PATCH 41/49] chore: remove unused deps --- .../widgets/profile_actions/profile_run_action.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart index 37999705f..9cf277f8f 100644 --- a/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart +++ b/packages/sshnp_gui/lib/src/presentation/widgets/profile_actions/profile_run_action.dart @@ -1,9 +1,6 @@ -import 'dart:io'; - import 'package:at_client_mobile/at_client_mobile.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:socket_connector/socket_connector.dart'; import 'package:noports_core/sshnp.dart'; import 'package:noports_core/sshrv.dart'; import 'package:sshnp_gui/src/controllers/background_session_controller.dart'; From 465c3b72bd58bcf5b71c0e42f09a3611a96a99c7 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 20:15:10 -0400 Subject: [PATCH 42/49] fix: no arg when not using legacy daemon for backward client compatability --- tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh b/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh index 3f19b9d39..a058e619b 100755 --- a/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh +++ b/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh @@ -29,6 +29,6 @@ legacy_sub='' if [ "$legacy" ]; then legacy_sub="s/legacy/--legacy-daemon/g" else - legacy_sub='s/legacy/--no-legacy-daemon/g' + legacy_sub='s/legacy//g' fi eval "$prefix" "$legacy_sub" ../sshnp/entrypoint.sh \ No newline at end of file From c9e411c11f61da0657ade53d2048c680a2a24c7e Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 20:18:03 -0400 Subject: [PATCH 43/49] ci: use legacy placeholder --- tests/end2end_tests/entrypoints/sshnp_installer_entrypoint.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/end2end_tests/entrypoints/sshnp_installer_entrypoint.sh b/tests/end2end_tests/entrypoints/sshnp_installer_entrypoint.sh index 55b3ab918..3211cb921 100644 --- a/tests/end2end_tests/entrypoints/sshnp_installer_entrypoint.sh +++ b/tests/end2end_tests/entrypoints/sshnp_installer_entrypoint.sh @@ -1,6 +1,5 @@ #!/bin/bash -ARGS="" -SSHNP_COMMAND="$HOME/.local/bin/sshnp -f @sshnpatsign -t @sshnpdatsign -d deviceName -h @sshrvdatsign -s id_ed25519.pub -v $ARGS > sshnp.log" +SSHNP_COMMAND="$HOME/.local/bin/sshnp -f @sshnpatsign -t @sshnpdatsign -d deviceName -h @sshrvdatsign -s id_ed25519.pub -v legacy > sshnp.log" echo "Running: $SSHNP_COMMAND" eval "$SSHNP_COMMAND" cat sshnp.log From ae95c7be7b4b9ff27c7376c24d674c2a4303c190 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 20:28:30 -0400 Subject: [PATCH 44/49] ci: default legacy_daemon false --- .github/workflows/end2end_tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/end2end_tests.yaml b/.github/workflows/end2end_tests.yaml index a45882a81..f660c346a 100644 --- a/.github/workflows/end2end_tests.yaml +++ b/.github/workflows/end2end_tests.yaml @@ -202,6 +202,7 @@ jobs: matrix: np: [local, installer] npd: [local, installer] + legacy_daemon: [false] exclude: # Don't run these against themselves, pointless to test - np: local From 8d97b4ca62e222ab8b6e6a026995182c0f17f962 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 20:33:35 -0400 Subject: [PATCH 45/49] chore: hardcode the legacy daemon conditions for installer --- .github/workflows/end2end_tests.yaml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/end2end_tests.yaml b/.github/workflows/end2end_tests.yaml index f660c346a..d475986f9 100644 --- a/.github/workflows/end2end_tests.yaml +++ b/.github/workflows/end2end_tests.yaml @@ -202,16 +202,10 @@ jobs: matrix: np: [local, installer] npd: [local, installer] - legacy_daemon: [false] exclude: # Don't run these against themselves, pointless to test - np: local npd: local - - include: - - np: local - npd: installer - legacy_daemon: true steps: - name: Show Matrix Values run: | @@ -250,7 +244,7 @@ jobs: sshnpd_atsign: ${{ env.SSHNPD_ATSIGN }} sshrvd_atsign: ${{ env[env.PROD_RVD_ATSIGN] }} devicename: ${{ env.DEVICENAME }} - legacy_daemon: ${{ matrix.legacy_daemon || false }} + legacy_daemon: ${{ matrix.np == 'local' && matrix.npd == 'installer' }} - name: Ensure entrypoints exist working-directory: tests/end2end_tests/contexts From b29498b34ecd4e44c4a28ba3696c79e1abf6bf14 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 2 Oct 2023 21:00:39 -0400 Subject: [PATCH 46/49] chore: fix legacy check --- tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh b/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh index a058e619b..72ae98636 100755 --- a/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh +++ b/tests/end2end_tests/contexts/_init_/setup-sshnp-entrypoint.sh @@ -26,7 +26,7 @@ eval "$prefix" "s/@sshnpdatsign/${sshnpd}/g" ../sshnp/entrypoint.sh eval "$prefix" "s/@sshrvdatsign/${sshrvd}/g" ../sshnp/entrypoint.sh eval "$prefix" "s/deviceName/${device}/g" ../sshnp/entrypoint.sh legacy_sub='' -if [ "$legacy" ]; then +if [ "$legacy" == 'true' ]; then legacy_sub="s/legacy/--legacy-daemon/g" else legacy_sub='s/legacy//g' From 940ae1be8f19e3576185dcc9111765b880bf9007 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Tue, 3 Oct 2023 09:39:27 -0400 Subject: [PATCH 47/49] chore: rename asciimatcher to avoid scope pollution --- packages/noports_core/lib/src/common/utils.dart | 4 ++-- .../noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/noports_core/lib/src/common/utils.dart b/packages/noports_core/lib/src/common/utils.dart index ac8c36a76..366904ece 100644 --- a/packages/noports_core/lib/src/common/utils.dart +++ b/packages/noports_core/lib/src/common/utils.dart @@ -49,10 +49,10 @@ Future fileExists(String file) async { return f; } -const String asciiMatcher = r'[a-zA-Z0-9_]{0,15}'; +const String sshnpDeviceNameRegex = r'[a-zA-Z0-9_]{0,15}'; bool checkNonAscii(String test) { - return RegExp(asciiMatcher).allMatches(test).first.group(0) != test; + return RegExp(sshnpDeviceNameRegex).allMatches(test).first.group(0) != test; } String getDefaultAtKeysFilePath(String homeDirectory, String? atSign) { diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart index 13cb3279d..bd5ed0fda 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart @@ -472,7 +472,8 @@ abstract class SSHNPImpl implements SSHNP { Future<(Iterable, Iterable, Map)> listDevices() async { // get all the keys device_info.*.sshnpd - var scanRegex = 'device_info\\.$asciiMatcher\\.${DefaultArgs.namespace}'; + var scanRegex = + 'device_info\\.$sshnpDeviceNameRegex\\.${DefaultArgs.namespace}'; var atKeys = await _getAtKeysRemote(regex: scanRegex, sharedBy: sshnpdAtSign); @@ -483,7 +484,8 @@ abstract class SSHNPImpl implements SSHNP { // Listen for heartbeat notifications atClient.notificationService - .subscribe(regex: 'heartbeat\\.$asciiMatcher', shouldDecrypt: true) + .subscribe( + regex: 'heartbeat\\.$sshnpDeviceNameRegex', shouldDecrypt: true) .listen((notification) { var deviceInfo = jsonDecode(notification.value ?? '{}'); var devicename = deviceInfo['devicename']; From afa2157790888f72a819820c52bfea99cdaf7af9 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Tue, 3 Oct 2023 14:04:53 -0400 Subject: [PATCH 48/49] chore: final cleanup --- .../sshnp_impl/sshnp_forward_dart_impl.dart | 1 - packages/noports_core/lib/sshnp.dart | 1 + packages/noports_core/lib/sshnp_params.dart | 3 ++- packages/sshnoports/bin/sshnp.dart | 27 +++++++++++-------- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart index 01707e29a..1a92d5711 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_forward_dart_impl.dart @@ -165,7 +165,6 @@ class SSHNPForwardDartImpl extends SSHNPImpl with SSHNPForwardDirection { if (counter == 0 || client.isClosed) { timer.cancel(); if (!client.isClosed) client.close(); - await client.done; doneCompleter.complete(); logger.shout( '$sessionId | no active connections - ssh session complete'); diff --git a/packages/noports_core/lib/sshnp.dart b/packages/noports_core/lib/sshnp.dart index 7980ee005..3aba9d0ed 100644 --- a/packages/noports_core/lib/sshnp.dart +++ b/packages/noports_core/lib/sshnp.dart @@ -3,3 +3,4 @@ library noports_core_sshnp; export 'src/sshnp/sshnp.dart'; export 'src/sshnp/sshnp_result.dart'; export 'src/sshnp/sshnp_params/sshnp_params.dart'; +export 'src/common/supported_ssh_clients.dart'; diff --git a/packages/noports_core/lib/sshnp_params.dart b/packages/noports_core/lib/sshnp_params.dart index b7b776350..536bb4f72 100644 --- a/packages/noports_core/lib/sshnp_params.dart +++ b/packages/noports_core/lib/sshnp_params.dart @@ -3,4 +3,5 @@ library noports_core_sshnp_params; export 'src/sshnp/sshnp_params/config_file_repository.dart'; export 'src/sshnp/sshnp_params/config_key_repository.dart'; export 'src/sshnp/sshnp_params/sshnp_params.dart'; -export 'src/sshnp/sshnp_params/sshnp_arg.dart'; \ No newline at end of file +export 'src/sshnp/sshnp_params/sshnp_arg.dart'; +export 'src/common/supported_ssh_clients.dart'; \ No newline at end of file diff --git a/packages/sshnoports/bin/sshnp.dart b/packages/sshnoports/bin/sshnp.dart index 2957c875b..7b371eee8 100644 --- a/packages/sshnoports/bin/sshnp.dart +++ b/packages/sshnoports/bin/sshnp.dart @@ -28,7 +28,7 @@ void main(List args) async { if (help) { printVersion(); - stdout.writeln(SSHNPPartialParams.parser.usage); + stderr.writeln(SSHNPPartialParams.parser.usage); exit(0); } @@ -61,7 +61,7 @@ void main(List args) async { }); if (params.listDevices) { - stdout.writeln('Searching for devices...'); + stderr.writeln('Searching for devices...'); var (active, off, info) = await sshnp!.listDevices(); printDevices(active, off, info); exit(0); @@ -77,11 +77,16 @@ void main(List args) async { } throw res; } - if (res is SSHNPCommand || res is SSHNPNoOpSuccess) { + if (res is SSHNPCommand) { stdout.write('$res\n'); await sshnp!.done; exit(0); } + if (res is SSHNPNoOpSuccess) { + stderr.write('$res\n'); + await sshnp!.done; + exit(0); + } } on ArgumentError catch (error, stackTrace) { usageCallback(error, stackTrace); exit(1); @@ -109,7 +114,7 @@ void main(List args) async { void usageCallback(Object e, StackTrace s) { printVersion(); - stdout.writeln(SSHNPPartialParams.parser.usage); + stderr.writeln(SSHNPPartialParams.parser.usage); stderr.writeln('\n$e'); } @@ -119,26 +124,26 @@ void printDevices( Map info, ) { if (active.isEmpty && off.isEmpty) { - stdout.writeln('[X] No devices found\n'); - stdout.writeln( + stderr.writeln('[X] No devices found\n'); + stderr.writeln( 'Note: only devices with sshnpd version 3.4.0 or higher are supported by this command.'); - stdout.writeln( + stderr.writeln( 'Please update your devices to sshnpd version >= 3.4.0 and try again.'); exit(0); } - stdout.writeln('Active Devices:'); + stderr.writeln('Active Devices:'); printDeviceList(active, info); - stdout.writeln('Inactive Devices:'); + stderr.writeln('Inactive Devices:'); printDeviceList(off, info); } void printDeviceList(Iterable devices, Map info) { if (devices.isEmpty) { - stdout.writeln(' No devices found'); + stderr.writeln(' No devices found'); return; } for (var device in devices) { - stdout.writeln(' $device - v${info[device]?['version']}'); + stderr.writeln(' $device - v${info[device]?['version']}'); } } From 8a1019a58557bf27febfbd3a6c8dbd2b65dbc1ff Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Tue, 3 Oct 2023 14:36:50 -0400 Subject: [PATCH 49/49] fix: double list devices timeout --- packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart index bd5ed0fda..6a2ce6b10 100644 --- a/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart +++ b/packages/noports_core/lib/src/sshnp/sshnp_impl/sshnp_impl.dart @@ -529,7 +529,7 @@ abstract class SSHNPImpl implements SSHNP { } // wait for 10 seconds in case any are being slow - await Future.delayed(const Duration(seconds: 5)); + await Future.delayed(const Duration(seconds: 10)); // The intersection is in place on the off chance that some random device // sends a heartbeat notification, but is not on the list of devices