CH-J SM Mobileδιαχείριση διακομιστή πάνω από SSH
Μενού
Δημοσίευση πηγής

CH-J SM Mobile

Περιηγηθείτε σε καταλόγους και αρχεία για μια συγκεκριμένη έκδοση εφαρμογών.

Κατεβάστε την πηγή ZIP
CH-J / SM Mobile 134 αρχεία
sm-mobile-20261016T050549Z-3678f2a05fa9 Προστέθηκε: · Χτισμένο:
Αρχεία
ssh_service.dart Προστέθηκε: Ημερομηνία που δεν έχει καταγραφεί 6,6 KB · 240 γραμμές
Λήψη αρχείου
1import 'dart:async';
2import 'dart:convert';
3import 'dart:io';
4import 'package:dartssh2/dartssh2.dart';
5import 'package:flutter/foundation.dart';
6import 'package:xterm/xterm.dart';
7import '../core/failure.dart';
8import '../models/server_profile.dart';
9import 'host_verifier.dart';
10import 'vault.dart';
12String failureCode(Object error) {
13 if (error is Failure) return error.code;
14 if (error is TimeoutException) return 'timeout';
15 if ((error is SSHHandshakeError && error.message == 'Handshake timed out') ||
16 (error is SSHAuthAbortError &&
17 error.message == 'Authentication timed out')) {
18 return 'timeout';
19 }
20 if (error is SSHAuthError) return 'authFailed';
21 if (error is SSHHostkeyError) return 'hostRejected';
22 if (error is SocketException) return 'networkError';
23 if (error is SftpStatusError) {
24 return error.code == 3 ? 'permissionDenied' : 'fileError';
25 }
26 if (error is SSHKeyDecodeError) return 'invalidKey';
27 return 'operationFailed';
30class SshService extends ChangeNotifier {
31 final Vault vault;
32 final Duration connectTimeout, handshakeTimeout, authTimeout;
33 SSHClient? _client;
34 SSHSession? _shell;
35 SSHSocket? _pendingSocket;
36 ServerProfile? profile;
37 Terminal terminal = Terminal(maxLines: 3000);
38 bool connecting = false;
39 bool get connected => _client != null && _shell != null;
40 String? error;
41 int _epoch = 0;
42 int? handshakeMs;
43 bool ctrlNext = false, altNext = false;
44 SshService(
45 this.vault, {
46 this.connectTimeout = const Duration(seconds: 15),
47 this.handshakeTimeout = const Duration(seconds: 90),
48 this.authTimeout = const Duration(seconds: 30),
49 });
50 SSHClient get client {
51 if (!connected || !vault.unlocked) throw const Failure('disconnected');
52 return _client!;
53 }
55 Future<void> connect(
56 ServerProfile target, {
57 required String credential,
58 String? passphrase,
59 required TrustPrompt prompt,
60 }) async {
61 disconnect();
62 final epoch = _epoch;
63 final vaultEpoch = vault.generation;
64 connecting = true;
65 profile = target;
66 error = null;
67 notifyListeners();
68 SSHClient? candidate;
69 Object? hostFailure;
70 void check() {
71 if (_epoch != epoch ||
72 !vault.unlocked ||
73 vaultEpoch != vault.generation) {
74 throw const Failure('locked');
75 }
76 }
78 try {
79 final identities = target.auth == AuthMethod.privateKey
80 ? SSHKeyPair.fromPem(credential, passphrase)
81 : null;
82 check();
83 final watch = Stopwatch()..start();
84 final socket = await SSHSocket.connect(
85 target.host,
86 target.port,
87 timeout: connectTimeout,
88 );
89 if (_epoch != epoch || !vault.unlocked) {
90 socket.close();
91 throw const Failure('locked');
92 }
93 _pendingSocket = socket;
94 candidate = SSHClient(
95 socket,
96 username: target.username,
97 identities: identities,
98 onPasswordRequest: target.auth == AuthMethod.password
99 ? () {
100 check();
101 return credential;
102 }
103 : null,
104 handshakeTimeout: handshakeTimeout,
105 authTimeout: authTimeout,
106 onVerifyHostKey: (type, fingerprint) async {
107 watch.stop();
108 try {
109 check();
110 final accepted = await HostVerifier(
111 vault,
112 ).verify(target.endpoint, type, utf8.decode(fingerprint), prompt);
113 check();
114 return accepted;
115 } catch (e) {
116 hostFailure = e;
117 return false;
118 } finally {
119 watch.start();
120 }
121 },
122 );
123 unawaited(
124 candidate.done.then(
125 (_) {
126 if (_epoch == epoch) _lost('disconnected');
127 },
128 onError: (Object e) {
129 if (_epoch == epoch) _lost(failureCode(hostFailure ?? e));
130 },
131 ),
132 );
133 await candidate.authenticated;
134 check();
135 handshakeMs = watch.elapsedMilliseconds;
136 final shell = await candidate
137 .shell(
138 pty: const SSHPtyConfig(
139 type: 'xterm-256color',
140 width: 80,
141 height: 24,
142 ),
143 )
144 .timeout(const Duration(seconds: 15));
145 check();
146 _client = candidate;
147 _shell = shell;
148 _pendingSocket = null;
149 terminal = Terminal(maxLines: 3000);
150 terminal.onOutput = (output) {
151 final hadModifier = ctrlNext || altNext;
152 if (ctrlNext && output.length == 1) {
153 final n = output.codeUnitAt(0);
154 if (n >= 0x40 && n <= 0x7f) output = String.fromCharCode(n & 0x1f);
155 }
156 if (altNext) output = '\x1b$output';
157 ctrlNext = false;
158 altNext = false;
159 if (connected && vault.unlocked) {
160 _shell!.write(Uint8List.fromList(utf8.encode(output)));
161 }
162 if (hadModifier) notifyListeners();
163 };
164 terminal.onResize = (w, h, pw, ph) {
165 if (connected) _shell!.resizeTerminal(w, h, pw, ph);
166 };
167 void consume(Stream<Uint8List> stream) {
168 stream
169 .cast<List<int>>()
170 .transform(const Utf8Decoder(allowMalformed: true))
171 .listen(
172 (s) {
173 if (_epoch == epoch && vault.unlocked) terminal.write(s);
174 },
175 onError: (Object e) {
176 if (_epoch == epoch) _lost(failureCode(e));
177 },
178 );
179 }
181 consume(shell.stdout);
182 consume(shell.stderr);
183 unawaited(
184 shell.done.then(
185 (_) {
186 if (_epoch == epoch) _lost('disconnected');
187 },
188 onError: (Object e) {
189 if (_epoch == epoch) _lost(failureCode(e));
190 },
191 ),
192 );
193 } catch (e) {
194 if (candidate != null) unawaited(candidate.close());
195 if (_epoch == epoch) {
196 _pendingSocket?.close();
197 _pendingSocket = null;
198 error = failureCode(hostFailure ?? e);
199 }
200 } finally {
201 if (_epoch == epoch) {
202 connecting = false;
203 notifyListeners();
204 }
205 }
206 }
208 void _lost(String code) {
209 disconnect();
210 error = code;
211 notifyListeners();
212 }
214 void disconnect({bool clear = false}) {
215 ctrlNext = false;
216 altNext = false;
217 _epoch++;
218 connecting = false;
219 final c = _client;
220 _client = null;
221 _shell?.close();
222 _shell = null;
223 _pendingSocket?.close();
224 _pendingSocket = null;
225 if (c != null) unawaited(c.close());
226 if (clear) {
227 profile = null;
228 terminal = Terminal(maxLines: 3000);
229 error = null;
230 handshakeMs = null;
231 }
232 notifyListeners();
233 }
235 @override
236 void dispose() {
237 disconnect(clear: true);
238 super.dispose();
239 }
SHA-256281e845e0759b099582c37c6930844bffcefd220fc49f3b4cabc56fae584617c

CH-J Proprietary Software License 1.14 · Η πηγή παρέχεται στο CH-J Proprietary Software License 1.14. Η διαθεσιμότητά του δεν αλλάζει τους όρους της άδειας χρήσης ούτε χορηγεί πρόσθετα δικαιώματα.

Archive SHA-256c486a807e4828998b9c57ab0f2e760f7980b31965fe118e89d3e1a5a2b65be65