mirror of
https://github.com/neon443/ShhShell.git
synced 2026-03-11 05:19:13 +00:00
made more funcitons throw errors instead of just return, more informative
added name to host removed hostkey from connectionview as its in keys now fix crash on force unrap in keypair navigatoin labels added hostinvalid() to check if the host is invalid added case notconnected for keyerror openshell is throwing moved stuff round
This commit is contained in:
@@ -180,8 +180,8 @@
|
||||
A92538C72DEE0742007E0A18 /* ShhShell */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A93143C22DF61F5700FCD5DB /* ShhShell.entitlements */,
|
||||
A92538C62DEE0742007E0A18 /* ShhShellApp.swift */,
|
||||
A93143C22DF61F5700FCD5DB /* ShhShell.entitlements */,
|
||||
A98554572E055398009051BD /* Keys */,
|
||||
A98554562E055394009051BD /* Host */,
|
||||
A93143C12DF61E8500FCD5DB /* SSH */,
|
||||
@@ -210,12 +210,11 @@
|
||||
A92538D32DEE0749007E0A18 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A98554532E05534F009051BD /* Keys */,
|
||||
A92538C52DEE0742007E0A18 /* ContentView.swift */,
|
||||
A98554622E0587DF009051BD /* HostsView.swift */,
|
||||
A985545C2E055D4D009051BD /* ConnectionView.swift */,
|
||||
A93143C52DF61FE300FCD5DB /* ViewModifiers.swift */,
|
||||
A98554532E05534F009051BD /* Keys */,
|
||||
A96C6B042E0C523E00F377FE /* Hosts */,
|
||||
A923172B2E0712F200ECE1E6 /* Terminal */,
|
||||
A96C6B032E0C523600F377FE /* Misc */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@@ -248,11 +247,29 @@
|
||||
path = ci_scripts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A96C6B032E0C523600F377FE /* Misc */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A96C6B012E0C49E800F377FE /* CenteredLabel.swift */,
|
||||
A93143C52DF61FE300FCD5DB /* ViewModifiers.swift */,
|
||||
);
|
||||
path = Misc;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A96C6B042E0C523E00F377FE /* Hosts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A985545C2E055D4D009051BD /* ConnectionView.swift */,
|
||||
A98554622E0587DF009051BD /* HostsView.swift */,
|
||||
);
|
||||
path = Hosts;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A98554532E05534F009051BD /* Keys */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A96C6AFF2E0C45FE00F377FE /* KeyDetailView.swift */,
|
||||
A98554542E05535F009051BD /* KeyManagerView.swift */,
|
||||
A96C6AFD2E0C43B600F377FE /* Keypair.swift */,
|
||||
);
|
||||
path = Keys;
|
||||
sourceTree = "<group>";
|
||||
@@ -271,8 +288,7 @@
|
||||
children = (
|
||||
A98554582E0553AA009051BD /* KeyManager.swift */,
|
||||
A985545E2E056EDD009051BD /* KeychainLayer.swift */,
|
||||
A96C6AFF2E0C45FE00F377FE /* KeyDetailView.swift */,
|
||||
A96C6B012E0C49E800F377FE /* CenteredLabel.swift */,
|
||||
A96C6AFD2E0C43B600F377FE /* Keypair.swift */,
|
||||
);
|
||||
path = Keys;
|
||||
sourceTree = "<group>";
|
||||
|
||||
@@ -9,6 +9,7 @@ import Foundation
|
||||
|
||||
protocol HostPr: Codable, Identifiable, Equatable {
|
||||
var id: UUID { get set }
|
||||
var name: String { get set }
|
||||
var address: String { get set }
|
||||
var port: Int { get set }
|
||||
var username: String { get set }
|
||||
@@ -21,6 +22,7 @@ protocol HostPr: Codable, Identifiable, Equatable {
|
||||
|
||||
struct Host: HostPr {
|
||||
var id = UUID()
|
||||
var name: String = ""
|
||||
var address: String = ""
|
||||
var port: Int
|
||||
var username: String
|
||||
@@ -31,6 +33,7 @@ struct Host: HostPr {
|
||||
var key: String?
|
||||
|
||||
init(
|
||||
name: String = "",
|
||||
address: String,
|
||||
port: Int = 22,
|
||||
username: String = "",
|
||||
@@ -40,6 +43,7 @@ struct Host: HostPr {
|
||||
passphrase: String = "",
|
||||
hostkey: String? = nil
|
||||
) {
|
||||
self.name = name
|
||||
self.address = address
|
||||
self.port = port
|
||||
self.username = username
|
||||
@@ -57,6 +61,7 @@ extension Host {
|
||||
}
|
||||
static var debug: Host {
|
||||
Host(
|
||||
name: "name for localhost",
|
||||
address: "localhost",
|
||||
port: 22,
|
||||
username: "neon443",
|
||||
|
||||
@@ -19,6 +19,7 @@ enum AuthError: Error {
|
||||
}
|
||||
|
||||
enum KeyError: Error {
|
||||
case notConnected
|
||||
case importPubkeyError
|
||||
case importPrivkeyError
|
||||
case pubkeyRejected
|
||||
|
||||
@@ -82,7 +82,13 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
openShell()
|
||||
|
||||
do {
|
||||
try openShell()
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
|
||||
setTitle("\(host.username)@\(host.address)")
|
||||
ssh_channel_request_env(channel, "TERM", "xterm-256color")
|
||||
ssh_channel_request_env(channel, "LANG", "en_US.UTF-8")
|
||||
@@ -90,6 +96,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
||||
}
|
||||
|
||||
func connect() throws(SSHError) {
|
||||
guard !host.address.isEmpty else { throw .connectionFailed("No address to connect to.") }
|
||||
withAnimation { state = .connecting }
|
||||
|
||||
var verbosity: Int = 0
|
||||
@@ -153,6 +160,14 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func hostInvalid() -> Bool {
|
||||
if host.address.isEmpty && host.username.isEmpty {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func testExec() {
|
||||
var success = false
|
||||
defer {
|
||||
@@ -174,9 +189,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
||||
var nbytes: CInt
|
||||
|
||||
let testChannel = ssh_channel_new(session)
|
||||
guard testChannel != nil else {
|
||||
return
|
||||
}
|
||||
guard testChannel != nil else { return }
|
||||
|
||||
status = ssh_channel_open_session(testChannel)
|
||||
guard status == SSH_OK else {
|
||||
@@ -220,9 +233,7 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
||||
|
||||
//MARK: auth
|
||||
func authWithPubkey(pub pubInp: Data, priv privInp: Data, pass: String) throws(KeyError) {
|
||||
guard session != nil else {
|
||||
return
|
||||
}
|
||||
guard session != nil else { throw .notConnected }
|
||||
|
||||
let fileManager = FileManager.default
|
||||
let tempDir = fileManager.temporaryDirectory
|
||||
@@ -328,32 +339,36 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
||||
}
|
||||
|
||||
//MARK: shell
|
||||
func openShell() {
|
||||
func openShell() throws(SSHError) {
|
||||
var status: CInt
|
||||
|
||||
channel = ssh_channel_new(session)
|
||||
guard let channel else { return }
|
||||
guard let channel else { throw .communicationError("Not connected") }
|
||||
|
||||
status = ssh_channel_open_session(channel)
|
||||
guard status == SSH_OK else {
|
||||
ssh_channel_free(channel)
|
||||
return
|
||||
throw .communicationError("Failed opening channel")
|
||||
}
|
||||
|
||||
interactiveShellSession()
|
||||
do {
|
||||
try interactiveShellSession()
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private func interactiveShellSession() {
|
||||
private func interactiveShellSession() throws(SSHError) {
|
||||
var status: CInt
|
||||
|
||||
status = ssh_channel_request_pty(self.channel)
|
||||
guard status == SSH_OK else { return }
|
||||
guard status == SSH_OK else { throw .communicationError("PTY request failed") }
|
||||
|
||||
status = ssh_channel_change_pty_size(self.channel, 80, 24)
|
||||
guard status == SSH_OK else { return }
|
||||
guard status == SSH_OK else { throw .communicationError("Failed setting PTY size") }
|
||||
|
||||
status = ssh_channel_request_shell(self.channel)
|
||||
guard status == SSH_OK else { return }
|
||||
guard status == SSH_OK else { throw .communicationError("Failed requesting shell") }
|
||||
|
||||
withAnimation { state = .shellOpen }
|
||||
}
|
||||
@@ -398,9 +413,9 @@ class SSHHandler: @unchecked Sendable, ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func resizePTY(toRows: Int, toCols: Int) {
|
||||
guard ssh_channel_is_open(channel) != 0 else { return }
|
||||
guard ssh_channel_is_eof(channel) == 0 else { return }
|
||||
func resizePTY(toRows: Int, toCols: Int) throws(SSHError) {
|
||||
guard ssh_channel_is_open(channel) != 0 else { throw .communicationError("Channel not open") }
|
||||
guard ssh_channel_is_eof(channel) == 0 else { throw .backendError("Channel is EOF") }
|
||||
|
||||
ssh_channel_change_pty_size(channel, Int32(toCols), Int32(toRows))
|
||||
// print("resized tty to \(toRows)rows and \(toCols)cols")
|
||||
|
||||
@@ -76,17 +76,6 @@ struct ConnectionView: View {
|
||||
TextField("", text: $passphrase, prompt: Text("Passphrase (Optional)"))
|
||||
}
|
||||
|
||||
if handler.host.key != nil {
|
||||
Text("Hostkey: \(handler.getHostkey() ?? hostsManager.getHostMatching(handler.host)?.key ?? "nil")")
|
||||
.onChange(of: handler.host.key) { _ in
|
||||
guard let previousKnownHost = hostsManager.getHostMatching(handler.host) else { return }
|
||||
guard handler.host.key == previousKnownHost.key else {
|
||||
hostKeyChangedAlert = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button() {
|
||||
showTerminal.toggle()
|
||||
} label: {
|
||||
@@ -130,12 +119,20 @@ struct ConnectionView: View {
|
||||
systemImage: handler.connected ? "xmark.app.fill" : "power"
|
||||
)
|
||||
}
|
||||
.disabled(handler.hostInvalid())
|
||||
}
|
||||
}
|
||||
}
|
||||
.fullScreenCover(isPresented: $showTerminal) {
|
||||
ShellView(handler: handler)
|
||||
}
|
||||
.onChange(of: handler.host.key) { _ in
|
||||
guard let previousKnownHost = hostsManager.getHostMatching(handler.host) else { return }
|
||||
guard handler.host.key == previousKnownHost.key else {
|
||||
hostKeyChangedAlert = true
|
||||
return
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
guard hostsManager.getHostMatching(handler.host) == handler.host else {
|
||||
hostsManager.updateHost(handler.host)
|
||||
@@ -19,13 +19,31 @@ struct KeyManagerView: View {
|
||||
NavigationLink {
|
||||
KeyDetailView(hostsManager: hostsManager, keypair: keypair)
|
||||
} label: {
|
||||
Text(String(data: keypair.publicKey!, encoding: .utf8) ?? "nil")
|
||||
if let publicKey = keypair.publicKey {
|
||||
Text(String(data: publicKey, encoding: .utf8) ?? "nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Section {
|
||||
NavigationLink {
|
||||
List {
|
||||
if hostsManager.savedHosts.isEmpty {
|
||||
VStack(alignment: .center) {
|
||||
Text("Looking empty 'round here...")
|
||||
.font(.title3)
|
||||
.bold()
|
||||
.padding(.bottom)
|
||||
VStack(alignment: .leading) {
|
||||
Text("Connect to some hosts to collect more hostkeys!")
|
||||
.padding(.bottom)
|
||||
Text("ShhShell remembers hostkey fingerprints for you, and can alert you if they change.")
|
||||
.font(.subheadline)
|
||||
Text("This could be due a man in the middle attack, where a bad actor tries to impersonate your server.")
|
||||
.font(.subheadline)
|
||||
}
|
||||
}
|
||||
}
|
||||
ForEach(hostsManager.savedHosts) { host in
|
||||
VStack(alignment: .leading) {
|
||||
Text(host.address)
|
||||
|
||||
@@ -75,7 +75,7 @@ final class SSHTerminalView: TerminalView, Sendable, @preconcurrency TerminalVie
|
||||
}
|
||||
|
||||
public func sizeChanged(source: TerminalView, newCols: Int, newRows: Int) {
|
||||
handler?.resizePTY(toRows: newRows, toCols: newCols)
|
||||
try? handler?.resizePTY(toRows: newRows, toCols: newCols)
|
||||
}
|
||||
|
||||
public func send(source: TerminalView, data: ArraySlice<UInt8>) {
|
||||
|
||||
Reference in New Issue
Block a user