170 lines
6.2 KiB
Zig
170 lines
6.2 KiB
Zig
|
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| {
|
||
|
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;
|
||
|
}
|