|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"net"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
registerSwitch("no-ipv6", "skip IPv6 entries")
|
|
|
|
registerSwitch("append-default-ports", "always append default ports even for targets in host:port format")
|
|
|
|
registerSwitch("strict-subnets", "strict subnet behaviour: ignore network and broadcast addresses in /30 and bigger subnets")
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.Init()
|
|
|
|
ep.passwordPos.Init()
|
|
|
|
ep.state = ES_Normal
|
|
|
|
ep.listElement = endpoints.PushBack(&ep)
|
|
|
|
log("eparse", 3, "registered endpoint: %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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseCIDR registers multiple endpoints from a CIDR netmask.
|
|
|
|
func parseCIDR(ip string, ports []int, isIPv6 bool) int {
|
|
|
|
na, nm, err := net.ParseCIDR(ip)
|
|
|
|
if err != nil {
|
|
|
|
log("eparse", 0, "failed to parse CIDR notation for \"%v\": %v", ip, err.Error())
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
mask, maskBits := nm.Mask.Size()
|
|
|
|
if mask < maskBits-maxNetmaskSize {
|
|
|
|
log("eparse", 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 := getParamSwitch("strict-subnets")
|
|
|
|
|
|
|
|
log("eparse", 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("eparse", 1, "ignoring network/broadcast address due to strict-subnets: \"%v\"", expIP.String())
|
|
|
|
} else {
|
|
|
|
numParsed += RegisterEndpoint(expIP.String(), ports, isIPv6)
|
|
|
|
}
|
|
|
|
curHost++
|
|
|
|
}
|
|
|
|
|
|
|
|
return numParsed
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseIPOrCIDR expands plain IP or CIDR to multiple endpoints.
|
|
|
|
func parseIPOrCIDR(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("eparse", 0, "invalid CIDR subnet format: \"%v\", ignoring", ip)
|
|
|
|
return 0
|
|
|
|
} else { // otherwise, just register
|
|
|
|
return RegisterEndpoint(ip, ports, isIPv6)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// extractIPAndPort extracts all endpoint components.
|
|
|
|
func extractIPAndPort(str string) (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
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseEndpoints takes a string slice of IPs/CIDR subnets and converts it to a list of endpoints.
|
|
|
|
func ParseEndpoints(source []string, name string) {
|
|
|
|
log("eparse", 1, "parsing endpoints from %v", name)
|
|
|
|
|
|
|
|
totalIPv6Skipped := 0
|
|
|
|
numParsed := 0
|
|
|
|
|
|
|
|
for _, str := range source {
|
|
|
|
str = strings.TrimSpace(str)
|
|
|
|
if str == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.Contains(str, ":") {
|
|
|
|
// no ":": this is an ipv4/dn without port,
|
|
|
|
// parse it with all known ports
|
|
|
|
|
|
|
|
numParsed += parseIPOrCIDR(str, getParamIntSlice("port"), false)
|
|
|
|
} else {
|
|
|
|
// either ipv4/dn with port, or ipv6 with/without port
|
|
|
|
|
|
|
|
isIPv6 := strings.Count(str, ":") > 1
|
|
|
|
if isIPv6 && getParamSwitch("no-ipv6") {
|
|
|
|
totalIPv6Skipped++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.Contains(str, "]:") && strings.Contains(str, "::") {
|
|
|
|
// ipv6 without port
|
|
|
|
numParsed += parseIPOrCIDR(str, getParamIntSlice("port"), true)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
ip, port, err := extractIPAndPort(str)
|
|
|
|
if err != nil {
|
|
|
|
log("eparse", 0, "failed to extract ip/port for \"%v\": %v, ignoring endpoint", str, err.Error())
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
ports := []int{port}
|
|
|
|
// append all default ports
|
|
|
|
if getParamSwitch("append-default-ports") {
|
|
|
|
for _, port2 := range getParamIntSlice("port") {
|
|
|
|
if port != port2 {
|
|
|
|
ports = append(ports, port2)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
numParsed += parseIPOrCIDR(ip, ports, isIPv6)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
logIf(totalIPv6Skipped > 0, "eparse", 0, "skipped %v IPv6 targets due to no-ipv6 flag", totalIPv6Skipped)
|
|
|
|
log("eparse", 1, "finished parsing endpoints from %v: parsed %v out of total %v", name, numParsed, endpoints.Len())
|
|
|
|
}
|