add zig connector

This commit is contained in:
Jeffrey C. Ollie 2024-01-12 22:47:10 -06:00
parent 78789b68ea
commit 4b2e5b33b6
Signed by: jeff
GPG key ID: 6F86035A6D97044E
8 changed files with 730 additions and 44 deletions

2
.gitignore vendored
View file

@ -1,3 +1,5 @@
__pycache__
/result
/.venv
/zig-cache
/zig-out

85
build.zig Normal file
View file

@ -0,0 +1,85 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const ssh_path = b.option(
[]const u8,
"ssh",
"path to ssh binary",
) orelse "ssh";
const telnet_path = b.option(
[]const u8,
"telnet",
"path to telnet binary",
) orelse "telnet";
const exe = b.addExecutable(.{
.name = "hostapps",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
const build_options = b.addOptions();
build_options.addOption([]const u8, "ssh_path", ssh_path);
build_options.addOption([]const u8, "telnet_path", telnet_path);
exe.root_module.addOptions("build_options", build_options);
const anzi = b.dependency(
"anzi",
.{
.target = target,
.optimize = optimize,
},
);
exe.root_module.addImport("anzi", anzi.module("anzi"));
const logz = b.dependency(
"logz",
.{
.target = target,
.optimize = optimize,
},
);
exe.root_module.addImport("logz", logz.module("logz"));
const yazap = b.dependency(
"yazap",
.{
.target = target,
.optimize = optimize,
},
);
exe.root_module.addImport("yazap", yazap.module("yazap"));
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
const exe_unit_tests = b.addTest(.{
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_exe_unit_tests.step);
}

28
build.zig.zon Normal file
View file

@ -0,0 +1,28 @@
.{
.name = "hostapps",
.version = "0.1.0",
.minimum_zig_version = "0.12.0",
.dependencies = .{
.logz = .{
.url = "https://github.com/karlseguin/log.zig/archive/a70984c80eb67c448480377849ba2adb6e51cf73.tar.gz",
.hash = "122090c83a4e52b454e006da4a804b2210136ddec0dccfae00e7097314e5acfd01d0",
},
.yazap = .{
.url = "https://github.com/prajwalch/yazap/archive/5f0d5d8928d5cd1907760dc41fa6f05dd232aaa5.tar.gz",
.hash = "1220e4674826a70402974f13b7e2aaa4e9242e1b2b9d592015de9e21c7fa5fe200bd",
},
.anzi = .{
.url = "https://git.ocjtech.us/jeff/anzi/archive/0e9d395a55b16da1d5bf7bfb59e2cfa59a7f2630.tar.gz",
.hash = "12203f0ed986047bcd860b00496979a7734c2f462da4e56a72add4b17a1a7981f8ec",
},
},
.paths = .{
// "",
// For example...
"build.zig",
"build.zig.zon",
"src",
// "LICENSE",
// "README.md",
},
}

View file

@ -1,15 +1,31 @@
{
"nodes": {
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
@ -18,6 +34,54 @@
"type": "github"
}
},
"flake-utils_2": {
"locked": {
"lastModified": 1659877975,
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"zls",
"nixpkgs"
]
},
"locked": {
"lastModified": 1703887061,
"narHash": "sha256-gGPa9qWNc6eCXT/+Z5/zMkyYOuRZqeFZBDbopNZQkuY=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "43e1aa1308018f37118e34d3a9cb4f5e75dc11d5",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"langref": {
"flake": false,
"locked": {
"narHash": "sha256-mYdDCBdNEIeMbavdhSo8qXqW+3fqPC8BAich7W3umrI=",
"type": "file",
"url": "https://raw.githubusercontent.com/ziglang/zig/63bd2bff12992aef0ce23ae4b344e9cb5d65f05d/doc/langref.html.in"
},
"original": {
"type": "file",
"url": "https://raw.githubusercontent.com/ziglang/zig/63bd2bff12992aef0ce23ae4b344e9cb5d65f05d/doc/langref.html.in"
}
},
"make-shell": {
"locked": {
"lastModified": 1634940815,
@ -41,11 +105,11 @@
]
},
"locked": {
"lastModified": 1693660503,
"narHash": "sha256-B/g2V4v6gjirFmy+I5mwB2bCYc0l3j5scVfwgl6WOl8=",
"lastModified": 1698974481,
"narHash": "sha256-yPncV9Ohdz1zPZxYHQf47S8S0VrnhV7nNhCawY46hDA=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "bd5bdbb52350e145c526108f4ef192eb8e554fa0",
"rev": "4bb5e752616262457bc7ca5882192a564c0472d2",
"type": "github"
},
"original": {
@ -56,11 +120,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1698318101,
"narHash": "sha256-gUihHt3yPD7bVqg+k/UVHgngyaJ3DMEBchbymBMvK1E=",
"lastModified": 1704722960,
"narHash": "sha256-mKGJ3sPsT6//s+Knglai5YflJUF2DGj7Ai6Ynopz0kI=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "63678e9f3d3afecfeafa0acead6239cdb447574c",
"rev": "317484b1ead87b9c1b8ac5261a8d2dd748a0492d",
"type": "github"
},
"original": {
@ -83,11 +147,11 @@
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1698640399,
"narHash": "sha256-mXzyx79/iFLZ0UDuSkqgFfejYRcSJfsCnJ9WlMusaI0=",
"lastModified": 1705060653,
"narHash": "sha256-puYyylgrBS4AFAHeyVRTjTUVD8DZdecJfymWJe7H438=",
"owner": "nix-community",
"repo": "poetry2nix",
"rev": "626111646fe236cb1ddc8191a48c75e072a82b7c",
"rev": "e0b44e9e2d3aa855d1dd77b06f067cd0e0c3860d",
"type": "github"
},
"original": {
@ -101,7 +165,9 @@
"flake-utils": "flake-utils",
"make-shell": "make-shell",
"nixpkgs": "nixpkgs",
"poetry2nix": "poetry2nix"
"poetry2nix": "poetry2nix",
"zig": "zig",
"zls": "zls"
}
},
"systems": {
@ -141,11 +207,11 @@
]
},
"locked": {
"lastModified": 1697388351,
"narHash": "sha256-63N2eBpKaziIy4R44vjpUu8Nz5fCJY7okKrkixvDQmY=",
"lastModified": 1699786194,
"narHash": "sha256-3h3EH1FXQkIeAuzaWB+nK0XK54uSD46pp+dMD3gAcB4=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "aae39f64f5ecbe89792d05eacea5cb241891292a",
"rev": "e82f32aa7f06bbbd56d7b12186d555223dc399d1",
"type": "github"
},
"original": {
@ -153,6 +219,56 @@
"repo": "treefmt-nix",
"type": "github"
}
},
"zig": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils_2",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1705040559,
"narHash": "sha256-6SjLyxWAVMfVfkz2x/3IlAJBJ0ywus6Hr9JrBbT9zCk=",
"owner": "mitchellh",
"repo": "zig-overlay",
"rev": "6022b38d2fd4e7504f1e8b6dcfccab9b655764a9",
"type": "github"
},
"original": {
"owner": "mitchellh",
"repo": "zig-overlay",
"type": "github"
}
},
"zls": {
"inputs": {
"flake-utils": [
"flake-utils"
],
"gitignore": "gitignore",
"langref": "langref",
"nixpkgs": [
"nixpkgs"
],
"zig-overlay": [
"zig"
]
},
"locked": {
"lastModified": 1705106613,
"narHash": "sha256-nqAXd8pEiEPJfjTrs0WB2UFOuBg5QRNbHWPDPGtrULI=",
"owner": "zigtools",
"repo": "zls",
"rev": "abe83cf2291381291d4c20629a5018f124c4bed2",
"type": "github"
},
"original": {
"owner": "zigtools",
"repo": "zls",
"type": "github"
}
}
},
"root": "root",

