package adlin import ( "context" "crypto/ed25519" "crypto/rand" "crypto/sha512" "encoding/base64" "fmt" "net" "os/exec" "strings" "sync" "time" ) const StdNetmask = 80 var ( collector_secret ed25519.PrivateKey ) func SetCollectorSecret(b []byte) { collector_secret = ed25519.NewKeyFromSeed(b) } func GetCollectorPublic() ed25519.PublicKey { return collector_secret.Public().(ed25519.PublicKey) } func StudentIP(idstd int64, overrideid int) net.IP { return net.ParseIP(fmt.Sprintf("2a01:e0a:518:833:%x%x::", overrideid, idstd)) } func StudentNet(idstd int64, overrideid int) *net.IPNet { return &net.IPNet{ IP: StudentIP(idstd, overrideid), Mask: net.CIDRMask(StdNetmask, 128), } } type WGDump struct { PubKey string PSK string Endpoint string AllowedIPs string LastHandS string RX string TX string KeepAlive string } func (d *WGDump) GetPubKey() ([]byte, error) { return base64.StdEncoding.DecodeString(d.PubKey) } var ( wgDumpCache_data map[string]*WGDump = nil wgDumpCache_time time.Time wgDumpCache_mutex sync.RWMutex ) func _readWgDump() (wgd map[string]*WGDump, err error) { ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() out, err := exec.CommandContext(ctx, "wg", "show", "wg-adlin", "dump").Output() if err != nil { return nil, err } 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 } func readWgDump() (wgd map[string]*WGDump, err error) { wgDumpCache_mutex.RLock() defer wgDumpCache_mutex.RUnlock() wgd = wgDumpCache_data if time.Since(wgDumpCache_time) > time.Second*10 { wgDumpCache_mutex.RUnlock() wgDumpCache_mutex.Lock() if time.Since(wgDumpCache_time) > time.Second*10 { wgd, err = _readWgDump() if err != nil { wgDumpCache_mutex.Unlock() wgDumpCache_mutex.RLock() return } wgDumpCache_data = wgd wgDumpCache_time = time.Now() } wgDumpCache_mutex.Unlock() wgDumpCache_mutex.RLock() } return } type TunnelToken struct { token []byte TokenText string IdStudent int64 PubKey []byte Time time.Time SuffixIP int OverrideID int // Version stores the TP number where the token is used Version int Dump *WGDump } func (tt *TunnelToken) GetStudentIP() string { if tt.SuffixIP == 0 { return fmt.Sprintf("%s%x", StudentIP(tt.IdStudent, tt.OverrideID).String(), 1) } else { return fmt.Sprintf("%s%x", StudentIP(tt.IdStudent, tt.OverrideID).String(), tt.SuffixIP) } } func (tt *TunnelToken) GetServerIP(suffix int) string { return fmt.Sprintf("%s%x", StudentIP(tt.IdStudent, tt.OverrideID).String(), suffix) } func (tt *TunnelToken) GenKeySign() []byte { stdprivkey := ed25519.NewKeyFromSeed(tt.token[:ed25519.SeedSize]) stdpublic := []byte(stdprivkey.Public().(ed25519.PublicKey)) return ed25519.Sign(collector_secret, stdpublic) } func TokenFromText(token string) []byte { sha := sha512.Sum512([]byte(token)) return sha[:] } func GetTunnelToken(token []byte) (t *TunnelToken, err error) { t = new(TunnelToken) err = DBQueryRow("SELECT token, token_text, id_student, pubkey, time, suffixip, idoverride, 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.OverrideID, &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 = new(TunnelToken) t.TokenText = strings.Replace(strings.Replace(strings.Replace(strings.Replace(strings.Replace(base64.RawStdEncoding.EncodeToString(tok), "/", ".", -1), "+", "_", -1), "O", "<", -1), "l", "$", -1), "I", ">", -1) t.token = TokenFromText(t.TokenText) t.IdStudent = student.Id _, err = DBExec("INSERT INTO student_tunnel_tokens (token, token_text, id_student, time, suffixip, version) VALUES (?, ?, ?, ?, ?, 0)", t.token, t.TokenText, student.Id, time.Now(), suffixip) return } func (student *Student) GetTunnelTokens() (ts []*TunnelToken, err error) { if rows, errr := DBQuery("SELECT token, token_text, id_student, pubkey, time, suffixip, idoverride, 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() { t := &TunnelToken{} if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.OverrideID, &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) GetActivesTunnels() (ts []*TunnelToken, err error) { if rows, errr := DBQuery("SELECT token, token_text, id_student, pubkey, time, suffixip, idoverride, 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() { t := &TunnelToken{} if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.OverrideID, &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) GetDefaultTunnels() (ts []*TunnelToken, err error) { t := &TunnelToken{IdStudent: student.Id} ts = append(ts, t) return } func (student *Student) GetActivesTunnelsPubKey() (ts []ed25519.PublicKey, err error) { var activeTuns []*TunnelToken activeTuns, err = student.GetActivesTunnels() if err != nil { return } for _, tun := range activeTuns { if tun.Dump != nil { pk := ed25519.NewKeyFromSeed(tun.token[:ed25519.SeedSize]) ts = append(ts, pk.Public().(ed25519.PublicKey)) } } return } func (student *Student) GetTunnelToken(token []byte) (t *TunnelToken, err error) { t = new(TunnelToken) err = DBQueryRow("SELECT token, token_text, id_student, pubkey, time, suffixip, idoverride, 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.OverrideID, &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 (t *TunnelToken) Delete() (int64, error) { if res, err := DBExec("DELETE FROM student_tunnel_tokens WHERE token = ? AND id_student = ?", t.token, t.IdStudent); err != nil { return 0, err } else if nb, err := res.RowsAffected(); err != nil { return 0, err } else { return nb, err } } func GetStudentsTunnels() (ts []*TunnelToken, err error) { if rows, errr := DBQuery("SELECT T.token, T.token_text, T.id_student, T.pubkey, T.time, T.suffixip, T.idoverride, T.version FROM student_tunnel_tokens T INNER JOIN (SELECT B.id_student, MAX(B.time) AS time, B.idoverride FROM student_tunnel_tokens B WHERE B.pubkey IS NOT NULL GROUP BY id_student, idoverride) 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() { t := &TunnelToken{} if err = rows.Scan(&t.token, &t.TokenText, &t.IdStudent, &t.PubKey, &t.Time, &t.SuffixIP, &t.OverrideID, &t.Version); err != nil { return } ts = append(ts, t) } err = rows.Err() return } }