PWGen/pwgenerator/funcs_genopts.go

307 lines
6.6 KiB
Go
Raw Normal View History

2022-03-02 06:24:55 -05:00
package pwgenerator
import (
"strings"
"r00t2.io/goutils/multierr"
2022-03-02 06:24:55 -05:00
)
// Generate generates a list of passwords for a GenOpts.
func (o *GenOpts) Generate() (passwords []string, err error) {
var passwds []string = make([]string, o.Count)
var charset CharSet = o.Chars()
var errs *multierr.MultiError = multierr.NewMultiError(nil)
if o.Count == 0 {
o.Count = DefCount
}
if o.LengthMax == 0 {
o.LengthMax = DefMaxLen
}
for idx, _ := range passwds {
if passwds[idx], err = o.generatePassword(charset); err != nil {
errs.AddError(err)
err = nil
}
}
passwords = passwds
if !errs.IsEmpty() {
err = errs
}
return
}
// generatePassword generates a single password from CharSet c (plus any minimum requirements).
func (o *GenOpts) generatePassword(c CharSet) (password string, err error) {
var maxMin uint
var trueMinLen uint
var passLenGap uint
var passLen int
var passAlloc []rune
var filter *selectFilter = o.getFilter()
var isDisabled bool
// Sanity checks/error conditions.
if o.explicitCharset != nil && len(o.explicitCharset) != 0 {
if o.CountUpper > 0 && !o.Alpha {
err = ErrIncompatCharsetFilter
return
}
if o.CountLower > 0 && !o.Alpha {
err = ErrIncompatCharsetFilter
return
}
if o.CountNumbers > 0 && !o.Numeric {
err = ErrIncompatCharsetFilter
return
}
if o.CountSymbols > 0 && !o.Symbols {
err = ErrIncompatCharsetFilter
return
}
if o.CountExtended > 0 && !o.ExtendedSymbols {
err = ErrIncompatCharsetFilter
return
}
}
maxMin = o.CountUpper + o.CountLower + o.CountNumbers + o.CountSymbols + o.CountExtended
if maxMin > o.LengthMax {
err = ErrTooSmall
return
}
if o.LengthMin > o.LengthMax {
err = ErrSwitchedLenLimits
return
}
// Defaults.
trueMinLen = o.LengthMin
if trueMinLen == 0 {
trueMinLen = DefMinLin
}
// Get a fixed password length...
passLenGap = o.LengthMax - trueMinLen
if passLen, err = saferRandInt(int(passLenGap)); err != nil {
return
}
passLen = passLen + int(trueMinLen) // (We need to re-add; it was subtracted above to get a zero-shifted number for start in saferRandInt.)
// And make the rune slice of that length.
passAlloc = make([]rune, passLen)
idx := 0
for idx < passLen {
var char Char
if o.explicitCharset != nil && len(o.explicitCharset) != 0 {
// Add upperCounter chars (if necessary).
for filter.upperCounter > 0 {
if char, err = upper.RandChar(); err != nil {
continue
}
if isDisabled, err = o.DisabledChars.Has(char); err != nil || isDisabled {
err = nil
continue
}
passAlloc[idx] = rune(char)
filter.upperCounter--
idx++
continue
}
// Add lowerCounter chars (if necessary).
for filter.lowerCounter > 0 {
if char, err = lower.RandChar(); err != nil {
continue
}
if isDisabled, err = o.DisabledChars.Has(char); err != nil || isDisabled {
err = nil
continue
}
passAlloc[idx] = rune(char)
filter.lowerCounter--
idx++
continue
}
// Add numberCounter chars (if necessary).
for filter.numberCounter > 0 {
if char, err = numeric.RandChar(); err != nil {
continue
}
if isDisabled, err = o.DisabledChars.Has(char); err != nil || isDisabled {
err = nil
continue
}
passAlloc[idx] = rune(char)
filter.numberCounter--
idx++
continue
}
// Add symbolCounter chars (if necessary).
for filter.symbolCounter > 0 {
if char, err = symbols.RandChar(); err != nil {
continue
}
if isDisabled, err = o.DisabledChars.Has(char); err != nil || isDisabled {
err = nil
continue
}
passAlloc[idx] = rune(char)
filter.symbolCounter--
idx++
continue
}
// Add extendedCounter chars (if necessary).
for filter.extendedCounter > 0 {
if char, err = extendedSymbols.RandChar(); err != nil {
continue
}
if isDisabled, err = o.DisabledChars.Has(char); err != nil || isDisabled {
err = nil
continue
}
passAlloc[idx] = rune(char)
filter.extendedCounter--
idx++
continue
}
// Break here if we've reached the full password with just the filter.
if idx > passLen {
err = ErrTooSmall
return
} else if idx == passLen {
break
}
}
// Minimum requirements satisfied; continue with default GenOpts-specific charset.
2022-03-03 05:51:22 -05:00
for {
if char, err = c.RandChar(); err != nil {
return
}
if isDisabled, err = o.DisabledChars.Has(char); err != nil || isDisabled {
err = nil
continue
}
2022-03-03 05:51:22 -05:00
passAlloc[idx] = rune(char)
idx++
2022-03-03 05:51:22 -05:00
break
}
}
password = string(passAlloc)
passwordShuffle(&password)
2022-03-02 06:24:55 -05:00
return
}
// Chars returns the list of evaluated characters that would be used in a GenOpts.
func (o *GenOpts) Chars() (chars CharSet) {
chars = make(CharSet, 0)
if o.explicitCharset != nil && len(o.explicitCharset) != 0 {
chars = o.explicitCharset
return
}
if o.Alpha {
chars = append(chars, alpha...)
}
if o.Numeric {
chars = append(chars, numeric...)
}
if o.Symbols {
chars = append(chars, symbols...)
}
if o.ExtendedSymbols {
chars = append(chars, extendedSymbols...)
}
// TODO: Count* fields
2022-03-02 06:24:55 -05:00
return
}
/*
SetExplicitCharset sets a GenOpts' charset to an explicit selection. It is deduplicated before being used unless inclDupes is true.
(Duplicate runes can be used to increase the likelihood of specific characters.)
Note that if an explicit charset is defined, the following opts will have NO effect:
Alpha
Numeric
Symbols
ExtendedSymbols
CountUpper
CountLower
CountSymbols
CountExtended
DisabledChars
To have these fields be used again, call GenOpts.UnsetExplicitCharset.
chars can be a CharSet, []rune, []byte, []string, or string
*/
func (o *GenOpts) SetExplicitCharset(chars interface{}, inclDupes bool) (err error) {
var charset CharSet
switch t := chars.(type) {
case []rune:
charset = CharSet(string(t))
case []byte:
charset = CharSet(string(t))
case []string:
s := strings.Join(t, "")
charset = CharSet(s)
case string:
charset = CharSet(t)
default:
err = ErrBadType
return
}
if !inclDupes {
sortDedupe(&charset)
}
o.explicitCharset = charset
return
}
/*
UnsetExplicitCharset removes the explicit charset used for generation and instead uses the fields specified in the actual GenOpts.
See GenOpts.SetExplicitCharset for more details.
*/
func (o *GenOpts) UnsetExplicitCharset() {
o.explicitCharset = nil
return
}
// getFilter gets a filter counter.
func (o *GenOpts) getFilter() (f *selectFilter) {
f = &selectFilter{
upperCounter: o.CountUpper,
lowerCounter: o.CountLower,
numberCounter: o.CountNumbers,
symbolCounter: o.CountSymbols,
extendedCounter: o.CountExtended,
}
return
}