135
flake.nix
View file

@ -15,6 +15,16 @@
make-shell = {
url = "github:ursi/nix-make-shell";
};
zig = {
url = "github:mitchellh/zig-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
zls = {
url = "github:zigtools/zls";
inputs.nixpkgs.follows = "nixpkgs";
inputs.zig-overlay.follows = "zig";
inputs.flake-utils.follows = "flake-utils";
};
};
outputs = {
self,
@ -22,6 +32,8 @@
poetry2nix,
flake-utils,
make-shell,
zig,
zls,
} @ inputs:
flake-utils.lib.eachDefaultSystem
(
@ -39,33 +51,98 @@
};
inherit (poetry2nix.lib.mkPoetry2Nix {inherit pkgs;}) mkPoetryApplication overrides;
in {
packages.hostapps = mkPoetryApplication {
python = pkgs.python311;
projectDir = ./.;
propagatedBuildInputs = [
pkgs.inetutils
pkgs.openssh
pkgs.sshpass
];
overrides = overrides.withDefaults (
self: super: {
pyansi = super.pyansi.overridePythonAttrs (
old: {
buildInputs = old.buildInputs ++ [self.poetry];
}
);
# annotated-types = super.annotated-types.overridePythonAttrs (
# old: {
# buildInputs = old.buildInputs ++ [self.hatchling];
# }
# );
# pydantic-core = super.pydantic-core.overridePythonAttrs (
# old: {
# buildInputs = old.buildInputs ++ [self.maturin];
# }
# );
}
);
packages = {
hostapps = mkPoetryApplication {
python = pkgs.python311;
projectDir = ./.;
propagatedBuildInputs = [
pkgs.inetutils
pkgs.openssh
pkgs.sshpass
];
overrides = overrides.withDefaults (
self: super: {
pyansi = super.pyansi.overridePythonAttrs (
old: {
buildInputs = old.buildInputs ++ [self.poetry];
}
);
# annotated-types = super.annotated-types.overridePythonAttrs (
# old: {
# buildInputs = old.buildInputs ++ [self.hatchling];
# }
# );
# pydantic-core = super.pydantic-core.overridePythonAttrs (
# old: {
# buildInputs = old.buildInputs ++ [self.maturin];
# }
# );
}
);
};
hostapps-zig = let
cache = src:
pkgs.stdenvNoCC.mkDerivation {
inherit src;
name = "hostapps-zig-cache";
buildInputs = [
zig.packages.${pkgs.system}.master
];
dontUseZigInstall = true;
dontUseZigBuild = true;
dontConfigure = true;
dontFixup = true;
preBuild = ''
export ZIG_GLOBAL_CACHE_DIR=$(mktemp -d)
'';
buildPhase = ''
runHook preBuild
zig build --fetch
runHook postBuild
'';
installPhase = ''
runHook preInstall
cp -r --reflink=auto $ZIG_GLOBAL_CACHE_DIR/p $out
runHook postInstall
'';
outputHash = "sha256-GUddHnPI5zAU2JpQ1R9qjWyrYF7ytmroQxbz2kjj6ro=";
outputHashMode = "recursive";
};
in
pkgs.stdenvNoCC.mkDerivation (
attrs: {
pname = "hostapps-zig";
version = "0.0.0";
src = ./.;
buildInputs = [
zig.packages.${pkgs.system}.master
];
preBuild = ''
export ZIG_GLOBAL_CACHE_DIR=$(mktemp -d)
cp -r --reflink=auto ${cache attrs.src} $ZIG_GLOBAL_CACHE_DIR/p
chmod -R u+rwX $ZIG_GLOBAL_CACHE_DIR
'';
buildPhase = ''
runHook preBuild
zig build -Doptimize=ReleaseSafe
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out/bin
cp zig-out/bin/hostapps $out/bin
runHook postInstall
'';
}
);
};
defaultPackage = self.packages.${system}.hostapps;
devShells.default = let
@ -84,9 +161,11 @@
pkgs.openssh
pkgs.poetry
pkgs.sshpass
zig.packages.${system}.master
zls.packages.${system}.zls
];
env = {
NIX_PROJECT = project;
name = project;
};
};
}

