diff options
Diffstat (limited to 'src/go/main.go')
-rw-r--r-- | src/go/main.go | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/src/go/main.go b/src/go/main.go new file mode 100644 index 0000000..a5241a3 --- /dev/null +++ b/src/go/main.go @@ -0,0 +1,331 @@ +// +// Author : Dennis Eriksen <d@ennis.no> +// File : makepass.go +// Created : 2023-09-05 +// Licence : BSD-3-Clause +// +// Copyright (c) 2018-2023 Dennis Eriksen <d@ennis.no> + +package main + +import ( + "bufio" + "flag" + "fmt" + "golang.org/x/term" + "math/rand" + "os" + "strconv" + "strings" + "time" +) + +// Basic constant definitions +const ( + max = 255 // Maximum length of passwords + rangeMax = 42 // Maximum range for password length + rangeMin = 8 // Minimum range for password length + passWords = 8 // Number of words in passphrases + + lower = "abcdefghijklmnopqrstuvwxyz" + upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + digit = "0123456789" + other = "!$%&#/()=?+-_,.;:<>[]{}|@*" // Special characters for special passwords + + defaultFile = "/usr/share/dict/words" // Default path to the file of words used for passphrases +) + +// Variables for different types of passwords +var ( + alpha []string = strings.Split(lower+upper, "") // Array of alphabets (lowercase and uppercase) + alnum []string = append(alpha, strings.Split(digit, "")...) // Array of alphanumeric characters + every []string = append(alnum, strings.Split(other, "")...) // Array of all characters (alphabet, digit and special) + normal []string = append(alnum, "-", "_") // Characters for normal passwords + special []string = every // Characters for special passwords + wordlist string = defaultFile // Path to a dictionary to use for passphrases + colWidth int = 1 + colNum int = 1 + + length int = 0 // Length of passwords + number int = 10 + printBool bool = false + printLen int = 0 + help bool = false +) + +func main() { + + // define error-vars + var errl error // err for length + var errn error // err for number + + // Handle environment + if len(os.Getenv("MAKEPASS_LENGTH")) > 0 { + length, errl = strconv.Atoi(os.Getenv("MAKEPASS_LENGTH")) + } + if len(os.Getenv("MAKEPASS_NUMBER")) > 0 { + number, errn = strconv.Atoi(os.Getenv("MAKEPASS_NUMBER")) + } + // Get wordlist from env + if len(os.Getenv("MAKEPASS_WORDLIST")) > 0 { + wordlist = os.Getenv("MAKEPASS_WORDLIST") + } + + // + // Flag handling + // + flag.BoolVar(&help, "h", help, "print helptext") + flag.IntVar(&length, "l", length, "length of passwords to output\nmust be a number between 0 and "+strconv.Itoa(max)) + flag.IntVar(&number, "n", number, "number of passwords to output\nmust be a number between 1 and "+strconv.Itoa(max)) + flag.BoolVar(&printBool, "p", printBool, "print length of each password") + flag.Parse() + + if help { + printHelp() + } + + // Handle cmd-line args that are not flags + if len(flag.Args()) == 1 { + length, errl = strconv.Atoi(flag.Arg(0)) + } + + // + // Error handling + // + + // We take max one argument + if len(flag.Args()) > 1 { + fmt.Println("only one argument") + os.Exit(1) + } + + // If there is an error in conversion, or if the length is not within limits, exit the program with an error message + if errl != nil || (length > max || length < 0) { + fmt.Printf("length must be a number between 0 and %d.\n", max) + os.Exit(1) + } + if errn != nil || (number < 1 || max < number) { + fmt.Printf("number-argument must be between 1 and %d.\n", max) + os.Exit(1) + } + + // + // move on + // + + // initialise the random seed + // TODO: Get seed from /dev/random, like we do in the perl- and zsh-versions? + rand.Seed(time.Now().UnixNano()) + + // get screen width + termWidth, _, err := term.GetSize(0) + if err != nil { + termWidth = 0 + } + + // col width of printLen + if printBool { + // convert length to string and count length of string, and add one for space + // In the other versions we add the space when calculating colNum, but go doesn't support ternary operators. This way we don't need an extra if-statement. Just subtract the space when printing the length in printColumns(). + if length <= 100 { + printLen = 2 + 1 + } else { + printLen = 3 + 1 + } + } else { + printLen = 0 + } + + // column width + colWidth = rangeMax + 2 + if length > 0 { + colWidth = length + 2 + } + + // number of colums + colNum = termWidth / (colWidth + printLen) + if colNum == 0 { + colNum = 1 + } + + // + // print passwords + // + + // Generate and print normal and special passwords + printColumns("Normal passwords:", number, normal) + + fmt.Println("") + + printColumns("Passwords with special characters:", number/3*2+1, special) + + // Generate and print passphrases if wordlist exists + if _, err := os.Stat(wordlist); err == nil { + // Read wordlist from file + file, err := os.Open(wordlist) + if err != nil { + fmt.Printf("failed to open file: %s", err) + } + scanner := bufio.NewScanner(file) + scanner.Split(bufio.ScanLines) + var words []string + for scanner.Scan() { + words = append(words, scanner.Text()) + } + file.Close() + + // Print it + fmt.Println("") + fmt.Println("Passphrases:") + + for i := 0; i < number/2; i++ { + fmt.Println(passphrase(words)) + } + } +} + +// Function to generate and print passwords +func printColumns(title string, num int, chars []string) { + var strings []string + + for i := 0; i < num; i++ { + strings = append(strings, randstring(chars)) + } + + // Print title + fmt.Println(title) + + // Print passwords in neat columns + for i, str := range strings { + if printBool { + fmt.Printf("%0[1]*[2]d ", printLen-1, len(str)) + } + fmt.Printf("%-[1]*[2]s", colWidth, str) + if (i+1)%colNum == 0 || (i+1 == num && (i+1)%colNum > 0) { // Add newlines + fmt.Println("") + } + } +} + +// Function to generate a random string of given length from given characters +func randstring(chars []string) string { + l := length + if length == 0 { // Random length if not specified + l = rand.Intn(rangeMax-rangeMin+1) + rangeMin + } + var str strings.Builder + + // Add random characters to str + for i := 1; i <= l; i++ { + if i == 1 { // don't want passwords to start or end with special characters + str.WriteString(alnum[rand.Intn(len(alpha))]) + } else if i == l { + str.WriteString(alnum[rand.Intn(len(alnum))]) + } else { + str.WriteString(chars[rand.Intn(len(chars))]) + } + } + return str.String() +} + +// Function to generate passphrases +func passphrase(words []string) string { + var str strings.Builder + for i := 0; i < passWords; i++ { + str.WriteString(words[rand.Intn(len(words))]) // Write a random word to the passphrase + if i != passWords-1 { + str.WriteString("-") // Add hyphen between words + } + } + return str.String() +} + +// Help-function +func printHelp() { + fmt.Println(`NAME + makepass - create several random passwords + +SYNOPSIS + makepass [OPTIONS] [NUM] + + If a NUM is provided, passwords will be NUM characters long. + + By default ` + "`makepass`" + ` will output passwords from the three following classes: + + - Normal passwords - random strings with letters (both lower and upper + case), numbers, and dashes and underscores. + + - Passwords with special characters - random strings generated from lower + and upper case letters, numbers, and the following characters: + !#$%&/()=?+-_,.;:<>[]{}|@* + + - Passphrases - if we find a dictionary, a series of eight random words + from the dictionary, separated by dashes. The number of words can not be + changed, but you do not have to use all of them. Use as mane as you want. + + The first character will always be alphabetic, and the last will always be + alphanumeric. + +DESCRIPTION + makepass has the following options: + + -h + output this help-text + -l + length of passwords. See MAKEPASS_LENGTH below + -n + number of passwords. See MAKEPASS_NUMBER below + -p + print length of number + +ENVIRONMENT + makepass examines the following environmental variables. + + MAKEPASS_LENGTH + Specifies the length of passwords. Valid values are 0-255. If 0, a + random value between 8 and 42 will be used for each password. -l + overrides this environmental variable, and the argument NUM overrides + that again. So ` + "MAKEPASS_LENGTH=10 makepass -l 12 14" + ` will give + passwords that are 14 characters long, even though both -l and + MAKEPASS_LENGTH also specifies a length. + + MAKEPASS_NUMBER + The number of passwords to generate. This formula is used to determine + how many passwords from each group should be generated: + - (n) normal passwords + - (n / 3 * 2 + 1) special passwords + - (n / 2) passphrases + Where n is 10 by default. Valid values for n are 1-255. Floating-poing + math is not used, so results may vary. + + MAKEPASS_PRINTLEN + If 1, print length of all passwords. If 0, don\'t. + + MAKEPASS_NORMAL + String of characters from which to generate "normal" passwords. + Defaults to: + abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_ + + MAKEPASS_SPECIAL + String of characters from which to generate passwords with special + characters. Defaults to the same characters as in MAKEPASS_NORMAL, plus + these: + !#$%&/()=?+-_,.;:<>[]{}|@* + + MAKEPASS_WORDLIST + Specifies the dictionary we find words for passphrases in. If this is + unset or empty, we try "/usr/share/dict/words". If that file does not + exist, no passphrases will be provided. + +NOTES + This scripts makes use of $RANDOM - a builtin in zsh which produces a + pseudo-random integer between 0 and 32767, newly generated each time the + parameter is referenced. We initially seed the random number generator with + a random 32bit integer generated from /dev/random. This should provide + enough randomnes to generate sufficiently secure passwords. + +AUTHOR + Dennis Eriksen <https://dnns.no>`) + os.Exit(0) + +} |