From 4669be666903f872c6b608ac3e20cb1496d7e843 Mon Sep 17 00:00:00 2001 From: ssongliu <73214554+ssongliu@users.noreply.github.com> Date: Fri, 4 Apr 2025 00:09:58 +0800 Subject: [PATCH] fix: Modify the pure-ftpd encryption method (#8312) --- agent/utils/toolbox/helper/sha512_crypt.go | 224 +++++++++++++++++++++ agent/utils/toolbox/pure-ftpd.go | 137 ++++++++++--- core/init/migration/helper/script.go | 14 +- 3 files changed, 339 insertions(+), 36 deletions(-) create mode 100644 agent/utils/toolbox/helper/sha512_crypt.go diff --git a/agent/utils/toolbox/helper/sha512_crypt.go b/agent/utils/toolbox/helper/sha512_crypt.go new file mode 100644 index 000000000..262033a3f --- /dev/null +++ b/agent/utils/toolbox/helper/sha512_crypt.go @@ -0,0 +1,224 @@ +package helper + +import ( + "bytes" + "crypto/rand" + "crypto/sha512" + "errors" + "strconv" +) + +const ( + SaltLenMin = 1 + SaltLenMax = 16 + RoundsMin = 1000 + RoundsMax = 999999999 + RoundsDefault = 5000 +) + +var _rounds = []byte("rounds=") + +func Generate(key []byte) (string, error) { + var rounds int + var isRoundsDef bool + + salt := generateWRounds() + magicPrefix := []byte("$6$") + if !bytes.HasPrefix(salt, magicPrefix) { + return "", errors.New("invalid magic prefix") + } + + saltItem := bytes.Split(salt, []byte{'$'}) + if len(saltItem) < 3 { + return "", errors.New("invalid salt format") + } + + if bytes.HasPrefix(saltItem[2], _rounds) { + isRoundsDef = true + pr, err := strconv.ParseInt(string(saltItem[2][7:]), 10, 32) + if err != nil { + return "", errors.New("invalid rounds") + } + rounds = int(pr) + if rounds < RoundsMin { + rounds = RoundsMin + } else if rounds > RoundsMax { + rounds = RoundsMax + } + salt = saltItem[3] + } else { + rounds = RoundsDefault + salt = saltItem[2] + } + + if len(salt) > SaltLenMax { + salt = salt[0:SaltLenMax] + } + + Alternate := sha512.New() + Alternate.Write(key) + Alternate.Write(salt) + Alternate.Write(key) + AlternateSum := Alternate.Sum(nil) + + A := sha512.New() + A.Write(key) + A.Write(salt) + i := len(key) + for ; i > 64; i -= 64 { + A.Write(AlternateSum) + } + A.Write(AlternateSum[0:i]) + + for i = len(key); i > 0; i >>= 1 { + if (i & 1) != 0 { + A.Write(AlternateSum) + } else { + A.Write(key) + } + } + A_sum := A.Sum(nil) + + P := sha512.New() + for i = 0; i < len(key); i++ { + P.Write(key) + } + P_sum := P.Sum(nil) + P_seq := make([]byte, 0, len(key)) + for i = len(key); i > 64; i -= 64 { + P_seq = append(P_seq, P_sum...) + } + P_seq = append(P_seq, P_sum[0:i]...) + + S := sha512.New() + for i = 0; i < (16 + int(A_sum[0])); i++ { + S.Write(salt) + } + S_sum := S.Sum(nil) + S_seq := make([]byte, 0, len(salt)) + for i = len(salt); i > 64; i -= 64 { + S_seq = append(S_seq, S_sum...) + } + S_seq = append(S_seq, S_sum[0:i]...) + + C_sum := A_sum + + for i = 0; i < rounds; i++ { + C := sha512.New() + if (i & 1) != 0 { + C.Write(P_seq) + } else { + C.Write(C_sum) + } + if (i % 3) != 0 { + C.Write(S_seq) + } + if (i % 7) != 0 { + C.Write(P_seq) + } + if (i & 1) != 0 { + C.Write(C_sum) + } else { + C.Write(P_seq) + } + + C_sum = C.Sum(nil) + } + + out := make([]byte, 0, 123) + out = append(out, magicPrefix...) + if isRoundsDef { + out = append(out, []byte("rounds="+strconv.Itoa(rounds)+"$")...) + } + out = append(out, salt...) + out = append(out, '$') + out = append(out, base64_24Bit([]byte{ + C_sum[42], C_sum[21], C_sum[0], + C_sum[1], C_sum[43], C_sum[22], + C_sum[23], C_sum[2], C_sum[44], + C_sum[45], C_sum[24], C_sum[3], + C_sum[4], C_sum[46], C_sum[25], + C_sum[26], C_sum[5], C_sum[47], + C_sum[48], C_sum[27], C_sum[6], + C_sum[7], C_sum[49], C_sum[28], + C_sum[29], C_sum[8], C_sum[50], + C_sum[51], C_sum[30], C_sum[9], + C_sum[10], C_sum[52], C_sum[31], + C_sum[32], C_sum[11], C_sum[53], + C_sum[54], C_sum[33], C_sum[12], + C_sum[13], C_sum[55], C_sum[34], + C_sum[35], C_sum[14], C_sum[56], + C_sum[57], C_sum[36], C_sum[15], + C_sum[16], C_sum[58], C_sum[37], + C_sum[38], C_sum[17], C_sum[59], + C_sum[60], C_sum[39], C_sum[18], + C_sum[19], C_sum[61], C_sum[40], + C_sum[41], C_sum[20], C_sum[62], + C_sum[63], + })...) + + A.Reset() + Alternate.Reset() + P.Reset() + for i = 0; i < len(A_sum); i++ { + A_sum[i] = 0 + } + for i = 0; i < len(AlternateSum); i++ { + AlternateSum[i] = 0 + } + for i = 0; i < len(P_seq); i++ { + P_seq[i] = 0 + } + + return string(out), nil +} + +func generateWRounds() []byte { + salt := make([]byte, 16) + _, _ = rand.Read(salt) + + magicPrefix := "$6$" + out := make([]byte, len(magicPrefix)+5000) + copy(out, magicPrefix) + copy(out[len(magicPrefix):], base64_24Bit(salt)) + return out +} + +func base64_24Bit(src []byte) (hash []byte) { + if len(src) == 0 { + return []byte{} + } + alphabet := "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + + hashSize := (len(src) * 8) / 6 + if (len(src) % 6) != 0 { + hashSize += 1 + } + hash = make([]byte, hashSize) + + dst := hash + for len(src) > 0 { + switch len(src) { + default: + dst[0] = alphabet[src[0]&0x3f] + dst[1] = alphabet[((src[0]>>6)|(src[1]<<2))&0x3f] + dst[2] = alphabet[((src[1]>>4)|(src[2]<<4))&0x3f] + dst[3] = alphabet[(src[2]>>2)&0x3f] + src = src[3:] + dst = dst[4:] + case 2: + dst[0] = alphabet[src[0]&0x3f] + dst[1] = alphabet[((src[0]>>6)|(src[1]<<2))&0x3f] + dst[2] = alphabet[(src[1]>>4)&0x3f] + src = src[2:] + dst = dst[3:] + case 1: + dst[0] = alphabet[src[0]&0x3f] + dst[1] = alphabet[(src[0]>>6)&0x3f] + src = src[1:] + dst = dst[2:] + } + } + + return +} diff --git a/agent/utils/toolbox/pure-ftpd.go b/agent/utils/toolbox/pure-ftpd.go index 6936a518c..870458139 100644 --- a/agent/utils/toolbox/pure-ftpd.go +++ b/agent/utils/toolbox/pure-ftpd.go @@ -1,12 +1,12 @@ package toolbox import ( + "bufio" "errors" "fmt" "os" "os/user" "path" - "path/filepath" "strings" "time" @@ -14,6 +14,7 @@ import ( "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/systemctl" + "github.com/1Panel-dev/1Panel/agent/utils/toolbox/helper" ) type Ftp struct { @@ -21,6 +22,21 @@ type Ftp struct { DefaultGroup string } +type FtpList struct { + User string + Path string + Status string +} + +type FtpLog struct { + IP string `json:"ip"` + User string `json:"user"` + Time string `json:"time"` + Operation string `json:"operation"` + Status string `json:"status"` + Size string `json:"size"` +} + type FtpClient interface { Status() (bool, bool) Operate(operate string) error @@ -88,9 +104,19 @@ func (f *Ftp) Operate(operate string) error { } func (f *Ftp) UserAdd(username, passwd, path string) error { - std, err := cmd.Execf("pure-pw useradd %s -u %s -d %s <> "$PURE_FTPD_CONF" - sed -i 's/^# UnixAuthentication/UnixAuthentication/' "$PURE_FTPD_CONF" - echo "NoAnonymous yes" >> "$PURE_FTPD_CONF" - echo "PassivePortRange 39000 40000" >> "$PURE_FTPD_CONF" - echo "ChrootEveryone yes" >> "$PURE_FTPD_CONF" - echo "VerboseLog yes" >> "$PURE_FTPD_CONF" + sed -i 's/^NoAnonymous[[:space:]]\+no$/NoAnonymous yes/' "$PURE_FTPD_CONF" + sed -i 's/^PAMAuthentication[[:space:]]\+yes$/PAMAuthentication no/' "$PURE_FTPD_CONF" + sed -i 's/^# PassivePortRange[[:space:]]\+30000 50000$/PassivePortRange 39000 40000/' "$PURE_FTPD_CONF" + sed -i 's/^VerboseLog[[:space:]]\+no$/VerboseLog yes/' "$PURE_FTPD_CONF" + sed -i 's/^# PureDB[[:space:]]\+\/etc\/pure-ftpd\/pureftpd\.pdb[[:space:]]*$/PureDB \/etc\/pure-ftpd\/pureftpd.pdb/' "$PURE_FTPD_CONF" else echo '/etc/pure-ftpd/pureftpd.pdb' > /etc/pure-ftpd/conf/PureDB echo yes > /etc/pure-ftpd/conf/VerboseLog echo yes > /etc/pure-ftpd/conf/NoAnonymous echo '39000 40000' > /etc/pure-ftpd/conf/PassivePortRange + echo 'no' > /etc/pure-ftpd/conf/PAMAuthentication + echo 'no' > /etc/pure-ftpd/conf/UnixAuthentication + echo 'clf:/var/log/pure-ftpd/transfer.log' > /etc/pure-ftpd/conf/AltLog ln -s /etc/pure-ftpd/conf/PureDB /etc/pure-ftpd/auth/50puredb fi