This commit is contained in:
Jeffrey C. Ollie 2023-09-21 10:54:27 -05:00
commit dcb58a7554
Signed by: jeff
GPG key ID: 6F86035A6D97044E
7 changed files with 792 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/zig-cache
/zig-out

70
build.zig Normal file
View file

@ -0,0 +1,70 @@
const std = @import("std");
// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
pub fn build(b: *std.Build) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});
// Standard optimization options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "ztacacs",
// In this case the main source file is merely a path, however, in more
// complicated build scripts, this could be a generated file.
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
// This declares intent for the executable to be installed into the
// standard location when the user invokes the "install" step (the default
// step when running `zig build`).
b.installArtifact(exe);
// This *creates* a Run step in the build graph, to be executed when another
// step is evaluated that depends on it. The next line below will establish
// such a dependency.
const run_cmd = b.addRunArtifact(exe);
// By making the run step depend on the install step, it will be run from the
// installation directory rather than directly from within the cache directory.
// This is not necessary, however, if the application depends on other installed
// files, this ensures they will be present and in the expected location.
run_cmd.step.dependOn(b.getInstallStep());
// This allows the user to pass arguments to the application in the build
// command itself, like this: `zig build run -- arg1 arg2 etc`
if (b.args) |args| {
run_cmd.addArgs(args);
}
// This creates a build step. It will be visible in the `zig build --help` menu,
// and can be selected like this: `zig build run`
// This will evaluate the `run` step rather than the default, which is "install".
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
// Creates a step for unit testing. This only builds the test executable
// but does not run it.
const unit_tests = b.addTest(.{
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
const run_unit_tests = b.addRunArtifact(unit_tests);
// Similar to creating the run step earlier, this exposes a `test` step to
// the `zig build --help` menu, providing a way for the user to request
// running the unit tests.
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_unit_tests.step);
}

292
flake.lock Normal file
View file

@ -0,0 +1,292 @@
{
"nodes": {
"bash": {
"locked": {
"lastModified": 1678247195,
"narHash": "sha256-m/wSwlSket+hob3JED4XUvoWJLtW7yhtOiZrlRDMShs=",
"ref": "refs/heads/main",
"rev": "e7a00dcc0e75bc3ef6856bdd94d7d809245f5636",
"revCount": 1,
"type": "git",
"url": "https://git.ocjtech.us/jeff/nixos-bash-prompt-builder.git"
},
"original": {
"type": "git",
"url": "https://git.ocjtech.us/jeff/nixos-bash-prompt-builder.git"
}
},
"binned_allocator": {
"flake": false,
"locked": {
"narHash": "sha256-m/kr4kmkG2rLkAj5YwvM0HmXTd+chAiQHzYK6ozpWlw=",
"type": "tarball",
"url": "https://gist.github.com/antlilja/8372900fcc09e38d7b0b6bbaddad3904/archive/6c3321e0969ff2463f8335da5601986cf2108690.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://gist.github.com/antlilja/8372900fcc09e38d7b0b6bbaddad3904/archive/6c3321e0969ff2463f8335da5601986cf2108690.tar.gz"
}
},
"diffz": {
"flake": false,
"locked": {
"narHash": "sha256-3CdYo6WevT0alRwKmbABahjhFKz7V9rdkDUZ43VtDeU=",
"type": "tarball",
"url": "https://github.com/ziglibs/diffz/archive/90353d401c59e2ca5ed0abe5444c29ad3d7489aa.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://github.com/ziglibs/diffz/archive/90353d401c59e2ca5ed0abe5444c29ad3d7489aa.tar.gz"
}
},
"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=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"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"
}
},
"flake-utils_3": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"zls",
"nixpkgs"
]
},
"locked": {
"lastModified": 1694102001,
"narHash": "sha256-vky6VPK1n1od6vXbqzOXnekrQpTL4hbPAwUhT5J9c9E=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "9e21c80adf67ebcb077d75bd5e7d724d21eeafd6",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"known_folders": {
"flake": false,
"locked": {
"narHash": "sha256-bZfn+jgCzrtm8vKPDDMNWLkJYoo7vKxZu+e2tGvSGHY=",
"type": "tarball",
"url": "https://github.com/ziglibs/known-folders/archive/a564f582122326328dad6b59209d070d57c4e6ae.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://github.com/ziglibs/known-folders/archive/a564f582122326328dad6b59209d070d57c4e6ae.tar.gz"
}
},
"langref": {
"flake": false,
"locked": {
"narHash": "sha256-UDwr6vJynfpD5SEoZzhXouoKu+Okdtpv20Vx2E5Ltcc=",
"type": "file",
"url": "https://raw.githubusercontent.com/ziglang/zig/f1992a39a59b941f397b8501a525b38e5863a527/doc/langref.html.in"
},
"original": {
"type": "file",
"url": "https://raw.githubusercontent.com/ziglang/zig/f1992a39a59b941f397b8501a525b38e5863a527/doc/langref.html.in"
}
},
"make-shell": {
"locked": {
"lastModified": 1634940815,
"narHash": "sha256-P69OmveboXzS+es1vQGS4bt+ckwbeIExqxfGLjGuJqA=",
"owner": "ursi",
"repo": "nix-make-shell",
"rev": "8add91681170924e4d0591b22f294aee3f5516f9",
"type": "github"
},
"original": {
"owner": "ursi",
"repo": "nix-make-shell",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1694959747,
"narHash": "sha256-CXQ2MuledDVlVM5dLC4pB41cFlBWxRw4tCBsFrq3cRk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "970a59bd19eff3752ce552935687100c46e820a5",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-unstable",
"type": "indirect"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1689088367,
"narHash": "sha256-Y2tl2TlKCWEHrOeM9ivjCLlRAKH3qoPUE/emhZECU14=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5c9ddb86679c400d6b7360797b8a22167c2053f8",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "release-23.05",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"bash": "bash",
"flake-utils": "flake-utils",
"make-shell": "make-shell",
"nixpkgs": "nixpkgs",
"zig": "zig",
"zls": "zls"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"zig": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1695125316,
"narHash": "sha256-9Ewco7m4zgajBhppCM1mEmQE/K6ObkbwUhtJ3lJlfto=",
"owner": "mitchellh",
"repo": "zig-overlay",
"rev": "078666381440c3303566a8a0f34628703202b54e",
"type": "github"
},
"original": {
"owner": "mitchellh",
"repo": "zig-overlay",
"type": "github"
}
},
"zls": {
"inputs": {
"binned_allocator": "binned_allocator",
"diffz": "diffz",
"flake-utils": "flake-utils_3",
"gitignore": "gitignore",
"known_folders": "known_folders",
"langref": "langref",
"nixpkgs": [
"nixpkgs"
],
"zig-overlay": [
"zig"
]
},
"locked": {
"lastModified": 1695231678,
"narHash": "sha256-R6z0+6U7okQxmOR867nUTCzbRwtuGokGgtqvXP78XK8=",
"owner": "zigtools",
"repo": "zls",
"rev": "20b80784998e342296473a2192ea70935ab84b8d",
"type": "github"
},
"original": {
"owner": "zigtools",
"repo": "zls",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

78
flake.nix Normal file
View file

@ -0,0 +1,78 @@
{
description = "ztacacs";
inputs = {
nixpkgs = {
url = "nixpkgs/nixos-unstable";
};
flake-utils = {
url = "github:numtide/flake-utils";
};
bash = {
url = "git+https://git.ocjtech.us/jeff/nixos-bash-prompt-builder.git";
};
make-shell = {
url = "github:ursi/nix-make-shell";
};
zig = {
url = "github:mitchellh/zig-overlay";
};
zls = {
url = "github:zigtools/zls";
inputs.nixpkgs.follows = "nixpkgs";
inputs.zig-overlay.follows = "zig";
};
};
outputs = { self, nixpkgs, flake-utils, bash, ... }@inputs:
let
# overlays = [
# (
# final: prev: {
# zigpkgs = inputs.zig.packages.${prev.system};
# }
# )
# ];
systems = builtins.attrNames inputs.zig.packages;
in
flake-utils.lib.eachSystem systems (
system:
let
pkgs = import nixpkgs {
inherit system;
};
in
{
devShells.default =
let
project = "ztacacs";
prompt = (
bash.build_prompt
bash.ansi_normal_blue
"${project} - ${bash.username}@${bash.hostname_short}: ${bash.current_working_directory}"
"${project}:${bash.current_working_directory}"
);
make-shell = import inputs.make-shell {
inherit system;
pkgs = pkgs;
};
in
make-shell {
packages = [
inputs.zig.packages.${system}.master
inputs.zls.packages.${system}.zls
];
env = {
PS1 = prompt;
};
};
# packages.default = inputs.zig.packages.${system}.zigStdenv.mkDerivation {
# pname = "ztacacs";
# version = "0.1.0";
# buildInputs = [ ];
# src = ./.;
# };
}
);
}

57
src/main.zig Normal file
View file

@ -0,0 +1,57 @@
const std = @import("std");
const serde = @import("serde.zig");
const packet = @import("packet.zig");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
var socket = std.net.StreamServer.init(.{ .reuse_address = true, .reuse_port = true });
defer socket.deinit();
try socket.listen(std.net.Address.initIp4(.{ 127, 0, 0, 1 }, 4949));
while (true) {
const server = try allocator.create(Server);
server.* = Server{
.allocator = allocator,
.conn = try socket.accept(),
};
const thread = try std.Thread.spawn(.{ .allocator = allocator }, Server.run, .{server});
thread.detach();
}
}
const Server = struct {
allocator: std.mem.Allocator,
conn: std.net.StreamServer.Connection,
const Self = @This();
pub fn run(self: *Self) !void {
std.debug.print("starting: {}\n", .{self.conn.address});
defer self.deinit();
while (true) {
var buffer: [@sizeOf(packet.Header)]u8 = undefined;
const len = try self.conn.stream.read(buffer[0..]);
// if (len < @sizeOf(packet.Header)) break;
if (len == 0) break;
std.debug.print("{}\n", .{len});
_ = try self.conn.stream.write(buffer[0..len]);
}
std.debug.print("finished\n", .{});
}
fn deinit(self: *Self) void {
std.debug.print("start deinit\n", .{});
self.conn.stream.close();
self.allocator.destroy(self);
std.debug.print("end deinit\n", .{});
}
};
test "main" {
std.debug.print("hello\n", .{});
}

189
src/packet.zig Normal file
View file

@ -0,0 +1,189 @@
const std = @import("std");
const serde = @import("serde.zig");
pub const MajorVersion = enum(u4) {
Default = 0xc,
_,
};
pub const MinorVersion = enum(u4) {
Default = 0x0,
One = 0x1,
_,
};
pub const PacketType = enum(u8) {
Authentication = 1,
Authorization = 2,
Accounting = 3,
_,
};
pub const HeaderFlags = packed struct(u8) {
Unencrypted: bool = false,
_p1: u1 = 0,
SingleConnect: bool = false,
_p2: u5 = 0,
pub fn format(value: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) std.os.WriteError!void {
try writer.print("{s}{{ .Unencrypted = {}, .SingleConnect = {} }}", .{
@typeName(@This()),
value.Unencrypted,
value.SingleConnect,
});
}
};
pub const Header = struct {
major: MajorVersion,
minor: MinorVersion,
type: PacketType,
seq_no: u8,
flags: HeaderFlags,
session_id: u32,
length: u32,
};
test "header 1" {
const packet = [_]u8{ 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
var reader = serde.Reader.init(&packet);
const header = try reader.read(Header);
std.debug.print("{}\n", .{header});
try std.testing.expect(header.major == .Default);
try std.testing.expect(header.minor == .Default);
try std.testing.expect(header.type == .Authentication);
try std.testing.expect(header.seq_no == 0);
try std.testing.expect(!header.flags.Unencrypted);
try std.testing.expect(!header.flags.SingleConnect);
try std.testing.expect(header.session_id == 0);
try std.testing.expect(header.length == 0);
}
test "header 2" {
const packet = [_]u8{ 0xc0, 0x02, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
var reader = serde.Reader.init(&packet);
const header = try reader.read(Header);
std.debug.print("{}\n", .{header});
try std.testing.expect(header.major == .Default);
try std.testing.expect(header.minor == .Default);
try std.testing.expect(header.type == .Authorization);
try std.testing.expect(header.seq_no == 2);
try std.testing.expect(header.flags.Unencrypted);
try std.testing.expect(!header.flags.SingleConnect);
try std.testing.expect(header.session_id == 0);
try std.testing.expect(header.length == 0);
}
test "header 3" {
const packet = [_]u8{ 0xc0, 0x03, 0xff, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
var reader = serde.Reader.init(&packet);
const header = try reader.read(Header);
std.debug.print("{}\n", .{header});
try std.testing.expect(header.major == .Default);
try std.testing.expect(header.minor == .Default);
try std.testing.expect(header.type == .Accounting);
try std.testing.expect(header.seq_no == 255);
try std.testing.expect(!header.flags.Unencrypted);
try std.testing.expect(header.flags.SingleConnect);
try std.testing.expect(header.session_id == 0);
try std.testing.expect(header.length == 0);
}
pub const AuthenticationAction = enum(u8) {
Login = 0x01,
ChangePassword = 0x02,
SendAuth = 0x03,
_,
};
pub const AuthenticationType = enum(u8) {
ASCII = 0x01,
PAP = 0x02,
CHAP = 0x03,
MSCHAP = 0x05,
MSCHAPV2 = 0x06,
_,
};
pub const AuthenticationService = enum(u8) {
NONE = 0x00,
LOGIN = 0x01,
ENABLE = 0x02,
PPP = 0x03,
PT = 0x05,
RCMD = 0x06,
X25 = 0x07,
NASI = 0x08,
FWPROXY = 0x09,
_,
};
pub const AuthenticationStartHeader = struct {
action: AuthenticationAction,
priv_lvl: u8,
authen_type: AuthenticationType,
authen_service: AuthenticationService,
user_len: u8,
port_len: u8,
rem_addr_len: u8,
data_len: u8,
};
pub const AuthenticationStatus = enum(u8) {
PASS = 0x01,
FAIL = 0x02,
GETDATA = 0x03,
GETUSER = 0x04,
GETPASS = 0x05,
RESTART = 0x06,
ERROR = 0x07,
FOLLOW = 0x21,
};
pub const AuthenticationReplyFlags = packed struct(u8) {
NoEcho: bool,
_p1: u7 = 0,
pub fn format(value: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) std.os.WriteError!void {
try writer.print("{s}{{ .NoEcho = {}, }}", .{
@typeName(@This()),
value.NoEcho,
});
}
};
pub const AuthenticationReplyHeader = struct {
status: AuthenticationStatus,
flags: AuthenticationReplyFlags,
server_msg_len: u16,
data_len: u16,
};
pub const AuthenticationContinueFlags = packed struct(u8) {
Abort: bool,
_p1: u7 = 0,
pub fn format(value: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) std.os.WriteError!void {
try writer.print("{s}{{ .Abort = {}, }}", .{
@typeName(@This()),
value.Abort,
});
}
};
pub const AuthenticationContinueHeader = struct {
user_msg: u16,
data_len: u16,
flags: AuthenticationContinueFlags,
};
pub const AuthorizationRequestHeader = struct {
authen_method: u8,
priv_lvl: u8,
authen_type: u8,
authen_service: u8,
user_len: u8,
port_len: u8,
rem_addr_len: u8,
arg_cnt: u8,
};

103
src/serde.zig Normal file
View file

@ -0,0 +1,103 @@
const std = @import("std");
pub const SerdeError = error{
IllegalSize,
LeftoverBits,
NotEnoughBitsLeft,
NotEnoughBytesLeft,
};
pub const Reader = struct {
bytes: []const u8,
index: usize,
bits_left: usize,
pub fn init(bytes: []const u8) Reader {
return .{
.bytes = bytes,
.index = 0,
.bits_left = 8,
};
}
pub fn read(self: *Reader, comptime T: type) SerdeError!T {
if (self.index >= self.bytes.len) return error.NotEnoughBytesLeft;
return switch (@typeInfo(T)) {
.Int => try self.readInt(T),
.Bool => try self.readBool(T),
.Enum => try self.readEnum(T),
.Struct => try self.readStruct(T),
.Array => |array| {
var arr: [array.len]array.child = undefined;
var index: usize = 0;
while (index < array.len) : (index += 1) {
arr[index] = try self.read(array.child);
}
return arr;
},
else => @compileError("unsupported type"),
};
}
fn readInt(self: *Reader, comptime T: type) SerdeError!T {
const bits = @typeInfo(T).Int.bits;
if (bits < 8) {
if (bits > self.bits_left) return error.NotEnoughBitsLeft;
const shift: u3 = @truncate(self.bits_left - bits);
const mask = (1 << bits) - 1;
const b = (self.bytes[self.index] >> shift) & mask;
self.bits_left -= bits;
if (self.bits_left == 0) {
self.bits_left = 8;
self.index += 1;
}
return @intCast(b);
}
if (bits % 8 == 0 and self.bits_left != 8) return error.LeftoverBits;
if (bits % 8 != 0) return error.IllegalSize;
const size = bits / 8;
if (self.index + size > self.bytes.len) return error.NotEnoughBytesLeft;
const slice = self.bytes[self.index .. self.index + size];
const value = @as(*align(1) const T, @ptrCast(slice)).*;
self.index += size;
return std.mem.bigToNative(T, value);
}
fn readBool(self: *Reader, comptime T: type) SerdeError!T {
const x = try self.read(u1);
std.debug.print("bool {}\n", .{x});
return x == 1;
}
fn readEnum(self: *Reader, comptime T: type) SerdeError!T {
return @enumFromInt(try self.read(@typeInfo(T).Enum.tag_type));
}
fn readStruct(self: *Reader, comptime T: type) SerdeError!T {
if (@typeInfo(T).Struct.layout == .Packed) {
const value = try self.read(@typeInfo(T).Struct.backing_integer.?);
return @as(*const T, @ptrCast(&value)).*;
}
const fields = std.meta.fields(T);
var value: T = undefined;
inline for (fields) |field| {
@field(value, field.name) = try self.read(field.type);
}
return value;
}
pub fn isComplete(self: *Reader) bool {
return self.index >= self.bytes.len;
}
};