package main
import (
"bufio"
"fmt"
"golang.org/x/term"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
// Basic constant definitions
const (
normNum = 10 // Number of normal passwords
specNum = 6 // Number of special passwords
passNum = 6 // Number of passphrases
maxLength = 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
length int = 0 // Length of passwords
)
func main() {
//
// Error handling
//
var err error
// Checking command line argument and environment variable for password length
if len(os.Args) == 2 {
length, err = strconv.Atoi(os.Args[1])
} else if len(os.Getenv("MAKEPASS_DEFAULT_LENGTH")) > 0 {
length, err = strconv.Atoi(os.Getenv("MAKEPASS_DEFAULT_LENGTH"))
}
// If there is an error in conversion, or if the length is not within limits, exit the program with an error message
if err != nil || (length > maxLength || length < 0) {
fmt.Printf("Error: Argument must be a whole number between 0 and %d.\n", maxLength)
os.Exit(1)
}
// Get wordlist from env
if len(os.Getenv("MAKEPASS_WORDLIST")) > 0 {
wordlist = os.Getenv("MAKEPASS_WORDLIST")
}
// Check if the wordlist file exists
if _, err := os.Stat(wordlist); os.IsNotExist(err) {
fmt.Println("The file", wordlist, "does not exist")
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)
// 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 text []string
for scanner.Scan() {
text = append(text, scanner.Text())
}
file.Close()
//
// print passwords
//
// Generate and print normal and special passwords
printColumns("Normal passwords:", 10, normal, termWidth)
fmt.Println("")
printColumns("Passwords with special characters:", 6, special, termWidth)
fmt.Println("")
// Generate and print passphrases
for i := 0; i < passNum; i++ {
fmt.Println(passphrase(&text))
}
}
// 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) + rangeMin
}
var str strings.Builder
// Add random characters to str
for i := 1; i <= l; i++ {
if i == 1 || i == l { // don't want passwords to start or end with special characters
str.WriteString(alnum[rand.Intn(len(alnum))])
} else {
str.WriteString(chars[rand.Intn(len(chars))])
}
}
return str.String()
}
// Function to generate and print passwords
func printColumns(title string, num int, chars []string, termWidth int) {
// column width
colWidth := rangeMax + 2
if length > 0 {
colWidth = length + 2
}
// number of colums
columns := termWidth / colWidth
if columns == 0 {
columns = 1
}
// Print title
fmt.Println(title)
// Print passwords in columns
for i := 1; i <= num; i++ {
fmt.Printf("%-[1]*[2]s", colWidth, randstring(chars))
if i%columns == 0 || (i == num && i%columns > 0) { // Add newlines
fmt.Println("")
}
}
}
// Function to generate passphrases
func passphrase(arrh *[]string) string {
text := *arrh
var str strings.Builder
for i := 0; i < passWords; i++ {
str.WriteString(text[rand.Intn(len(text))]) // Write a random word to the passphrase
if i != passWords-1 {
str.WriteString("-") // Add hyphen between words
}
}
return str.String()
}