28
src/config.zig Normal file
View file

@ -0,0 +1,28 @@
const std = @import("std");
pub const ConnectionType = enum {
ssh,
telnet,
};
pub const Config = struct {
name: []const u8,
type: ConnectionType,
comment: []const u8,
address: []const u8,
port: u16,
username: []const u8,
manufacturer: []const u8,
model: []const u8,
part_number: []const u8,
class: []const u8,
const Self = @This();
pub fn read(alloc: std.mem.Allocator, path: []const u8) !Self {
const data = try std.fs.cwd().readFileAlloc(alloc, path, 2048);
const parsed = try std.json.parseFromSlice(Self, alloc, data, .{});
const config = parsed.value;
return config;
}
};

158
src/connect.zig Normal file
View file

@ -0,0 +1,158 @@
const std = @import("std");
const builtin = @import("builtin");
const Config = @import("config.zig").Config;
const ansi = @import("anzi").ANSI(.{});
const kex_algorithms = [_][]const u8{
"diffie-hellman-group14-sha1",
"diffie-hellman-group1-sha1",
"diffie-hellman-group-exchange-sha1",
"diffie-hellman-group-exchange-sha256",
"ecdh-sha2-nistp256",
};
const ciphers = [_][]const u8{
"aes256-cbc",
"aes192-cbc",
"3des-cbc",
"aes128-cbc",
"aes256-ctr",
"aes192-ctr",
"aes128-ctr",
};
const macs = [_][]const u8{
"hmac-md5",
"hmac-sha1",
"hmac-sha2-256-etm@openssh.com",
"hmac-sha2-512-etm@openssh.com",
"hmac-sha2-256",
"hmac-sha2-512",
};
const options = [_]struct { key: []const u8, value: []const u8 }{
.{ .key = "ControlMaster", .value = "no" },
.{ .key = "ControlPath", .value = "none" },
.{ .key = "ForwardX11", .value = "no" },
.{ .key = "ForwardX11Trusted", .value = "no" },
.{ .key = "HostKeyAlgorithms", .value = "+ssh-rsa" },
.{ .key = "PubkeyAcceptedKeyTypes", .value = "+ssh-rsa" },
};
pub fn connect(
allocator: std.mem.Allocator,
config_path: []const u8,
identities: ?[][]const u8,
proxy_jump: ?[]const u8,
ssh_path: []const u8,
telnet_path: []const u8,
) !void {
var arena = std.heap.ArenaAllocator.init(allocator);
const alloc = arena.allocator();
defer arena.deinit();
const config = try Config.read(alloc, config_path);
var path: []const u8 = undefined;
var args = std.ArrayList([]const u8).init(alloc);
std.log.info("name: {s}", .{config.name});
std.log.info("class {s}", .{config.class});
std.log.info("type: {}", .{config.type});
std.log.info("comment: {s}", .{config.comment});
std.log.info("address: {s}", .{config.address});
std.log.info("port: {d}", .{config.port});
std.log.info("username: {s}", .{config.username});
std.log.info("manufacturer: {s}", .{config.manufacturer});
std.log.info("model: {s}", .{config.model});
std.log.info("part number: {s}", .{config.part_number});
switch (config.type) {
.ssh => {
path = ssh_path;
try args.append("ssh");
try args.append("-y");
if (identities) |i| {
for (i) |identity| {
try args.append("-i");
try args.append(identity);
}
}
if (proxy_jump) |p| {
try args.append("-o");
try args.append(try std.fmt.allocPrint(alloc, "ProxyJump={s}", .{p}));
}
for (options) |option| {
try args.append("-o");
try args.append(try std.fmt.allocPrint(alloc, "{s}={s}", .{ option.key, option.value }));
}
try args.append("-o");
try args.append(try std.fmt.allocPrint(alloc, "Ciphers={s}", .{try std.mem.join(alloc, ",", &ciphers)}));
try args.append("-o");
try args.append(try std.fmt.allocPrint(alloc, "KexAlgorithms={s}", .{try std.mem.join(alloc, ",", &kex_algorithms)}));
try args.append("-o");
try args.append(try std.fmt.allocPrint(alloc, "MACs={s}", .{try std.mem.join(alloc, ",", &macs)}));
try args.append("-l");
try args.append(config.username);
if (config.port != 22) {
try args.append("-p");
try args.append(try std.fmt.allocPrint(alloc, "{d}", .{config.port}));
}
try args.append(config.address);
},
.telnet => {
if (proxy_jump) |p| {
path = ssh_path;
try args.append("ssh");
try args.append("-t");
try args.append(try std.fmt.allocPrint(alloc, "ssh://{s}", .{p}));
try args.append("telnet");
} else {
path = telnet_path;
try args.append("telnet");
}
try args.append(config.address);
if (config.port != 23) {
try args.append(try std.fmt.allocPrint(alloc, "{d}", .{config.port}));
}
},
}
const pathZ = try alloc.dupeZ(u8, path);
const argsZ = try alloc.allocSentinel(?[*:0]const u8, args.items.len, null);
for (args.items, 0..) |arg, i| argsZ[i] = (try alloc.dupeZ(u8, arg)).ptr;
for (argsZ, 0..) |arg, i| {
if (arg) |a| std.log.info("{d} {s}", .{ i, a });
}
if (builtin.output_mode == .Exe) {
const stdout_file = std.io.getStdOut().writer();
var bw = std.io.bufferedWriter(stdout_file);
const stdout = bw.writer();
try stdout.print(
"{}",
.{
ansi.IconNameAndWindowTitle{
.icon_name = config.name,
.window_title = try std.fmt.allocPrint(alloc, "{s} \u{2013} {s}", .{ config.name, config.address }),
},
},
);
try bw.flush();
const envp = @as([*:null]const ?[*:0]const u8, @ptrCast(std.os.environ.ptr));
return std.os.execveZ(pathZ, argsZ, envp);
}
}

