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]); }