diff --git a/libadlin/db.go b/libadlin/db.go index 1f291a8..44551f1 100644 --- a/libadlin/db.go +++ b/libadlin/db.go @@ -123,6 +123,8 @@ CREATE TABLE IF NOT EXISTS student_tunnel_tokens( id_student INTEGER NOT NULL, time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, pubkey BLOB(50) DEFAULT NULL, + suffixip INTEGER NOT NULL, + version INTEGER NOT NULL, FOREIGN KEY(id_student) REFERENCES students(id_student) ) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; `); err != nil { diff --git a/libadlin/tunnel.go b/libadlin/tunnel.go new file mode 100644 index 0000000..6a57218 --- /dev/null +++ b/libadlin/tunnel.go @@ -0,0 +1,164 @@ +package adlin + +import ( + "crypto/rand" + "crypto/sha512" + "encoding/base64" + "fmt" + "net" + "os/exec" + "strings" + "time" +) + +func StudentIP(idstd int64) net.IP { + return net.ParseIP(fmt.Sprintf("2a01:e0a:2b:2252:%x::", idstd)) +} + +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 +} + +type TunnelToken struct { + token []byte + TokenText string + IdStudent int64 + PubKey []byte + Time time.Time + SuffixIP int + // Version stores the TP number where the token is used + Version int + Dump *WGDump +} + +func tokenFromText(token string) []byte { + sha := sha512.Sum512([]byte(token)) + return sha[:] +} + +func GetTunnelToken(token []byte) (t TunnelToken, err error) { + err = DBQueryRow("SELECT token, token_text, id_student, pubkey, time, suffixip, version FROM student_tunnel_tokens WHERE token=? ORDER BY time DESC", token).Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version) + if err == nil && t.PubKey != nil { + if wgd, errr := readWgDump(); errr == nil { + if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok { + t.Dump = &v + } + } + } + return +} + +func (student Student) NewTunnelToken(suffixip int) (t TunnelToken, err error) { + tok := make([]byte, 7) + if _, err = rand.Read(tok); err != nil { + return + } + + t.TokenText = strings.Replace(strings.Replace(strings.Replace(strings.Replace(strings.Replace(base64.RawStdEncoding.EncodeToString(tok), "/", ".", -1), "+", "_", -1), "O", "#", -1), "l", "$", -1), "I", ">", -1) + t.token = tokenFromText(t.TokenText) + t.IdStudent = student.Id + + _, err = DBExec("INSERT INTO student_tunnel_tokens (token, token_text, id_student, time, suffixip, version) VALUES (?, ?, ?, ?, ?, 0)", t.token, t.TokenText, student.Id, time.Now(), suffixip) + return +} + +func (student Student) GetTunnelTokens() (ts []TunnelToken, err error) { + if rows, errr := DBQuery("SELECT token, token_text, id_student, pubkey, time, suffixip, version FROM student_tunnel_tokens WHERE id_student = ? ORDER BY time DESC", student.Id); errr != nil { + return nil, errr + } else if wgd, errr := readWgDump(); errr != nil { + 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, &t.SuffixIP, &t.Version); 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, suffixip, version FROM student_tunnel_tokens WHERE token = ? AND id_student = ? ORDER BY time DESC", token, student.Id).Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version) + if err == nil && t.PubKey != nil { + if wgd, errr := readWgDump(); errr == nil { + if v, ok := wgd[base64.StdEncoding.EncodeToString(t.PubKey)]; ok { + t.Dump = &v + } + } + } + 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 = ?, suffixip = ?, version = ? WHERE token = ?", newtoken, t.TokenText, t.IdStudent, t.PubKey, tm, t.SuffixIP, t.Version, 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, T.suffixip, T.version FROM student_tunnel_tokens T INNER JOIN (SELECT B.id_student, MAX(B.time) AS time FROM student_tunnel_tokens B WHERE B.pubkey IS NOT NULL GROUP BY id_student) L ON T.id_student = L.id_student AND T.time = L.time"); errr != nil { + return nil, errr + } else { + defer rows.Close() + + for rows.Next() { + var t TunnelToken + if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.Version); err != nil { + return + } + ts = append(ts, t) + } + + err = rows.Err() + return + } +} diff --git a/token-validator/wg.go b/token-validator/wg.go index d56ef12..b48b646 100644 --- a/token-validator/wg.go +++ b/token-validator/wg.go @@ -42,7 +42,7 @@ func showWgTunnel(student adlin.Student, ps httprouter.Params, body []byte) (int func genWgToken(student adlin.Student, ps httprouter.Params, body []byte) (interface{}, error) { // Generate a token to access related wg info - return student.NewTunnelToken() + return student.NewTunnelToken(0) } type TunnelInfo struct {