337 lines
8.5 KiB
Go
337 lines
8.5 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/miekg/dns"
|
|
"github.com/sparrc/go-ping"
|
|
|
|
"git.nemunai.re/lectures/adlin/libadlin"
|
|
)
|
|
|
|
const (
|
|
DEFAULT_RESOLVER = "2a01:e0a:2b:2250::1"
|
|
)
|
|
|
|
var verbose = false
|
|
|
|
// ICMP
|
|
|
|
func check_ping(ip string, cb func(pkt *ping.Packet)) (err error) {
|
|
var pinger *ping.Pinger
|
|
pinger, err = ping.NewPinger(ip)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer pinger.Stop()
|
|
|
|
pinger.Timeout = time.Second * 5
|
|
pinger.Count = 1
|
|
pinger.OnRecv = cb
|
|
pinger.SetPrivileged(true)
|
|
pinger.Run()
|
|
|
|
return
|
|
}
|
|
|
|
// PORT 53
|
|
|
|
func get_GLUE(domain string) (aaaa net.IP, err error) {
|
|
client := dns.Client{Net: "tcp", Timeout: time.Second * 5}
|
|
|
|
m := new(dns.Msg)
|
|
m.SetQuestion(domain, dns.TypeNS)
|
|
m.RecursionDesired = false
|
|
m.SetEdns0(4096, true)
|
|
|
|
var r *dns.Msg
|
|
r, _, err = client.Exchange(m, "[2a01:e0a:2b:2250::b]:53")
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if r == nil {
|
|
return nil, errors.New("response is nil")
|
|
}
|
|
if r.Rcode != dns.RcodeSuccess {
|
|
return nil, errors.New("failed to get a valid answer")
|
|
}
|
|
|
|
for _, extra := range r.Extra {
|
|
if t, ok := extra.(*dns.AAAA); ok {
|
|
aaaa = t.AAAA
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func check_dns(domain, ip string) (aaaa net.IP, err error) {
|
|
client := dns.Client{Timeout: time.Second * 5}
|
|
|
|
m := new(dns.Msg)
|
|
m.SetQuestion(domain, dns.TypeAAAA)
|
|
|
|
var r *dns.Msg
|
|
r, _, err = client.Exchange(m, fmt.Sprintf("[%s]:53", ip))
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if r == nil {
|
|
err = errors.New("response is nil")
|
|
}
|
|
if r.Rcode != dns.RcodeSuccess {
|
|
err = errors.New("failed to get a valid answer")
|
|
}
|
|
|
|
for _, answer := range r.Answer {
|
|
if t, ok := answer.(*dns.AAAA); ok {
|
|
aaaa = t.AAAA
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// PORT 80
|
|
|
|
func check_http(ip, dn string) (err error) {
|
|
client := &http.Client{
|
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
return http.ErrUseLastResponse
|
|
},
|
|
}
|
|
|
|
req, errr := http.NewRequest("GET", fmt.Sprintf("http://[%s]/", ip), nil)
|
|
if errr != nil {
|
|
return errr
|
|
}
|
|
|
|
if dn != "" {
|
|
req.Header.Add("Host", strings.TrimSuffix(dn, "."))
|
|
}
|
|
|
|
var resp *http.Response
|
|
resp, err = client.Do(req)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if dn != "" && resp.StatusCode >= 400 {
|
|
return fmt.Errorf("Bad status, got: %d (%s)", resp.StatusCode, resp.Status)
|
|
}
|
|
|
|
_, err = ioutil.ReadAll(resp.Body)
|
|
return
|
|
}
|
|
|
|
// PORT 443
|
|
|
|
func check_https(domain, ip string) (err error) {
|
|
var resp *http.Response
|
|
resp, err = http.Get(fmt.Sprintf("https://%s/", strings.TrimSuffix(domain, ".")))
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode >= 300 && resp.StatusCode < 400 {
|
|
loc := resp.Header.Get("Location")
|
|
if loc != "" && strings.HasSuffix(dns.Fqdn(loc), domain) {
|
|
if dns.Fqdn(loc) == domain {
|
|
return fmt.Errorf("Redirection loop %s redirect to %s", domain, loc)
|
|
} else if err = check_https(dns.Fqdn(loc), ip); err != nil {
|
|
return fmt.Errorf("Error after following redirection to %s: %w", loc, err)
|
|
} else {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
if resp.StatusCode >= 300 {
|
|
return fmt.Errorf("Bad status, got: %d (%s)", resp.StatusCode, resp.Status)
|
|
}
|
|
|
|
_, err = ioutil.ReadAll(resp.Body)
|
|
return
|
|
}
|
|
|
|
// 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 studentsChecker() {
|
|
students, err := adlin.GetStudents()
|
|
if err != nil {
|
|
log.Println("Unable to check students:", err)
|
|
return
|
|
}
|
|
log.Println("Checking students...")
|
|
|
|
for _, s := range students {
|
|
time.Sleep(250 * time.Millisecond)
|
|
// Check ping
|
|
std := s
|
|
tuns, err := std.GetActivesTunnels()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
for _, tun := range tuns {
|
|
stdIP := tun.GetStudentIP()
|
|
go 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 tunnel_version == 2147483647 || tunnel_version == 0 {
|
|
log.Printf("%s unknown tunnel version: %d skipping tests (%v)", std.Login, tunnel_version, err)
|
|
return
|
|
}
|
|
|
|
dnsIP := stdIP
|
|
// Is GLUE defined?
|
|
if glueIP, err := get_GLUE(std.MyDelegatedDomain()); 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)
|
|
}
|
|
|
|
// Check DNS
|
|
if addr, err := check_dns(std.MyDelegatedDomain(), dnsIP); err == nil {
|
|
if verbose {
|
|
log.Printf("%s just unlocked DNS challenge\n", std.Login)
|
|
}
|
|
if _, err := std.UnlockChallenge(100*(tunnel_version-1)+3, ""); err != nil {
|
|
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
|
|
}
|
|
|
|
// Check HTTP with DNS
|
|
if addr == nil {
|
|
log.Printf("%s and HTTP (with DNS ip=%s): skipped due to empty response\n", std.Login, addr.String())
|
|
} else if err := check_http(addr.String(), std.MyDelegatedDomain()); err == nil {
|
|
if verbose {
|
|
log.Printf("%s just unlocked HTTP challenge\n", std.Login)
|
|
}
|
|
if _, err := std.UnlockChallenge(100*(tunnel_version-1)+4, ""); err != nil {
|
|
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
|
|
}
|
|
} else {
|
|
std.RegisterChallengeError(100*(tunnel_version-1)+4, err)
|
|
if verbose {
|
|
log.Printf("%s and HTTP (with DNS ip=%s): %s\n", std.Login, addr.String(), err)
|
|
}
|
|
}
|
|
|
|
// Check HTTPs with DNS
|
|
if addr == nil {
|
|
log.Printf("%s and HTTPS (with DNS ip=%s): skipped due to empty response\n", std.Login, addr.String())
|
|
} else if err := check_https(std.MyDelegatedDomain(), addr.String()); err == nil {
|
|
if verbose {
|
|
log.Printf("%s just unlocked HTTPS challenge\n", std.Login)
|
|
}
|
|
if _, err := std.UnlockChallenge(100*(tunnel_version-1)+5, ""); err != nil {
|
|
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
|
|
}
|
|
} else {
|
|
std.RegisterChallengeError(100*(tunnel_version-1)+5, err)
|
|
if verbose {
|
|
log.Printf("%s and HTTPS (with DNS ip=%s): %s\n", std.Login, addr.String(), err)
|
|
}
|
|
}
|
|
} else {
|
|
if errreg := std.RegisterChallengeError(100*(tunnel_version-1)+3, 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 verbose {
|
|
log.Printf("%s just unlocked HTTP IP (without DNS) challenge\n", std.Login)
|
|
}
|
|
if _, err := std.UnlockChallenge(100*(tunnel_version-1)+0, ""); err != nil {
|
|
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
|
|
}
|
|
} else {
|
|
std.RegisterChallengeError(100*(tunnel_version-1)+0, 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(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(100*(tunnel_version-1)+1, ""); err != nil {
|
|
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
|
|
}
|
|
} else {
|
|
std.RegisterChallengeError(100*(tunnel_version-1)+1, 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(), stdIP); err == nil {
|
|
if verbose {
|
|
log.Printf("%s just unlocked HTTPS challenge\n", std.Login)
|
|
}
|
|
if _, err := std.UnlockChallenge(100*(tunnel_version-1)+2, ""); err != nil {
|
|
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())
|
|
}
|
|
} else {
|
|
std.RegisterChallengeError(100*(tunnel_version-1)+2, err)
|
|
if verbose {
|
|
log.Printf("%s and HTTPS (without DNS): %s\n", std.Login, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
})
|
|
}
|
|
}
|
|
}
|