package m9zTtyPwd import ( "crypto/md5" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "os" "os/exec" "path/filepath" "strings" "sync" g "tgk-touch/internal/global" "time" "github.com/towgo/towgo/lib/system" "github.com/towgo/towgo/towgo" ) const ( DefaultPassword = "123456" DefaultSalt = "m9zTty" SessionExpireMinutes = 10 ) var ( manager *PasswordManager = &PasswordManager{ verifiedSessions: make(map[string]time.Time), } passwordEnabled bool ) var noInterceptor bool var ( whiteListMethods = map[string]bool{ "/m9z/password/verify": true, "/m9z/password/change": true, //"/m9z/password/enable": true, //"/m9z/password/disable": true, "/m9z/password/status": true, // 实时监控 "/m9z/touchdisplay/getDeviceID": true, "/m9z/getDeviceStatus2": true, "/getMessageInterval": true, "/adl400/readPhaseData": true, // 报警预览 "/m9z/fault/list": true, } whiteListPrefixes = []string{ //"/m9z/get", //"/m9z/system/get", //"/m9z/fault", } ) type PasswordManager struct { mu sync.RWMutex hashedPwd string salt string enabledState int verifiedSessions map[string]time.Time } type PasswordData struct { HashedPassword string `json:"hashed_password"` Salt string `json:"salt"` Enabled int `json:"enabled"` } func getPwdFilePath() string { return filepath.Join(system.GetPathOfProgram(), "data", "m9zTtyPwd.json") } func hashPassword(pwd, salt string) string { data := []byte(pwd + salt) sum := sha256.Sum256(data) return hex.EncodeToString(sum[:]) } func (m *PasswordManager) loadFromFile() error { m.mu.Lock() defer m.mu.Unlock() filePath := getPwdFilePath() data, err := os.ReadFile(filePath) if err != nil { if os.IsNotExist(err) { m.hashedPwd = hashPassword(DefaultPassword, DefaultSalt) m.salt = DefaultSalt return m.saveToFile() } return err } var pwdData PasswordData if err := json.Unmarshal(data, &pwdData); err != nil { return err } m.hashedPwd = pwdData.HashedPassword m.salt = pwdData.Salt m.enabledState = pwdData.Enabled switch pwdData.Enabled { case 1: passwordEnabled = true case 2: passwordEnabled = false default: passwordEnabled = g.Config().Password.Enabled m.enabledState = 0 } return nil } func (m *PasswordManager) saveToFile() error { filePath := getPwdFilePath() dir := filepath.Dir(filePath) if err := os.MkdirAll(dir, 0755); err != nil { return err } var enabledState int switch { case passwordEnabled: enabledState = 1 case !passwordEnabled && m.enabledState != 0: enabledState = 2 default: enabledState = m.enabledState } pwdData := PasswordData{ HashedPassword: m.hashedPwd, Salt: m.salt, Enabled: enabledState, } data, err := json.Marshal(pwdData) if err != nil { return err } return os.WriteFile(filePath, data, 0600) } func (m *PasswordManager) verify(pwd string) bool { m.mu.RLock() defer m.mu.RUnlock() return m.hashedPwd == hashPassword(pwd, m.salt) } func (m *PasswordManager) enabled() bool { m.mu.RLock() defer m.mu.RUnlock() return passwordEnabled } func (m *PasswordManager) setEnabled(enabled bool) error { m.mu.Lock() defer m.mu.Unlock() passwordEnabled = enabled if enabled { m.enabledState = 1 } else { m.enabledState = 2 } return m.saveToFile() } func (m *PasswordManager) change(newPwd string) error { m.mu.Lock() defer m.mu.Unlock() m.hashedPwd = hashPassword(newPwd, m.salt) return m.saveToFile() } func (m *PasswordManager) addVerifiedSession(sessionId string) { m.mu.Lock() defer m.mu.Unlock() m.verifiedSessions[sessionId] = time.Now().Add(SessionExpireMinutes * time.Minute) } func (m *PasswordManager) isSessionVerified(sessionId string) bool { m.mu.RLock() defer m.mu.RUnlock() expireTime, ok := m.verifiedSessions[sessionId] if !ok { return false } if time.Now().After(expireTime) { return false } return true } func (m *PasswordManager) removeSession(sessionId string) { m.mu.Lock() defer m.mu.Unlock() delete(m.verifiedSessions, sessionId) } func (m *PasswordManager) cleanupExpiredSessions() { m.mu.Lock() defer m.mu.Unlock() now := time.Now() for sessionId, expireTime := range m.verifiedSessions { if now.After(expireTime) { delete(m.verifiedSessions, sessionId) } } } func (m *PasswordManager) isWhiteListed(method string) bool { if whiteListMethods[method] { return true } for _, prefix := range whiteListPrefixes { if strings.HasPrefix(method, prefix) { return true } } return false } func NoInterceptor(b bool) { noInterceptor = b } func UnauthorizedMethodAdd(method string) { whiteListMethods[method] = true } func UnauthorizedMethodPrefixAdd(prefix string) { whiteListPrefixes = append(whiteListPrefixes, prefix) } var mu sync.Mutex func Init() { mu.Lock() defer mu.Unlock() if err := manager.loadFromFile(); err != nil { g.GVA_LOG.Sugar().Warnf("load password data failed: %v, using default", err) manager.hashedPwd = hashPassword(DefaultPassword, DefaultSalt) manager.salt = DefaultSalt manager.enabledState = 0 manager.saveToFile() } towgo.SetFunc("/m9z/password/verify", verifyPwd) towgo.SetFunc("/m9z/password/change", changePwd) towgo.SetFunc("/m9z/password/enable", enablePassword) towgo.SetFunc("/m9z/password/disable", disablePassword) towgo.SetFunc("/m9z/password/status", getPasswordStatus) towgo.SetFunc("/m9z/system/getTime", getSystemTime) towgo.SetFunc("/m9z/system/setTime", setSystemTime) towgo.AddInterceptor(pwdInterceptor) go func() { ticker := time.NewTicker(5 * time.Minute) for range ticker.C { manager.cleanupExpiredSessions() } }() } func generateSessionId() string { data := []byte(time.Now().String()) sum := md5.Sum(data) return hex.EncodeToString(sum[:]) } func verifyPwd(rpcConn towgo.JsonRpcConnection) { var req struct { Password string `json:"password"` } rpcConn.ReadParams(&req) sessionId := generateSessionId() if manager.verify(req.Password) { manager.addVerifiedSession(sessionId) rpcConn.WriteResult(map[string]interface{}{ "success": true, "session_id": sessionId, "message": "verified", }) } else { rpcConn.WriteError(401, "invalid password") } } type ChangePwdReq struct { OldPassword string `json:"old_password"` NewPassword string `json:"new_password"` } func changePwd(rpcConn towgo.JsonRpcConnection) { var req ChangePwdReq rpcConn.ReadParams(&req) if !manager.verify(req.OldPassword) { rpcConn.WriteError(500, "旧密码错误") return } if len(req.NewPassword) < 6 { rpcConn.WriteError(500, "新密码长度小于 6") return } if err := manager.change(req.NewPassword); err != nil { rpcConn.WriteError(500, "change password failed: "+err.Error()) return } rpcConn.WriteResult(map[string]interface{}{ "success": true, "message": "password changed", }) } func pwdInterceptor(conn towgo.JsonRpcConnection) error { if noInterceptor || !passwordEnabled { return nil } rpcRequest := conn.GetRpcRequest() rpcResponse := conn.GetRpcResponse() if manager.isWhiteListed(rpcRequest.Method) { return nil } sessionId := rpcRequest.Session if sessionId == "" { rpcResponse.Error.Set(401, "password verification required") conn.Write() return errors.New("password verification required") } if !manager.isSessionVerified(sessionId) { rpcResponse.Error.Set(401, "session expired or invalid") conn.Write() return errors.New("password verification required") } return nil } func getSystemTime(rpcConn towgo.JsonRpcConnection) { now := time.Now() rpcConn.WriteResult(map[string]interface{}{ "timestamp": now.Unix(), "datetime": now.Format("2006-01-02 15:04:05"), "timezone": now.Location().String(), }) } type SetTimeReq struct { Datetime string `json:"datetime"` } func setSystemTime(rpcConn towgo.JsonRpcConnection) { var req SetTimeReq rpcConn.ReadParams(&req) if req.Datetime == "" { rpcConn.WriteError(400, "datetime is required") return } layouts := []string{ "2006-01-02 15:04:05", "2006-01-02T15:04:05Z", "2006-01-02 15:04", "2006-01-02", } var targetTime time.Time var parseErr error for _, layout := range layouts { targetTime, parseErr = time.Parse(layout, req.Datetime) if parseErr == nil { break } } if parseErr != nil { rpcConn.WriteError(400, "invalid datetime format, supported: 2006-01-02 15:04:05, 2006-01-02T15:04:05Z, 2006-01-02 15:04, 2006-01-02") return } unixTime := targetTime.Unix() cmd := exec.Command("date", "-s", targetTime.Format("2006-01-02 15:04:05")) cmd.Run() cmd = exec.Command("hwclock", "-w") cmd.Run() cmd = exec.Command("timedatectl", "set-ntp", "false") cmd.Run() rpcConn.WriteResult(map[string]interface{}{ "success": true, "message": "system time updated", "old_time": time.Now().Format("2006-01-02 15:04:05"), "new_time": targetTime.Format("2006-01-02 15:04:05"), "unix_time": unixTime, }) } func enablePassword(rpcConn towgo.JsonRpcConnection) { if err := manager.setEnabled(true); err != nil { rpcConn.WriteError(500, "save failed: "+err.Error()) return } rpcConn.WriteResult(map[string]interface{}{ "success": true, "message": "password enabled", }) } func disablePassword(rpcConn towgo.JsonRpcConnection) { if err := manager.setEnabled(false); err != nil { rpcConn.WriteError(500, "save failed: "+err.Error()) return } rpcConn.WriteResult(map[string]interface{}{ "success": true, "message": "password disabled", }) } func getPasswordStatus(rpcConn towgo.JsonRpcConnection) { rpcConn.WriteResult(map[string]interface{}{ "enabled": passwordEnabled, }) }