diff --git a/Makefile b/Makefile index 278134f..1359610 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,10 @@ pkg/wg-manager: pkg/wg-manager/cmd/register.go pkg/wg-manager/cmd/main.go pkg/wg server.iso: server.yml students.csv ssl/fullchain.pem ssl/privkey.pem challenge-initrd.img pkg/arp-spoofer pkg/login-validator pkg/monit pkg/postfix pkg/tftpd pkg/unbound pkg/wg-manager challenge-kernel login-initrd.img linuxkit build -docker -format iso-bios $< +pkg/debian-tuto2: pkg/debian-tuto2/sshd_config pkg/debian-tuto2/gai.conf pkg/debian-tuto2/isolinux.cfg pkg/debian-tuto2/build.yml pkg/debian-tuto2/default.script pkg/debian-tuto2/issue pkg/debian-tuto2/Dockerfile + linuxkit pkg build -org nemunaire pkg/debian-tuto2/ + touch pkg/debian-tuto2 + tuto2-kernel: tuto2.yml linuxkit build -docker $< tuto2-initrd.img: tuto2.yml @@ -57,7 +61,7 @@ tuto2-initrd.img: tuto2.yml tuto2-cmdline: tuto2.yml linuxkit build -docker $< -tuto2.iso: tuto2.yml tuto2-kernel tuto2-initrd.img tuto2-cmdline +tuto2.iso: tuto2.yml pkg/debian-tuto2 tuto2-kernel tuto2-initrd.img tuto2-cmdline linuxkit build -docker -format iso-bios $< tuto2-srs.iso: tuto2.iso pkg/debian-tuto2/isolinux.cfg diff --git a/checker/checker.go b/checker/checker.go index ddddaad..5d510aa 100644 --- a/checker/checker.go +++ b/checker/checker.go @@ -1,12 +1,14 @@ package main import ( + "encoding/json" "errors" "fmt" "io/ioutil" "log" "net" "net/http" + "strings" "time" "github.com/miekg/dns" @@ -15,6 +17,10 @@ import ( "git.nemunai.re/lectures/adlin/libadlin" ) +const ( + DEFAULT_RESOLVER = "2a01:e0a:2b:2250::1" +) + var verbose = false // ICMP @@ -25,6 +31,7 @@ func check_ping(ip string, cb func(pkt *ping.Packet)) (err error) { if err != nil { return } + defer pinger.Stop() pinger.Timeout = time.Second * 5 pinger.Count = 1 @@ -46,7 +53,7 @@ func get_GLUE(domain string) (aaaa net.IP, err error) { m.SetEdns0(4096, true) var r *dns.Msg - r, _, err = client.Exchange(m, "[2a01:e0a:25a:9160::2]:53") + r, _, err = client.Exchange(m, "[2a01:e0a:2b:2250::b]:53") if err != nil { return } @@ -86,8 +93,10 @@ func check_dns(domain, ip string) (aaaa net.IP, err error) { err = errors.New("failed to get a valid answer") } - if len(r.Answer) > 0 { - aaaa = r.Answer[0].(*dns.AAAA).AAAA + for _, answer := range r.Answer { + if t, ok := answer.(*dns.AAAA); ok { + aaaa = t.AAAA + } } return @@ -95,19 +104,33 @@ func check_dns(domain, ip string) (aaaa net.IP, err error) { // PORT 80 -func check_http(ip string) (err error) { +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.Get(fmt.Sprintf("http://[%s]/", ip)) + 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 } @@ -116,18 +139,97 @@ func check_http(ip string) (err error) { func check_https(domain, ip string) (err error) { var resp *http.Response - resp, err = http.Get(fmt.Sprintf("https://%s/", domain)) + 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 } +// MATRIX + +type matrix_result struct { + WellKnownResult struct { + 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 +} + +func check_matrix(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", domain) + } + + var federationTest matrix_result + if federationTest.FederationOK { + version = federationTest.Version.Name + " " + federationTest.Version.Version + return version, nil + } else if err = json.NewDecoder(resp.Body).Decode(&federationTest); err != nil { + log.Printf("Error in chech_matrix, when decoding json:", err.Error()) + return "", fmt.Errorf("Sorry, the federation tester is broken. Check on https://federationtester.matrix.org/#%s", domain) + } 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 { + 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", domain) + } +} + // Main -func minTunnelVersion(std adlin.Student) (int, error) { +func minTunnelVersion(std *adlin.Student, suffixip int) (int, error) { tunnels, err := std.GetTunnelTokens() if err != nil { return 0, err @@ -139,7 +241,7 @@ func minTunnelVersion(std adlin.Student) (int, error) { continue } - if tunnel.Dump != nil && tunnel.Version < minversion { + if tunnel.Dump != nil && tunnel.Version < minversion && suffixip == tunnel.SuffixIP { minversion = tunnel.Version } } @@ -153,110 +255,175 @@ func studentsChecker() { log.Println("Unable to check students:", err) return } - log.Println("Checking students...") + check_matrix_for := (time.Now().Second()/30)*5 + time.Now().Minute()%5 - for _, s := range students { + log.Printf("Checking students... (std_matrix%%10=%d)\n", check_matrix_for) + + for istd, s := range students { time.Sleep(250 * time.Millisecond) // Check ping std := s - stdIP := adlin.StudentIP(std.Id).String() + "1" - go check_ping(stdIP, func(pkt *ping.Packet) { - tunnel_version, err := minTunnelVersion(std) - if verbose { - log.Printf("%s PONG; version=%d (%v)\n", std.Login, 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() + 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 has defined GLUE: %s\n", std.Login, dnsIP) + log.Printf("%s PONG (on %x); version=%d (%v)\n", std.Login, tun.SuffixIP, tunnel_version, err) } - } else if err != nil { - log.Printf("%s and GLUE: %s\n", std.Login, err) - } + std.OnPong(true) - // 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 tunnel_version == 2147483647 || tunnel_version == 0 { + log.Printf("%s unknown tunnel version: %d skipping tests (%v)", std.Login, tunnel_version, err) + return } - if _, err := std.UnlockNewChallenge(100*(tunnel_version-1)+2, ""); err != nil { - if _, err := std.UpdateUnlockedChallenge(100*(tunnel_version-1)+2, ""); err != nil { + + dnsIP := stdIP + var glueErr error + // 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) + glueErr = err + } + + // Check DNS + if addr, err := check_dns(std.MyDelegatedDomain(), dnsIP); err == nil { + if addr == nil { + dnsAt := " at " + dnsIP + if glueErr != nil { + dnsAt = " + there is a problem with the GLUE record: " + glueErr.Error() + } + if errreg := std.RegisterChallengeError(100*(tunnel_version-1)+3, fmt.Errorf("%s: empty response from the server%s", std.MyDelegatedDomain(), dnsAt)); 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(100*(tunnel_version-1)+3, 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(100*(tunnel_version-1)+4, fmt.Errorf("Unable to perform the test due to GLUE problem: %w", err)) + } 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 glueErr != nil { + std.RegisterChallengeError(100*(tunnel_version-1)+5, fmt.Errorf("Unable to perform the test due to GLUE problem: %w", err)) + } 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) + } + } + + // Check Matrix (only if GLUE Ok and) + if glueErr == nil && istd%10 == check_matrix_for { + if v, err := check_matrix(std.MyDelegatedDomain()); err == nil { + if verbose { + log.Printf("%s just unlocked Matrix challenge\n", std.Login) + } + if _, err := std.UnlockChallenge(100*(tunnel_version-1)+6, v); err != nil { + log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error()) + } + } else { + std.RegisterChallengeError(100*(tunnel_version-1)+6, err) + if verbose { + log.Printf("%s and Matrix: %s\n", std.Login, 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 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()); err == nil { - if verbose { - log.Printf("%s just unlocked HTTP challenge\n", std.Login) - } - if _, err := std.UnlockNewChallenge(100*(tunnel_version-1)+0, ""); err != nil { - if _, err := std.UpdateUnlockedChallenge(100*(tunnel_version-1)+0, ""); err != nil { + // 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) + } } - } else 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.UnlockNewChallenge(100*(tunnel_version-1)+1, ""); err != nil { - if _, err := std.UpdateUnlockedChallenge(100*(tunnel_version-1)+1, ""); err != nil { - log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error()) - } - } - } else if verbose { - log.Printf("%s and HTTPS (with DNS ip=%s): %s\n", std.Login, addr.String(), err) - } - } else { - // Check HTTP without DNS - if err := check_http(stdIP); err == nil { - if verbose { - log.Printf("%s just unlocked HTTP challenge\n", std.Login) - } - if _, err := std.UnlockNewChallenge(100*(tunnel_version-1)+0, ""); err != nil { - if _, err := std.UpdateUnlockedChallenge(100*(tunnel_version-1)+0, ""); err != nil { - log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error()) - } - } - } else 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.UnlockNewChallenge(100*(tunnel_version-1)+1, ""); err != nil { - if _, err := std.UpdateUnlockedChallenge(100*(tunnel_version-1)+1, ""); err != nil { - log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error()) - } - } - } else if verbose { - log.Printf("%s and HTTPS (without DNS): %s\n", std.Login, err) - } - } - - return - }) + return + }) + } } } diff --git a/libadlin/db.go b/libadlin/db.go index 44551f1..5fafcc9 100644 --- a/libadlin/db.go +++ b/libadlin/db.go @@ -56,7 +56,9 @@ func DBCreate() (err error) { CREATE TABLE IF NOT EXISTS students( id_student INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, login VARCHAR(255) NOT NULL UNIQUE, - time TIMESTAMP NOT NULL + time TIMESTAMP NOT NULL, + associatedDomain VARCHAR(255) UNIQUE, + delegatedDomain VARCHAR(255) UNIQUE ) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; `); err != nil { return @@ -93,6 +95,19 @@ CREATE TABLE IF NOT EXISTS student_challenges( CONSTRAINT token_found UNIQUE (id_student,challenge), FOREIGN KEY(id_student) REFERENCES students(id_student) ) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; +`); err != nil { + return err + } + if _, err := db.Exec(` +CREATE TABLE IF NOT EXISTS student_challenge_errors( + id_st INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + id_student INTEGER NOT NULL, + challenge INTEGER NOT NULL, + time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + error VARCHAR(255) NOT NULL, + CONSTRAINT token_found UNIQUE (id_student,challenge), + FOREIGN KEY(id_student) REFERENCES students(id_student) +) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; `); err != nil { return err } diff --git a/libadlin/domain.go b/libadlin/domain.go index 4a88dd2..4553012 100644 --- a/libadlin/domain.go +++ b/libadlin/domain.go @@ -6,22 +6,39 @@ import ( ) const ( - AssociatedDomainSuffix = "adlin2021.p0m.fr." + AssociatedDomainSuffix = "adlin2022.p0m.fr." DelegatedDomainSuffix = "srs.p0m.fr." ) -func (student Student) MyDelegatedDomain() string { - return fmt.Sprintf("%s.%s", strings.Trim(strings.Replace(student.Login, "_", "-", -1), "-_"), DelegatedDomainSuffix) +func (student *Student) MyDelegatedDomain() string { + if student.DelegatedDomain != nil { + return *student.DelegatedDomain + } else { + return fmt.Sprintf("%s.%s", strings.Trim(strings.Replace(student.Login, "_", "-", -1), "-_"), DelegatedDomainSuffix) + } } -func (student Student) MyAssociatedDomain() string { +func (student *Student) DefaultAssociatedDomain() string { return fmt.Sprintf("%s.%s", strings.Trim(strings.Replace(student.Login, "_", "-", -1), "-_"), AssociatedDomainSuffix) } -func (student Student) GetAssociatedDomains() (ds []string) { +func (student *Student) MyAssociatedDomain() string { + if student.AssociatedDomain != nil { + return *student.AssociatedDomain + } else { + return student.DefaultAssociatedDomain() + } +} + +func (student *Student) GetAssociatedDomains() (ds []string) { + defdn := student.DefaultAssociatedDomain() + ds = append(ds, defdn) + studentDomain := student.MyAssociatedDomain() - ds = append(ds, studentDomain) + if defdn != studentDomain { + ds = append(ds, studentDomain) + } return } diff --git a/libadlin/ping.go b/libadlin/ping.go index c8bef5a..a93fc9c 100644 --- a/libadlin/ping.go +++ b/libadlin/ping.go @@ -9,14 +9,14 @@ type Pong struct { State bool } -func (s Student) LastPongs() (pongs []Pong, err error) { +func (s *Student) LastPongs() (pongs []*Pong, err error) { if rows, errr := DBQuery("SELECT time, state FROM student_pong WHERE id_student = ? ORDER BY time DESC", s.Id); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { - var p Pong + p := &Pong{} if err = rows.Scan(&p.Date, &p.State); err != nil { return } diff --git a/libadlin/session.go b/libadlin/session.go index 933f425..2665987 100644 --- a/libadlin/session.go +++ b/libadlin/session.go @@ -11,40 +11,41 @@ type Session struct { Time time.Time `json:"time"` } -func GetSession(id []byte) (s Session, err error) { +func GetSession(id []byte) (s *Session, err error) { + s = new(Session) err = DBQueryRow("SELECT id_session, id_student, time FROM student_sessions WHERE id_session=?", id).Scan(&s.Id, &s.IdStudent, &s.Time) return } -func NewSession() (Session, error) { +func NewSession() (*Session, error) { session_id := make([]byte, 255) if _, err := rand.Read(session_id); err != nil { - return Session{}, err + return nil, err } else if _, err := DBExec("INSERT INTO student_sessions (id_session, time) VALUES (?, ?)", session_id, time.Now()); err != nil { - return Session{}, err + return nil, err } else { - return Session{session_id, nil, time.Now()}, nil + return &Session{session_id, nil, time.Now()}, nil } } -func (student Student) NewSession() (Session, error) { +func (student *Student) NewSession() (*Session, error) { session_id := make([]byte, 255) if _, err := rand.Read(session_id); err != nil { - return Session{}, err + return nil, err } else if _, err := DBExec("INSERT INTO student_sessions (id_session, id_student, time) VALUES (?, ?, ?)", session_id, student.Id, time.Now()); err != nil { - return Session{}, err + return nil, err } else { - return Session{session_id, &student.Id, time.Now()}, nil + return &Session{session_id, &student.Id, time.Now()}, nil } } -func (s Session) SetStudent(student Student) (Session, error) { +func (s *Session) SetStudent(student *Student) (*Session, error) { s.IdStudent = &student.Id _, err := s.Update() return s, err } -func (s Session) Update() (int64, error) { +func (s *Session) Update() (int64, error) { if res, err := DBExec("UPDATE student_sessions SET id_student = ?, time = ? WHERE id_session = ?", s.IdStudent, s.Time, s.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { @@ -54,7 +55,7 @@ func (s Session) Update() (int64, error) { } } -func (s Session) Delete() (int64, error) { +func (s *Session) Delete() (int64, error) { if res, err := DBExec("DELETE FROM student_sessions WHERE id_session = ?", s.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { diff --git a/libadlin/ssh.go b/libadlin/ssh.go index 9e8f1be..f699860 100644 --- a/libadlin/ssh.go +++ b/libadlin/ssh.go @@ -16,14 +16,14 @@ type StudentKey struct { Time time.Time `json:"time"` } -func GetStudentKeys() (keys []StudentKey, err error) { +func GetStudentKeys() (keys []*StudentKey, err error) { if rows, errr := DBQuery("SELECT id_key, id_student, sshkey, time FROM student_keys"); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { - var k StudentKey + k := &StudentKey{} if err = rows.Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time); err != nil { return } @@ -37,14 +37,14 @@ func GetStudentKeys() (keys []StudentKey, err error) { } } -func (s Student) GetKeys() (keys []StudentKey, err error) { +func (s *Student) GetKeys() (keys []*StudentKey, err error) { if rows, errr := DBQuery("SELECT id_key, id_student, sshkey, time FROM student_keys WHERE id_student = ?", s.Id); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { - var k StudentKey + k := &StudentKey{} if err = rows.Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time); err != nil { return } @@ -58,12 +58,13 @@ func (s Student) GetKeys() (keys []StudentKey, err error) { } } -func getStudentKey(id int) (k StudentKey, err error) { +func getStudentKey(id int) (k *StudentKey, err error) { + k = new(StudentKey) err = DBQueryRow("SELECT id_key, id_student, sshkey, time FROM student_keys WHERE id_key=?", id).Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time) return } -func (s Student) NewKey(key string) (k StudentKey, err error) { +func (s *Student) NewKey(key string) (k *StudentKey, err error) { // Check key before importing it cmd := exec.Command("ssh-keygen", "-l", "-f", "-") cmd.Stdin = strings.NewReader(key) @@ -101,20 +102,20 @@ func (s Student) NewKey(key string) (k StudentKey, err error) { key = keyf[0] + " " + keyf[1] if res, err := DBExec("INSERT INTO student_keys (id_student, sshkey, time) VALUES (?, ?, ?)", s.Id, key, time.Now()); err != nil { - return StudentKey{}, err + return nil, err } else if kid, err := res.LastInsertId(); err != nil { - return StudentKey{}, err + return nil, err } else { s.UnlockNewChallenge(11, "") - return StudentKey{kid, s.Id, key, time.Now()}, nil + return &StudentKey{kid, s.Id, key, time.Now()}, nil } } -func (k StudentKey) GetStudent() (Student, error) { +func (k *StudentKey) GetStudent() (*Student, error) { return GetStudent(int(k.IdStudent)) } -func (k StudentKey) Update() (int64, error) { +func (k *StudentKey) Update() (int64, error) { if res, err := DBExec("UPDATE student_keys SET id_student = ?, sshkey = ?, time = ? WHERE id_key = ?", k.IdStudent, k.Key, k.Time, k.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { @@ -124,7 +125,7 @@ func (k StudentKey) Update() (int64, error) { } } -func (k StudentKey) Delete() (int64, error) { +func (k *StudentKey) Delete() (int64, error) { if res, err := DBExec("DELETE FROM student_keys WHERE id_key = ?", k.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { diff --git a/libadlin/students.go b/libadlin/students.go index c4790da..9903cc0 100644 --- a/libadlin/students.go +++ b/libadlin/students.go @@ -3,26 +3,29 @@ package adlin import ( "crypto/hmac" "crypto/sha512" + "fmt" "time" ) type Student struct { - Id int64 `json:"id"` - Login string `json:"login"` - Time *time.Time `json:"time"` - IP *string `json:"ip"` - MAC *string `json:"mac"` + Id int64 `json:"id"` + Login string `json:"login"` + Time *time.Time `json:"time"` + IP *string `json:"ip"` + MAC *string `json:"mac"` + AssociatedDomain *string `json:"associated_domain,omitempty"` + DelegatedDomain *string `json:"delegated_domain,omitempty"` } -func GetStudents() (students []Student, err error) { - if rows, errr := DBQuery("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student GROUP BY id_student"); errr != nil { +func GetStudents() (students []*Student, err error) { + if rows, errr := DBQuery("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac, S.associatedDomain, S.delegatedDomain FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student GROUP BY id_student"); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { - var s Student - if err = rows.Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC); err != nil { + s := &Student{} + if err = rows.Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC, &s.AssociatedDomain, &s.DelegatedDomain); err != nil { return } students = append(students, s) @@ -35,13 +38,15 @@ func GetStudents() (students []Student, err error) { } } -func GetStudent(id int) (s Student, err error) { - err = DBQueryRow("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student WHERE S.id_student=?", id).Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC) +func GetStudent(id int) (s *Student, err error) { + s = new(Student) + err = DBQueryRow("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac, S.associatedDomain, S.delegatedDomain FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student WHERE S.id_student=?", id).Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC, &s.AssociatedDomain, &s.DelegatedDomain) return } -func GetStudentByLogin(login string) (s Student, err error) { - err = DBQueryRow("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student WHERE login=?", login).Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC) +func GetStudentByLogin(login string) (s *Student, err error) { + s = new(Student) + err = DBQueryRow("SELECT S.id_student, S.login, MAX(L.time), L.ip, L.mac, S.associatedDomain, S.delegatedDomain FROM students S INNER JOIN (SELECT a.id_student, a.time, a.ip, a.mac FROM student_login a INNER JOIN (SELECT id_student, MAX(time) AS time FROM student_login GROUP BY id_student) b ON a.id_student = b.id_student AND a.time = b.time) L ON S.id_student = L.id_student WHERE login=?", login).Scan(&s.Id, &s.Login, &s.Time, &s.IP, &s.MAC, &s.AssociatedDomain, &s.DelegatedDomain) return } @@ -51,23 +56,23 @@ func StudentExists(login string) bool { return err == nil && z == 1 } -func NewStudent(login string) (Student, error) { +func NewStudent(login string) (*Student, error) { t := time.Now() if res, err := DBExec("INSERT INTO students (login, time) VALUES (?, ?)", login, t); err != nil { - return Student{}, err + return nil, err } else if sid, err := res.LastInsertId(); err != nil { - return Student{}, err + return nil, err } else { - return Student{sid, login, &t, nil, nil}, nil + return &Student{sid, login, &t, nil, nil, nil, nil}, nil } } -func (s Student) GetPKey() []byte { +func (s *Student) GetPKey() []byte { return hmac.New(sha512.New512_224, []byte(SharedSecret)).Sum([]byte(s.Login)) } -func (s Student) Update() (int64, error) { - if res, err := DBExec("UPDATE students SET login = ?, time = ? WHERE id_student = ?", s.Login, s.Time, s.Id); err != nil { +func (s *Student) Update() (int64, error) { + if res, err := DBExec("UPDATE students SET login = ?, time = ?, associatedDomain = ?, delegatedDomain = ? WHERE id_student = ?", s.Login, s.Time, s.AssociatedDomain, s.DelegatedDomain, s.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err @@ -76,7 +81,7 @@ func (s Student) Update() (int64, error) { } } -func (s Student) Delete() (int64, error) { +func (s *Student) Delete() (int64, error) { if res, err := DBExec("DELETE FROM students WHERE id_student = ?", s.Id); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { @@ -100,18 +105,20 @@ type UnlockedChallenge struct { Id int64 `json:"id,omitempty"` IdStudent int64 `json:"id_student"` Challenge int `json:"challenge,omitempty"` - Time time.Time `json:"time"` + Time *time.Time `json:"time,omitempty"` Value interface{} `json:"value,omitempty"` + LastCheck *time.Time `json:"last_check,omitempty"` + Error string `json:"error,omitempty"` } -func (s Student) GetStates() (ucs []UnlockedChallenge, err error) { +func (s *Student) GetStates() (ucs []*UnlockedChallenge, err error) { if rows, errr := DBQuery("SELECT id_st, challenge, time FROM student_challenges WHERE id_student = ?", s.Id); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { - var u UnlockedChallenge + u := &UnlockedChallenge{} u.IdStudent = s.Id if err = rows.Scan(&u.Id, &u.Challenge, &u.Time); err != nil { return @@ -126,14 +133,36 @@ func (s Student) GetStates() (ucs []UnlockedChallenge, err error) { } } -func (s Student) GetStatesByChallenge() (ucs []UnlockedChallenge, err error) { +func (s *Student) GetChallengeErrors() (ucs []*ErroredChallenge, err error) { + if rows, errr := DBQuery("SELECT id_st, challenge, time, error FROM student_challenge_errors WHERE id_student = ?", s.Id); errr != nil { + return nil, errr + } else { + defer rows.Close() + + for rows.Next() { + u := &ErroredChallenge{} + u.IdStudent = s.Id + if err = rows.Scan(&u.Id, &u.Challenge, &u.Time, &u.Error); err != nil { + return + } + ucs = append(ucs, u) + } + if err = rows.Err(); err != nil { + return + } + + return + } +} + +func (s *Student) GetStatesByChallenge() (ucs []*UnlockedChallenge, err error) { if rows, errr := DBQuery("SELECT id_st, challenge, MIN(time), value FROM student_challenges WHERE id_student = ? GROUP BY challenge, id_student", s.Id); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { - var u UnlockedChallenge + u := &UnlockedChallenge{} u.IdStudent = s.Id if err = rows.Scan(&u.Id, &u.Challenge, &u.Time, &u.Value); err != nil { return @@ -148,25 +177,57 @@ func (s Student) GetStatesByChallenge() (ucs []UnlockedChallenge, err error) { } } -func (s Student) UnlockNewChallenge(challenge int, value string) (UnlockedChallenge, error) { +func (s *Student) UnlockChallenge(challenge int, value string) (uc *UnlockedChallenge, err error) { + if uc, err = s.UnlockNewChallenge(challenge, value); err != nil { + if uc, err = s.UpdateUnlockedChallenge(challenge, value); err != nil { + return + } + } + + s.RegisterChallengeError(challenge, fmt.Errorf("OK")) + + return +} + +func (s *Student) UnlockNewChallenge(challenge int, value string) (*UnlockedChallenge, error) { if res, err := DBExec("INSERT INTO student_challenges (id_student, challenge, time, value) VALUES (?, ?, ?, ?)", s.Id, challenge, time.Now(), value); err != nil { - return UnlockedChallenge{}, err + return nil, err } else if utid, err := res.LastInsertId(); err != nil { - return UnlockedChallenge{}, err + return nil, err } else { - return UnlockedChallenge{utid, s.Id, challenge, time.Now(), value}, err + now := time.Now() + return &UnlockedChallenge{utid, s.Id, challenge, &now, value, nil, ""}, err } } -func (s Student) UpdateUnlockedChallenge(challenge int, value string) (UnlockedChallenge, error) { +func (s *Student) UpdateUnlockedChallenge(challenge int, value string) (*UnlockedChallenge, error) { if _, err := DBExec("UPDATE student_challenges SET time = ?, value = ? WHERE id_student = ? AND challenge = ?", time.Now(), value, s.Id, challenge); err != nil { - return UnlockedChallenge{}, err + return nil, err } else { - return UnlockedChallenge{0, s.Id, challenge, time.Now(), value}, err + now := time.Now() + return &UnlockedChallenge{0, s.Id, challenge, &now, value, nil, ""}, err } } -func (s Student) RegisterAccess(ip, mac string) error { +type ErroredChallenge struct { + Id int64 `json:"id,omitempty"` + IdStudent int64 `json:"id_student"` + Challenge int `json:"challenge,omitempty"` + Time time.Time `json:"time"` + Error string `json:"error,omitempty"` +} + +func (s *Student) RegisterChallengeError(challenge int, err error) error { + if _, errr := DBExec("INSERT INTO student_challenge_errors (id_student, challenge, time, error) VALUES (?, ?, ?, ?)", s.Id, challenge, time.Now(), err.Error()); errr == nil { + return nil + } else if _, errr := DBExec("UPDATE student_challenge_errors SET time = ?, error = ? WHERE id_student = ? AND challenge = ?", time.Now(), err.Error(), s.Id, challenge); errr != nil { + return errr + } else { + return nil + } +} + +func (s *Student) RegisterAccess(ip, mac string) error { if res, err := DBExec("INSERT INTO student_login (id_student, ip, mac, time) VALUES (?, ?, ?, ?)", s.Id, ip, mac, time.Now()); err != nil { return err } else if _, err := res.LastInsertId(); err != nil { diff --git a/libadlin/tunnel.go b/libadlin/tunnel.go index 5a177b0..c22a520 100644 --- a/libadlin/tunnel.go +++ b/libadlin/tunnel.go @@ -12,10 +12,19 @@ import ( "time" ) +const StdNetmask = 80 + func StudentIP(idstd int64) net.IP { return net.ParseIP(fmt.Sprintf("2a01:e0a:2b:2252:%x::", idstd)) } +func StudentNet(idstd int64) *net.IPNet { + return &net.IPNet{ + IP: StudentIP(idstd), + Mask: net.CIDRMask(StdNetmask, 128), + } +} + type WGDump struct { PubKey string PSK string @@ -28,32 +37,32 @@ type WGDump struct { } var ( - wgDumpCache_data map[string]WGDump = nil + wgDumpCache_data map[string]*WGDump = nil wgDumpCache_time time.Time wgDumpCache_mutex sync.RWMutex ) -func _readWgDump() (wgd map[string]WGDump, err error) { +func _readWgDump() (wgd map[string]*WGDump, err error) { out, errr := exec.Command("wg", "show", "wg-adlin", "dump").Output() if errr != nil { return nil, errr } - wgd = map[string]WGDump{} + wgd = map[string]*WGDump{} for _, line := range strings.Split(string(out), "\n") { cols := strings.Fields(line) if len(cols) != 8 { continue } - wgd[cols[0]] = WGDump{cols[0], cols[1], cols[2], cols[3], cols[4], cols[5], cols[6], cols[7]} + wgd[cols[0]] = &WGDump{cols[0], cols[1], cols[2], cols[3], cols[4], cols[5], cols[6], cols[7]} } return } -func readWgDump() (wgd map[string]WGDump, err error) { +func readWgDump() (wgd map[string]*WGDump, err error) { wgDumpCache_mutex.RLock() defer wgDumpCache_mutex.RUnlock() @@ -94,38 +103,48 @@ type TunnelToken struct { Dump *WGDump } -func tokenFromText(token string) []byte { +func (tt *TunnelToken) GetStudentIP() string { + if tt.SuffixIP == 0 { + return fmt.Sprintf("%s%x", StudentIP(tt.IdStudent).String(), 1) + } else { + return fmt.Sprintf("%s%x", StudentIP(tt.IdStudent).String(), tt.SuffixIP) + } +} + +func TokenFromText(token string) []byte { sha := sha512.Sum512([]byte(token)) return sha[:] } -func GetTunnelToken(token []byte) (t TunnelToken, err error) { +func GetTunnelToken(token []byte) (t *TunnelToken, err error) { + t = new(TunnelToken) err = DBQueryRow("SELECT token, token_text, id_student, pubkey, time, suffixip, version FROM student_tunnel_tokens WHERE token=? ORDER BY time DESC", token).Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version) if err == nil && t.PubKey != nil { if wgd, errr := readWgDump(); errr == nil { if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok { - t.Dump = &v + t.Dump = v } } } return } -func (student Student) NewTunnelToken(suffixip int) (t TunnelToken, err error) { +func (student *Student) NewTunnelToken(suffixip int) (t *TunnelToken, err error) { tok := make([]byte, 7) if _, err = rand.Read(tok); err != nil { return } - t.TokenText = strings.Replace(strings.Replace(strings.Replace(strings.Replace(strings.Replace(base64.RawStdEncoding.EncodeToString(tok), "/", ".", -1), "+", "_", -1), "O", "#", -1), "l", "$", -1), "I", ">", -1) - t.token = tokenFromText(t.TokenText) + t = new(TunnelToken) + t.TokenText = strings.Replace(strings.Replace(strings.Replace(strings.Replace(strings.Replace(base64.RawStdEncoding.EncodeToString(tok), "/", ".", -1), "+", "_", -1), "O", "<", -1), "l", "$", -1), "I", ">", -1) + t.token = TokenFromText(t.TokenText) t.IdStudent = student.Id _, err = DBExec("INSERT INTO student_tunnel_tokens (token, token_text, id_student, time, suffixip, version) VALUES (?, ?, ?, ?, ?, 0)", t.token, t.TokenText, student.Id, time.Now(), suffixip) return } -func (student Student) GetTunnelTokens() (ts []TunnelToken, err error) { +func (student *Student) GetTunnelTokens() (ts []*TunnelToken, err error) { if rows, errr := DBQuery("SELECT token, token_text, id_student, pubkey, time, suffixip, version FROM student_tunnel_tokens WHERE id_student = ? ORDER BY time DESC", student.Id); errr != nil { return nil, errr } else if wgd, errr := readWgDump(); errr != nil { @@ -134,13 +153,13 @@ func (student Student) GetTunnelTokens() (ts []TunnelToken, err error) { defer rows.Close() for rows.Next() { - var t TunnelToken + t := &TunnelToken{} if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version); err != nil { return } if t.PubKey != nil { if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok { - t.Dump = &v + t.Dump = v } } ts = append(ts, t) @@ -153,7 +172,7 @@ func (student Student) GetTunnelTokens() (ts []TunnelToken, err error) { } } -func (student Student) GetActivesTunnels() (ts []TunnelToken, err error) { +func (student *Student) GetActivesTunnels() (ts []*TunnelToken, err error) { if rows, errr := DBQuery("SELECT token, token_text, id_student, pubkey, time, suffixip, version FROM student_tunnel_tokens WHERE id_student = ? ORDER BY time DESC", student.Id); errr != nil { return nil, errr } else if wgd, errr := readWgDump(); errr != nil { @@ -162,13 +181,13 @@ func (student Student) GetActivesTunnels() (ts []TunnelToken, err error) { defer rows.Close() for rows.Next() { - var t TunnelToken + t := &TunnelToken{} if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version); err != nil { return } if t.PubKey != nil { if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok { - t.Dump = &v + t.Dump = v ts = append(ts, t) } } @@ -181,12 +200,13 @@ func (student Student) GetActivesTunnels() (ts []TunnelToken, err error) { } } -func (student Student) GetTunnelToken(token []byte) (t TunnelToken, err error) { +func (student *Student) GetTunnelToken(token []byte) (t *TunnelToken, err error) { + t = new(TunnelToken) err = DBQueryRow("SELECT token, token_text, id_student, pubkey, time, suffixip, version FROM student_tunnel_tokens WHERE token = ? AND id_student = ? ORDER BY time DESC", token, student.Id).Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version) if err == nil && t.PubKey != nil { if wgd, errr := readWgDump(); errr == nil { if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok { - t.Dump = &v + t.Dump = v } } } @@ -194,7 +214,7 @@ func (student Student) GetTunnelToken(token []byte) (t TunnelToken, err error) { } func (t *TunnelToken) Update() (int64, error) { - newtoken := tokenFromText(t.TokenText) + newtoken := TokenFromText(t.TokenText) tm := time.Now() if res, err := DBExec("UPDATE student_tunnel_tokens SET token = ?, token_text = ?, id_student = ?, pubkey = ?, time = ?, suffixip = ?, version = ? WHERE token = ?", newtoken, t.TokenText, t.IdStudent, t.PubKey, tm, t.SuffixIP, t.Version, t.token); err != nil { @@ -208,14 +228,24 @@ func (t *TunnelToken) Update() (int64, error) { } } -func GetStudentsTunnels() (ts []TunnelToken, err error) { +func (t *TunnelToken) Delete() (int64, error) { + if res, err := DBExec("DELETE FROM student_tunnel_tokens WHERE token = ? AND id_student = ?", t.token, t.IdStudent); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func GetStudentsTunnels() (ts []*TunnelToken, err error) { if rows, errr := DBQuery("SELECT T.token, T.token_text, T.id_student, T.pubkey, T.time, T.suffixip, T.version FROM student_tunnel_tokens T INNER JOIN (SELECT B.id_student, MAX(B.time) AS time FROM student_tunnel_tokens B WHERE B.pubkey IS NOT NULL GROUP BY id_student) L ON T.id_student = L.id_student AND T.time = L.time"); errr != nil { return nil, errr } else { defer rows.Close() for rows.Next() { - var t TunnelToken + t := &TunnelToken{} if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version); err != nil { return } diff --git a/token-validator/auth.go b/token-validator/auth.go index f90fec9..97be90b 100644 --- a/token-validator/auth.go +++ b/token-validator/auth.go @@ -27,7 +27,7 @@ func init() { router.POST("/api/auth/logout", apiRawHandler(logout)) } -func validateAuthToken(s adlin.Student, _ httprouter.Params, _ []byte) (interface{}, error) { +func validateAuthToken(s *adlin.Student, _ httprouter.Params, _ []byte) (interface{}, error) { return s, nil } @@ -50,7 +50,7 @@ type loginForm struct { } func completeAuth(w http.ResponseWriter, username string, session *adlin.Session) (err error) { - var std adlin.Student + var std *adlin.Student if !adlin.StudentExists(username) { if std, err = adlin.NewStudent(username); err != nil { return err @@ -60,9 +60,7 @@ func completeAuth(w http.ResponseWriter, username string, session *adlin.Session } if session == nil { - var s adlin.Session - s, err = std.NewSession() - session = &s + session, err = std.NewSession() } else { _, err = session.SetStudent(std) } diff --git a/token-validator/auth_oidc.go b/token-validator/auth_oidc.go index 9118aea..590676a 100644 --- a/token-validator/auth_oidc.go +++ b/token-validator/auth_oidc.go @@ -111,7 +111,7 @@ func OIDC_CRI_complete(w http.ResponseWriter, r *http.Request, ps httprouter.Par return } - if err := completeAuth(w, claims.Username, &session); err != nil { + if err := completeAuth(w, claims.Username, session); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } diff --git a/token-validator/challenge.go b/token-validator/challenge.go index d21db5d..51144f1 100644 --- a/token-validator/challenge.go +++ b/token-validator/challenge.go @@ -338,7 +338,7 @@ func receiveChallenge(r *http.Request, ps httprouter.Params, body []byte) (inter return nil, errors.New("This is not the expected token.") } - var std adlin.Student + var std *adlin.Student if stdid, err := strconv.Atoi(gt.Login); err == nil { if std, err = adlin.GetStudent(stdid); err != nil { @@ -388,7 +388,7 @@ func receiveToken(r *http.Request, body []byte, chid int) (interface{}, error) { if std, err := adlin.GetStudentByLogin(gt.Login); err != nil { return nil, err } else { - if err := challenges[chid-1].Check(&std, >, chid); err != nil { + if err := challenges[chid-1].Check(std, >, chid); err != nil { log.Printf("%s just try ch#%d: %s\n", std.Login, chid, err) return nil, err } diff --git a/token-validator/check.go b/token-validator/check.go index 8e18faf..722e54f 100644 --- a/token-validator/check.go +++ b/token-validator/check.go @@ -19,7 +19,7 @@ type checkGLUE struct { } func init() { - router.POST("/api/check/GLUE", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.POST("/api/check/GLUE", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var uc checkGLUE if err := json.Unmarshal(body, &uc); err != nil { return nil, err @@ -27,7 +27,7 @@ func init() { return true, check_GLUE_respond(student, uc.Domain, uc.IP) })) } -func check_GLUE_respond(student adlin.Student, domain string, ip string) (err error) { +func check_GLUE_respond(student *adlin.Student, domain string, ip string) (err error) { if !strings.HasPrefix(ip, adlin.StudentIP(student.Id).String()) { return fmt.Errorf("%q is not your IP range") } diff --git a/token-validator/domain.go b/token-validator/domain.go index 2497a72..596f3bd 100644 --- a/token-validator/domain.go +++ b/token-validator/domain.go @@ -22,103 +22,151 @@ var ( ) func init() { - router.GET("/api/adomains/", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.GET("/api/adomains/", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return student.GetAssociatedDomains(), nil })) - router.POST("/api/adomains/", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.POST("/api/adomains/", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { ue := &struct { Domain string `json:"domain"` A string `json:"a"` AAAA string `json:"aaaa"` + CNAME string `json:"cname,omitempty"` }{} if err := json.Unmarshal(body, &ue); err != nil { return nil, err } - var aaaa net.IP - if ue != nil && len(ue.AAAA) > 0 { - aaaa = net.ParseIP(ue.AAAA) - } + if ue.Domain != "" && ue.A == "" && ue.AAAA == "" && ue.CNAME == "" { + student.AssociatedDomain = nil - return true, AddAssociatedDomains(student, aaaa) + if _, err := student.Update(); err != nil { + return nil, err + } + + return true, nil + } else if ue.CNAME != "" { + cname := dns.Fqdn(ue.CNAME) + student.AssociatedDomain = &cname + + if _, err := student.Update(); err != nil { + return nil, err + } + + return true, nil + } else { + var aaaa net.IP + if ue != nil && len(ue.AAAA) > 0 { + aaaa = net.ParseIP(ue.AAAA) + } + + return true, AddAssociatedDomains(student, aaaa) + } })) - router.GET("/api/adomains/:dn", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.GET("/api/adomains/:dn", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return GetAssociatedDomain(student, ps.ByName("dn")) })) - router.GET("/api/ddomains/", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.GET("/api/ddomains/", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return []string{student.MyDelegatedDomain()}, nil })) - router.GET("/api/ddomains/:dn/", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.POST("/api/ddomains/", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + ue := &struct { + NS string `json:"ns"` + }{} + + if err := json.Unmarshal(body, &ue); err != nil { + return nil, err + } + + if ue.NS == "" { + student.DelegatedDomain = nil + + if _, err := student.Update(); err != nil { + return nil, err + } + + return true, nil + } else { + ns := dns.Fqdn(ue.NS) + student.DelegatedDomain = &ns + + if _, err := student.Update(); err != nil { + return nil, err + } + + return true, nil + } + })) + router.GET("/api/ddomains/:dn/", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return getRRDelegatedDomain(student, ps.ByName("dn"), "") })) - router.GET("/api/ddomains/:dn/NS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.GET("/api/ddomains/:dn/NS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return getRRDelegatedDomain(student, ps.ByName("dn"), "NS") })) - router.POST("/api/ddomains/:dn/NS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.POST("/api/ddomains/:dn/NS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, AddNSDelegatedDomain(student, ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " ")) })) - router.PATCH("/api/ddomains/:dn/NS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.PATCH("/api/ddomains/:dn/NS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, UpdateNSDelegatedDomain(student, ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, "")) })) - router.DELETE("/api/ddomains/:dn/NS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.DELETE("/api/ddomains/:dn/NS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, DeleteRRDelegatedDomain(student, ps.ByName("dn"), "NS", strings.Join(ue.Values, " ")) })) - router.GET("/api/ddomains/:dn/GLUE", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.GET("/api/ddomains/:dn/GLUE", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return getRRDelegatedDomain(student, ps.ByName("dn"), "AAAA") })) - router.POST("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.POST("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, AddGLUEDelegatedDomain(student, ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " ")) })) - router.PATCH("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.PATCH("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, UpdateGLUEDelegatedDomain(student, ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, " ")) })) - router.POST("/api/ddomains/:dn/GLUE", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.POST("/api/ddomains/:dn/GLUE", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, UpdateGLUEDelegatedDomain(student, ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, " ")) })) - router.DELETE("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.DELETE("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, DeleteRRDelegatedDomain(student, ps.ByName("dn"), "AAAA", strings.Join(ue.Values, " ")) })) - router.GET("/api/ddomains/:dn/DS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.GET("/api/ddomains/:dn/DS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { return getRRDelegatedDomain(student, ps.ByName("dn"), "DS") })) - router.POST("/api/ddomains/:dn/DS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.POST("/api/ddomains/:dn/DS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err } return true, AddDSDelegatedDomain(student, ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " ")) })) - router.DELETE("/api/ddomains/:dn/DS", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { + router.DELETE("/api/ddomains/:dn/DS", apiAuthHandler(func(student *adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { var ue Entry if err := json.Unmarshal(body, &ue); err != nil { return nil, err @@ -175,7 +223,7 @@ func parseZoneRead(globalDomain string, domain string) (rr []Entry, err error) { return } -func GetAssociatedDomain(student adlin.Student, dn string) (rrs []Entry, err error) { +func GetAssociatedDomain(student *adlin.Student, dn string) (rrs []Entry, err error) { domains := student.GetAssociatedDomains() found := false for _, d := range domains { @@ -201,7 +249,7 @@ func GetAssociatedDomain(student adlin.Student, dn string) (rrs []Entry, err err return } -func delAssociatedDomains(student adlin.Student, dn string) (err error) { +func delAssociatedDomains(student *adlin.Student, dn string) (err error) { var adomains []Entry adomains, err = GetAssociatedDomain(student, dn) if err != nil { @@ -238,15 +286,15 @@ func delAssociatedDomains(student adlin.Student, dn string) (err error) { return } -func AddAssociatedDomains(student adlin.Student, aaaa net.IP) (err error) { - err = delAssociatedDomains(student, student.MyAssociatedDomain()) +func AddAssociatedDomains(student *adlin.Student, aaaa net.IP) (err error) { + err = delAssociatedDomains(student, student.DefaultAssociatedDomain()) if err != nil { return } if aaaa == nil { aaaa = net.ParseIP(adlin.StudentIP(student.Id).String() + "1") - } else if !strings.HasPrefix(aaaa.String(), adlin.StudentIP(student.Id).String()) { + } else if !adlin.StudentNet(student.Id).Contains(aaaa) { return errors.New("The associated IP has to be in your IP range.") } @@ -257,12 +305,12 @@ func AddAssociatedDomains(student adlin.Student, aaaa net.IP) (err error) { m2.Question[0] = dns.Question{adlin.AssociatedDomainSuffix, dns.TypeSOA, dns.ClassINET} rrA := new(dns.A) - rrA.Hdr = dns.RR_Header{Name: student.MyAssociatedDomain(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 3600} + rrA.Hdr = dns.RR_Header{Name: student.DefaultAssociatedDomain(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 3600} rrA.A = net.IPv4(82, 64, 31, 248) m2.Insert([]dns.RR{rrA}) rrAAAA := new(dns.AAAA) - rrAAAA.Hdr = dns.RR_Header{Name: student.MyAssociatedDomain(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 3600} + rrAAAA.Hdr = dns.RR_Header{Name: student.DefaultAssociatedDomain(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 3600} rrAAAA.AAAA = aaaa m2.Insert([]dns.RR{rrAAAA}) @@ -274,7 +322,7 @@ func AddAssociatedDomains(student adlin.Student, aaaa net.IP) (err error) { return } -func getRRDelegatedDomain(student adlin.Student, dn string, rr string) (rrs []Entry, err error) { +func getRRDelegatedDomain(student *adlin.Student, dn string, rr string) (rrs []Entry, err error) { domains := []string{student.MyDelegatedDomain()} found := false for _, d := range domains { @@ -300,7 +348,7 @@ func getRRDelegatedDomain(student adlin.Student, dn string, rr string) (rrs []En return } -func AddNSDelegatedDomain(student adlin.Student, dn string, ttl uint32, ns string) (err error) { +func AddNSDelegatedDomain(student *adlin.Student, dn string, ttl uint32, ns string) (err error) { for _, d := range []string{student.MyDelegatedDomain()} { m1 := new(dns.Msg) m1.Id = dns.Id() @@ -323,7 +371,7 @@ func AddNSDelegatedDomain(student adlin.Student, dn string, ttl uint32, ns strin return } -func UpdateNSDelegatedDomain(student adlin.Student, dn string, ttl uint32, oldns string, ns string) (err error) { +func UpdateNSDelegatedDomain(student *adlin.Student, dn string, ttl uint32, oldns string, ns string) (err error) { for _, d := range []string{student.MyDelegatedDomain()} { m1 := new(dns.Msg) m1.Id = dns.Id() @@ -351,7 +399,7 @@ func UpdateNSDelegatedDomain(student adlin.Student, dn string, ttl uint32, oldns return } -func AddGLUEDelegatedDomain(student adlin.Student, dn string, ttl uint32, aaaa string) (err error) { +func AddGLUEDelegatedDomain(student *adlin.Student, dn string, ttl uint32, aaaa string) (err error) { domains := []string{student.MyDelegatedDomain()} found := false for _, d := range domains { @@ -387,7 +435,7 @@ func AddGLUEDelegatedDomain(student adlin.Student, dn string, ttl uint32, aaaa s return } -func UpdateGLUEDelegatedDomain(student adlin.Student, dn string, ttl uint32, oldaaaa string, aaaa string) (err error) { +func UpdateGLUEDelegatedDomain(student *adlin.Student, dn string, ttl uint32, oldaaaa string, aaaa string) (err error) { domains := []string{student.MyDelegatedDomain()} found := false for _, d := range domains { @@ -429,7 +477,7 @@ func UpdateGLUEDelegatedDomain(student adlin.Student, dn string, ttl uint32, old return } -func AddDSDelegatedDomain(student adlin.Student, dn string, ttl uint32, rdata string) (err error) { +func AddDSDelegatedDomain(student *adlin.Student, dn string, ttl uint32, rdata string) (err error) { domains := []string{student.MyDelegatedDomain()} found := false for _, d := range domains { @@ -479,7 +527,7 @@ func AddDSDelegatedDomain(student adlin.Student, dn string, ttl uint32, rdata st return } -func DeleteRRDelegatedDomain(student adlin.Student, dn string, rr string, values ...string) (err error) { +func DeleteRRDelegatedDomain(student *adlin.Student, dn string, rr string, values ...string) (err error) { domains := []string{student.MyDelegatedDomain()} found := false for _, d := range domains { diff --git a/token-validator/handler.go b/token-validator/handler.go index c93c842..d40be85 100644 --- a/token-validator/handler.go +++ b/token-validator/handler.go @@ -65,7 +65,7 @@ func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, [] http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusUnauthorized) return } else { - student = &std + student = std } } @@ -158,7 +158,7 @@ func apiHandler(f DispatchFunction, access ...func(*adlin.Student, *http.Request return rawHandler(responseHandler(func(_ *http.Request, ps httprouter.Params, b []byte) (interface{}, error) { return f(ps, b) }), access...) } -func apiAuthHandler(f func(adlin.Student, httprouter.Params, []byte) (interface{}, error), access ...func(*adlin.Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) { +func apiAuthHandler(f func(*adlin.Student, httprouter.Params, []byte) (interface{}, error), access ...func(*adlin.Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) { return rawHandler(responseHandler(func(r *http.Request, ps httprouter.Params, b []byte) (interface{}, error) { if cookie, err := r.Cookie("auth"); err != nil { return nil, errors.New("Authorization required") @@ -176,7 +176,7 @@ func apiAuthHandler(f func(adlin.Student, httprouter.Params, []byte) (interface{ }), access...) } -func studentHandler(f func(adlin.Student, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) { +func studentHandler(f func(*adlin.Student, []byte) (interface{}, error)) func(httprouter.Params, []byte) (interface{}, error) { return func(ps httprouter.Params, body []byte) (interface{}, error) { if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil { if student, err := adlin.GetStudentByLogin(ps.ByName("sid")); err != nil { diff --git a/token-validator/htdocs/dashboard.html b/token-validator/htdocs/dashboard.html index 8815e14..8783eb6 100644 --- a/token-validator/htdocs/dashboard.html +++ b/token-validator/htdocs/dashboard.html @@ -24,7 +24,7 @@ height: calc(100vh / 5 - 0.3vh); margin-left: 0.2%; margin-bottom: 0.2vh; - width: calc(100vw / 13 - 0.22vw); + width: calc(100vw / 13 - 0.26vw); } .student-title { width: calc(2 * (100vw / 13 - 0.22vw) + 0.2vw); @@ -47,7 +47,7 @@ .login { max-width: 122px; } - .badge { + .badge.badge-sm { font-size: 59%; padding: .15em .35em; } @@ -72,12 +72,12 @@