190
src/main.zig Normal file
View file

@ -0,0 +1,190 @@
const std = @import("std");
const builtin = @import("builtin");
const build_options = @import("build_options");
const yazap = @import("yazap");
const logz = @import("logz");
const Config = @import("config.zig").Config;
const connect = @import("connect.zig");
const App = yazap.App;
const Arg = yazap.Arg;
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator();
try logz.setup(alloc, .{
.level = .Debug,
.pool_size = 16,
.max_size = 4096,
.output = .stderr,
});
var app = App.init(alloc, "hostapps", "hostapps");
defer app.deinit();
var root = app.rootCommand();
try root.addArg(Arg.booleanOption("version", null, "Print version and exit"));
var connect_command = app.createCommand("connect", "connect to remote host");
try connect_command.addArg(Arg.singleValueOption("config", null, "Path to configuration file"));
try connect_command.addArg(Arg.multiValuesOption("identity", null, "Path to identity file", 8));
try connect_command.addArg(Arg.singleValueOption("proxy-jump", null, "Proxy jump"));
try connect_command.addArg(Arg.singleValueOption("ssh-command", null, "Path to ssh command"));
try connect_command.addArg(Arg.singleValueOption("telnet-command", null, "Path to telnet command"));
try root.addSubcommand(connect_command);
const matches = try app.parseProcess();
if (!(matches.containsArgs())) {
try app.displayHelp();
return;
}
if (matches.containsArg("version")) {
std.log.info("version number", .{});
return;
}
if (matches.subcommandMatches("connect")) |connect_matches| {
// if (!(connect_matches.containsArgs())) {
// try app.displaySubcommandHelp();
// return;
// }
var it = connect_matches.args.keyIterator();
while (it.next()) |key| {
logz.info().string("key", key.*).log();
}
const config_path = connect_matches.getSingleValue("config") orelse {
try app.displaySubcommandHelp();
return;
};
const identities = id: {
const ids = connect_matches.getMultiValues("identity");
if (ids != null) break :id ids;
const id = connect_matches.getSingleValue("identity");
if (id) |i| {
break :id @as(?[][]const u8, @constCast(&[_][]const u8{i}));
}
break :id null;
};
std.log.info("id: {any}", .{identities});
if (identities) |i| {
logz.info().string("test", "test").log();
for (i) |identity| {
logz.info().string("identity", identity).log();
const fullpath = fp: {
if (identity[0] == '~') {
const home = std.os.getenv("HOME") orelse {
logz.err().src(@src()).string("message", "unable to get HOME environment variable to expand tilde").log();
return;
};
var fp = try alloc.alloc(u8, home.len + identity.len - 1);
@memcpy(fp[0..home.len], home);
@memcpy(fp[home.len..], identity[1..]);
break :fp fp;
}
break :fp identity;
};
logz.info().string("fullpath", fullpath).log();
std.fs.cwd().access(fullpath, .{ .mode = .read_only }) catch |err| {
logz.err().src(@src()).err(err).string("identity", identity).string("fullpath", fullpath).log();
return;
};
if (identity.ptr != fullpath.ptr) alloc.free(fullpath);
}
} else {
logz.warn().string("message", "no identities").log();
}
const proxy_jump = connect_matches.getSingleValue("proxy-jump");
var ssh_path = connect_matches.getSingleValue("ssh-command") orelse build_options.ssh_path;
if (!std.fs.path.isAbsolute(ssh_path)) {
const expanded = expandPath(alloc, ssh_path) catch |err| expanded: {
std.log.warn("failed to expand ssh path={s} err={}", .{ ssh_path, err });
break :expanded null;
};
if (expanded) |v| {
ssh_path = v;
// defer alloc.free(ssh_path);
}
}
var telnet_path = connect_matches.getSingleValue("telnet-command") orelse build_options.telnet_path;
std.log.info("telnet path: {s}", .{telnet_path});
if (!std.fs.path.isAbsolute(telnet_path)) {
const expanded = expandPath(alloc, telnet_path) catch |err| expanded: {
std.log.warn("failed to expand telnet path={s} err={}", .{ telnet_path, err });
break :expanded null;
};
if (expanded) |v| {
std.log.info("expanded telnet path: {s}", .{v});
telnet_path = v;
// defer alloc.free(telnet_path);
}
}
std.log.info("ssh path: {s}", .{ssh_path});
std.log.info("telnet path: {s}", .{telnet_path});
try connect.connect(alloc, config_path, identities, proxy_jump, ssh_path, telnet_path);
return;
}
}
fn expandPath(alloc: std.mem.Allocator, cmd: []const u8) !?[]u8 {
const PATH = switch (builtin.os.tag) {
.windows => blk: {
const win_path = std.os.getenvW(std.unicode.utf8ToUtf16LeStringLiteral("PATH")) orelse return null;
const path = try std.unicode.utf16leToUtf8Alloc(alloc, win_path);
break :blk path;
},
else => std.os.getenvZ("PATH") orelse return null,
};
defer if (builtin.os.tag == .windows) alloc.free(PATH);
var path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
var it = std.mem.tokenizeScalar(u8, PATH, std.fs.path.delimiter);
var seen_eaccess = false;
while (it.next()) |search_path| {
const path_len = search_path.len + cmd.len + 1;
if (path_buf.len < path_len) return error.PathTooLong;
@memcpy(path_buf[0..search_path.len], search_path);
path_buf[search_path.len] = std.fs.path.sep;
@memcpy(path_buf[search_path.len + 1 ..][0..cmd.len], cmd);
path_buf[path_len] = 0;
const full_path = path_buf[0..path_len :0];
std.log.debug("path {s}", .{full_path});
const stat = std.fs.cwd().statFile(full_path) catch |err| switch (err) {
error.FileNotFound => continue,
error.AccessDenied => {
seen_eaccess = true;
continue;
},
else => return err,
};
if (stat.kind != .directory and isExecutable(stat.mode)) {
std.log.debug("executable: {s}", .{full_path});
return try alloc.dupe(u8, full_path);
}
}
if (seen_eaccess) return error.AccessDenied;
return null;
}
fn isExecutable(mode: std.fs.File.Mode) bool {
if (builtin.os.tag == .windows) return true;
return mode & 0o0111 != 0;
}