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