mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2025-11-08 10:41:16 +08:00
fix: Modify the pure-ftpd encryption method (#8312)
This commit is contained in:
parent
4a249ba772
commit
4669be6669
3 changed files with 339 additions and 36 deletions
224
agent/utils/toolbox/helper/sha512_crypt.go
Normal file
224
agent/utils/toolbox/helper/sha512_crypt.go
Normal file
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 <<EOF \n%s\n%s\nEOF", username, f.DefaultUser, path, passwd, passwd)
|
||||
entry, err := generatePureFtpEntrySimple(username, passwd, path)
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
return fmt.Errorf("generate pure-ftpd entry failed, err: %v", err)
|
||||
}
|
||||
pwdFile, err := os.OpenFile("/etc/pure-ftpd/pureftpd.passwd", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer pwdFile.Close()
|
||||
|
||||
_, err = pwdFile.WriteString("\n" + entry + "\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = f.Reload()
|
||||
std2, err := cmd.Execf("chown -R %s:%s %s", f.DefaultUser, f.DefaultGroup, path)
|
||||
|
|
@ -110,10 +136,52 @@ func (f *Ftp) UserDel(username string) error {
|
|||
}
|
||||
|
||||
func (f *Ftp) SetPasswd(username, passwd string) error {
|
||||
std, err := cmd.Execf("pure-pw passwd %s <<EOF \n%s\n%s\nEOF", username, passwd, passwd)
|
||||
hashedPassword, err := helper.Generate([]byte(passwd))
|
||||
if err != nil {
|
||||
return errors.New(std)
|
||||
return err
|
||||
}
|
||||
pwdFile, err := os.Open("/etc/pure-ftpd/pureftpd.passwd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer pwdFile.Close()
|
||||
|
||||
var entrys []string
|
||||
scanner := bufio.NewScanner(pwdFile)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
userEntry := strings.Split(line, ":")
|
||||
if len(userEntry) < 2 {
|
||||
continue
|
||||
}
|
||||
if userEntry[0] == username {
|
||||
userEntry[1] = string(hashedPassword)
|
||||
line = strings.Join(userEntry, ":")
|
||||
}
|
||||
entrys = append(entrys, line)
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
pwdFile.Close()
|
||||
|
||||
pwdFile, err = os.Create("/etc/pure-ftpd/pureftpd.passwd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer pwdFile.Close()
|
||||
|
||||
for _, entry := range entrys {
|
||||
_, err := pwdFile.WriteString(entry + "\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -155,7 +223,7 @@ func (f *Ftp) LoadList() ([]FtpList, error) {
|
|||
}
|
||||
std2, err := cmd.Execf("pure-pw show %s | grep 'Allowed client IPs :'", parts[0])
|
||||
if err != nil {
|
||||
global.LOG.Errorf("handle pure-pw show %s faile, err: %v", parts[0], std2)
|
||||
global.LOG.Errorf("handle pure-pw show %s failed, err: %v", parts[0], std2)
|
||||
continue
|
||||
}
|
||||
status := constant.StatusDisable
|
||||
|
|
@ -168,12 +236,6 @@ func (f *Ftp) LoadList() ([]FtpList, error) {
|
|||
return lists, nil
|
||||
}
|
||||
|
||||
type FtpList struct {
|
||||
User string
|
||||
Path string
|
||||
Status string
|
||||
}
|
||||
|
||||
func (f *Ftp) Reload() error {
|
||||
std, err := cmd.Exec("pure-pw mkdb")
|
||||
if err != nil {
|
||||
|
|
@ -188,7 +250,7 @@ func (f *Ftp) LoadLogs(user, operation string) ([]FtpLog, error) {
|
|||
if _, err := os.Stat("/etc/pure-ftpd/conf"); err != nil && os.IsNotExist(err) {
|
||||
std, err := cmd.Exec("cat /etc/pure-ftpd/pure-ftpd.conf | grep AltLog | grep clf:")
|
||||
logItem = "/var/log/pureftpd.log"
|
||||
if err == nil && !strings.HasPrefix(logItem, "#") {
|
||||
if err == nil && !strings.HasPrefix(std, "#") {
|
||||
logItem = std
|
||||
}
|
||||
} else {
|
||||
|
|
@ -197,7 +259,7 @@ func (f *Ftp) LoadLogs(user, operation string) ([]FtpLog, error) {
|
|||
}
|
||||
std, err := cmd.Exec("cat /etc/pure-ftpd/conf/AltLog")
|
||||
logItem = "/var/log/pure-ftpd/transfer.log"
|
||||
if err != nil && !strings.HasPrefix(logItem, "#") {
|
||||
if err != nil && !strings.HasPrefix(std, "#") {
|
||||
logItem = std
|
||||
}
|
||||
}
|
||||
|
|
@ -207,23 +269,39 @@ func (f *Ftp) LoadLogs(user, operation string) ([]FtpLog, error) {
|
|||
logItem = strings.ReplaceAll(logItem, "\n", "")
|
||||
logPath := strings.Trim(logItem, " ")
|
||||
|
||||
fileName := path.Base(logPath)
|
||||
logDir := path.Dir(logPath)
|
||||
filesItem, err := os.ReadDir(logDir)
|
||||
if err != nil {
|
||||
return logs, err
|
||||
}
|
||||
var fileList []string
|
||||
if err := filepath.Walk(path.Dir(logPath), func(pathItem string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
for i := 0; i < len(filesItem); i++ {
|
||||
if filesItem[i].IsDir() {
|
||||
continue
|
||||
}
|
||||
if !info.IsDir() && strings.HasPrefix(info.Name(), fileName) {
|
||||
fileList = append(fileList, pathItem)
|
||||
itemPath := path.Join(logDir, filesItem[i].Name())
|
||||
if !strings.HasSuffix(itemPath, ".gz") {
|
||||
fileList = append(fileList, itemPath)
|
||||
continue
|
||||
}
|
||||
itemFileName := strings.TrimSuffix(itemPath, ".gz")
|
||||
if _, err := os.Stat(itemFileName); err != nil && os.IsNotExist(err) {
|
||||
if err := handleGunzip(itemPath); err == nil {
|
||||
fileList = append(fileList, itemFileName)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logs = loadLogsByFiles(fileList, user, operation)
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
func handleGunzip(path string) error {
|
||||
if _, err := cmd.Execf("gunzip %s", path); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadLogsByFiles(fileList []string, user, operation string) []FtpLog {
|
||||
var logs []FtpLog
|
||||
layout := "02/Jan/2006:15:04:05-0700"
|
||||
|
|
@ -262,11 +340,10 @@ func loadLogsByFiles(fileList []string, user, operation string) []FtpLog {
|
|||
return logs
|
||||
}
|
||||
|
||||
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"`
|
||||
func generatePureFtpEntrySimple(username, password, path string) (string, error) {
|
||||
passwdAfterSha512, err := helper.Generate([]byte(password))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s:%s:1000:1000::%s/./::::::::::::", username, passwdAfterSha512, path), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,17 +153,19 @@ echo "配置 Pure-FTPd..."
|
|||
PURE_FTPD_CONF="/etc/pure-ftpd/pure-ftpd.conf"
|
||||
if [ -f "$PURE_FTPD_CONF" ]; then
|
||||
cp "$PURE_FTPD_CONF" "$PURE_FTPD_CONF.bak"
|
||||
echo "PureDB /etc/pure-ftpd/pureftpd.pdb" >> "$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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue