This repository has been archived on 2024-03-03. You can view files and clone it, but cannot push or open issues or pull requests.
adlin/checker/checker.go

413 lines
15 KiB
Go

package main
import (
"fmt"
"log"
"strings"
"time"
ping "github.com/prometheus-community/pro-bing"
"git.nemunai.re/srs/adlin/libadlin"
)
const (
DEFAULT_RESOLVER = "2a01:e0a:518:830::1"
year68 = 1 << 31 // For RFC1982 (Serial Arithmetic) calculations in 32 bits. Taken from miekg/dns
)
var (
verbose = false
verbose2 = false
domainsHostingMap = map[string]string{}
)
// Main
func minTunnelVersion(std *adlin.Student, suffixip int) (int, error) {
tunnels, err := std.GetTunnelTokens()
if err != nil {
return 0, err
}
var minversion int = 2147483647
for _, tunnel := range tunnels {
if tunnel.Version == 0 {
continue
}
if tunnel.Dump != nil && tunnel.Version < minversion && suffixip == tunnel.SuffixIP {
minversion = tunnel.Version
}
}
return minversion, nil
}
func studentChecker(std *adlin.Student, also_check_matrix bool, offline bool) {
tuns, err := std.GetActivesTunnels()
if err != nil {
if offline {
tuns, err = std.GetDefaultTunnels()
if err != nil {
if verbose {
log.Printf("SKip %s as I'm unable to generate default tunnels: %s", std.Login, err.Error())
}
return
}
} else {
if verbose2 {
log.Printf("SKip %s due to error when getting active tunnels: %s", std.Login, err.Error())
}
return
}
}
if verbose2 && len(tuns) == 0 {
log.Printf("%s has no active tunnels: %s", std.Login, err.Error())
}
for _, tun := range tuns {
stdIP := tun.GetStudentIP()
if verbose2 {
log.Printf("Tests %s on %s...", std.Login, stdIP)
}
// Check ping
err = check_ping(stdIP, func(pkt *ping.Packet) {
tunnel_version, err := minTunnelVersion(std, tun.SuffixIP)
if verbose {
log.Printf("%s PONG (on %x); version=%d (%v)\n", std.Login, tun.SuffixIP, tunnel_version, err)
}
std.OnPong(true)
if !offline && (tunnel_version == 2147483647 || tunnel_version == 0) {
log.Printf("%s unknown tunnel version: %d skipping tests (%v)", std.Login, tunnel_version, err)
return
}
// PingResolver
if has_test(CheckMap[tunnel_version], PingResolver) {
tmp := strings.Split(stdIP, ":")
tmp[len(tmp)-1] = "2"
stdResolverIP := strings.Join(tmp, ":")
go check_ping(stdResolverIP, func(_ *ping.Packet) {
if verbose {
log.Printf("%s resolver PONG", std.Login)
}
if _, err := std.UnlockChallenge(CheckMap[tunnel_version][PingResolver], ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
}, nil)
}
// Firewalled
if has_test(CheckMap[tunnel_version], Firewalled) {
if err = check_firewall("tcp", stdIP); err == nil {
if verbose {
log.Printf("%s just unlocked firewalled challenge\n", std.Login)
}
if _, err := std.UnlockChallenge(CheckMap[tunnel_version][Firewalled], ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
} else {
std.RegisterChallengeError(CheckMap[tunnel_version][Firewalled], err)
if verbose {
log.Printf("%s and firewalled: %s\n", std.Login, err)
}
}
}
dnsIP := stdIP
var glueErr error
// Is GLUE defined?
if glueIP, err := get_GLUE(std); glueIP != nil {
dnsIP = glueIP.String()
if verbose {
log.Printf("%s has defined GLUE: %s\n", std.Login, dnsIP)
}
} else if err != nil {
log.Printf("%s and GLUE: %s\n", std.Login, err)
glueErr = err
}
snicheck1 := false
snicheck1_tested := false
// Check DNS
if addr, err := check_dns_both(std.MyDelegatedDomain(), dnsIP); err == nil {
if addr == nil {
dnsAt := ""
if glueErr != nil {
dnsAt = " + there is a problem with the GLUE record: " + glueErr.Error()
}
if errreg := std.RegisterChallengeError(CheckMap[tunnel_version][DNSDelegation], fmt.Errorf("dig @%s %s AAAA: empty response from the server%s", dnsIP, std.MyDelegatedDomain(), dnsAt)); errreg != nil {
log.Printf("Unable to register challenge error for %s: %s\n", std.Login, errreg)
}
} else if tunnel_version == 3 && dnsIP == stdIP {
if errreg := std.RegisterChallengeError(CheckMap[tunnel_version][DNSDelegation], fmt.Errorf("%s: you shouldn't use DNAT on IPv6 (NAT on IPv6 is bad); instead, point your GLUE record to nsauth directly", std.MyDelegatedDomain())); errreg != nil {
log.Printf("Unable to register challenge error for %s: %s\n", std.Login, errreg)
}
} else {
if verbose {
log.Printf("%s just unlocked DNS challenge\n", std.Login)
}
if _, err := std.UnlockChallenge(CheckMap[tunnel_version][DNSDelegation], addr.String()); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
// Check HTTP with DNS
if glueErr != nil {
std.RegisterChallengeError(CheckMap[tunnel_version][HTTPonDelegatedDomain], fmt.Errorf("Unable to perform the test due to GLUE problem: %w", glueErr))
} else if err := check_http(addr.String(), std.MyDelegatedDomain()); err == nil {
if tunnel_version == 3 {
// Try port 80 on miniflux => should not respond if DNAT is correctly configured
minifluxIP := tun.GetServerIP(6)
if err := check_http(minifluxIP, std.MyDelegatedDomain()); err == nil {
if verbose {
log.Printf("%s and HTTP (with DNS ip=%s): %s\n", std.Login, addr.String(), "Bad DNAT config")
}
if errreg := std.RegisterChallengeError(CheckMap[tunnel_version][HTTPonDelegatedDomain], fmt.Errorf("Your DNAT on IPv6 is badly configured. See HTTP IP result.")); errreg != nil {
log.Printf("Unable to register challenge error for %s: %s\n", std.Login, errreg)
}
} else {
if verbose {
log.Printf("%s just unlocked HTTP challenge\n", std.Login)
}
if _, err := std.UnlockChallenge(CheckMap[tunnel_version][HTTPonDelegatedDomain], ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
}
} else {
if verbose {
log.Printf("%s just unlocked HTTP challenge\n", std.Login)
}
if _, err := std.UnlockChallenge(CheckMap[tunnel_version][HTTPonDelegatedDomain], ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
}
} else {
std.RegisterChallengeError(CheckMap[tunnel_version][HTTPonDelegatedDomain], err)
if verbose {
log.Printf("%s and HTTP (with DNS ip=%s): %s\n", std.Login, addr.String(), err)
}
}
// Check HTTPs with DNS
if glueErr != nil {
std.RegisterChallengeError(CheckMap[tunnel_version][HTTPSonDelegatedDomain], fmt.Errorf("Unable to perform the test due to GLUE problem: %w", glueErr))
} else if err := check_https(std.MyDelegatedDomain()); err == nil {
snicheck1 = true
snicheck1_tested = true
if verbose {
log.Printf("%s just unlocked HTTPS challenge\n", std.Login)
}
if _, err := std.UnlockChallenge(CheckMap[tunnel_version][HTTPSonDelegatedDomain], ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
} else {
snicheck1_tested = true
std.RegisterChallengeError(CheckMap[tunnel_version][HTTPSonDelegatedDomain], err)
if verbose {
log.Printf("%s and HTTPS (with DNS ip=%s): %s\n", std.Login, addr.String(), err)
}
}
// Check Matrix (only if GLUE Ok and defer contraint)
if glueErr == nil && also_check_matrix {
if has_test(CheckMap[tunnel_version], MatrixSrv) {
// Check Matrix Federation first
if v, err := check_matrix_federation(std.MyDelegatedDomain()); err == nil {
if verbose {
log.Printf("%s just unlocked Matrix federation challenge\n", std.Login)
}
if _, err := std.UnlockChallenge(CheckMap[tunnel_version][MatrixSrv], v); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
} else {
std.RegisterChallengeError(CheckMap[tunnel_version][MatrixSrv], err)
if verbose {
log.Printf("%s and Matrix federation: %s\n", std.Login, err)
}
}
}
// Check Matrix Client
if has_test(CheckMap[tunnel_version], MatrixClt) {
if v, err := check_matrix_client(std.MyDelegatedDomain()); err == nil {
if verbose {
log.Printf("%s just unlocked Matrix client challenge\n", std.Login)
}
if _, err := std.UnlockChallenge(CheckMap[tunnel_version][MatrixClt], v); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
} else {
std.RegisterChallengeError(CheckMap[tunnel_version][MatrixClt], err)
if verbose {
log.Printf("%s and Matrix client: %s\n", std.Login, err)
}
}
}
}
// Check DNSSEC (only if GLUE Ok)
if glueErr == nil {
if err := check_dnssec(std.MyDelegatedDomain(), dnsIP); err == nil {
if verbose {
log.Printf("%s just unlocked DNSSEC challenge\n", std.Login)
}
if _, err := std.UnlockChallenge(CheckMap[tunnel_version][DNSSEC], ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
} else {
std.RegisterChallengeError(CheckMap[tunnel_version][DNSSEC], err)
if verbose {
log.Printf("%s and DNSSEC: %s\n", std.Login, err)
}
}
}
}
} else {
if errreg := std.RegisterChallengeError(CheckMap[tunnel_version][DNSDelegation], err); errreg != nil {
log.Printf("Unable to register challenge error for %s: %s\n", std.Login, errreg)
}
if verbose {
log.Printf("%s and DNS: %s\n", std.Login, err)
}
}
// Check HTTP without DNS
if err := check_http(stdIP, ""); err == nil {
if tunnel_version == 3 {
// Try port 80 on miniflux => should not respond if DNAT is correctly configured
minifluxIP := tun.GetServerIP(6)
if err := check_http(minifluxIP, ""); err == nil {
if verbose {
log.Printf("%s and HTTP IP (without DNS): %s\n", std.Login, "Bad DNAT config")
}
if errreg := std.RegisterChallengeError(CheckMap[tunnel_version][HTTPonIP], fmt.Errorf("Your DNAT on IPv6 is too large: it seems that all requests to port 80 behind the router are redirected to web host. Eg. [news]:80 should not respond, however it responds with contents.")); errreg != nil {
log.Printf("Unable to register challenge error for %s: %s\n", std.Login, errreg)
}
} else {
if verbose {
log.Printf("%s just unlocked HTTP IP (without DNS) challenge\n", std.Login)
}
if _, err := std.UnlockChallenge(CheckMap[tunnel_version][HTTPonIP], ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
}
} else {
if verbose {
log.Printf("%s just unlocked HTTP IP (without DNS) challenge\n", std.Login)
}
if _, err := std.UnlockChallenge(CheckMap[tunnel_version][HTTPonIP], ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
}
} else {
std.RegisterChallengeError(CheckMap[tunnel_version][HTTPonIP], err)
if verbose {
log.Printf("%s and HTTP IP (without DNS): %s\n", std.Login, err)
}
}
// Check DNS for association
if addr, err := check_dns_udp(std.MyAssociatedDomain(), DEFAULT_RESOLVER); err == nil {
// Check HTTP on delegated domain
if err := check_http(addr.String(), std.MyAssociatedDomain()); err == nil {
if verbose {
log.Printf("%s just unlocked HTTP (without DNS) challenge\n", std.Login)
}
if _, err := std.UnlockChallenge(CheckMap[tunnel_version][HTTPonAssociatedDomain], ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
} else {
std.RegisterChallengeError(CheckMap[tunnel_version][HTTPonAssociatedDomain], err)
if verbose {
log.Printf("%s and HTTP (without DNS): %s\n", std.Login, err)
}
}
// Check HTTPs without DNS
if err := check_https(std.MyAssociatedDomain()); err == nil {
if verbose {
log.Printf("%s just unlocked HTTPS challenge\n", std.Login)
}
if _, err := std.UnlockChallenge(CheckMap[tunnel_version][HTTPSonAssociatedDomain], ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
// SNI check: validate if this check + HTTPS on delegation is validated
if snicheck1 {
if std.MyAssociatedDomain() == std.MyDelegatedDomain() {
std.RegisterChallengeError(CheckMap[tunnel_version][HTTPSSNI], fmt.Errorf("associated and delegated domains have to be different. Please use eg. adlin.example.com as associated domain and wonderfulwebsite.example.com as delegation. Feel free to choose whatever you want that doesn't already exists in your zone!"))
if verbose {
log.Printf("%s and HTTPS-SNI: %s\n", std.Login, "associated and delegated domains not accessible at the same time through HTTPS")
}
} else {
if verbose {
log.Printf("%s just unlocked HTTPS-SNI challenge\n", std.Login)
}
if _, err := std.UnlockChallenge(CheckMap[tunnel_version][HTTPSSNI], ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
}
}
}
} else {
std.RegisterChallengeError(CheckMap[tunnel_version][HTTPSonAssociatedDomain], err)
if verbose {
log.Printf("%s and HTTPS (without DNS): %s\n", std.Login, err)
}
}
}
if snicheck1_tested && !snicheck1 {
std.RegisterChallengeError(CheckMap[tunnel_version][HTTPSSNI], fmt.Errorf("associated and delegated domain are not accessible through HTTPS at the same time, see errors for thoses checks"))
if verbose {
log.Printf("%s and HTTPS-SNI: %s\n", std.Login, "associated and delegated domains not accessible at the same time through HTTPS")
}
}
return
}, func(stats *ping.Statistics) {
if verbose2 {
log.Printf("%s: %s ping statistics: %d packets transmitted, %d packets received, %v%% packet loss", std.Login, stdIP, stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss)
}
if stats.PacketsRecv > 0 {
return
}
tunnel_version, err := minTunnelVersion(std, tun.SuffixIP)
if err != nil {
return
}
if !has_test(CheckMap[tunnel_version], Firewalled) {
return
}
if e, err := std.GetChallengeError(CheckMap[tunnel_version][Firewalled]); err == nil && len(e.Error) > 0 {
std.RegisterChallengeError(CheckMap[tunnel_version][Firewalled], fmt.Errorf("Your host is unreachable, no more tests are performed. Check that you are not filtering the tunnel (Wireguard uses UDP datagrams), nor ICMPv6 as your host needs to respond to PING."))
}
})
if err != nil && verbose {
log.Printf("%s: Unable to perform ping to %s: %s", std.Login, stdIP, err.Error())
}
}
}
func studentsChecker(offline bool) {
students, err := adlin.GetStudents()
if err != nil {
log.Println("Unable to check students:", err)
return
}
check_matrix_for := (time.Now().Second()/30)*5 + time.Now().Minute()%5
log.Printf("Checking students... (std_matrix%%10=%d)\n", check_matrix_for)
for istd, s := range students {
time.Sleep(250 * time.Millisecond)
go studentChecker(s, istd%10 == check_matrix_for, offline)
}
}