You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
368 lines
7.7 KiB
368 lines
7.7 KiB
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),
|
|
}
|
|
)
|
|
|
|
var noInterceptor bool
|
|
|
|
var (
|
|
whiteListMethods = map[string]bool{
|
|
"/m9z/password/verify": true,
|
|
"/m9z/password/change": 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
|
|
verifiedSessions map[string]time.Time
|
|
}
|
|
|
|
type PasswordData struct {
|
|
HashedPassword string `json:"hashed_password"`
|
|
Salt string `json:"salt"`
|
|
}
|
|
|
|
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
|
|
return nil
|
|
}
|
|
|
|
func (m *PasswordManager) saveToFile() error {
|
|
filePath := getPwdFilePath()
|
|
dir := filepath.Dir(filePath)
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
pwdData := PasswordData{
|
|
HashedPassword: m.hashedPwd,
|
|
Salt: m.salt,
|
|
}
|
|
|
|
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) 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.saveToFile()
|
|
}
|
|
|
|
towgo.SetFunc("/m9z/password/verify", verifyPwd)
|
|
towgo.SetFunc("/m9z/password/change", changePwd)
|
|
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 {
|
|
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,
|
|
})
|
|
}
|