From 265c035bd4462ca78b19fc4569ff8e1d6dd26e50 Mon Sep 17 00:00:00 2001 From: "Jeffrey C. Ollie" Date: Sun, 28 Jan 2024 20:20:37 -0600 Subject: [PATCH] work --- build.zig | 25 ++++++-- build.zig.zon | 21 ++++--- src/connect.zig | 38 +++++------ src/main.zig | 163 ++---------------------------------------------- src/options.zig | 84 +++++++++++++++++++++++++ src/path.zig | 50 +++++++++++++++ 6 files changed, 187 insertions(+), 194 deletions(-) create mode 100644 src/options.zig create mode 100644 src/path.zig diff --git a/build.zig b/build.zig index 72d198c..4db2b4f 100644 --- a/build.zig +++ b/build.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const path = @import("src/path.zig"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); @@ -8,13 +9,27 @@ pub fn build(b: *std.Build) void { []const u8, "ssh", "path to ssh binary", - ) orelse "ssh"; + ) orelse ssh: { + const p = path.expandPath(b.allocator, "ssh") catch { + break :ssh "ssh"; + } orelse { + break :ssh "ssh"; + }; + break :ssh p; + }; const telnet_path = b.option( []const u8, "telnet", "path to telnet binary", - ) orelse "telnet"; + ) orelse telnet: { + const p = path.expandPath(b.allocator, "telnet") catch { + break :telnet "telnet"; + } orelse { + break :telnet "telnet"; + }; + break :telnet p; + }; const exe = b.addExecutable(.{ .name = "hostapps", @@ -49,15 +64,15 @@ pub fn build(b: *std.Build) void { exe.root_module.addImport("logz", logz.module("logz")); - const yazap = b.dependency( - "yazap", + const @"zig-cli" = b.dependency( + "zig-cli", .{ .target = target, .optimize = optimize, }, ); - exe.root_module.addImport("yazap", yazap.module("yazap")); + exe.root_module.addImport("zig-cli", @"zig-cli".module("zig-cli")); b.installArtifact(exe); diff --git a/build.zig.zon b/build.zig.zon index c6e91fb..476c22c 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -4,25 +4,26 @@ .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", + .url = "https://github.com/karlseguin/log.zig/archive/eb86766b91cf2ca0a6e87dd8568fab80695cf2e8.tar.gz", + .hash = "1220b6cf56de826aead943a6db2a62ed40d81a150c24dcb019b3eb88673c82a6e7b7", }, + // .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", }, + .@"zig-cli" = .{ + .url = "https://github.com/sam701/zig-cli/archive/50201f0086da689cd104c2fef350f3dbe894eea7.tar.gz", + // .hash = "12208a4377c7699927605e5a797bad5ae2ba8be4b49f68b9f522b3a755597a21f1cf", + .hash = "12208a4377c7699927605e5a797bad5ae2ba8be4b49f68b9f522b3a755597a21f1cf", + }, }, .paths = .{ - // "", - // For example... "build.zig", "build.zig.zon", "src", - // "LICENSE", - // "README.md", }, } diff --git a/src/connect.zig b/src/connect.zig index 8798a69..9ecb709 100644 --- a/src/connect.zig +++ b/src/connect.zig @@ -1,6 +1,7 @@ const std = @import("std"); const builtin = @import("builtin"); const Config = @import("config.zig").Config; +const options = @import("options.zig"); const ansi = @import("anzi").ANSI(.{}); const kex_algorithms = [_][]const u8{ @@ -30,7 +31,7 @@ const macs = [_][]const u8{ "hmac-sha2-512", }; -const options = [_]struct { key: []const u8, value: []const u8 }{ +const ssh_options = [_]struct { key: []const u8, value: []const u8 }{ .{ .key = "ControlMaster", .value = "no" }, .{ .key = "ControlPath", .value = "none" }, .{ .key = "ForwardX11", .value = "no" }, @@ -39,19 +40,14 @@ const options = [_]struct { key: []const u8, value: []const u8 }{ .{ .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); +pub fn connect() !void { + var arena = std.heap.ArenaAllocator.init(options.options.alloc); const alloc = arena.allocator(); defer arena.deinit(); - const config = try Config.read(alloc, config_path); + std.log.info("config: {}", .{options.options}); + + const config = try Config.read(options.options.alloc, options.options.config); var path: []const u8 = undefined; var args = std.ArrayList([]const u8).init(alloc); @@ -69,23 +65,21 @@ pub fn connect( switch (config.type) { .ssh => { - path = ssh_path; + path = options.options.ssh_path; try args.append("ssh"); try args.append("-y"); - if (identities) |i| { - for (i) |identity| { - try args.append("-i"); - try args.append(identity); - } + for (options.options.identities) |identity| { + try args.append("-i"); + try args.append(identity); } - if (proxy_jump) |p| { + if (options.options.proxy_jump) |p| { try args.append("-o"); try args.append(try std.fmt.allocPrint(alloc, "ProxyJump={s}", .{p})); } - for (options) |option| { + for (ssh_options) |option| { try args.append("-o"); try args.append(try std.fmt.allocPrint(alloc, "{s}={s}", .{ option.key, option.value })); } @@ -111,14 +105,14 @@ pub fn connect( }, .telnet => { - if (proxy_jump) |p| { - path = ssh_path; + if (options.options.proxy_jump) |p| { + path = options.options.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; + path = options.options.telnet_path; try args.append("telnet"); } diff --git a/src/main.zig b/src/main.zig index 60abb54..487e104 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,169 +1,18 @@ 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 cli = @import("zig-cli"); const connect = @import("connect.zig"); - -const App = yazap.App; -const Arg = yazap.Arg; +const options = @import("options.zig"); 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, - }); + options.options.alloc = alloc; - var app = App.init(alloc, "hostapps", "hostapps"); - defer app.deinit(); + var iter = try std.process.argsWithAllocator(alloc); + defer iter.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| { - 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; - }; - - if (identities) |i| { - 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); - } - } - - 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; - } - } - - var telnet_path = connect_matches.getSingleValue("telnet-command") orelse build_options.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| { - telnet_path = v; - } - } - - 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]; - - 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; + return cli.run(options.app, alloc); } diff --git a/src/options.zig b/src/options.zig new file mode 100644 index 0000000..f82c034 --- /dev/null +++ b/src/options.zig @@ -0,0 +1,84 @@ +const std = @import("std"); +const cli = @import("zig-cli"); +const build_options = @import("build_options"); +const connect = @import("connect.zig"); + +pub const Options = struct { + alloc: std.mem.Allocator = undefined, + config: []const u8 = undefined, + identities: []const []const u8 = undefined, + proxy_jump: ?[]const u8 = null, + ssh_path: []const u8, + telnet_path: []const u8, +}; + +pub var options: Options = .{ + .ssh_path = build_options.ssh_path, + .telnet_path = build_options.telnet_path, +}; + +var config_option = cli.Option{ + .long_name = "config", + .help = "config file", + .value_ref = cli.mkRef(&options.config), + .required = true, +}; + +var identity_option = cli.Option{ + .long_name = "identity", + .help = "ssh identity", + .value_ref = cli.mkRef(&options.identities), +}; + +var proxy_jump_option = cli.Option{ + .long_name = "proxy-jump", + .help = "proxy jump", + .value_ref = cli.mkRef(&options.proxy_jump), +}; + +var ssh_command_option = cli.Option{ + .long_name = "ssh-command", + .help = "ssh command", + .value_ref = cli.mkRef(&options.ssh_path), +}; + +var telnet_command_option = cli.Option{ + .long_name = "telnet-command", + .help = "telnet command", + .value_ref = cli.mkRef(&options.telnet_path), +}; + +var connect_command = cli.Command{ + .name = "connect", + .description = cli.Description{ + .one_line = "connect", + }, + .options = &.{ + &config_option, + &identity_option, + &proxy_jump_option, + &ssh_command_option, + &telnet_command_option, + }, + .target = cli.CommandTarget{ + .action = cli.CommandAction{ + .exec = connect.connect, + }, + }, +}; + +pub const app = &cli.App{ + .command = cli.Command{ + .name = "hostapps", + .description = cli.Description{ + .one_line = "hostapps", + }, + .target = cli.CommandTarget{ + .subcommands = &.{ + &connect_command, + }, + }, + }, + .version = "0.1.0", + .author = "Jeffrey C. Ollie ", +}; diff --git a/src/path.zig b/src/path.zig new file mode 100644 index 0000000..b6cf159 --- /dev/null +++ b/src/path.zig @@ -0,0 +1,50 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +pub 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]; + + 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; +} + +pub fn isExecutable(mode: std.fs.File.Mode) bool { + if (builtin.os.tag == .windows) return true; + return mode & 0o0111 != 0; +}