diff --git a/apg.go b/apg.go index 3398de8..d879695 100644 --- a/apg.go +++ b/apg.go @@ -1,89 +1,30 @@ package main import ( - "flag" "fmt" + "github.com/wneessen/apg.go/config" "log" - "os" ) -// Constants -const DefaultPwLenght int = 20 -const VersionString string = "0.2.6" -const PwLowerCharsHuman string = "abcdefghjkmnpqrstuvwxyz" -const PwUpperCharsHuman string = "ABCDEFGHJKMNPQRSTUVWXYZ" -const PwLowerChars string = "abcdefghijklmnopqrstuvwxyz" -const PwUpperChars string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -const PwSpecialCharsHuman string = "\"#%*+-/:;=\\_|~" -const PwSpecialChars string = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" -const PwNumbersHuman string = "23456789" -const PwNumbers string = "1234567890" - -type cliOpts struct { - minPassLen int - maxPassLen int - numOfPass int - useComplex bool - useLowerCase bool - useUpperCase bool - useNumber bool - useSpecial bool - humanReadable bool - excludeChars string - newStyleModes string - spellPassword bool - showHelp bool - showVersion bool - outputMode int -} - -var config cliOpts - -// Read flags -func init() { - // Bool flags - flag.BoolVar(&config.useLowerCase, "L", true, "Use lower case characters in passwords") - flag.BoolVar(&config.useUpperCase, "U", true, "Use upper case characters in passwords") - flag.BoolVar(&config.useNumber, "N", true, "Use numbers in passwords") - flag.BoolVar(&config.useSpecial, "S", false, "Use special characters in passwords") - flag.BoolVar(&config.useComplex, "C", false, "Generate complex passwords (implies -L -U -N -S, disables -H)") - flag.BoolVar(&config.spellPassword, "l", false, "Spell generated password") - flag.BoolVar(&config.humanReadable, "H", false, "Generate human-readable passwords") - flag.BoolVar(&config.showVersion, "v", false, "Show version") - - // Int flags - flag.IntVar(&config.minPassLen, "m", DefaultPwLenght, "Minimum password length") - flag.IntVar(&config.maxPassLen, "x", DefaultPwLenght, "Maxiumum password length") - flag.IntVar(&config.numOfPass, "n", 1, "Number of passwords to generate") - - // String flags - flag.StringVar(&config.excludeChars, "E", "", "Exclude list of characters from generated password") - flag.StringVar(&config.newStyleModes, "M", "", - "New style password parameters (higher priority than single parameters)") - - flag.Parse() - if config.showVersion { - _, _ = os.Stderr.WriteString("Winni's Advanced Password Generator Clone (apg.go) v" + VersionString + "\n") - _, _ = os.Stderr.WriteString("(C) 2021 by Winni Neessen\n") - os.Exit(0) - } - - log.SetFlags(log.Ltime | log.Ldate | log.Lshortfile) -} - // Main function that generated the passwords and returns them func main() { - parseParams() - pwLength := getPwLengthFromParams() - charRange := getCharRange() + // Log config + log.SetFlags(log.Ltime | log.Ldate | log.Lshortfile) - for i := 1; i <= config.numOfPass; i++ { + // Create config + conf := config.NewConfig() + conf.ParseParams() + pwLength := conf.GetPwLengthFromParams() + charRange := getCharRange(conf) + + // Generate passwords + for i := 1; i <= conf.NumOfPass; i++ { pwString, err := getRandChar(&charRange, pwLength) if err != nil { log.Fatalf("getRandChar returned an error: %q\n", err) } - switch config.outputMode { + switch conf.OutputMode { case 1: { spelledPw, err := spellPasswordString(pwString) diff --git a/apg_test.go b/apg_test.go index ac5f887..fe96d66 100644 --- a/apg_test.go +++ b/apg_test.go @@ -1,15 +1,10 @@ package main import ( + "github.com/wneessen/apg.go/config" "testing" ) -// Make sure the flags are initalized -var _ = func() bool { - testing.Init() - return true -}() - // Test getRandNum with max 1000 func TestGetRandNum(t *testing.T) { testTable := []struct { @@ -118,16 +113,19 @@ func TestGetCharRange(t *testing.T) { {"special_only", specialBytes, false, false, false, true, false}, {"special_only_human", specialHumanBytes, false, false, false, true, true}, } + + conf := config.NewConfig() + conf.ParseParams() for _, testCase := range testTable { t.Run(testCase.testName, func(t *testing.T) { - config.useLowerCase = testCase.useLowerCase - config.useUpperCase = testCase.useUpperCase - config.useNumber = testCase.useNumber - config.useSpecial = testCase.useSpecial - config.humanReadable = testCase.humanReadable - charRange := getCharRange() + conf.UseLowerCase = testCase.useLowerCase + conf.UseUpperCase = testCase.useUpperCase + conf.UseNumber = testCase.useNumber + conf.UseSpecial = testCase.useSpecial + conf.HumanReadable = testCase.humanReadable + charRange := getCharRange(conf) for _, curChar := range charRange { - searchAllowedBytes := containsByte(testCase.allowedBytes, int(curChar)) + searchAllowedBytes := containsByte(testCase.allowedBytes, int(curChar), t) if !searchAllowedBytes { t.Errorf("Character range returned invalid value: %v", string(curChar)) } @@ -149,6 +147,9 @@ func TestConvert(t *testing.T) { {"convert_0_to_ZERO", '0', "ZERO", false}, {"convert_/_to_SLASH", '/', "SLASH", false}, } + conf := config.NewConfig() + conf.ParseParams() + for _, testCase := range testTable { t.Run(testCase.testName, func(t *testing.T) { charToString, err := convertCharToName(testCase.givenVal) @@ -170,12 +171,12 @@ func TestConvert(t *testing.T) { } t.Run("all_chars_must_return_a_conversion_string", func(t *testing.T) { - config.useUpperCase = true - config.useLowerCase = true - config.useNumber = true - config.useSpecial = true - config.humanReadable = false - charRange := getCharRange() + conf.UseUpperCase = true + conf.UseLowerCase = true + conf.UseNumber = true + conf.UseSpecial = true + conf.HumanReadable = false + charRange := getCharRange(conf) for _, curChar := range charRange { _, err := convertCharToName(byte(curChar)) if err != nil { @@ -214,12 +215,15 @@ func BenchmarkGetRandChar(b *testing.B) { // Benchmark: Random char generation func BenchmarkConvertChar(b *testing.B) { - config.useUpperCase = true - config.useLowerCase = true - config.useNumber = true - config.useSpecial = true - config.humanReadable = false - charRange := getCharRange() + conf := config.NewConfig() + conf.ParseParams() + + conf.UseUpperCase = true + conf.UseLowerCase = true + conf.UseNumber = true + conf.UseSpecial = true + conf.HumanReadable = false + charRange := getCharRange(conf) for i := 0; i < b.N; i++ { charToConv, _ := getRandChar(&charRange, 1) charBytes := []byte(charToConv) @@ -228,7 +232,9 @@ func BenchmarkConvertChar(b *testing.B) { } // Contains function to search a given slice for values -func containsByte(allowedBytes []int, currentChar int) bool { +func containsByte(allowedBytes []int, currentChar int, t *testing.T) bool { + t.Helper() + for _, charInt := range allowedBytes { if charInt == currentChar { return true diff --git a/chars.go b/chars.go index 2302745..39c64fb 100644 --- a/chars.go +++ b/chars.go @@ -1,14 +1,26 @@ package main -import "regexp" +import ( + "github.com/wneessen/apg.go/config" + "regexp" +) + +const PwLowerCharsHuman string = "abcdefghjkmnpqrstuvwxyz" +const PwUpperCharsHuman string = "ABCDEFGHJKMNPQRSTUVWXYZ" +const PwLowerChars string = "abcdefghijklmnopqrstuvwxyz" +const PwUpperChars string = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +const PwSpecialCharsHuman string = "\"#%*+-/:;=\\_|~" +const PwSpecialChars string = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" +const PwNumbersHuman string = "23456789" +const PwNumbers string = "1234567890" // Provide the range of available characters based on provided parameters -func getCharRange() string { +func getCharRange(config *config.Config) string { pwUpperChars := PwUpperChars pwLowerChars := PwLowerChars pwNumbers := PwNumbers pwSpecialChars := PwSpecialChars - if config.humanReadable { + if config.HumanReadable { pwUpperChars = PwUpperCharsHuman pwLowerChars = PwLowerCharsHuman pwNumbers = PwNumbersHuman @@ -16,20 +28,20 @@ func getCharRange() string { } var charRange string - if config.useLowerCase { + if config.UseLowerCase { charRange = charRange + pwLowerChars } - if config.useUpperCase { + if config.UseUpperCase { charRange = charRange + pwUpperChars } - if config.useNumber { + if config.UseNumber { charRange = charRange + pwNumbers } - if config.useSpecial { + if config.UseSpecial { charRange = charRange + pwSpecialChars } - if config.excludeChars != "" { - regExp := regexp.MustCompile("[" + regexp.QuoteMeta(config.excludeChars) + "]") + if config.ExcludeChars != "" { + regExp := regexp.MustCompile("[" + regexp.QuoteMeta(config.ExcludeChars) + "]") charRange = regExp.ReplaceAllLiteralString(charRange, "") } diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..94e298c --- /dev/null +++ b/config/config.go @@ -0,0 +1,154 @@ +package config + +import ( + "flag" + "log" + "os" +) + +// Constants +const DefaultPwLenght int = 20 +const VersionString string = "0.2.7" + +type Config struct { + MinPassLen int + MaxPassLen int + NumOfPass int + UseComplex bool + UseLowerCase bool + UseUpperCase bool + UseNumber bool + UseSpecial bool + HumanReadable bool + ExcludeChars string + NewStyleModes string + SpellPassword bool + ShowHelp bool + showVersion bool + OutputMode int +} + +var config Config + +func init() { + // Bool flags + flag.BoolVar(&config.UseLowerCase, "L", true, "Use lower case characters in passwords") + flag.BoolVar(&config.UseUpperCase, "U", true, "Use upper case characters in passwords") + flag.BoolVar(&config.UseNumber, "N", true, "Use numbers in passwords") + flag.BoolVar(&config.UseSpecial, "S", false, "Use special characters in passwords") + flag.BoolVar(&config.UseComplex, "C", false, "Generate complex passwords (implies -L -U -N -S, disables -H)") + flag.BoolVar(&config.SpellPassword, "l", false, "Spell generated password") + flag.BoolVar(&config.HumanReadable, "H", false, "Generate human-readable passwords") + flag.BoolVar(&config.showVersion, "v", false, "Show version") + + // Int flags + flag.IntVar(&config.MinPassLen, "m", DefaultPwLenght, "Minimum password length") + flag.IntVar(&config.MaxPassLen, "x", DefaultPwLenght, "Maxiumum password length") + flag.IntVar(&config.NumOfPass, "n", 1, "Number of passwords to generate") + + // String flags + flag.StringVar(&config.ExcludeChars, "E", "", "Exclude list of characters from generated password") + flag.StringVar(&config.NewStyleModes, "M", "", + "New style password parameters (higher priority than single parameters)") +} + +func NewConfig() *Config { + flag.Parse() + + if config.showVersion { + _, _ = os.Stderr.WriteString("Advanced Password Generator Clone (apg.go) v" + VersionString + "\n") + _, _ = os.Stderr.WriteString("(C) 2021 by Winni Neessen\n") + os.Exit(0) + } + + return &config +} + +// Parse the parameters and set the according config flags +func (config *Config) ParseParams() { + config.parseNewStyleParams() + + // Complex overrides everything + if config.UseComplex { + config.UseUpperCase = true + config.UseLowerCase = true + config.UseSpecial = true + config.UseNumber = true + config.HumanReadable = false + } + + if config.UseUpperCase == false && + config.UseLowerCase == false && + config.UseNumber == false && + config.UseSpecial == false { + log.Fatalf("No password mode set. Cannot generate password from empty character set.") + } + + // Set output mode + if config.SpellPassword { + config.OutputMode = 1 + } +} + +// Get the password length from the given cli flags +func (config *Config) GetPwLengthFromParams() int { + pwLength := config.MinPassLen + if pwLength < config.MinPassLen { + pwLength = config.MinPassLen + } + if pwLength > config.MaxPassLen { + pwLength = config.MaxPassLen + } + + return pwLength +} + +// Parse the new style parameters +func (config *Config) parseNewStyleParams() { + if config.NewStyleModes == "" { + return + } + + for _, curParam := range config.NewStyleModes { + switch curParam { + case 'S': + config.UseSpecial = true + break + case 's': + config.UseSpecial = false + break + case 'N': + config.UseNumber = true + break + case 'n': + config.UseNumber = false + break + case 'L': + config.UseLowerCase = true + break + case 'l': + config.UseLowerCase = false + break + case 'U': + config.UseUpperCase = true + break + case 'u': + config.UseUpperCase = false + break + case 'H': + config.HumanReadable = true + break + case 'h': + config.HumanReadable = false + break + case 'C': + config.UseComplex = true + break + case 'c': + config.UseComplex = false + break + default: + log.Fatalf("Unknown password style parameter: %q\n", string(curParam)) + } + } +} diff --git a/params.go b/params.go deleted file mode 100644 index 5adc5b3..0000000 --- a/params.go +++ /dev/null @@ -1,94 +0,0 @@ -package main - -import ( - "log" -) - -// Parse the parameters and set the according config flags -func parseParams() { - parseNewStyleParams() - - // Complex overrides everything - if config.useComplex { - config.useUpperCase = true - config.useLowerCase = true - config.useSpecial = true - config.useNumber = true - config.humanReadable = false - } - - if config.useUpperCase == false && - config.useLowerCase == false && - config.useNumber == false && - config.useSpecial == false { - log.Fatalf("No password mode set. Cannot generate password from empty character set.") - } - - // Set output mode - if config.spellPassword { - config.outputMode = 1 - } -} - -// Get the password length from the given cli flags -func getPwLengthFromParams() int { - pwLength := config.minPassLen - if pwLength < config.minPassLen { - pwLength = config.minPassLen - } - if pwLength > config.maxPassLen { - pwLength = config.maxPassLen - } - - return pwLength -} - -// Parse the new style parameters -func parseNewStyleParams() { - if config.newStyleModes == "" { - return - } - - for _, curParam := range config.newStyleModes { - switch curParam { - case 'S': - config.useSpecial = true - break - case 's': - config.useSpecial = false - break - case 'N': - config.useNumber = true - break - case 'n': - config.useNumber = false - break - case 'L': - config.useLowerCase = true - break - case 'l': - config.useLowerCase = false - break - case 'U': - config.useUpperCase = true - break - case 'u': - config.useUpperCase = false - break - case 'H': - config.humanReadable = true - break - case 'h': - config.humanReadable = false - break - case 'C': - config.useComplex = true - break - case 'c': - config.useComplex = false - break - default: - log.Fatalf("Unknown password style parameter: %q\n", string(curParam)) - } - } -}