2021-03-22 00:08:39 +00:00
package main
2022-04-29 20:54:26 +00:00
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"strings"
"time"
"github.com/go-ping/ping"
"github.com/miekg/dns"
"git.nemunai.re/srs/adlin/libadlin"
)
2021-03-22 00:08:39 +00:00
type AdlinTest int
const (
HTTPonIP AdlinTest = iota
HTTPonAssociatedDomain
HTTPSonAssociatedDomain
DNSDelegation
HTTPonDelegatedDomain
HTTPSonDelegatedDomain
2022-03-08 12:15:41 +00:00
HTTPSSNI
2021-10-31 16:30:29 +00:00
MatrixSrv
MatrixClt
2021-03-22 00:08:39 +00:00
SNI
DNSSEC
PingResolver
DHCPonRH
2022-04-14 11:55:39 +00:00
DHCPonDG
DHCPonCM
2021-03-22 00:08:39 +00:00
DHCPonGuests
RHaccessNews
RHaccessNet
GuestNet
)
var CheckMap = map [ int ] map [ AdlinTest ] int {
2 : map [ AdlinTest ] int {
HTTPonIP : 100 ,
HTTPonAssociatedDomain : 101 ,
HTTPSonAssociatedDomain : 102 ,
DNSDelegation : 103 ,
HTTPonDelegatedDomain : 104 ,
HTTPSonDelegatedDomain : 105 ,
2022-03-08 12:15:41 +00:00
HTTPSSNI : 106 ,
MatrixSrv : 107 ,
MatrixClt : 108 ,
DNSSEC : 109 ,
2021-03-22 00:08:39 +00:00
} ,
3 : map [ AdlinTest ] int {
PingResolver : 200 ,
HTTPonIP : 201 ,
DNSDelegation : 203 ,
HTTPonDelegatedDomain : 204 ,
HTTPSonDelegatedDomain : 205 ,
2021-10-31 16:30:29 +00:00
MatrixSrv : 206 ,
MatrixClt : 207 ,
2022-04-14 11:55:39 +00:00
DHCPonRH : 208 ,
DHCPonDG : 209 ,
DHCPonCM : 210 ,
DHCPonGuests : 213 ,
RHaccessNews : 211 ,
RHaccessNet : 212 ,
GuestNet : 214 ,
2021-03-22 00:08:39 +00:00
} ,
}
2022-04-29 20:54:26 +00:00
// 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 {
if verbose {
log . Printf ( "check_ping: %s: %s" , ip , err . Error ( ) )
}
return
}
defer pinger . Stop ( )
pinger . Timeout = time . Second * 5
pinger . Count = 1
pinger . OnRecv = cb
pinger . SetPrivileged ( true )
err = pinger . Run ( )
return
}
// PORT 53
func get_GLUE ( student * adlin . Student ) ( aaaa net . IP , err error ) {
client := dns . Client { Net : "tcp" , Timeout : time . Second * 5 }
domain := student . MyDelegatedDomain ( )
dnssrv := "[2a01:e0a:2b:2250::b]:53"
if strings . HasSuffix ( domain , student . MyDelegatedDomainSuffix ( ) ) {
dnssrv = "[2a01:e0a:2b:2250::b]:53"
} else if v , ok := domainsHostingMap [ domain ] ; ok {
dnssrv = v
} else {
// Looking for root NS
m := new ( dns . Msg )
m . SetQuestion ( "." , dns . TypeNS )
m . RecursionDesired = false
m . SetEdns0 ( 4096 , true )
var r * dns . Msg
r , _ , err = client . Exchange ( m , dnssrv )
if err != nil {
return
}
if r == nil {
return nil , errors . New ( "response is nil during initial recursion" )
}
if r . Rcode != dns . RcodeSuccess {
return nil , errors . New ( "failed to get a valid answer during initial recursion" )
}
for _ , answer := range r . Answer {
if t , ok := answer . ( * dns . NS ) ; ok {
dnssrv = t . Ns + ":53"
}
}
// Do casual recursion
i := 0
recursion :
for i = 0 ; i < 10 ; i ++ {
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 , dnssrv )
if err != nil {
return
}
if r == nil {
return nil , errors . New ( "response is nil during recursion" )
}
if r . Rcode != dns . RcodeSuccess {
return nil , errors . New ( "failed to get a valid answer during recursion" )
}
for _ , answer := range r . Ns {
if t , ok := answer . ( * dns . NS ) ; ok {
dnssrv = t . Ns + ":53"
if t . Header ( ) . Name == domain {
break recursion
}
}
}
}
if i >= 10 {
return nil , fmt . Errorf ( "too much name recursions" )
} else {
domainsHostingMap [ domain ] = dnssrv
}
}
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 , dnssrv )
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" )
return
}
if r . Rcode != dns . RcodeSuccess {
err = errors . New ( "failed to get a valid answer" )
return
}
for _ , answer := range r . Answer {
if t , ok := answer . ( * dns . AAAA ) ; ok {
aaaa = t . AAAA
}
}
return
}
func check_dnssec ( domain , ip string ) ( err error ) {
client := dns . Client { Net : "tcp" , Timeout : time . Second * 10 }
// Get DNSKEY
m := new ( dns . Msg )
m . SetEdns0 ( 4096 , true )
m . SetQuestion ( domain , dns . TypeDNSKEY )
var r * dns . Msg
r , _ , err = client . Exchange ( m , fmt . Sprintf ( "[%s]:53" , ip ) )
if err != nil {
return
}
if r == nil {
return errors . New ( "response is nil" )
}
if r . Rcode != dns . RcodeSuccess {
return errors . New ( "failed to get a valid answer when getting DNSKEY" )
}
var rrs [ ] dns . RR
var dnskeys [ ] * dns . DNSKEY
var dnskeysig * dns . RRSIG
for _ , answer := range r . Answer {
if t , ok := answer . ( * dns . DNSKEY ) ; ok {
dnskeys = append ( dnskeys , t )
rrs = append ( rrs , dns . RR ( t ) )
} else if t , ok := answer . ( * dns . RRSIG ) ; ok {
dnskeysig = t
}
}
if dnskeysig == nil {
return fmt . Errorf ( "Unable to verify DNSKEY record signature: No RRSIG found for DNSKEY record." )
}
found := false
for _ , dnskey := range dnskeys {
if err = dnskeysig . Verify ( dnskey , rrs ) ; err == nil {
found = true
break
}
}
if ! found {
return fmt . Errorf ( "Unable to verify DNSKEY record signature: %w" , err )
}
// Check AAAA validity
m = new ( dns . Msg )
m . SetEdns0 ( 4096 , true )
m . SetQuestion ( domain , dns . TypeAAAA )
r , _ , err = client . Exchange ( m , fmt . Sprintf ( "[%s]:53" , ip ) )
if err != nil {
return
}
if r == nil {
return errors . New ( "response is nil" )
}
if r . Rcode != dns . RcodeSuccess {
return errors . New ( "failed to get a valid answer when getting AAAA records" )
}
rrs = [ ] dns . RR { }
var aaaas [ ] * dns . AAAA
var aaaasig * dns . RRSIG
for _ , answer := range r . Answer {
if t , ok := answer . ( * dns . AAAA ) ; ok {
aaaas = append ( aaaas , t )
rrs = append ( rrs , t )
} else if t , ok := answer . ( * dns . RRSIG ) ; ok {
aaaasig = t
}
}
if len ( aaaas ) == 0 {
return errors . New ( "Something odd happen: no AAAA record found." )
}
if aaaasig == nil {
return fmt . Errorf ( "Unable to verify AAAA record signature: No RRSIG found for AAAA record." )
}
found = false
for _ , dnskey := range dnskeys {
if err = aaaasig . Verify ( dnskey , rrs ) ; err == nil {
found = true
if ! aaaasig . ValidityPeriod ( time . Now ( ) ) {
utc := time . Now ( ) . UTC ( ) . Unix ( )
modi := ( int64 ( aaaasig . Inception ) - utc ) / year68
ti := int64 ( aaaasig . Inception ) + modi * year68
mode := ( int64 ( aaaasig . Expiration ) - utc ) / year68
te := int64 ( aaaasig . Expiration ) + mode * year68
if ti > utc {
return fmt . Errorf ( "Unable to verify AAAA record signature: signature not yet valid" )
} else if utc > te {
return fmt . Errorf ( "Unable to verify AAAA record signature: signature expired" )
} else {
return fmt . Errorf ( "Unable to verify AAAA record signature: signature expired or not yet valid" )
}
}
break
}
}
if ! found {
return fmt . Errorf ( "Unable to verify AAAA record signature: %w" , err )
}
// Check DS
m = new ( dns . Msg )
m . SetQuestion ( domain , dns . TypeDS )
m . RecursionDesired = false
m . SetEdns0 ( 4096 , true )
r , _ , err = client . Exchange ( m , "[2a01:e0a:2b:2250::b]:53" )
if err != nil {
return
}
if r == nil {
return errors . New ( "response is nil" )
}
if r . Rcode != dns . RcodeSuccess {
return errors . New ( "failed to get a valid answer when getting DS records in parent server" )
}
found = false
for _ , answer := range r . Answer {
if t , ok := answer . ( * dns . DS ) ; ok {
for _ , dnskey := range dnskeys {
expectedDS := dnskey . ToDS ( dns . SHA256 )
if expectedDS . KeyTag == t . KeyTag && expectedDS . Algorithm == t . Algorithm && expectedDS . DigestType == t . DigestType && expectedDS . Digest == t . Digest {
found = true
err = nil
break
} else {
err = fmt . Errorf ( "DS record found in parent zone differs from DNSKEY %v vs. %v." , expectedDS , t )
}
}
}
}
if ! found {
if err == nil {
return fmt . Errorf ( "Unable to find a valid DS record in parent zone (if you use your own domain (ie. not given by maatma), this can be due to a previously cached response, you should wait)." )
} else {
return err
}
}
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
2022-04-30 00:35:34 +00:00
func check_https ( domain string ) ( err error ) {
2022-04-29 20:54:26 +00:00
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 )
2022-04-30 00:35:34 +00:00
} else if err = check_https ( dns . Fqdn ( loc ) ) ; err != nil {
2022-04-29 20:54:26 +00:00
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
}
// MATRIX
type matrix_result struct {
WellKnownResult struct {
Server string ` json:"m.server" `
Result string ` json:"result" `
}
DNSResult struct {
SRVError * struct {
Message string
}
}
ConnectionReports map [ string ] struct {
Errors [ ] string
}
ConnectionErrors map [ string ] struct {
Message string
}
Version struct {
Name string ` json:"name" `
Version string ` json:"version" `
}
FederationOK bool ` json:"FederationOK" `
}
func check_matrix_federation ( domain string ) ( version string , err error ) {
var resp * http . Response
resp , err = http . Get ( fmt . Sprintf ( "https://federation-tester.adlin.nemunai.re/api/report?server_name=%s" , strings . TrimSuffix ( domain , "." ) ) )
if err != nil {
return
}
defer resp . Body . Close ( )
if resp . StatusCode >= 300 {
return "" , fmt . Errorf ( "Sorry, the federation tester is broken. Check on https://federationtester.matrix.org/#%s" , strings . TrimSuffix ( domain , "." ) )
}
var federationTest matrix_result
if err = json . NewDecoder ( resp . Body ) . Decode ( & federationTest ) ; err != nil {
log . Printf ( "Error in check_matrix_federation, when decoding json: %s" , err . Error ( ) )
return "" , fmt . Errorf ( "Sorry, the federation tester is broken. Check on https://federationtester.matrix.org/#%s" , strings . TrimSuffix ( domain , "." ) )
} else if federationTest . FederationOK {
version = federationTest . Version . Name + " " + federationTest . Version . Version
return version , nil
} else if federationTest . DNSResult . SRVError != nil && federationTest . WellKnownResult . Result != "" {
return "" , fmt . Errorf ( "%s OR %s" , federationTest . DNSResult . SRVError . Message , federationTest . WellKnownResult . Result )
} else if len ( federationTest . ConnectionErrors ) > 0 {
var msg strings . Builder
for srv , cerr := range federationTest . ConnectionErrors {
if msg . Len ( ) > 0 {
msg . WriteString ( "; " )
}
msg . WriteString ( srv )
msg . WriteString ( ": " )
msg . WriteString ( cerr . Message )
}
return "" , fmt . Errorf ( "Connection errors: %s" , msg . String ( ) )
} else if federationTest . WellKnownResult . Server != strings . TrimSuffix ( domain , "." ) {
return "" , fmt . Errorf ( "Bad homeserver_name: got %s, expected %s." , federationTest . WellKnownResult . Server , strings . TrimSuffix ( domain , "." ) )
} else {
return "" , fmt . Errorf ( "An unimplemented error occurs. Please report to nemunaire. But know that federation seems to be broken. Check https://federationtester.matrix.org/#%s" , strings . TrimSuffix ( domain , "." ) )
}
}
type matrix_wk_client struct {
Homeserver struct {
BaseURL string ` json:"base_url" `
} ` json:"m.homeserver" `
IdentityServer struct {
BaseURL string ` json:"base_url" `
} ` json:"m.identity_server" `
}
type matrix_client_versions struct {
Versions [ ] string ` json:"versions" `
UnstableFeatures map [ string ] bool ` json:"unstable_features" `
}
func check_matrix_client ( domain string ) ( version string , err error ) {
var resp * http . Response
resp , err = http . Get ( fmt . Sprintf ( "https://%s/.well-known/matrix/client" , strings . TrimSuffix ( domain , "." ) ) )
if err != nil {
return
}
defer resp . Body . Close ( )
var HomeserverBase = fmt . Sprintf ( "https://%s" , strings . TrimSuffix ( domain , "." ) )
if resp . StatusCode < 300 {
var wellknown matrix_wk_client
if err = json . NewDecoder ( resp . Body ) . Decode ( & wellknown ) ; err != nil {
log . Printf ( "Error in check_matrix_client, when decoding json: %s" , err . Error ( ) )
return "" , fmt . Errorf ( "File at https://%s/.well-known/matrix/client is invalid: JSON parse error" , strings . TrimSuffix ( domain , "." ) )
} else if wellknown . Homeserver . BaseURL != "" {
if baseurl , err := url . Parse ( wellknown . Homeserver . BaseURL ) ; err != nil {
return "" , fmt . Errorf ( "File at https://%s/.well-known/matrix/client is invalid: Bad homeserver URL: %s" , strings . TrimSuffix ( domain , "." ) , err . Error ( ) )
} else if ! strings . HasSuffix ( strings . TrimSuffix ( baseurl . Host , "." ) , strings . TrimSuffix ( domain , "." ) ) {
return "" , fmt . Errorf ( "Your homeserver base_url is not under %s" , strings . TrimSuffix ( domain , "." ) )
} else if strings . TrimSuffix ( baseurl . Host , "." ) == strings . TrimSuffix ( domain , "." ) {
// This test can be optional
return "" , fmt . Errorf ( "Your homeserver should be on its own subdomain" )
} else {
HomeserverBase = wellknown . Homeserver . BaseURL
}
}
}
var resp2 * http . Response
resp2 , err = http . Get ( fmt . Sprintf ( "%s/_matrix/client/versions" , HomeserverBase ) )
if err != nil {
return
}
defer resp2 . Body . Close ( )
if resp2 . StatusCode != http . StatusOK {
return "" , fmt . Errorf ( "Unable to fetch your homeserver versions at %s/_matrix/client/versions: %s" , HomeserverBase , resp2 . Status )
}
var clientTest matrix_client_versions
if err = json . NewDecoder ( resp2 . Body ) . Decode ( & clientTest ) ; err != nil {
log . Printf ( "Error in check_matrix_client, when decoding versions json: %s" , err . Error ( ) )
return "" , fmt . Errorf ( "File at %s/_matrix/client/versions is invalid: JSON parse error: %s" , HomeserverBase , err . Error ( ) )
} else if len ( clientTest . Versions ) == 0 {
return "" , fmt . Errorf ( "File at %s/_matrix/client/versions is invalid: no protocol version supported" , HomeserverBase )
} else {
return clientTest . Versions [ len ( clientTest . Versions ) - 1 ] , nil
}
}