aboutsummaryrefslogblamecommitdiffstats
path: root/go/makepass.go
blob: 542b63e22988f70ec2aa0627456843a2e22a30af (plain) (tree)










































































































































































                                                                                                                               
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()
}