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:
neon443
2025-06-25 17:17:47 +01:00
parent 2548dde85b
commit 76601e0470
12 changed files with 91 additions and 39 deletions

View File

@@ -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>";

View File

@@ -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",

View File

@@ -19,6 +19,7 @@ enum AuthError: Error {
}
enum KeyError: Error {
case notConnected
case importPubkeyError
case importPrivkeyError
case pubkeyRejected

View File

@@ -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")

View File

@@ -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)

View File

@@ -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)

View File

@@ -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>) {