aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDennis Eriksen <d@ennis.no>2023-11-10 09:54:17 +0100
committerDennis Eriksen <d@ennis.no>2023-11-10 09:54:17 +0100
commitcf84fad0955ff07dd6fe3ec30a17dc134e23c356 (patch)
tree6f85e6017e7a5feacd88270cb942e4e2e007ffcb /src
parentAdding a python-verion (diff)
downloadmakepass-cf84fad0955ff07dd6fe3ec30a17dc134e23c356.tar.gz
trying out new project structure
Diffstat (limited to 'src')
-rw-r--r--src/go/go.mod7
-rw-r--r--src/go/go.sum4
-rw-r--r--src/go/main.go331
-rw-r--r--src/rust/Cargo.lock363
-rw-r--r--src/rust/Cargo.toml14
-rw-r--r--src/rust/src/main.rs393
6 files changed, 1112 insertions, 0 deletions
diff --git a/src/go/go.mod b/src/go/go.mod
new file mode 100644
index 0000000..108f34b
--- /dev/null
+++ b/src/go/go.mod
@@ -0,0 +1,7 @@
+module makepass.go
+
+go 1.20
+
+require golang.org/x/term v0.12.0
+
+require golang.org/x/sys v0.12.0 // indirect
diff --git a/src/go/go.sum b/src/go/go.sum
new file mode 100644
index 0000000..8c01403
--- /dev/null
+++ b/src/go/go.sum
@@ -0,0 +1,4 @@
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
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)
+
+}
diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
new file mode 100644
index 0000000..50747ce
--- /dev/null
+++ b/src/rust/Cargo.lock
@@ -0,0 +1,363 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "anstream"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is-terminal",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c"
+dependencies = [
+ "anstyle",
+ "windows-sys",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
+
+[[package]]
+name = "cc"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clap"
+version = "4.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb690e81c7840c0d7aade59f242ea3b41b9bc27bcd5997890e7702ae4b32e487"
+dependencies = [
+ "clap_builder",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.3.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ed2e96bc16d8d740f6f48d663eddf4b8a0983e79210fd55479b7bcd0a69860e"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "once_cell",
+ "strsim",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+
+[[package]]
+name = "errno"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "is-terminal"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
+dependencies = [
+ "hermit-abi",
+ "rustix 0.38.13",
+ "windows-sys",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.147"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"
+
+[[package]]
+name = "makepass"
+version = "0.1.0"
+dependencies = [
+ "clap",
+ "rand",
+ "terminal_size",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rustix"
+version = "0.37.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
+dependencies = [
+ "bitflags 1.3.2",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys 0.3.8",
+ "windows-sys",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662"
+dependencies = [
+ "bitflags 2.4.0",
+ "errno",
+ "libc",
+ "linux-raw-sys 0.4.7",
+ "windows-sys",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "terminal_size"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237"
+dependencies = [
+ "rustix 0.37.23",
+ "windows-sys",
+]
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml
new file mode 100644
index 0000000..181b7a7
--- /dev/null
+++ b/src/rust/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "makepass"
+authors = ["Dennis Eriksen <d@ennis.no>"]
+version = "0.1.0"
+edition = "2021"
+rust-version = "1.68"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+
+rand = "0.8.4"
+clap = { version = "4.3.24", features = ["cargo"] }
+terminal_size = "0.2.6"
diff --git a/src/rust/src/main.rs b/src/rust/src/main.rs
new file mode 100644
index 0000000..083a99f
--- /dev/null
+++ b/src/rust/src/main.rs
@@ -0,0 +1,393 @@
+//
+// Author : Dennis Eriksen <d@ennis.no>
+// File : main.rs
+// Bin : makepass.rs
+// Created : 2023-09-05
+// Licence : BSD-3-Clause
+//
+// Copyright (c) 2018-2023 Dennis Eriksen <d@ennis.no>
+
+//
+// Imports
+//
+use clap::{Arg, ArgAction, Command, crate_authors, value_parser};
+use rand::{seq::SliceRandom, Rng};
+use std::env;
+use std::fs;
+use std::io;
+use std::io::prelude::*;
+use std::process::exit;
+use terminal_size::{terminal_size, Width};
+
+//
+// Constants
+//
+const MAX: u8 = 255;
+const RANGE_MAX: u8 = 42;
+const RANGE_MIN: u8 = 8;
+const PASS_WORDS: u8 = 8;
+
+// Character sets
+const LOWER: &str = "abcdefghijklmnopqrstuvwxyz";
+const UPPER: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+const DIGIT: &str = "0123456789";
+const OTHER: &str = "!$%&#/()=?+-_,.;:<>[]{}|@*";
+const EXTRA: &str = "-_";
+
+// Defaults
+const DEFAULT_LENGTH: u8 = 0;
+const DEFAULT_NUMBER: u8 = 10;
+const DEFAULT_WORDLIST: &str = "/usr/share/dict/words";
+
+// Structure used to pass common data to the print-function
+struct CommonPrintData<'a> {
+ length: u8,
+ alpha: &'a [char],
+ alnum: &'a [char],
+ printbool: bool,
+ printlen: u8,
+ col_width: u8,
+ col_num: u16,
+}
+
+//
+// Main function. This is where the magic happens
+//
+fn main() {
+ // Construct arrays of the character sets
+ let lower = LOWER.chars().collect::<Vec<_>>();
+ let upper = UPPER.chars().collect::<Vec<_>>();
+ let digit = DIGIT.chars().collect::<Vec<_>>();
+ let other = OTHER.chars().collect::<Vec<_>>();
+ let extra = EXTRA.chars().collect::<Vec<_>>();
+ let alpha = [&lower[..], &upper[..]].concat();
+ let alnum = [&alpha[..], &digit[..]].concat();
+ let every = [&alnum[..], &other[..]].concat();
+
+ // These are the sets we actually use
+ let mut normal = [&alnum[..], &extra[..]].concat();
+ let mut special = every;
+
+ // Set the defaults
+ let mut length = DEFAULT_LENGTH;
+ let mut number = DEFAULT_NUMBER;
+ let mut wordlist = DEFAULT_WORDLIST.to_string();
+
+ //
+ // env
+ // Deal with env before cli-args, because cli-args take precedence.
+ //
+ let env = (
+ env::var("MAKEPASS_LENGTH").unwrap_or_default(),
+ env::var("MAKEPASS_NUMBER").unwrap_or_default(),
+ env::var("MAKEPASS_WORDLIST").unwrap_or_default(),
+ env::var("MAKEPASS_NORMAL").unwrap_or_default(),
+ env::var("MAKEPASS_SPECIAL").unwrap_or_default(),
+ );
+
+ // length
+ if !env.0.is_empty() {
+ length = env.0.parse().unwrap_or_else(|_| {
+ // length is a <u8>, which is 0-255, so no need for further error-checking
+ eprintln!(
+ "Error: MAKEPASS_LENGTH is not a valid number. Valid numbers are between 0 and 255"
+ );
+ exit(1);
+ });
+ }
+ // number
+ if !env.1.is_empty() {
+ // number is a <u8>, but takes a minimum value of 1
+ match env.1.parse::<u8>() {
+ Ok(n) if n >= 1 => number = n,
+ _ => {
+ eprintln!("Error: MAKEPASS_NUMBER is not a valid number. Valid numbers are between 1 and 255");
+ exit(1);
+ }
+ }
+ }
+ if !env.2.is_empty() { // wordlist
+ wordlist = env.2;
+ }
+ if !env.3.is_empty() { // Overwrite normal character array if MAKEPASS_NORMAL is present
+ normal = env.3.chars().collect::<Vec<_>>();
+ }
+ if !env.4.is_empty() { // Overwrite special character array if MAKEPASS_SPECIAL is present
+ special = env.4.chars().collect::<Vec<_>>();
+ }
+
+ //
+ // Args
+ //
+ let opts = cli().get_matches();
+
+ // Set to new value if exists, or just set to old value
+ length = *opts.get_one::<u8>("length_flag").unwrap_or(&length);
+ number = *opts.get_one::<u8>("number").unwrap_or(&number);
+ length = *opts.get_one::<u8>("length_arg").unwrap_or(&length);
+ let printbool = opts.get_flag("printlen");
+
+ //
+ // Other logic
+ //
+ let printlen: u8 = if printbool && length < 100 {
+ 2
+ } else if printbool {
+ 3
+ } else {
+ 0
+ };
+
+ // Get the width of the terminal
+ let size = terminal_size();
+ let term_width: u16 = match size {
+ Some((Width(w), _)) => w,
+ None => 1,
+ };
+
+ // Calculate how wide each column has to be
+ let col_width: u8 = match length {
+ 0 => RANGE_MAX + 2,
+ _ => length + 2,
+ };
+
+ // Number of columns to print
+ // If printbool is true, col_num is term_width / (col_width + printlen + 1)
+ // Else, col_num is term_width / col_width
+ // col_num is never 0. Use std::cmp::max to use the largest value of 1 and the above.
+ let col_num: u16 = std::cmp::max(
+ 1,
+ match printbool {
+ true => term_width / (col_width + printlen + 1) as u16,
+ false => term_width / col_width as u16,
+ },
+ );
+
+ // Fill in common print data
+ let data = CommonPrintData {
+ length: length,
+ alpha: &alpha,
+ alnum: &alnum,
+ printbool: printbool,
+ printlen: printlen,
+ col_width: col_width,
+ col_num: col_num,
+ };
+
+ //
+ // Do the dirty!
+ //
+
+ // Normal passwords
+ print_columns("Normal passwords", number, &normal, &data);
+
+ println!("");
+
+ // Special passwords
+ print_columns(
+ "Passwords with special characters",
+ number / 3 * 2 + 1,
+ &special,
+ &data,
+ );
+
+ // Passphrases
+ if fs::metadata(&wordlist).is_ok() {
+ // Open the file
+ let file = fs::File::open(&wordlist).unwrap();
+
+ // Create a buffered reader to handle file contents
+ let reader = io::BufReader::new(file);
+
+ // Read words into a Vector
+ let words = reader.lines().map(|l| l.unwrap()).collect();
+
+ // Print
+ println!("");
+ println!("Passphrases:");
+
+ for _ in 0..number / 2 {
+ println!("{}", passphrase(&words));
+ }
+ }
+}
+
+//
+// Print passwords in neat columns
+//
+fn print_columns(title: &str, num: u8, chars: &Vec<char>, data: &CommonPrintData) {
+ let mut strings: Vec<String> = Vec::new();
+ for _ in 0..num {
+ strings.push(randstring(data.length, chars, data.alpha, data.alnum));
+ }
+
+ println!("{}:", title);
+
+ let mut i: u16 = 0;
+ for s in &strings {
+ i += 1;
+
+ if data.printbool {
+ print!("{1:00$} ", data.printlen as usize, s.len());
+ }
+ print!("{1:<0$}", data.col_width as usize, s);
+ if i % data.col_num == 0 || (i == num as u16 && i % data.col_num > 0) {
+ println!();
+ }
+ }
+}
+
+//
+// Generate random strings to use as passwords
+//
+fn randstring(len: u8, chars: &[char], alpha: &[char], alnum: &[char]) -> String {
+ let mut rng = rand::thread_rng();
+ let length = if len == 0 {
+ rng.gen_range(RANGE_MIN..=RANGE_MAX)
+ } else {
+ len
+ };
+
+ let mut s = String::with_capacity(length as usize);
+ for i in 0..length {
+ let c = if i == 0 {
+ alpha.choose(&mut rng)
+ } else if i == length - 1 {
+ alnum.choose(&mut rng)
+ } else {
+ chars.choose(&mut rng)
+ };
+ s.push(*c.unwrap());
+ }
+ s
+}
+
+//
+// Generate passphrases
+//
+fn passphrase(wordlist: &Vec<String>) -> String {
+ let mut rng = rand::thread_rng();
+ let mut passphrase = Vec::new();
+
+ for _ in 0..PASS_WORDS {
+ let word = wordlist.choose(&mut rng).unwrap().clone();
+ passphrase.push(word);
+ }
+
+ passphrase.join("-")
+}
+
+//
+// Handle command line arguments
+//
+fn cli() -> Command {
+ Command::new("makepass")
+ .about("makepass - create random passwords")
+ .author(crate_authors!())
+ .arg(
+ Arg::new("length_flag")
+ .short('l')
+ .value_name("LENGTH")
+ .help(format!("Length of passwords (0..={0}). 0 = random.", MAX))
+ .value_parser(value_parser!(u8).range(0..)),
+ )
+ .arg(
+ Arg::new("number")
+ .short('n')
+ .value_name("NUM")
+ .help(format!("Number of passwords (1..={0}). [default: 10]", MAX))
+ .value_parser(value_parser!(u8).range(1..))
+ .long_help("this is the long help. What is it?"),
+ )
+ .arg(
+ Arg::new("printlen")
+ .short('p')
+ .help("Print length")
+ .action(ArgAction::SetTrue),
+ )
+ .arg(
+ Arg::new("length_arg")
+ .value_name("LENGTH")
+ .help(format!(
+ "Length of passwords (0..={MAX}). 0 = random length."
+ ))
+ .value_parser(value_parser!(u8).range(0..)),
+ )
+ .override_help("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.
+
+AUTHOR
+ Dennis Eriksen <https://dnns.no>")
+}