split checker from token-validator

This commit is contained in:
nemunaire 2020-03-27 14:57:14 +01:00
parent 685dc0b0ea
commit 0c661f36f6
20 changed files with 634 additions and 748 deletions

View File

@ -9,6 +9,8 @@ import (
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/sparrc/go-ping" "github.com/sparrc/go-ping"
"git.nemunai.re/lectures/adlin/libadlin"
) )
// ICMP // ICMP
@ -29,11 +31,6 @@ func check_ping(ip string, cb func(pkt *ping.Packet)) (err error) {
return return
} }
func (s *Student) onPong(state bool) (err error) {
_, err = DBExec("INSERT INTO student_pong (id_student, time, state) VALUES (?, ?, ?)", s.Id, time.Now(), state)
return
}
// PORT 53 // PORT 53
func check_dns(domain, ip string) (err error) { func check_dns(domain, ip string) (err error) {
@ -75,7 +72,7 @@ func check_https(domain, ip string) (err error) {
// Main // Main
func studentsChecker() { func studentsChecker() {
students, err := getStudents() students, err := adlin.GetStudents()
if err != nil { if err != nil {
log.Println("Unable to check students:", err) log.Println("Unable to check students:", err)
return return
@ -85,9 +82,9 @@ func studentsChecker() {
time.Sleep(250 * time.Millisecond) time.Sleep(250 * time.Millisecond)
// Check ping // Check ping
std := s std := s
stdIP := studentIP(std.Id).String() + "1" stdIP := adlin.StudentIP(std.Id).String() + "1"
go check_ping(stdIP, func(pkt *ping.Packet) { go check_ping(stdIP, func(pkt *ping.Packet) {
std.onPong(true) std.OnPong(true)
// Check HTTP // Check HTTP
if err := check_http(stdIP); err == nil { if err := check_http(stdIP); err == nil {
@ -99,7 +96,7 @@ func studentsChecker() {
} }
// Check HTTPs // Check HTTPs
if err := check_https(std.myAssociatedDomain(), stdIP); err == nil { if err := check_https(std.MyAssociatedDomain(), stdIP); err == nil {
if _, err := std.UnlockNewChallenge(101, ""); err != nil { if _, err := std.UnlockNewChallenge(101, ""); err != nil {
if _, err := std.UpdateUnlockedChallenge(101, ""); err != nil { if _, err := std.UpdateUnlockedChallenge(101, ""); err != nil {
log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error()) log.Printf("Unable to register challenge for %s: %s\n", std.Login, err.Error())

View File

@ -1,4 +1,4 @@
package main package adlin
import ( import (
"database/sql" "database/sql"

27
libadlin/domain.go Normal file
View File

@ -0,0 +1,27 @@
package adlin
import (
"fmt"
"strings"
)
const (
AssociatedDomainSuffix = "adlin2021.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) MyAssociatedDomain() string {
return fmt.Sprintf("%s.%s", strings.Trim(strings.Replace(student.Login, "_", "-", -1), "-_"), AssociatedDomainSuffix)
}
func (student Student) GetAssociatedDomains() (ds []string) {
studentDomain := student.MyAssociatedDomain()
ds = append(ds, studentDomain)
return
}

36
libadlin/ping.go Normal file
View File

@ -0,0 +1,36 @@
package adlin
import (
"time"
)
type Pong struct {
Date time.Time
State bool
}
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
if err = rows.Scan(&p.Date, &p.State); err != nil {
return
}
pongs = append(pongs, p)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func (s *Student) OnPong(state bool) (err error) {
_, err = DBExec("INSERT INTO student_pong (id_student, time, state) VALUES (?, ?, ?)", s.Id, time.Now(), state)
return
}

3
libadlin/secret.go Normal file
View File

@ -0,0 +1,3 @@
package adlin
var SharedSecret string

View File

@ -1,4 +1,4 @@
package main package adlin
import ( import (
"crypto/rand" "crypto/rand"
@ -11,7 +11,7 @@ type Session struct {
Time time.Time `json:"time"` Time time.Time `json:"time"`
} }
func getSession(id []byte) (s Session, err error) { func GetSession(id []byte) (s Session, err error) {
err = DBQueryRow("SELECT id_session, id_student, time FROM student_sessions WHERE id_session=?", id).Scan(&s.Id, &s.IdStudent, &s.Time) err = DBQueryRow("SELECT id_session, id_student, time FROM student_sessions WHERE id_session=?", id).Scan(&s.Id, &s.IdStudent, &s.Time)
return return
} }

135
libadlin/ssh.go Normal file
View File

@ -0,0 +1,135 @@
package adlin
import (
"bytes"
"errors"
"os/exec"
"strconv"
"strings"
"time"
)
type StudentKey struct {
Id int64 `json:"id"`
IdStudent int64 `json:"id_student"`
Key string `json:"key"`
Time time.Time `json:"time"`
}
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
if err = rows.Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time); err != nil {
return
}
keys = append(keys, k)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
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
if err = rows.Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time); err != nil {
return
}
keys = append(keys, k)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func getStudentKey(id int) (k StudentKey, err error) {
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) {
// Check key before importing it
cmd := exec.Command("ssh-keygen", "-l", "-f", "-")
cmd.Stdin = strings.NewReader(key)
var stdoutStderr []byte
stdoutStderr, err = cmd.CombinedOutput()
if err != nil {
if _, ok := err.(*exec.ExitError); ok {
err = errors.New(string(stdoutStderr))
}
return
}
chunks := bytes.Fields(stdoutStderr)
keytype := string(chunks[len(chunks)-1])
minkeysize := 2048
if keytype == "(ED25519)" || keytype == "(ECDSA)" {
minkeysize = 256
}
var bits int
if bits, err = strconv.Atoi(string(chunks[0])); err != nil {
return
} else if bits < minkeysize {
err = errors.New("Keysize too small")
return
}
// Sanitize the given key
keyf := strings.Fields(key)
if len(keyf) < 2 {
err = errors.New("Unexpected key file, this should never happen")
return
}
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
} else if kid, err := res.LastInsertId(); err != nil {
return StudentKey{}, err
} else {
s.UnlockNewChallenge(9, "")
return StudentKey{kid, s.Id, key, time.Now()}, nil
}
}
func (k StudentKey) GetStudent() (Student, error) {
return GetStudent(int(k.IdStudent))
}
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 {
return 0, err
} else {
return nb, err
}
}
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 {
return 0, err
} else {
return nb, err
}
}

177
libadlin/students.go Normal file
View File

@ -0,0 +1,177 @@
package adlin
import (
"crypto/hmac"
"crypto/sha512"
"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"`
}
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 {
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 {
return
}
students = append(students, s)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
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)
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)
return
}
func StudentExists(login string) bool {
var z int
err := DBQueryRow("SELECT 1 FROM students WHERE login=?", login).Scan(&z)
return err == nil && z == 1
}
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
} else if sid, err := res.LastInsertId(); err != nil {
return Student{}, err
} else {
return Student{sid, login, &t, nil, nil}, nil
}
}
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 {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}
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 {
return 0, err
} else {
return nb, err
}
}
func ClearStudents() (int64, error) {
if res, err := DBExec("DELETE FROM students"); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}
type UnlockedChallenge struct {
Id int64 `json:"id,omitempty"`
IdStudent int64 `json:"id_student"`
Challenge int `json:"challenge,omitempty"`
Time time.Time `json:"time"`
Value interface{} `json:"value,omitempty"`
}
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.IdStudent = s.Id
if err = rows.Scan(&u.Id, &u.Challenge, &u.Time); 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.IdStudent = s.Id
if err = rows.Scan(&u.Id, &u.Challenge, &u.Time, &u.Value); err != nil {
return
}
ucs = append(ucs, u)
}
if err = rows.Err(); err != nil {
return
}
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
} else if utid, err := res.LastInsertId(); err != nil {
return UnlockedChallenge{}, err
} else {
return UnlockedChallenge{utid, s.Id, challenge, time.Now(), value}, err
}
}
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
} else {
return UnlockedChallenge{0, s.Id, challenge, time.Now(), value}, err
}
}
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 {
return err
} else {
return err
}
}

View File

@ -8,6 +8,8 @@ import (
"time" "time"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"git.nemunai.re/lectures/adlin/libadlin"
) )
var AuthFunc = checkAuth var AuthFunc = checkAuth
@ -20,7 +22,7 @@ func init() {
router.POST("/api/auth/logout", apiRawHandler(logout)) router.POST("/api/auth/logout", apiRawHandler(logout))
} }
func validateAuthToken(s Student, _ httprouter.Params, _ []byte) (interface{}, error) { func validateAuthToken(s adlin.Student, _ httprouter.Params, _ []byte) (interface{}, error) {
return s, nil return s, nil
} }
@ -28,8 +30,8 @@ func logout(w http.ResponseWriter, ps httprouter.Params, body []byte) (interface
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: "auth", Name: "auth",
Value: "", Value: "",
Path: baseURL, Path: baseURL + "/",
Expires: time.Unix(0,0), Expires: time.Unix(0, 0),
Secure: true, Secure: true,
HttpOnly: true, HttpOnly: true,
}) })
@ -42,18 +44,18 @@ type loginForm struct {
Password string Password string
} }
func completeAuth(w http.ResponseWriter, username string, session *Session) (err error) { func completeAuth(w http.ResponseWriter, username string, session *adlin.Session) (err error) {
var std Student var std adlin.Student
if !studentExists(username) { if !adlin.StudentExists(username) {
if std, err = NewStudent(username); err != nil { if std, err = adlin.NewStudent(username); err != nil {
return err return err
} }
} else if std, err = getStudentByLogin(username); err != nil { } else if std, err = adlin.GetStudentByLogin(username); err != nil {
return err return err
} }
if session == nil { if session == nil {
var s Session var s adlin.Session
s, err = std.NewSession() s, err = std.NewSession()
session = &s session = &s
} else { } else {
@ -67,7 +69,7 @@ func completeAuth(w http.ResponseWriter, username string, session *Session) (err
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: "auth", Name: "auth",
Value: base64.StdEncoding.EncodeToString(session.Id), Value: base64.StdEncoding.EncodeToString(session.Id),
Path: baseURL, Path: baseURL + "/",
Expires: time.Now().Add(30 * 24 * time.Hour), Expires: time.Now().Add(30 * 24 * time.Hour),
Secure: true, Secure: true,
HttpOnly: true, HttpOnly: true,
@ -91,7 +93,7 @@ func checkAuth(w http.ResponseWriter, _ httprouter.Params, body []byte) (interfa
return nil, err return nil, err
} }
if r, err := http.NewRequest("GET", "https://fic.srs.epita.fr/2021/", nil); err != nil { if r, err := http.NewRequest("GET", "https://owncloud.srs.epita.fr/remote.php/dav/", nil); err != nil {
return nil, err return nil, err
} else { } else {
r.SetBasicAuth(lf.Username, lf.Password) r.SetBasicAuth(lf.Username, lf.Password)

View File

@ -12,6 +12,8 @@ import (
"github.com/coreos/go-oidc" "github.com/coreos/go-oidc"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"git.nemunai.re/lectures/adlin/libadlin"
) )
var ( var (
@ -57,7 +59,7 @@ func initializeOIDC() {
} }
func redirectOIDC_CRI(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { func redirectOIDC_CRI(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
session, err := NewSession() session, err := adlin.NewSession()
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("{'errmsg':%q}", err.Error()), http.StatusInternalServerError) http.Error(w, fmt.Sprintf("{'errmsg':%q}", err.Error()), http.StatusInternalServerError)
} else { } else {
@ -72,7 +74,7 @@ func OIDC_CRI_complete(w http.ResponseWriter, r *http.Request, ps httprouter.Par
return return
} }
session, err := getSession(idsession) session, err := adlin.GetSession(idsession)
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("{'errmsg':%q}", err.Error()), http.StatusBadRequest) http.Error(w, fmt.Sprintf("{'errmsg':%q}", err.Error()), http.StatusBadRequest)
return return

View File

@ -15,27 +15,29 @@ import (
"time" "time"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"git.nemunai.re/lectures/adlin/libadlin"
) )
const IPgwDMZ = "172.23.200.1" const IPgwDMZ = "172.23.200.1"
type Challenge struct { type Challenge struct {
Accessible []func(*Student, *http.Request) error Accessible []func(*adlin.Student, *http.Request) error
Check func(*Student, *givenToken, int) error Check func(*adlin.Student, *givenToken, int) error
} }
/* Restrictions */ /* Restrictions */
func noAccessRestriction(*Student, *http.Request) error { func noAccessRestriction(*adlin.Student, *http.Request) error {
return nil return nil
} }
func noAccess(*Student, *http.Request) error { func noAccess(*adlin.Student, *http.Request) error {
return errors.New("This challenge cannot be accessed this way. ") return errors.New("This challenge cannot be accessed this way. ")
} }
func accessFrom(ip string) func(_ *Student, r *http.Request) error { func accessFrom(ip string) func(_ *adlin.Student, r *http.Request) error {
return func(_ *Student, r *http.Request) error { return func(_ *adlin.Student, r *http.Request) error {
if r.Header.Get("X-Forwarded-By") != ip { if r.Header.Get("X-Forwarded-By") != ip {
return errors.New("This challenge is not accessible this way.") return errors.New("This challenge is not accessible this way.")
} }
@ -43,8 +45,8 @@ func accessFrom(ip string) func(_ *Student, r *http.Request) error {
} }
} }
func notAccessFrom(ip string) func(_ *Student, r *http.Request) error { func notAccessFrom(ip string) func(_ *adlin.Student, r *http.Request) error {
return func(_ *Student, r *http.Request) error { return func(_ *adlin.Student, r *http.Request) error {
if r.Header.Get("X-Forwarded-By") == ip { if r.Header.Get("X-Forwarded-By") == ip {
return errors.New("This challenge is not accessible this way.") return errors.New("This challenge is not accessible this way.")
} }
@ -52,8 +54,8 @@ func notAccessFrom(ip string) func(_ *Student, r *http.Request) error {
} }
} }
func maxProxy(nb int) func(_ *Student, r *http.Request) error { func maxProxy(nb int) func(_ *adlin.Student, r *http.Request) error {
return func(_ *Student, r *http.Request) error { return func(_ *adlin.Student, r *http.Request) error {
if len(strings.Split(r.Header.Get("X-Forwarded-For"), ",")) > nb { if len(strings.Split(r.Header.Get("X-Forwarded-For"), ",")) > nb {
return errors.New("This challenge is not accessible this way.") return errors.New("This challenge is not accessible this way.")
} }
@ -61,50 +63,49 @@ func maxProxy(nb int) func(_ *Student, r *http.Request) error {
} }
} }
func sslOnly(_ *Student, r *http.Request) error { func sslOnly(_ *adlin.Student, r *http.Request) error {
if r.Header.Get("X-Forwarded-Proto") != "https" { if r.Header.Get("X-Forwarded-Proto") != "https" {
return errors.New("This challenge should be performed over TLS.") return errors.New("This challenge should be performed over TLS.")
} }
return nil return nil
} }
/* Challenges */ /* Challenges */
func challenge42(s *Student, t *givenToken, chid int) error { func challenge42(s *adlin.Student, t *givenToken, chid int) error {
pkey := s.GetPKey() pkey := s.GetPKey()
if expectedToken, err := GenerateToken(pkey, chid, []byte("42")); err != nil { if expectedToken, err := GenerateToken(pkey, chid, []byte("42")); err != nil {
return err return err
} else if ! hmac.Equal(expectedToken, t.token) { } else if !hmac.Equal(expectedToken, t.token) {
return errors.New("This is not the expected token.") return errors.New("This is not the expected token.")
} else { } else {
return nil return nil
} }
} }
func challengeDNS(s *Student, t *givenToken, chid int) error { func challengeDNS(s *adlin.Student, t *givenToken, chid int) error {
pkey := s.GetPKey() pkey := s.GetPKey()
if expectedToken, err := GenerateToken(pkey, chid, []byte("8dde678132d6c558fc6adaeb9f1d53bf6ec7b876308cf98c48604caa9138523c1ce58b672c87c7e7d9b7248b81804d3940dbf20bf263eeb683244f7c1143712d")); err != nil { if expectedToken, err := GenerateToken(pkey, chid, []byte("8dde678132d6c558fc6adaeb9f1d53bf6ec7b876308cf98c48604caa9138523c1ce58b672c87c7e7d9b7248b81804d3940dbf20bf263eeb683244f7c1143712d")); err != nil {
return err return err
} else if ! hmac.Equal(expectedToken, t.token) { } else if !hmac.Equal(expectedToken, t.token) {
return errors.New("This is not the expected token.") return errors.New("This is not the expected token.")
} }
return nil return nil
} }
func challengeTime(s *Student, t *givenToken, chid int) error { func challengeTime(s *adlin.Student, t *givenToken, chid int) error {
pkey := s.GetPKey() pkey := s.GetPKey()
if expectedToken, err := GenerateToken(pkey, chid, []byte(t.Data[0])); err != nil { if expectedToken, err := GenerateToken(pkey, chid, []byte(t.Data[0])); err != nil {
return err return err
} else if ! hmac.Equal(expectedToken, t.token) { } else if !hmac.Equal(expectedToken, t.token) {
return errors.New("This is not the expected token.") return errors.New("This is not the expected token.")
} else if t, err := strconv.ParseInt(t.Data[0], 10, 64); err != nil { } else if t, err := strconv.ParseInt(t.Data[0], 10, 64); err != nil {
return err return err
} else { } else {
var rt time.Time var rt time.Time
if t > 3000000000 { if t > 3000000000 {
rt = time.Unix(t / 1000000000, t % 1000000000) rt = time.Unix(t/1000000000, t%1000000000)
} else { } else {
rt = time.Unix(t, 0) rt = time.Unix(t, 0)
} }
@ -117,7 +118,7 @@ func challengeTime(s *Student, t *givenToken, chid int) error {
} }
} }
func challengePing(s *Student, t *givenToken, chid int) error { func challengePing(s *adlin.Student, t *givenToken, chid int) error {
var expected []byte var expected []byte
switch s.Id % 5 { switch s.Id % 5 {
case 1: case 1:
@ -164,7 +165,7 @@ func challengePing(s *Student, t *givenToken, chid int) error {
} }
} }
func challengeDisk(s *Student, t *givenToken, chid int) error { func challengeDisk(s *adlin.Student, t *givenToken, chid int) error {
pkey := fmt.Sprintf("%x", s.GetPKey()) pkey := fmt.Sprintf("%x", s.GetPKey())
n1, err := strconv.Atoi(t.Data[0][0:2]) n1, err := strconv.Atoi(t.Data[0][0:2])
@ -182,19 +183,19 @@ func challengeDisk(s *Student, t *givenToken, chid int) error {
} }
if n1+n2 > len(pkey) { if n1+n2 > len(pkey) {
n2 = len(pkey)-n1 n2 = len(pkey) - n1
} }
expectedToken := sha512.Sum512([]byte(pkey[n1:n1+n2])) expectedToken := sha512.Sum512([]byte(pkey[n1 : n1+n2]))
if ! hmac.Equal(expectedToken[:], sum) { if !hmac.Equal(expectedToken[:], sum) {
return errors.New("This is not the expected token.") return errors.New("This is not the expected token.")
} else { } else {
return nil return nil
} }
} }
func challengeEMail(s *Student, t *givenToken, chid int) error { func challengeEMail(s *adlin.Student, t *givenToken, chid int) error {
return errors.New("This is not the expected token.") return errors.New("This is not the expected token.")
} }
@ -204,61 +205,61 @@ func init() {
challenges = []Challenge{ challenges = []Challenge{
/* Challenge 1 : 42 */ /* Challenge 1 : 42 */
Challenge{ Challenge{
Accessible: []func(*Student, *http.Request) error{noAccessRestriction}, Accessible: []func(*adlin.Student, *http.Request) error{noAccessRestriction},
Check: challenge42, Check: challenge42,
}, },
/* Challenge 2 : 42 from DMZ */ /* Challenge 2 : 42 from DMZ */
Challenge{ Challenge{
Accessible: []func(*Student, *http.Request) error{accessFrom(IPgwDMZ)}, Accessible: []func(*adlin.Student, *http.Request) error{accessFrom(IPgwDMZ)},
Check: challenge42, Check: challenge42,
}, },
/* Challenge 3 : ssl (+ ntp) */ /* Challenge 3 : ssl (+ ntp) */
Challenge{ Challenge{
Accessible: []func(*Student, *http.Request) error{accessFrom(IPgwDMZ), sslOnly}, Accessible: []func(*adlin.Student, *http.Request) error{accessFrom(IPgwDMZ), sslOnly},
Check: challengeTime, Check: challengeTime,
}, },
/* Challenge 4 : DNS TXT */ /* Challenge 4 : DNS TXT */
Challenge{ Challenge{
Accessible: []func(*Student, *http.Request) error{accessFrom(IPgwDMZ), sslOnly}, Accessible: []func(*adlin.Student, *http.Request) error{accessFrom(IPgwDMZ), sslOnly},
Check: challengeDNS, Check: challengeDNS,
}, },
/* Challenge 5 : time net */ /* Challenge 5 : time net */
Challenge{ Challenge{
Accessible: []func(*Student, *http.Request) error{maxProxy(1)}, Accessible: []func(*adlin.Student, *http.Request) error{maxProxy(1)},
Check: challengeTime, Check: challengeTime,
}, },
/* Bonus 0 : toctoc (read in source code) */ /* Bonus 0 : toctoc (read in source code) */
Challenge{ Challenge{
Accessible: []func(*Student, *http.Request) error{noAccessRestriction}, Accessible: []func(*adlin.Student, *http.Request) error{noAccessRestriction},
Check: challenge42, Check: challenge42,
}, },
/* Bonus 1 : echo request */ /* Bonus 1 : echo request */
Challenge{ Challenge{
Accessible: []func(*Student, *http.Request) error{noAccessRestriction}, Accessible: []func(*adlin.Student, *http.Request) error{noAccessRestriction},
Check: challengePing, Check: challengePing,
}, },
/* Bonus 2 : disk */ /* Bonus 2 : disk */
Challenge{ Challenge{
Accessible: []func(*Student, *http.Request) error{noAccessRestriction}, Accessible: []func(*adlin.Student, *http.Request) error{noAccessRestriction},
Check: challengeDisk, Check: challengeDisk,
}, },
/* Bonus 3 : mail */ /* Bonus 3 : mail */
Challenge{ Challenge{
Accessible: []func(*Student, *http.Request) error{noAccessRestriction}, Accessible: []func(*adlin.Student, *http.Request) error{noAccessRestriction},
Check: challengeEMail, Check: challengeEMail,
}, },
/* Last : SSH key, see ssh.go:156 in NewKey function */ /* Last : SSH key, see ssh.go:156 in NewKey function */
Challenge{ Challenge{
Accessible: []func(*Student, *http.Request) error{noAccess}, Accessible: []func(*adlin.Student, *http.Request) error{noAccess},
}, },
} }
@ -293,7 +294,7 @@ func accessibleChallenge(r *http.Request, ps httprouter.Params, _ []byte) (inter
} else if chid == 0 || chid >= len(challenges) { } else if chid == 0 || chid >= len(challenges) {
return nil, errors.New("This challenge doesn't exist") return nil, errors.New("This challenge doesn't exist")
} else { } else {
for _, a := range challenges[chid - 1].Accessible { for _, a := range challenges[chid-1].Accessible {
if err := a(nil, r); err != nil { if err := a(nil, r); err != nil {
return nil, err return nil, err
} }
@ -319,13 +320,13 @@ func receiveChallenge(r *http.Request, ps httprouter.Params, body []byte) (inter
return nil, errors.New("This is not the expected token.") return nil, errors.New("This is not the expected token.")
} }
var std Student var std adlin.Student
if stdid, err := strconv.Atoi(gt.Login); err == nil { if stdid, err := strconv.Atoi(gt.Login); err == nil {
if std, err = getStudent(stdid); err != nil { if std, err = adlin.GetStudent(stdid); err != nil {
return nil, err return nil, err
} }
} else if std, err = getStudentByLogin(gt.Login); err != nil { } else if std, err = adlin.GetStudentByLogin(gt.Login); err != nil {
return nil, err return nil, err
} }
@ -355,21 +356,21 @@ func receiveToken(r *http.Request, body []byte, chid int) (interface{}, error) {
chid = gt.Challenge chid = gt.Challenge
} }
if chid <= 0 || chid - 1 > len(challenges) { if chid <= 0 || chid-1 > len(challenges) {
return nil, errors.New("This challenge doesn't exist") return nil, errors.New("This challenge doesn't exist")
} }
// Is the challenge accessible? // Is the challenge accessible?
for _, a := range challenges[chid - 1].Accessible { for _, a := range challenges[chid-1].Accessible {
if err := a(nil, r); err != nil { if err := a(nil, r); err != nil {
return nil, err return nil, err
} }
} }
if std, err := getStudentByLogin(gt.Login); err != nil { if std, err := adlin.GetStudentByLogin(gt.Login); err != nil {
return nil, err return nil, err
} else { } else {
if err := challenges[chid - 1].Check(&std, &gt, chid); err != nil { if err := challenges[chid-1].Check(&std, &gt, chid); err != nil {
log.Printf("%s just try ch#%d: %s\n", std.Login, chid, err) log.Printf("%s just try ch#%d: %s\n", std.Login, chid, err)
return nil, err return nil, err
} }

View File

@ -11,104 +11,104 @@ import (
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"github.com/miekg/dns" "github.com/miekg/dns"
"git.nemunai.re/lectures/adlin/libadlin"
) )
const ( const (
AssociatedDomainSuffix = "adlin2021.p0m.fr."
DelegatedDomainSuffix = "srs.p0m.fr."
ControlSocket = "[2a01:e0a:2b:2250::b]:53" ControlSocket = "[2a01:e0a:2b:2250::b]:53"
) )
var tsigSecret = map[string]string{"ddns.": "so6ZGir4GPAqINNh9U5c3A=="} var tsigSecret = map[string]string{"ddns.": "so6ZGir4GPAqINNh9U5c3A=="}
func init() { func init() {
router.GET("/api/adomains/", apiAuthHandler(func(student 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 return student.GetAssociatedDomains(), nil
})) }))
router.POST("/api/adomains/", apiAuthHandler(func(student Student, ps httprouter.Params, body []byte) (interface{}, error) { router.POST("/api/adomains/", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
return true, student.AddAssociatedDomains() return true, AddAssociatedDomains(student)
})) }))
router.GET("/api/adomains/:dn", apiAuthHandler(func(student 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 student.GetAssociatedDomain(ps.ByName("dn")) return GetAssociatedDomain(student, ps.ByName("dn"))
})) }))
router.GET("/api/ddomains/", apiAuthHandler(func(student 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 return []string{student.MyDelegatedDomain()}, nil
})) }))
router.GET("/api/ddomains/:dn/", apiAuthHandler(func(student Student, ps httprouter.Params, body []byte) (interface{}, error) { router.GET("/api/ddomains/:dn/", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
return student.getRRDelegatedDomain(ps.ByName("dn"), "") return getRRDelegatedDomain(student, ps.ByName("dn"), "")
})) }))
router.GET("/api/ddomains/:dn/NS", apiAuthHandler(func(student 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 student.getRRDelegatedDomain(ps.ByName("dn"), "NS") return getRRDelegatedDomain(student, ps.ByName("dn"), "NS")
})) }))
router.POST("/api/ddomains/:dn/NS", apiAuthHandler(func(student 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 var ue Entry
if err := json.Unmarshal(body, &ue); err != nil { if err := json.Unmarshal(body, &ue); err != nil {
return nil, err return nil, err
} }
return true, student.AddNSDelegatedDomain(ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " ")) return true, AddNSDelegatedDomain(student, ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " "))
})) }))
router.PATCH("/api/ddomains/:dn/NS", apiAuthHandler(func(student 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 var ue Entry
if err := json.Unmarshal(body, &ue); err != nil { if err := json.Unmarshal(body, &ue); err != nil {
return nil, err return nil, err
} }
return true, student.UpdateNSDelegatedDomain(ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, "")) return true, UpdateNSDelegatedDomain(student, ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, ""))
})) }))
router.DELETE("/api/ddomains/:dn/NS", apiAuthHandler(func(student 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 var ue Entry
if err := json.Unmarshal(body, &ue); err != nil { if err := json.Unmarshal(body, &ue); err != nil {
return nil, err return nil, err
} }
return true, student.DeleteRRDelegatedDomain(ps.ByName("dn"), "NS", strings.Join(ue.Values, " ")) return true, DeleteRRDelegatedDomain(student, ps.ByName("dn"), "NS", strings.Join(ue.Values, " "))
})) }))
router.GET("/api/ddomains/:dn/GLUE", apiAuthHandler(func(student 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 student.getRRDelegatedDomain(ps.ByName("dn"), "AAAA") return getRRDelegatedDomain(student, ps.ByName("dn"), "AAAA")
})) }))
router.POST("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student 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 var ue Entry
if err := json.Unmarshal(body, &ue); err != nil { if err := json.Unmarshal(body, &ue); err != nil {
return nil, err return nil, err
} }
return true, student.AddGLUEDelegatedDomain(ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " ")) return true, AddGLUEDelegatedDomain(student, ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " "))
})) }))
router.PATCH("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student 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 var ue Entry
if err := json.Unmarshal(body, &ue); err != nil { if err := json.Unmarshal(body, &ue); err != nil {
return nil, err return nil, err
} }
return true, student.UpdateGLUEDelegatedDomain(ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, " ")) return true, UpdateGLUEDelegatedDomain(student, ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, " "))
})) }))
router.POST("/api/ddomains/:dn/GLUE", apiAuthHandler(func(student 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 var ue Entry
if err := json.Unmarshal(body, &ue); err != nil { if err := json.Unmarshal(body, &ue); err != nil {
return nil, err return nil, err
} }
return true, student.UpdateGLUEDelegatedDomain(ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, " ")) return true, UpdateGLUEDelegatedDomain(student, ps.ByName("dn"), ue.TTL, ue.ValuesFrom, strings.Join(ue.Values, " "))
})) }))
router.DELETE("/api/ddomains/:dn/AAAA", apiAuthHandler(func(student 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 var ue Entry
if err := json.Unmarshal(body, &ue); err != nil { if err := json.Unmarshal(body, &ue); err != nil {
return nil, err return nil, err
} }
return true, student.DeleteRRDelegatedDomain(ps.ByName("dn"), "AAAA", strings.Join(ue.Values, " ")) return true, DeleteRRDelegatedDomain(student, ps.ByName("dn"), "AAAA", strings.Join(ue.Values, " "))
})) }))
router.GET("/api/ddomains/:dn/DS", apiAuthHandler(func(student 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 student.getRRDelegatedDomain(ps.ByName("dn"), "DS") return getRRDelegatedDomain(student, ps.ByName("dn"), "DS")
})) }))
router.POST("/api/ddomains/:dn/DS", apiAuthHandler(func(student 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 var ue Entry
if err := json.Unmarshal(body, &ue); err != nil { if err := json.Unmarshal(body, &ue); err != nil {
return nil, err return nil, err
} }
return true, student.AddDSDelegatedDomain(ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " ")) return true, AddDSDelegatedDomain(student, ps.ByName("dn"), ue.TTL, strings.Join(ue.Values, " "))
})) }))
router.DELETE("/api/ddomains/:dn/DS", apiAuthHandler(func(student 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 var ue Entry
if err := json.Unmarshal(body, &ue); err != nil { if err := json.Unmarshal(body, &ue); err != nil {
return nil, err return nil, err
} }
return true, student.DeleteRRDelegatedDomain(ps.ByName("dn"), "DS", strings.Join(ue.Values, " ")) return true, DeleteRRDelegatedDomain(student, ps.ByName("dn"), "DS", strings.Join(ue.Values, " "))
})) }))
} }
@ -160,19 +160,7 @@ func parseZoneRead(globalDomain string, domain string) (rr []Entry, err error) {
return return
} }
func (student Student) myAssociatedDomain() string { func GetAssociatedDomain(student adlin.Student, dn string) (rrs []Entry, err error) {
return fmt.Sprintf("%s.%s", strings.Trim(strings.Replace(student.Login, "_", "-", -1), "-_"), AssociatedDomainSuffix)
}
func (student Student) GetAssociatedDomains() (ds []string) {
studentDomain := student.myAssociatedDomain()
ds = append(ds, studentDomain)
return
}
func (student Student) GetAssociatedDomain(dn string) (rrs []Entry, err error) {
domains := student.GetAssociatedDomains() domains := student.GetAssociatedDomains()
found := false found := false
for _, d := range domains { for _, d := range domains {
@ -185,7 +173,7 @@ func (student Student) GetAssociatedDomain(dn string) (rrs []Entry, err error) {
err = errors.New(fmt.Sprintf("Unable to find domain %q.", dn)) err = errors.New(fmt.Sprintf("Unable to find domain %q.", dn))
} }
if entries, errr := parseZoneRead(AssociatedDomainSuffix, dn); err != nil { if entries, errr := parseZoneRead(adlin.AssociatedDomainSuffix, dn); err != nil {
return nil, errr return nil, errr
} else { } else {
for _, e := range entries { for _, e := range entries {
@ -198,19 +186,19 @@ func (student Student) GetAssociatedDomain(dn string) (rrs []Entry, err error) {
return return
} }
func (student Student) AddAssociatedDomains() (err error) { func AddAssociatedDomains(student adlin.Student) (err error) {
m1 := new(dns.Msg) m1 := new(dns.Msg)
m1.Id = dns.Id() m1.Id = dns.Id()
m1.Opcode = dns.OpcodeUpdate m1.Opcode = dns.OpcodeUpdate
m1.Question = make([]dns.Question, 1) m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{AssociatedDomainSuffix, dns.TypeSOA, dns.ClassINET} m1.Question[0] = dns.Question{adlin.AssociatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
rrAd := new(dns.A) rrAd := new(dns.A)
rrAd.Hdr = dns.RR_Header{Name: student.myAssociatedDomain(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0} rrAd.Hdr = dns.RR_Header{Name: student.MyAssociatedDomain(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0}
m1.Remove([]dns.RR{rrAd}) m1.Remove([]dns.RR{rrAd})
rrAAAAd := new(dns.AAAA) rrAAAAd := new(dns.AAAA)
rrAAAAd.Hdr = dns.RR_Header{Name: student.myAssociatedDomain(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 0} rrAAAAd.Hdr = dns.RR_Header{Name: student.MyAssociatedDomain(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 0}
m1.Remove([]dns.RR{rrAAAAd}) m1.Remove([]dns.RR{rrAAAAd})
c := new(dns.Client) c := new(dns.Client)
@ -226,16 +214,16 @@ func (student Student) AddAssociatedDomains() (err error) {
m2.Id = dns.Id() m2.Id = dns.Id()
m2.Opcode = dns.OpcodeUpdate m2.Opcode = dns.OpcodeUpdate
m2.Question = make([]dns.Question, 1) m2.Question = make([]dns.Question, 1)
m2.Question[0] = dns.Question{AssociatedDomainSuffix, dns.TypeSOA, dns.ClassINET} m2.Question[0] = dns.Question{adlin.AssociatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
rrA := new(dns.A) 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.MyAssociatedDomain(), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 3600}
rrA.A = net.IPv4(82, 64, 31, 248) rrA.A = net.IPv4(82, 64, 31, 248)
m2.Insert([]dns.RR{rrA}) m2.Insert([]dns.RR{rrA})
rrAAAA := new(dns.AAAA) 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.MyAssociatedDomain(), Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 3600}
rrAAAA.AAAA = studentIP(student.Id) rrAAAA.AAAA = adlin.StudentIP(student.Id)
rrAAAA.AAAA[15] = 1 rrAAAA.AAAA[15] = 1
m2.Insert([]dns.RR{rrAAAA}) m2.Insert([]dns.RR{rrAAAA})
@ -247,11 +235,7 @@ func (student Student) AddAssociatedDomains() (err error) {
return return
} }
func (student Student) MyDelegatedDomain() string { func getRRDelegatedDomain(student adlin.Student, dn string, rr string) (rrs []Entry, err error) {
return fmt.Sprintf("%s.%s", strings.Trim(strings.Replace(student.Login, "_", "-", -1), "-_"), DelegatedDomainSuffix)
}
func (student Student) getRRDelegatedDomain(dn string, rr string) (rrs []Entry, err error) {
domains := []string{student.MyDelegatedDomain()} domains := []string{student.MyDelegatedDomain()}
found := false found := false
for _, d := range domains { for _, d := range domains {
@ -264,7 +248,7 @@ func (student Student) getRRDelegatedDomain(dn string, rr string) (rrs []Entry,
err = errors.New(fmt.Sprintf("Unable to find domain %q.", dn)) err = errors.New(fmt.Sprintf("Unable to find domain %q.", dn))
} }
if entries, errr := parseZoneRead(DelegatedDomainSuffix, dn); err != nil { if entries, errr := parseZoneRead(adlin.DelegatedDomainSuffix, dn); err != nil {
return nil, errr return nil, errr
} else { } else {
for _, e := range entries { for _, e := range entries {
@ -277,13 +261,13 @@ func (student Student) getRRDelegatedDomain(dn string, rr string) (rrs []Entry,
return return
} }
func (student Student) AddNSDelegatedDomain(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()} { for _, d := range []string{student.MyDelegatedDomain()} {
m1 := new(dns.Msg) m1 := new(dns.Msg)
m1.Id = dns.Id() m1.Id = dns.Id()
m1.Opcode = dns.OpcodeUpdate m1.Opcode = dns.OpcodeUpdate
m1.Question = make([]dns.Question, 1) m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET} m1.Question[0] = dns.Question{adlin.DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
rrNS := new(dns.NS) rrNS := new(dns.NS)
rrNS.Hdr = dns.RR_Header{Name: d, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: ttl} rrNS.Hdr = dns.RR_Header{Name: d, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: ttl}
@ -300,13 +284,13 @@ func (student Student) AddNSDelegatedDomain(dn string, ttl uint32, ns string) (e
return return
} }
func (student Student) UpdateNSDelegatedDomain(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()} { for _, d := range []string{student.MyDelegatedDomain()} {
m1 := new(dns.Msg) m1 := new(dns.Msg)
m1.Id = dns.Id() m1.Id = dns.Id()
m1.Opcode = dns.OpcodeUpdate m1.Opcode = dns.OpcodeUpdate
m1.Question = make([]dns.Question, 1) m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET} m1.Question[0] = dns.Question{adlin.DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
rrOldNS := new(dns.NS) rrOldNS := new(dns.NS)
rrOldNS.Hdr = dns.RR_Header{Name: d, Rrtype: dns.TypeNS, Class: dns.ClassINET} rrOldNS.Hdr = dns.RR_Header{Name: d, Rrtype: dns.TypeNS, Class: dns.ClassINET}
@ -328,7 +312,7 @@ func (student Student) UpdateNSDelegatedDomain(dn string, ttl uint32, oldns stri
return return
} }
func (student Student) AddGLUEDelegatedDomain(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()} domains := []string{student.MyDelegatedDomain()}
found := false found := false
for _, d := range domains { for _, d := range domains {
@ -346,7 +330,7 @@ func (student Student) AddGLUEDelegatedDomain(dn string, ttl uint32, aaaa string
m1.Id = dns.Id() m1.Id = dns.Id()
m1.Opcode = dns.OpcodeUpdate m1.Opcode = dns.OpcodeUpdate
m1.Question = make([]dns.Question, 1) m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET} m1.Question[0] = dns.Question{adlin.DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
var rr dns.RR var rr dns.RR
rr, err = dns.NewRR(fmt.Sprintf("%s %d IN AAAA %s", dn, ttl, aaaa)) rr, err = dns.NewRR(fmt.Sprintf("%s %d IN AAAA %s", dn, ttl, aaaa))
@ -364,7 +348,7 @@ func (student Student) AddGLUEDelegatedDomain(dn string, ttl uint32, aaaa string
return return
} }
func (student Student) UpdateGLUEDelegatedDomain(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()} domains := []string{student.MyDelegatedDomain()}
found := false found := false
for _, d := range domains { for _, d := range domains {
@ -382,7 +366,7 @@ func (student Student) UpdateGLUEDelegatedDomain(dn string, ttl uint32, oldaaaa
m1.Id = dns.Id() m1.Id = dns.Id()
m1.Opcode = dns.OpcodeUpdate m1.Opcode = dns.OpcodeUpdate
m1.Question = make([]dns.Question, 1) m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET} m1.Question[0] = dns.Question{adlin.DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
var rr dns.RR var rr dns.RR
@ -406,7 +390,7 @@ func (student Student) UpdateGLUEDelegatedDomain(dn string, ttl uint32, oldaaaa
return return
} }
func (student Student) AddDSDelegatedDomain(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()} domains := []string{student.MyDelegatedDomain()}
found := false found := false
for _, d := range domains { for _, d := range domains {
@ -438,7 +422,7 @@ func (student Student) AddDSDelegatedDomain(dn string, ttl uint32, rdata string)
m1.Id = dns.Id() m1.Id = dns.Id()
m1.Opcode = dns.OpcodeUpdate m1.Opcode = dns.OpcodeUpdate
m1.Question = make([]dns.Question, 1) m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET} m1.Question[0] = dns.Question{adlin.DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
var ds *dns.DS var ds *dns.DS
ds = dnskey.ToDS(dns.SHA256) ds = dnskey.ToDS(dns.SHA256)
@ -456,7 +440,7 @@ func (student Student) AddDSDelegatedDomain(dn string, ttl uint32, rdata string)
return return
} }
func (student Student) DeleteRRDelegatedDomain(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()} domains := []string{student.MyDelegatedDomain()}
found := false found := false
for _, d := range domains { for _, d := range domains {
@ -474,7 +458,7 @@ func (student Student) DeleteRRDelegatedDomain(dn string, rr string, values ...s
m1.Id = dns.Id() m1.Id = dns.Id()
m1.Opcode = dns.OpcodeUpdate m1.Opcode = dns.OpcodeUpdate
m1.Question = make([]dns.Question, 1) m1.Question = make([]dns.Question, 1)
m1.Question[0] = dns.Question{DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET} m1.Question[0] = dns.Question{adlin.DelegatedDomainSuffix, dns.TypeSOA, dns.ClassINET}
rrr, errr := dns.NewRR(fmt.Sprintf("%s %s %s", dn, rr, strings.Join(values, " "))) rrr, errr := dns.NewRR(fmt.Sprintf("%s %s %s", dn, rr, strings.Join(values, " ")))
if errr != nil { if errr != nil {

View File

@ -2,6 +2,8 @@ package main
import ( import (
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"git.nemunai.re/lectures/adlin/libadlin"
) )
func init() { func init() {
@ -9,7 +11,7 @@ func init() {
} }
func computeGrades(_ httprouter.Params, _ []byte) (interface{}, error) { func computeGrades(_ httprouter.Params, _ []byte) (interface{}, error) {
if stds, err := getStudents(); err != nil { if stds, err := adlin.GetStudents(); err != nil {
return nil, err return nil, err
} else { } else {
res := map[string]map[string]float32{} res := map[string]map[string]float32{}
@ -20,7 +22,7 @@ func computeGrades(_ httprouter.Params, _ []byte) (interface{}, error) {
"TP2": 0, "TP2": 0,
} }
if states, err := std.getStatesByChallenge(); err != nil { if states, err := std.GetStatesByChallenge(); err != nil {
return nil, err return nil, err
} else { } else {
for _, st := range states { for _, st := range states {

View File

@ -14,6 +14,8 @@ import (
"time" "time"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"git.nemunai.re/lectures/adlin/libadlin"
) )
var router = httprouter.New() var router = httprouter.New()
@ -26,8 +28,8 @@ type DispatchFunction func(httprouter.Params, []byte) (interface{}, error)
func remoteValidatorHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params)) func(http.ResponseWriter, *http.Request, httprouter.Params) { func remoteValidatorHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params)) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
expectedMAC := hmac.New(sha512.New, []byte(sharedSecret)).Sum([]byte(fmt.Sprintf("%d", time.Now().Unix()/10))) expectedMAC := hmac.New(sha512.New, []byte(adlin.SharedSecret)).Sum([]byte(fmt.Sprintf("%d", time.Now().Unix()/10)))
previousMAC := hmac.New(sha512.New, []byte(sharedSecret)).Sum([]byte(fmt.Sprintf("%d", time.Now().Unix()/10-1))) previousMAC := hmac.New(sha512.New, []byte(adlin.SharedSecret)).Sum([]byte(fmt.Sprintf("%d", time.Now().Unix()/10-1)))
if aauth, err := base64.StdEncoding.DecodeString(r.Header.Get("X-ADLIN-Authentication")); err != nil { if aauth, err := base64.StdEncoding.DecodeString(r.Header.Get("X-ADLIN-Authentication")); err != nil {
http.Error(w, fmt.Sprintf("{\"errmsg\":%q}\n", err), http.StatusUnauthorized) http.Error(w, fmt.Sprintf("{\"errmsg\":%q}\n", err), http.StatusUnauthorized)
@ -39,7 +41,7 @@ func remoteValidatorHandler(f func(http.ResponseWriter, *http.Request, httproute
} }
} }
func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, []byte), access ...func(*Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) { func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, []byte), access ...func(*adlin.Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
if addr := r.Header.Get("X-Forwarded-For"); addr != "" { if addr := r.Header.Get("X-Forwarded-For"); addr != "" {
r.RemoteAddr = addr r.RemoteAddr = addr
@ -49,17 +51,17 @@ func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, []
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
// Read Authorization header // Read Authorization header
var student *Student = nil var student *adlin.Student = nil
if cookie, err := r.Cookie("auth"); err == nil { if cookie, err := r.Cookie("auth"); err == nil {
if sessionid, err := base64.StdEncoding.DecodeString(cookie.Value); err != nil { if sessionid, err := base64.StdEncoding.DecodeString(cookie.Value); err != nil {
http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusNotAcceptable) http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusNotAcceptable)
return return
} else if session, err := getSession(sessionid); err != nil { } else if session, err := adlin.GetSession(sessionid); err != nil {
http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusUnauthorized) http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusUnauthorized)
return return
} else if session.IdStudent == nil { } else if session.IdStudent == nil {
student = nil student = nil
} else if std, err := getStudent(int(*session.IdStudent)); err != nil { } else if std, err := adlin.GetStudent(int(*session.IdStudent)); err != nil {
http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusUnauthorized) http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusUnauthorized)
return return
} else { } else {
@ -132,41 +134,41 @@ func responseHandler(f func(*http.Request, httprouter.Params, []byte) (interface
} }
} }
func challengeHandler(f func (*http.Request, []byte, int) (interface{}, error)) func(*http.Request, httprouter.Params, []byte) (interface{}, error) { func challengeHandler(f func(*http.Request, []byte, int) (interface{}, error)) func(*http.Request, httprouter.Params, []byte) (interface{}, error) {
return func(r *http.Request, ps httprouter.Params, body []byte) (interface{}, error) { return func(r *http.Request, ps httprouter.Params, body []byte) (interface{}, error) {
return f(r, body, 0) return f(r, body, 0)
} }
} }
func definedChallengeHandler(f func (*http.Request, []byte, int) (interface{}, error), chid int) func(*http.Request, httprouter.Params, []byte) (interface{}, error) { func definedChallengeHandler(f func(*http.Request, []byte, int) (interface{}, error), chid int) func(*http.Request, httprouter.Params, []byte) (interface{}, error) {
return func(r *http.Request, ps httprouter.Params, body []byte) (interface{}, error) { return func(r *http.Request, ps httprouter.Params, body []byte) (interface{}, error) {
return f(r, body, chid) return f(r, body, chid)
} }
} }
func apiRawHandler(f func(http.ResponseWriter, httprouter.Params, []byte) (interface{}, error), access ...func(*Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) { func apiRawHandler(f func(http.ResponseWriter, httprouter.Params, []byte) (interface{}, error), access ...func(*adlin.Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return rawHandler(func (w http.ResponseWriter, r *http.Request, ps httprouter.Params, b []byte) { return rawHandler(func(w http.ResponseWriter, r *http.Request, ps httprouter.Params, b []byte) {
responseHandler(func (_ *http.Request, ps httprouter.Params, b []byte)(interface{}, error) { responseHandler(func(_ *http.Request, ps httprouter.Params, b []byte) (interface{}, error) {
return f(w, ps, b) return f(w, ps, b)
})(w, r, ps, b) })(w, r, ps, b)
}, access...) }, access...)
} }
func apiHandler(f DispatchFunction, access ...func(*Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) { func apiHandler(f DispatchFunction, access ...func(*adlin.Student, *http.Request) error) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return rawHandler(responseHandler(func (_ *http.Request, ps httprouter.Params, b []byte) (interface{}, error) { return f(ps, b) }), access...) return rawHandler(responseHandler(func(_ *http.Request, ps httprouter.Params, b []byte) (interface{}, error) { return f(ps, b) }), access...)
} }
func apiAuthHandler(f func(Student, httprouter.Params, []byte) (interface{}, error), access ...func(*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) { return rawHandler(responseHandler(func(r *http.Request, ps httprouter.Params, b []byte) (interface{}, error) {
if cookie, err := r.Cookie("auth"); err != nil { if cookie, err := r.Cookie("auth"); err != nil {
return nil, errors.New("Authorization required") return nil, errors.New("Authorization required")
} else if sessionid, err := base64.StdEncoding.DecodeString(cookie.Value); err != nil { } else if sessionid, err := base64.StdEncoding.DecodeString(cookie.Value); err != nil {
return nil, err return nil, err
} else if session, err := getSession(sessionid); err != nil { } else if session, err := adlin.GetSession(sessionid); err != nil {
return nil, err return nil, err
} else if session.IdStudent == nil { } else if session.IdStudent == nil {
return nil, errors.New("Authorization required") return nil, errors.New("Authorization required")
} else if std, err := getStudent(int(*session.IdStudent)); err != nil { } else if std, err := adlin.GetStudent(int(*session.IdStudent)); err != nil {
return nil, err return nil, err
} else { } else {
return f(std, ps, b) return f(std, ps, b)
@ -174,15 +176,15 @@ func apiAuthHandler(f func(Student, httprouter.Params, []byte) (interface{}, err
}), access...) }), access...)
} }
func studentHandler(f func(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) { return func(ps httprouter.Params, body []byte) (interface{}, error) {
if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil { if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil {
if student, err := getStudentByLogin(ps.ByName("sid")); err != nil { if student, err := adlin.GetStudentByLogin(ps.ByName("sid")); err != nil {
return nil, err return nil, err
} else { } else {
return f(student, body) return f(student, body)
} }
} else if student, err := getStudent(sid); err != nil { } else if student, err := adlin.GetStudent(sid); err != nil {
return nil, err return nil, err
} else { } else {
return f(student, body) return f(student, body)

View File

@ -4,23 +4,25 @@ import (
"fmt" "fmt"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"git.nemunai.re/lectures/adlin/libadlin"
) )
func init() { func init() {
router.GET("/api/ips", apiHandler(showIPs)) router.GET("/api/ips", apiHandler(showIPs))
router.GET("/api/students/:sid/ips", apiHandler(studentHandler(func(student Student, body []byte) (interface{}, error) { router.GET("/api/students/:sid/ips", apiHandler(studentHandler(func(student adlin.Student, body []byte) (interface{}, error) {
return getStudentIPs(student), nil return getStudentIPs(student), nil
}))) })))
} }
func (s Student) IPSuffix() int64 { func IPSuffix(s adlin.Student) int64 {
return s.Id * 5 + 10 return s.Id*4 + 10
} }
func showIPs(_ httprouter.Params, body []byte) (interface{}, error) { func showIPs(_ httprouter.Params, body []byte) (interface{}, error) {
r := make(map[string]map[string]string) r := make(map[string]map[string]string)
students, err := getStudents() students, err := adlin.GetStudents()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -40,13 +42,14 @@ func showIPs(_ httprouter.Params, body []byte) (interface{}, error) {
return r, nil return r, nil
} }
func getStudentIPs(student Student) (r map[string]string) { func getStudentIPs(student adlin.Student) (r map[string]string) {
r = make(map[string]string) r = make(map[string]string)
r["vlan0"] = fmt.Sprintf("172.23.0.%d", student.IPSuffix()) r["vlan0"] = fmt.Sprintf("172.23.0.%d", IPSuffix(student))
r["vlan7"] = fmt.Sprintf("172.23.142.%d", student.IPSuffix()) r["wg0"] = fmt.Sprintf("172.17.0.%d", IPSuffix(student))
r["wg"] = studentIP(student.Id).String() r["vlan7"] = fmt.Sprintf("172.23.142.%d", IPSuffix(student))
r["adn"] = student.myAssociatedDomain() r["wg"] = adlin.StudentIP(student.Id).String()
r["adn"] = student.MyAssociatedDomain()
r["ddn"] = student.MyDelegatedDomain() r["ddn"] = student.MyDelegatedDomain()
return return

View File

@ -12,11 +12,11 @@ import (
"path" "path"
"strings" "strings"
"syscall" "syscall"
"time"
"git.nemunai.re/lectures/adlin/libadlin"
) )
var baseURL string = "/" var baseURL string = "/"
var sharedSecret string
type ResponseWriterPrefix struct { type ResponseWriterPrefix struct {
real http.ResponseWriter real http.ResponseWriter
@ -60,9 +60,9 @@ func StripPrefix(prefix string, h http.Handler) http.Handler {
func main() { func main() {
var bind = flag.String("bind", ":8081", "Bind port/socket") var bind = flag.String("bind", ":8081", "Bind port/socket")
var dsn = flag.String("dsn", DSNGenerator(), "DSN to connect to the MySQL server") var dsn = flag.String("dsn", adlin.DSNGenerator(), "DSN to connect to the MySQL server")
flag.StringVar(&baseURL, "baseurl", baseURL, "URL prepended to each URL") flag.StringVar(&baseURL, "baseurl", baseURL, "URL prepended to each URL")
flag.StringVar(&sharedSecret, "sharedsecret", "adelina", "secret used to communicate with remote validator") flag.StringVar(&adlin.SharedSecret, "sharedsecret", "adelina", "secret used to communicate with remote validator")
flag.StringVar(&AuthorizedKeysLocation, "authorizedkeyslocation", AuthorizedKeysLocation, "File for allowing user to SSH to the machine") flag.StringVar(&AuthorizedKeysLocation, "authorizedkeyslocation", AuthorizedKeysLocation, "File for allowing user to SSH to the machine")
flag.StringVar(&SshPiperLocation, "sshPiperLocation", SshPiperLocation, "Directory containing directories for sshpiperd") flag.StringVar(&SshPiperLocation, "sshPiperLocation", SshPiperLocation, "Directory containing directories for sshpiperd")
var dummyauth = flag.Bool("dummyauth", false, "don't perform password check") var dummyauth = flag.Bool("dummyauth", false, "don't perform password check")
@ -88,13 +88,13 @@ func main() {
// Initialize contents // Initialize contents
log.Println("Opening database...") log.Println("Opening database...")
if err := DBInit(*dsn); err != nil { if err := adlin.DBInit(*dsn); err != nil {
log.Fatal("Cannot open the database: ", err) log.Fatal("Cannot open the database: ", err)
} }
defer DBClose() defer adlin.DBClose()
log.Println("Creating database...") log.Println("Creating database...")
if err := DBCreate(); err != nil { if err := adlin.DBCreate(); err != nil {
log.Fatal("Cannot create database: ", err) log.Fatal("Cannot create database: ", err)
} }
@ -107,14 +107,6 @@ func main() {
Handler: StripPrefix(baseURL, Router()), Handler: StripPrefix(baseURL, Router()),
} }
// Launch checker
go func() {
for {
studentsChecker()
time.Sleep(500 * time.Millisecond)
}
}()
// Serve content // Serve content
go func() { go func() {
log.Fatal(srv.ListenAndServe()) log.Fatal(srv.ListenAndServe())

View File

@ -3,21 +3,22 @@ package main
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"time"
"git.nemunai.re/lectures/adlin/libadlin"
) )
var PongSecret = "felixfixit" var PongSecret = "felixfixit"
func init() { func init() {
router.GET("/api/students/:sid/ping", apiHandler(studentHandler(lastPing))) router.GET("/api/students/:sid/ping", apiHandler(studentHandler(lastPing)))
router.GET("/api/students/:sid/pong", apiHandler(studentHandler(func(student Student, body []byte) (interface{}, error) { router.GET("/api/students/:sid/pong", apiHandler(studentHandler(func(student adlin.Student, body []byte) (interface{}, error) {
return student.lastPongs() return student.LastPongs()
}))) })))
router.POST("/api/students/:sid/pong", apiHandler(studentHandler(stdPong), sslOnly)) router.POST("/api/students/:sid/pong", apiHandler(studentHandler(stdPong), sslOnly))
} }
func lastPing(student Student, body []byte) (interface{}, error) { func lastPing(student adlin.Student, body []byte) (interface{}, error) {
if pongs, err := student.lastPongs(); err != nil { if pongs, err := student.LastPongs(); err != nil {
return nil, err return nil, err
} else if len(pongs) <= 0 { } else if len(pongs) <= 0 {
return false, nil return false, nil
@ -26,33 +27,7 @@ func lastPing(student Student, body []byte) (interface{}, error) {
} }
} }
type Pong struct { func stdPong(student adlin.Student, body []byte) (interface{}, error) {
Date time.Time
State bool
}
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
if err = rows.Scan(&p.Date, &p.State); err != nil {
return
}
pongs = append(pongs, p)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func stdPong(student Student, body []byte) (interface{}, error) {
var gt givenToken var gt givenToken
if err := json.Unmarshal(body, &gt); err != nil { if err := json.Unmarshal(body, &gt); err != nil {
return nil, err return nil, err
@ -62,5 +37,5 @@ func stdPong(student Student, body []byte) (interface{}, error) {
return nil, errors.New("This is not the expected token.") return nil, errors.New("This is not the expected token.")
} }
return true, student.onPong(gt.Challenge == 0) return true, student.OnPong(gt.Challenge == 0)
} }

View File

@ -1,23 +1,21 @@
package main package main
import ( import (
"bytes"
"crypto/hmac" "crypto/hmac"
"encoding/json"
"encoding/hex" "encoding/hex"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log" "log"
"net/http" "net/http"
"os" "os"
"os/exec"
"path" "path"
"strconv" "strconv"
"strings"
"time"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"git.nemunai.re/lectures/adlin/libadlin"
) )
var AuthorizedKeysLocation = "/root/.ssh/authorized_keys" var AuthorizedKeysLocation = "/root/.ssh/authorized_keys"
@ -26,7 +24,7 @@ var SshPiperLocation = "/var/sshpiper/"
func init() { func init() {
router.GET("/sshkeys", apiHandler( router.GET("/sshkeys", apiHandler(
func(httprouter.Params, []byte) (interface{}, error) { func(httprouter.Params, []byte) (interface{}, error) {
return getStudentKeys() return adlin.GetStudentKeys()
})) }))
router.POST("/sshkeys", rawHandler(responseHandler(receiveKey))) router.POST("/sshkeys", rawHandler(responseHandler(receiveKey)))
router.GET("/sshkeys/authorizedkeys", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { router.GET("/sshkeys/authorizedkeys", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
@ -35,153 +33,28 @@ func init() {
router.GET("/api/students/:sid/hassshkeys", apiHandler(studentHandler(hasSSHKeys))) router.GET("/api/students/:sid/hassshkeys", apiHandler(studentHandler(hasSSHKeys)))
router.GET("/api/students/:sid/authorizedkeys", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { router.GET("/api/students/:sid/authorizedkeys", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil { if sid, err := strconv.Atoi(string(ps.ByName("sid"))); err != nil {
if student, err := getStudentByLogin(ps.ByName("sid")); err != nil { if student, err := adlin.GetStudentByLogin(ps.ByName("sid")); err != nil {
http.Error(w, "Student doesn't exist.", http.StatusNotFound) http.Error(w, "Student doesn't exist.", http.StatusNotFound)
} else { } else {
student.dumpAuthorizedKeysFile(w) dumpStdAuthorizedKeysFile(student, w)
} }
} else if student, err := getStudent(sid); err != nil { } else if student, err := adlin.GetStudent(sid); err != nil {
http.Error(w, "Student doesn't exist.", http.StatusNotFound) http.Error(w, "Student doesn't exist.", http.StatusNotFound)
} else { } else {
student.dumpAuthorizedKeysFile(w) dumpStdAuthorizedKeysFile(student, w)
} }
}) })
} }
func hasSSHKeys(student Student, body []byte) (interface{}, error) { func hasSSHKeys(student adlin.Student, body []byte) (interface{}, error) {
if keys, err := student.getKeys(); err != nil { if keys, err := student.GetKeys(); err != nil {
return nil, err return nil, err
} else { } else {
return len(keys) > 0, nil return len(keys) > 0, nil
} }
} }
type StudentKey struct {
Id int64 `json:"id"`
IdStudent int64 `json:"id_student"`
Key string `json:"key"`
Time time.Time `json:"time"`
}
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
if err = rows.Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time); err != nil {
return
}
keys = append(keys, k)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
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
if err = rows.Scan(&k.Id, &k.IdStudent, &k.Key, &k.Time); err != nil {
return
}
keys = append(keys, k)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func getStudentKey(id int) (k StudentKey, err error) {
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) {
// Check key before importing it
cmd := exec.Command("ssh-keygen", "-l", "-f", "-")
cmd.Stdin = strings.NewReader(key)
var stdoutStderr []byte
stdoutStderr, err = cmd.CombinedOutput()
if err != nil {
if _, ok := err.(*exec.ExitError); ok {
err = errors.New(string(stdoutStderr))
}
return
}
chunks := bytes.Fields(stdoutStderr)
keytype := string(chunks[len(chunks)-1])
minkeysize := 2048
if keytype == "(ED25519)" || keytype == "(ECDSA)" {
minkeysize = 256
}
var bits int
if bits, err = strconv.Atoi(string(chunks[0])); err != nil {
return
} else if bits < minkeysize {
err = errors.New("Keysize too small")
return
}
// Sanitize the given key
keyf := strings.Fields(key)
if len(keyf) < 2 {
err = errors.New("Unexpected key file, this should never happen")
return
}
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
} else if kid, err := res.LastInsertId(); err != nil {
return StudentKey{}, err
} else {
s.UnlockNewChallenge(len(challenges), "")
return StudentKey{kid, s.Id, key, time.Now()}, nil
}
}
func (k StudentKey) GetStudent() (Student, error) {
return getStudent(int(k.IdStudent))
}
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 {
return 0, err
} else {
return nb, err
}
}
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 {
return 0, err
} else {
return nb, err
}
}
func receiveKey(r *http.Request, ps httprouter.Params, body []byte) (interface{}, error) { func receiveKey(r *http.Request, ps httprouter.Params, body []byte) (interface{}, error) {
var gt givenToken var gt givenToken
if err := json.Unmarshal(body, &gt); err != nil { if err := json.Unmarshal(body, &gt); err != nil {
@ -193,7 +66,7 @@ func receiveKey(r *http.Request, ps httprouter.Params, body []byte) (interface{}
return nil, err return nil, err
} }
if std, err := getStudentByLogin(gt.Login); err != nil { if std, err := adlin.GetStudentByLogin(gt.Login); err != nil {
return nil, err return nil, err
} else if len(gt.Data) < 2 { } else if len(gt.Data) < 2 {
return nil, errors.New("No key found!") return nil, errors.New("No key found!")
@ -207,7 +80,7 @@ func receiveKey(r *http.Request, ps httprouter.Params, body []byte) (interface{}
if expectedToken, err := GenerateToken(pkey, 0, data...); err != nil { if expectedToken, err := GenerateToken(pkey, 0, data...); err != nil {
return nil, err return nil, err
} else if ! hmac.Equal(expectedToken, gt.token) { } else if !hmac.Equal(expectedToken, gt.token) {
return nil, errors.New("This is not the expected token.") return nil, errors.New("This is not the expected token.")
} }
@ -240,7 +113,7 @@ func receiveKey(r *http.Request, ps httprouter.Params, body []byte) (interface{}
} }
defer file.Close() defer file.Close()
std.dumpAuthorizedKeysFile(file) dumpStdAuthorizedKeysFile(std, file)
os.Symlink(path.Join(SshPiperLocation, "sshpiper_upstream"), path.Join(SshPiperLocation, std.Login, "sshpiper_upstream")) os.Symlink(path.Join(SshPiperLocation, "sshpiper_upstream"), path.Join(SshPiperLocation, std.Login, "sshpiper_upstream"))
os.Symlink(path.Join(SshPiperLocation, "id_rsa"), path.Join(SshPiperLocation, std.Login, "id_rsa")) os.Symlink(path.Join(SshPiperLocation, "id_rsa"), path.Join(SshPiperLocation, std.Login, "id_rsa"))
} }
@ -254,7 +127,7 @@ func receiveKey(r *http.Request, ps httprouter.Params, body []byte) (interface{}
func dumpAuthorizedKeysFile(w io.Writer) { func dumpAuthorizedKeysFile(w io.Writer) {
seen := map[string]interface{}{} seen := map[string]interface{}{}
if keys, _ := getStudentKeys(); keys != nil { if keys, _ := adlin.GetStudentKeys(); keys != nil {
for _, k := range keys { for _, k := range keys {
if _, exists := seen[k.Key]; exists { if _, exists := seen[k.Key]; exists {
continue continue
@ -268,10 +141,10 @@ func dumpAuthorizedKeysFile(w io.Writer) {
} }
} }
func (s Student) dumpAuthorizedKeysFile(w io.Writer) { func dumpStdAuthorizedKeysFile(s adlin.Student, w io.Writer) {
seen := map[string]interface{}{} seen := map[string]interface{}{}
if keys, _ := s.getKeys(); keys != nil { if keys, _ := s.GetKeys(); keys != nil {
for _, k := range keys { for _, k := range keys {
if _, exists := seen[k.Key]; exists { if _, exists := seen[k.Key]; exists {
continue continue

View File

@ -1,34 +1,33 @@
package main package main
import ( import (
"crypto/hmac"
"crypto/sha512"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "log"
"strings" "strings"
"time"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"git.nemunai.re/lectures/adlin/libadlin"
) )
func init() { func init() {
router.GET("/api/progress", apiHandler( router.GET("/api/progress", apiHandler(
func(httprouter.Params, []byte) (interface{}, error) { func(httprouter.Params, []byte) (interface{}, error) {
if stds, err := getStudents(); err != nil { if stds, err := adlin.GetStudents(); err != nil {
return nil, err return nil, err
} else { } else {
ret := map[string]map[string]UnlockedChallenge{} ret := map[string]map[string]adlin.UnlockedChallenge{}
for _, std := range stds { for _, std := range stds {
if sts, err := std.getStates(); err == nil { if sts, err := std.GetStates(); err == nil {
ret[std.Login] = map[string]UnlockedChallenge{} ret[std.Login] = map[string]adlin.UnlockedChallenge{}
for _, s := range sts { for _, s := range sts {
ret[std.Login][fmt.Sprintf("%d", s.Challenge)] = s ret[std.Login][fmt.Sprintf("%d", s.Challenge)] = s
} }
if pongs, err := std.lastPongs(); err == nil && len(pongs) > 0 { if pongs, err := std.LastPongs(); err == nil && len(pongs) > 0 {
ret[std.Login]["ping"] = UnlockedChallenge{ ret[std.Login]["ping"] = adlin.UnlockedChallenge{
IdStudent: std.Id, IdStudent: std.Id,
Time: pongs[0].Date, Time: pongs[0].Date,
Value: pongs[0].State, Value: pongs[0].State,
@ -43,23 +42,23 @@ func init() {
})) }))
router.GET("/api/students/", apiHandler( router.GET("/api/students/", apiHandler(
func(httprouter.Params, []byte) (interface{}, error) { func(httprouter.Params, []byte) (interface{}, error) {
return getStudents() return adlin.GetStudents()
})) }))
router.POST("/api/students/", remoteValidatorHandler(apiHandler(createStudent))) router.POST("/api/students/", remoteValidatorHandler(apiHandler(createStudent)))
router.GET("/api/students/:sid/", apiHandler(studentHandler( router.GET("/api/students/:sid/", apiHandler(studentHandler(
func(std Student, _ []byte) (interface{}, error) { func(std adlin.Student, _ []byte) (interface{}, error) {
return std, nil return std, nil
}))) })))
router.PUT("/api/students/:sid/", remoteValidatorHandler(apiHandler(studentHandler(updateStudent)))) router.PUT("/api/students/:sid/", remoteValidatorHandler(apiHandler(studentHandler(updateStudent))))
router.DELETE("/api/students/:sid/", remoteValidatorHandler(apiHandler(studentHandler( router.DELETE("/api/students/:sid/", remoteValidatorHandler(apiHandler(studentHandler(
func(std Student, _ []byte) (interface{}, error) { func(std adlin.Student, _ []byte) (interface{}, error) {
return std.Delete() return std.Delete()
})))) }))))
router.GET("/api/students/:sid/progress", apiHandler(studentHandler( router.GET("/api/students/:sid/progress", apiHandler(studentHandler(
func(std Student, _ []byte) (interface{}, error) { func(std adlin.Student, _ []byte) (interface{}, error) {
ret := map[string]UnlockedChallenge{} ret := map[string]adlin.UnlockedChallenge{}
if sts, err := std.getStates(); err == nil { if sts, err := std.GetStates(); err == nil {
for _, s := range sts { for _, s := range sts {
ret[fmt.Sprintf("%d", s.Challenge)] = s ret[fmt.Sprintf("%d", s.Challenge)] = s
} }
@ -69,102 +68,12 @@ func init() {
}))) })))
} }
type Student struct {
Id int64 `json:"id"`
Login string `json:"login"`
Time *time.Time `json:"time"`
IP *string `json:"ip"`
MAC *string `json:"mac"`
}
type uploadedStudent struct { type uploadedStudent struct {
Login string `json:"login"` Login string `json:"login"`
IP string `json:"ip"` IP string `json:"ip"`
MAC string `json:"mac"` MAC string `json:"mac"`
} }
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 {
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 {
return
}
students = append(students, s)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
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)
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)
return
}
func studentExists(login string) bool {
var z int
err := DBQueryRow("SELECT 1 FROM students WHERE login=?", login).Scan(&z)
return err == nil && z == 1
}
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
} else if sid, err := res.LastInsertId(); err != nil {
return Student{}, err
} else {
return Student{sid, login, &t, nil, nil}, nil
}
}
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 {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}
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 {
return 0, err
} else {
return nb, err
}
}
func ClearStudents() (int64, error) {
if res, err := DBExec("DELETE FROM students"); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}
func createStudent(_ httprouter.Params, body []byte) (interface{}, error) { func createStudent(_ httprouter.Params, body []byte) (interface{}, error) {
var err error var err error
var std uploadedStudent var std uploadedStudent
@ -172,22 +81,22 @@ func createStudent(_ httprouter.Params, body []byte) (interface{}, error) {
return nil, err return nil, err
} }
var exist Student var exist adlin.Student
if exist, err = getStudentByLogin(strings.TrimSpace(std.Login)); err != nil { if exist, err = adlin.GetStudentByLogin(strings.TrimSpace(std.Login)); err != nil {
if exist, err = NewStudent(strings.TrimSpace(std.Login)); err != nil { if exist, err = adlin.NewStudent(strings.TrimSpace(std.Login)); err != nil {
return nil, err return nil, err
} }
} }
exist.registerAccess(std.IP, std.MAC) exist.RegisterAccess(std.IP, std.MAC)
ip := fmt.Sprintf("172.23.0.%d", exist.IPSuffix()) ip := fmt.Sprintf("172.23.0.%d", IPSuffix(exist))
exist.IP = &ip exist.IP = &ip
return exist, nil return exist, nil
} }
func updateStudent(current Student, body []byte) (interface{}, error) { func updateStudent(current adlin.Student, body []byte) (interface{}, error) {
var new Student var new adlin.Student
if err := json.Unmarshal(body, &new); err != nil { if err := json.Unmarshal(body, &new); err != nil {
return nil, err return nil, err
} }
@ -196,83 +105,3 @@ func updateStudent(current Student, body []byte) (interface{}, error) {
current.Time = new.Time current.Time = new.Time
return current.Update() return current.Update()
} }
type UnlockedChallenge struct {
Id int64 `json:"id,omitempty"`
IdStudent int64 `json:"id_student"`
Challenge int `json:"challenge,omitempty"`
Time time.Time `json:"time"`
Value interface{} `json:"value,omitempty"`
}
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.IdStudent = s.Id
if err = rows.Scan(&u.Id, &u.Challenge, &u.Time); 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.IdStudent = s.Id
if err = rows.Scan(&u.Id, &u.Challenge, &u.Time, &u.Value); err != nil {
return
}
ucs = append(ucs, u)
}
if err = rows.Err(); err != nil {
return
}
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
} else if utid, err := res.LastInsertId(); err != nil {
return UnlockedChallenge{}, err
} else {
return UnlockedChallenge{utid, s.Id, challenge, time.Now(), value}, err
}
}
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
} else {
return UnlockedChallenge{0, s.Id, challenge, time.Now(), value}, err
}
}
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 {
return err
} else {
return err
}
}

View File

@ -1,8 +1,6 @@
package main package main
import ( import (
"crypto/rand"
"crypto/sha512"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
@ -12,10 +10,10 @@ import (
"net" "net"
"net/http" "net/http"
"os/exec" "os/exec"
"strings"
"time"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"git.nemunai.re/lectures/adlin/libadlin"
) )
func init() { func init() {
@ -29,19 +27,19 @@ func init() {
} }
}) })
router.GET("/api/wg/", apiAuthHandler(showWgTunnel)) router.GET("/api/wg/", apiAuthHandler(showWgTunnel))
router.GET("/api/wginfo", apiAuthHandler(func (student Student, ps httprouter.Params, body []byte) (interface{}, error) { router.GET("/api/wginfo", apiAuthHandler(func(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
return getTunnelInfo(student.Id), nil return getTunnelInfo(student.Id), nil
})) }))
router.POST("/api/wg/", apiAuthHandler(genWgToken)) router.POST("/api/wg/", apiAuthHandler(genWgToken))
router.POST("/api/wg/:token", getWgTunnelInfo) router.POST("/api/wg/:token", getWgTunnelInfo)
} }
func showWgTunnel(student Student, ps httprouter.Params, body []byte) (interface{}, error) { func showWgTunnel(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
// Get tunnels assigned to the student // Get tunnels assigned to the student
return student.GetTunnelTokens() return student.GetTunnelTokens()
} }
func genWgToken(student Student, ps httprouter.Params, body []byte) (interface{}, error) { func genWgToken(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) {
// Generate a token to access related wg info // Generate a token to access related wg info
return student.NewTunnelToken() return student.NewTunnelToken()
} }
@ -61,7 +59,7 @@ func getTunnelInfo(student int64) TunnelInfo {
Status: "OK", Status: "OK",
SrvPubKey: srv_pubkey, SrvPubKey: srv_pubkey,
SrvPort: 42912, SrvPort: 42912,
CltIPv6: studentIP(student), CltIPv6: adlin.StudentIP(student),
CltRange: 80, CltRange: 80,
SrvGW6: "2a01:e0a:2b:2252::1", SrvGW6: "2a01:e0a:2b:2252::1",
} }
@ -92,7 +90,7 @@ func getWgTunnelInfo(w http.ResponseWriter, r *http.Request, ps httprouter.Param
return return
} }
token, err := GetTunnelToken(tokendec[:n]) token, err := adlin.GetTunnelToken(tokendec[:n])
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusBadRequest) http.Error(w, fmt.Sprintf("{errmsg:%q}", err), http.StatusBadRequest)
return return
@ -127,127 +125,8 @@ PersistentKeepalive = 5
`, base64.StdEncoding.EncodeToString(tinfo.SrvPubKey), "82.64.31.248", tinfo.SrvPort, tinfo.CltIPv6, 64, tinfo.CltIPv6, tinfo.CltRange, tinfo.SrvGW6))) `, base64.StdEncoding.EncodeToString(tinfo.SrvPubKey), "82.64.31.248", tinfo.SrvPort, tinfo.CltIPv6, 64, tinfo.CltIPv6, tinfo.CltRange, tinfo.SrvGW6)))
} }
func GenWGConfig(w io.Writer) error {
type TunnelToken struct { ts, err := adlin.GetStudentsTunnels()
token []byte
TokenText string
IdStudent int64
PubKey []byte
Time time.Time
Dump *WGDump
}
func GetTunnelToken(token []byte) (t TunnelToken, err error) {
err = DBQueryRow("SELECT token, token_text, id_student, pubkey, time FROM student_tunnel_tokens WHERE token=? ORDER BY time DESC", token).Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time)
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
}
}
}
return
}
func tokenFromText(token string) []byte {
sha := sha512.Sum512([]byte(token))
return sha[:]
}
func (student Student) NewTunnelToken() (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.IdStudent = student.Id
_, err = DBExec("INSERT INTO student_tunnel_tokens (token, token_text, id_student, time) VALUES (?, ?, ?, ?)", t.token, t.TokenText, student.Id, time.Now())
return
}
func (student Student) GetTunnelTokens() (ts []TunnelToken, err error) {
if rows, errr := DBQuery("SELECT token, token_text, id_student, pubkey, time 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 {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var t TunnelToken
if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time); err != nil {
return
}
if t.PubKey != nil {
if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok {
t.Dump = &v
}
}
ts = append(ts, t)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func (student Student) GetTunnelToken(token []byte) (t TunnelToken, err error) {
err = DBQueryRow("SELECT token, token_text, id_student, pubkey, time 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)
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
}
}
}
return
}
func (t *TunnelToken) Update() (int64, error) {
newtoken := tokenFromText(t.TokenText)
tm := time.Now()
if res, err := DBExec("UPDATE student_tunnel_tokens SET token = ?, token_text = ?, id_student = ?, pubkey = ?, time = ? WHERE token = ?", newtoken, t.TokenText, t.IdStudent, t.PubKey, tm, t.token); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
t.token = newtoken
t.Time = tm
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 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
if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time); err != nil {
return
}
ts = append(ts, t)
}
err = rows.Err()
return
}
}
func studentIP(idstd int64) net.IP {
return net.ParseIP(fmt.Sprintf("2a01:e0a:2b:2252:%x::", idstd))
}
func GenWGConfig(w io.Writer) (error) {
ts, err := GetStudentsTunnels()
if err != nil { if err != nil {
return err return err
} }
@ -261,46 +140,13 @@ func GenWGConfig(w io.Writer) (error) {
#IdStudent = %d #IdStudent = %d
PublicKey = %s PublicKey = %s
AllowedIPs = %s/%d AllowedIPs = %s/%d
`, t.IdStudent, base64.StdEncoding.EncodeToString(t.PubKey), studentIP(t.IdStudent), 80))) `, t.IdStudent, base64.StdEncoding.EncodeToString(t.PubKey), adlin.StudentIP(t.IdStudent), 80)))
} }
return nil return nil
} }
func syncWgConf() (err error) { func syncWgConf() (err error) {
_, err = exec.Command("sh", "/root/wg-sync.sh").Output() _, err = exec.Command("sh", "/root/wg-sync.sh").Output()
return return
} }
type WGDump struct {
PubKey string
PSK string
Endpoint string
AllowedIPs string
LastHandS string
RX string
TX string
KeepAlive string
}
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{}
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]}
}
return
}