const std = @import("std");
const Io = std.Io;
const randpw = @import("randpw");
const chars_digit = "0123456789";
const chars_basic = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ++ chars_digit;
// shell friendly characters
const chars_shell = "~@%*-_:,.";
const chars_special = "!#$^&()=+[{}]|\\;<>/?";
const password_chars_all = chars_basic ++ chars_shell ++ chars_special;
const Args = struct { character_set: []const u8, pass_length: u8 };
const defaultPassLength: u8 = 20;
const output_len = 255;
const usage =
\\USAGE: randpw generates passwords
\\randpw # generate 20 characters, including many special characters
\\randpw [0-255] # generate password 0-255 characters, including many special characters
\\randpw basic 10 # generate password 10 characters, alphanumeric
\\randpw numbers 10 # generate password 10 characters, numbers
\\randpw digits 10 # generate password 10 characters, numbers
\\randpw shell 10 # 10 characters, alphanumeric plus non shell interpolated
\\randpw [any chars] 10 # 10 characters, use user provided character set
\\
\\
;
fn getArgs(init: std.process.Init) !Args {
const theArgs = try init.minimal.args.toSlice(init.gpa);
defer init.gpa.free(theArgs);
const numArgs = theArgs.len;
var pass_length: u8 = defaultPassLength;
if (numArgs == 1) {
return Args{ .character_set = password_chars_all, .pass_length = pass_length };
}
var iLen: u2 = 1;
if (numArgs > 2) {
iLen = 2;
}
if ((numArgs == 2) and (std.mem.eql(u8, theArgs[1], "-h") or std.mem.eql(u8, theArgs[1], "--help"))) {
std.debug.print(usage, .{});
std.process.exit(0);
}
pass_length = std.fmt.parseInt(u8, theArgs[iLen], 10) catch |err| {
std.debug.print(usage, .{});
std.debug.print("bad password length \"{s}\", max length is {d}: {any}\n", .{ theArgs[iLen], output_len, err });
std.process.exit(1);
};
if (numArgs == 2) {
return Args{ .character_set = password_chars_all, .pass_length = pass_length };
}
var the_char_set: []const u8 = theArgs[1];
if (std.mem.eql(u8, the_char_set, "basic")) {
the_char_set = chars_basic;
} else if (std.mem.eql(u8, the_char_set, "shell")) {
the_char_set = chars_basic ++ chars_shell;
} else if (std.mem.eql(u8, the_char_set, "numbers") or std.mem.eql(u8, the_char_set, "digits")) {
the_char_set = chars_digit;
}
if (pass_length > output_len) {
std.debug.print(usage, .{});
std.debug.print("max allowed password length is {d}\n", .{output_len});
std.process.exit(1);
}
return Args{ .character_set = the_char_set, .pass_length = pass_length };
}
fn getPw(args: Args, rand: std.Random) [output_len]u8 {
var output: [output_len]u8 = undefined;
var i = args.pass_length;
const character_set_length = args.character_set.len;
while (i > 0) {
i -= 1;
const j = rand.intRangeAtMost(u8, 0, @as(u8, @truncate(character_set_length - 1)));
output[i] = args.character_set[j];
}
return output;
}
pub fn main(init: std.process.Init) !void {
// In order to do I/O operations need an `Io` instance.
const io = init.io;
const rng_impl: std.Random.IoSource = .{ .io = io };
const secureRand = rng_impl.interface();
// Stdout is for the actual output of your application, for example if you
// are implementing gzip, then only the compressed bytes should be sent to
// stdout, not any debugging messages.
var stdout_buffer: [1024]u8 = undefined;
var stdout_file_writer: Io.File.Writer = .init(.stdout(), io, &stdout_buffer);
const stdout_writer = &stdout_file_writer.interface;
const args = try getArgs(init);
const pw = getPw(args, secureRand);
try stdout_writer.print("{s}\n", .{pw[0..args.pass_length]});
try stdout_writer.flush(); // Don't forget to flush!
}
test "sanity check length and character set" {
const expectEqualStrings = std.testing.expectEqualStrings;
const rng_impl: std.Random.IoSource = .{ .io = std.testing.io };
const secure_rand = rng_impl.interface();
try expectEqualStrings("aaa", getPw(Args{ .character_set = "a", .pass_length = 3 }, secure_rand)[0..3]);
try expectEqualStrings(" ", getPw(Args{ .character_set = " ", .pass_length = 10 }, secure_rand)[0..10]);
}