Unverified Commit 00fd5d8a authored by Eric_Lee's avatar Eric_Lee Committed by GitHub

155dev (#163)

 sftp node tree
parent 700af064
......@@ -14,7 +14,6 @@ import (
"github.com/jumpserver/koko/pkg/logger"
"github.com/jumpserver/koko/pkg/model"
"github.com/jumpserver/koko/pkg/service"
"github.com/jumpserver/koko/pkg/srvconn"
)
......@@ -46,12 +45,11 @@ func SftpHandler(sess ssh.Session) {
}
func NewSFTPHandler(user *model.User, addr string) *sftpHandler {
assets := service.GetUserAllAssets(user.ID)
return &sftpHandler{srvconn.NewUserSFTP(user, addr, assets...)}
return &sftpHandler{srvconn.NewUserSftpConn(user, addr)}
}
type sftpHandler struct {
*srvconn.UserSftp
*srvconn.UserSftpConn
}
func (fs *sftpHandler) Filelist(r *sftp.Request) (sftp.ListerAt, error) {
......@@ -122,7 +120,7 @@ func (fs *sftpHandler) Fileread(r *sftp.Request) (io.ReaderAt, error) {
}
func (fs *sftpHandler) Close() {
fs.UserSftp.Close()
fs.UserSftpConn.Close()
}
type listerat []os.FileInfo
......
......@@ -18,14 +18,14 @@ import (
)
func NewUserVolume(user *model.User, addr, hostId string) *UserVolume {
var assets []model.Asset
var userSftp *srvconn.UserSftpConn
homename := "Home"
basePath := "/"
switch hostId {
case "":
assets = service.GetUserAllAssets(user.ID)
userSftp = srvconn.NewUserSftpConn(user, addr)
default:
assets = service.GetUserAssetByID(user.ID, hostId)
assets := service.GetUserAssetByID(user.ID, hostId)
if len(assets) == 1 {
homename = assets[0].Hostname
if assets[0].OrgID != "" {
......@@ -33,11 +33,12 @@ func NewUserVolume(user *model.User, addr, hostId string) *UserVolume {
}
basePath = filepath.Join("/", homename)
}
userSftp = srvconn.NewUserSftpConnWithAssets(user, addr, assets...)
}
rawID := fmt.Sprintf("%s@%s", user.Username, addr)
uVolume := &UserVolume{
Uuid: elfinder.GenerateID(rawID),
UserSftp: srvconn.NewUserSFTP(user, addr, assets...),
UserSftp: userSftp,
Homename: homename,
basePath: basePath,
chunkFilesMap: make(map[int]*sftp.File),
......@@ -47,8 +48,8 @@ func NewUserVolume(user *model.User, addr, hostId string) *UserVolume {
}
type UserVolume struct {
Uuid string
*srvconn.UserSftp
Uuid string
UserSftp *srvconn.UserSftpConn
Homename string
basePath string
......@@ -66,7 +67,7 @@ func (u *UserVolume) Info(path string) (elfinder.FileDir, error) {
if path == "/" {
return u.RootFileDir(), nil
}
originFileInfo, err := u.Stat(filepath.Join(u.basePath, path))
originFileInfo, err := u.UserSftp.Stat(filepath.Join(u.basePath, path))
if err != nil {
return rest, err
}
......
package model
import "encoding/json"
type NodeTreeList []NodeTreeAsset
type NodeTreeAsset struct {
ID string `json:"id"`
Name string `json:"name"`
Title string `json:"title"`
Meta map[string]interface{} `json:"meta"`
}
func ConvertMetaToNode(body []byte) (node Node, err error) {
err = json.Unmarshal(body, &node)
return
}
func ConvertMetaToAsset(body []byte) (asset Asset, err error) {
err = json.Unmarshal(body, &asset)
return
}
......@@ -165,3 +165,20 @@ func ValidateUserAssetPermission(userID, assetID, systemUserID, action string) b
return res.Msg
}
func GetUserNodeTreeWithAsset(userID, nodeID, cachePolicy string) (nodeTrees model.NodeTreeList) {
if cachePolicy == "" {
cachePolicy = "1"
}
payload := map[string]string{"cache_policy": cachePolicy}
if nodeID != "" {
payload["id"] = nodeID
}
Url := fmt.Sprintf(NodeTreeWithAssetURL, userID)
_, err := authClient.Get(Url, &nodeTrees, payload)
if err != nil {
logger.Error("Get user node tree error: ", err)
}
return
}
......@@ -43,4 +43,6 @@ const (
const (
UserTokenAuthURL = "/api/v1/authentication/tokens/" // 用户登录验证
UserConfirmAuthURL = "/api/v1/authentication/login-confirm-ticket/status/"
NodeTreeWithAssetURL = "/api/v1/perms/users/%s/nodes/children-with-assets/tree/" // 资产树
)
package srvconn
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"syscall"
"time"
"github.com/pkg/sftp"
"github.com/jumpserver/koko/pkg/common"
"github.com/jumpserver/koko/pkg/config"
"github.com/jumpserver/koko/pkg/logger"
"github.com/jumpserver/koko/pkg/model"
"github.com/jumpserver/koko/pkg/service"
)
func NewUserSFTP(user *model.User, addr string, assets ...model.Asset) *UserSftp {
u := UserSftp{
User: user, Addr: addr,
}
u.initial(assets)
return &u
}
type UserSftp struct {
type UserSftpConn struct {
User *model.User
Addr string
Dirs map[string]os.FileInfo
RootPath string
ShowHidden bool
ReuseConnection bool
Overtime time.Duration
hosts map[string]*HostnameDir // key hostname or hostname.orgName
sftpClients map[string]*SftpConn // key %s@%s suName hostName
LogChan chan *model.FTPLog
}
func (u *UserSftp) initial(assets []model.Asset) {
conf := config.GetConf()
u.RootPath = conf.SftpRoot
u.ShowHidden = conf.ShowHiddenFile
u.ReuseConnection = conf.ReuseConnection
u.Overtime = conf.SSHTimeout * time.Second
u.hosts = make(map[string]*HostnameDir)
u.sftpClients = make(map[string]*SftpConn)
u.LogChan = make(chan *model.FTPLog, 10)
for i := 0; i < len(assets); i++ {
if !assets[i].IsSupportProtocol("ssh") {
continue
}
key := assets[i].Hostname
if assets[i].OrgID != "" {
key = fmt.Sprintf("%s.%s", assets[i].Hostname, assets[i].OrgName)
}
u.hosts[key] = NewHostnameDir(&assets[i])
}
modeTime time.Time
logChan chan *model.FTPLog
go u.LoopPushFTPLog()
closed chan struct{}
}
func (u *UserSftp) ReadDir(path string) (res []os.FileInfo, err error) {
req := u.ParsePath(path)
if req.host == "" {
return u.RootDirInfo()
func (u *UserSftpConn) ReadDir(path string) (res []os.FileInfo, err error) {
fi, restPath := u.ParsePath(path)
if rootDir, ok := fi.(*UserSftpConn); ok {
return rootDir.List()
}
host, ok := u.hosts[req.host]
if !ok {
return res, sftp.ErrSshFxNoSuchFile
}
if req.su == "" {
for _, su := range host.GetSystemUsers() {
res = append(res, NewFakeFile(su.Name, true))
}
return
}
host.loadSystemUsers(u.User.ID)
su, ok := host.suMaps[req.su]
if !ok {
return res, sftp.ErrSshFxNoSuchFile
if nodeDir, ok := fi.(*NodeDir); ok {
return nodeDir.List()
}
if !u.validatePermission(su, model.ConnectAction) {
return res, sftp.ErrSshFxPermissionDenied
if assetDir, ok := fi.(*AssetDir); ok {
return assetDir.ReadDir(restPath)
}
conn, realPath := u.GetSFTPAndRealPath(req)
if conn == nil {
return res, sftp.ErrSshFxPermissionDenied
}
logger.Debug("inter sftp read dir real path: ", realPath)
res, err = conn.client.ReadDir(realPath)
if !u.ShowHidden {
noHiddenFiles := make([]os.FileInfo, 0, len(res))
for i := 0; i < len(res); i++ {
if !strings.HasPrefix(res[i].Name(), ".") {
noHiddenFiles = append(noHiddenFiles, res[i])
}
}
return noHiddenFiles, err
}
return res, err
return nil, sftp.ErrSSHFxNoSuchFile
}
func (u *UserSftp) Stat(path string) (res os.FileInfo, err error) {
req := u.ParsePath(path)
if req.host == "" {
return u.Info()
}
host, ok := u.hosts[req.host]
if !ok {
return res, sftp.ErrSshFxNoSuchFile
func (u *UserSftpConn) Stat(path string) (res os.FileInfo, err error) {
fi, restPath := u.ParsePath(path)
if rootDir, ok := fi.(*UserSftpConn); ok {
return rootDir, nil
}
if req.su == "" {
res = NewFakeFile(req.host, true)
return
}
host.loadSystemUsers(u.User.ID)
su, ok := host.suMaps[req.su]
if !ok {
return res, sftp.ErrSshFxNoSuchFile
if nodeDir, ok := fi.(*NodeDir); ok {
return nodeDir, nil
}
if !u.validatePermission(su, model.ConnectAction) {
return res, sftp.ErrSshFxPermissionDenied
if assetDir, ok := fi.(*AssetDir); ok {
return assetDir.Stat(restPath)
}
conn, realPath := u.GetSFTPAndRealPath(req)
if conn == nil {
return res, sftp.ErrSshFxPermissionDenied
}
return conn.client.Stat(realPath)
return nil, sftp.ErrSSHFxNoSuchFile
}
func (u *UserSftp) ReadLink(path string) (res string, err error) {
req := u.ParsePath(path)
if req.host == "" {
return res, sftp.ErrSshFxPermissionDenied
}
host, ok := u.hosts[req.host]
if !ok {
return res, sftp.ErrSshFxPermissionDenied
func (u *UserSftpConn) ReadLink(path string) (name string, err error) {
fi, restPath := u.ParsePath(path)
if _, ok := fi.(*UserSftpConn); ok && restPath == "" {
return "", sftp.ErrSshFxOpUnsupported
}
if req.su == "" {
return res, sftp.ErrSshFxPermissionDenied
if _, ok := fi.(*NodeDir); ok {
return "", sftp.ErrSshFxOpUnsupported
}
host.loadSystemUsers(u.User.ID)
su, ok := host.suMaps[req.su]
if !ok {
return res, sftp.ErrSshFxNoSuchFile
if assetDir, ok := fi.(*AssetDir); ok {
return assetDir.ReadLink(restPath)
}
if !u.validatePermission(su, model.ConnectAction) {
return res, sftp.ErrSshFxPermissionDenied
}
conn, realPath := u.GetSFTPAndRealPath(req)
if conn == nil {
return res, sftp.ErrSshFxPermissionDenied
}
return conn.client.ReadLink(realPath)
}
func (u *UserSftp) RemoveDirectory(path string) error {
req := u.ParsePath(path)
if req.host == "" {
return sftp.ErrSshFxPermissionDenied
}
host, ok := u.hosts[req.host]
if !ok {
return sftp.ErrSshFxPermissionDenied
}
if req.su == "" {
return sftp.ErrSshFxPermissionDenied
}
host.loadSystemUsers(u.User.ID)
su, ok := host.suMaps[req.su]
if !ok {
return sftp.ErrSshFxNoSuchFile
}
if !u.validatePermission(su, model.UploadAction) {
return sftp.ErrSshFxPermissionDenied
}
conn, realPath := u.GetSFTPAndRealPath(req)
if conn == nil {
return sftp.ErrSshFxPermissionDenied
}
err := u.removeDirectoryAll(conn.client, realPath)
filename := realPath
isSuccess := false
operate := model.OperateRemoveDir
if err == nil {
isSuccess = true
}
u.CreateFTPLog(host.asset, su, operate, filename, isSuccess)
return err
return "", sftp.ErrSshFxOpUnsupported
}
func (u *UserSftp) removeDirectoryAll(conn *sftp.Client, path string) error {
var err error
var files []os.FileInfo
files, err = conn.ReadDir(path)
if err != nil {
return err
}
for _, item := range files {
realPath := filepath.Join(path, item.Name())
if item.IsDir() {
err = u.removeDirectoryAll(conn, realPath)
if err != nil {
return err
func (u *UserSftpConn) Rename(oldNamePath, newNamePath string) (err error) {
oldFi, oldRestPath := u.ParsePath(oldNamePath)
newFi, newRestPath := u.ParsePath(newNamePath)
if oldAssetDir, ok := oldFi.(*AssetDir); ok {
if newAssetDir, newOk := newFi.(*AssetDir); newOk {
if oldAssetDir == newAssetDir {
return oldAssetDir.Rename(oldRestPath, newRestPath)
}
continue
}
err = conn.Remove(realPath)
if err != nil {
return err
}
}
return conn.RemoveDirectory(path)
return sftp.ErrSshFxOpUnsupported
}
func (u *UserSftp) Remove(path string) error {
req := u.ParsePath(path)
if req.host == "" {
return sftp.ErrSshFxPermissionDenied
}
host, ok := u.hosts[req.host]
if !ok {
return sftp.ErrSshFxPermissionDenied
}
if req.su == "" {
return sftp.ErrSshFxPermissionDenied
}
host.loadSystemUsers(u.User.ID)
su, ok := host.suMaps[req.su]
if !ok {
return sftp.ErrSshFxNoSuchFile
}
if !u.validatePermission(su, model.UploadAction) {
func (u *UserSftpConn) RemoveDirectory(path string) (err error) {
fi, restPath := u.ParsePath(path)
if _, ok := fi.(*UserSftpConn); ok && restPath == "" {
return sftp.ErrSshFxPermissionDenied
}
conn, realPath := u.GetSFTPAndRealPath(req)
if conn == nil {
if _, ok := fi.(*NodeDir); ok {
return sftp.ErrSshFxPermissionDenied
}
logger.Debug("remove file path", realPath)
err := conn.client.Remove(realPath)
filename := realPath
isSuccess := false
operate := model.OperateDelete
if err == nil {
isSuccess = true
if assetDir, ok := fi.(*AssetDir); ok {
return assetDir.RemoveDirectory(restPath)
}
u.CreateFTPLog(host.asset, su, operate, filename, isSuccess)
return err
return sftp.ErrSshFxPermissionDenied
}
func (u *UserSftp) MkdirAll(path string) error {
req := u.ParsePath(path)
if req.host == "" {
return sftp.ErrSshFxPermissionDenied
}
host, ok := u.hosts[req.host]
if !ok {
return sftp.ErrSshFxPermissionDenied
}
if req.su == "" {
return sftp.ErrSshFxPermissionDenied
}
host.loadSystemUsers(u.User.ID)
su, ok := host.suMaps[req.su]
if !ok {
return sftp.ErrSshFxNoSuchFile
}
if !u.validatePermission(su, model.UploadAction) {
func (u *UserSftpConn) Remove(path string) (err error) {
fi, restPath := u.ParsePath(path)
if _, ok := fi.(*UserSftpConn); ok && restPath == "" {
return sftp.ErrSshFxPermissionDenied
}
conn, realPath := u.GetSFTPAndRealPath(req)
if conn == nil {
if _, ok := fi.(*NodeDir); ok {
return sftp.ErrSshFxPermissionDenied
}
err := conn.client.MkdirAll(realPath)
filename := realPath
isSuccess := false
operate := model.OperateMkdir
if err == nil {
isSuccess = true
if assetDir, ok := fi.(*AssetDir); ok {
return assetDir.Remove(restPath)
}
u.CreateFTPLog(host.asset, su, operate, filename, isSuccess)
return err
return sftp.ErrSshFxPermissionDenied
}
func (u *UserSftp) Rename(oldNamePath, newNamePath string) error {
req1 := u.ParsePath(oldNamePath)
req2 := u.ParsePath(newNamePath)
if req1.host == "" || req2.host == "" || req1.su == "" || req2.su == "" {
return sftp.ErrSshFxPermissionDenied
} else if req1.host != req2.host || req1.su != req2.su {
func (u *UserSftpConn) MkdirAll(path string) (err error) {
fi, restPath := u.ParsePath(path)
if _, ok := fi.(*UserSftpConn); ok && restPath == "" {
return sftp.ErrSshFxPermissionDenied
}
host, ok := u.hosts[req1.host]
if !ok {
return sftp.ErrSshFxPermissionDenied
}
host.loadSystemUsers(u.User.ID)
su, ok := host.suMaps[req1.su]
if !ok {
return sftp.ErrSshFxNoSuchFile
}
if !u.validatePermission(su, model.UploadAction) {
return sftp.ErrSshFxPermissionDenied
}
conn1, oldRealPath := u.GetSFTPAndRealPath(req1)
conn2, newRealPath := u.GetSFTPAndRealPath(req2)
if conn1 != conn2 {
return sftp.ErrSshFxOpUnsupported
}
err := conn1.client.Rename(oldRealPath, newRealPath)
filename := fmt.Sprintf("%s=>%s", oldRealPath, newRealPath)
isSuccess := false
operate := model.OperateRename
if err == nil {
isSuccess = true
}
u.CreateFTPLog(host.asset, su, operate, filename, isSuccess)
return err
}
func (u *UserSftp) Symlink(oldNamePath, newNamePath string) error {
req1 := u.ParsePath(oldNamePath)
req2 := u.ParsePath(newNamePath)
if req1.host == "" || req2.host == "" || req1.su == "" || req2.su == "" {
return sftp.ErrSshFxPermissionDenied
} else if req1.host != req2.host || req1.su != req2.su {
return sftp.ErrSshFxPermissionDenied
}
host, ok := u.hosts[req1.host]
if !ok {
return sftp.ErrSshFxPermissionDenied
}
host.loadSystemUsers(u.User.ID)
su, ok := host.suMaps[req1.su]
if !ok {
return sftp.ErrSshFxNoSuchFile
}
if !u.validatePermission(su, model.UploadAction) {
if _, ok := fi.(*NodeDir); ok {
return sftp.ErrSshFxPermissionDenied
}
conn1, oldRealPath := u.GetSFTPAndRealPath(req1)
conn2, newRealPath := u.GetSFTPAndRealPath(req2)
if conn1 != conn2 {
return sftp.ErrSshFxOpUnsupported
if assetDir, ok := fi.(*AssetDir); ok {
return assetDir.MkdirAll(restPath)
}
err := conn1.client.Symlink(oldRealPath, newRealPath)
return sftp.ErrSshFxPermissionDenied
}
filename := fmt.Sprintf("%s=>%s", oldRealPath, newRealPath)
isSuccess := false
operate := model.OperateSymlink
if err == nil {
isSuccess = true
func (u *UserSftpConn) Symlink(oldNamePath, newNamePath string) (err error) {
oldFi, oldRestPath := u.ParsePath(oldNamePath)
newFi, newRestPath := u.ParsePath(newNamePath)
if oldAssetDir, ok := oldFi.(*AssetDir); ok {
if newAssetDir, newOk := newFi.(*AssetDir); newOk {
if oldAssetDir == newAssetDir {
return oldAssetDir.Symlink(oldRestPath, newRestPath)
}
}
}
u.CreateFTPLog(host.asset, su, operate, filename, isSuccess)
return err
return sftp.ErrSshFxPermissionDenied
}
func (u *UserSftp) Create(path string) (*sftp.File, error) {
req := u.ParsePath(path)
if req.host == "" {
return nil, sftp.ErrSshFxPermissionDenied
}
host, ok := u.hosts[req.host]
if !ok {
return nil, sftp.ErrSshFxPermissionDenied
}
if req.su == "" {
return nil, sftp.ErrSshFxPermissionDenied
}
host.loadSystemUsers(u.User.ID)
su, ok := host.suMaps[req.su]
if !ok {
return nil, sftp.ErrSshFxNoSuchFile
}
if !u.validatePermission(su, model.UploadAction) {
func (u *UserSftpConn) Create(path string) (*sftp.File, error) {
fi, restPath := u.ParsePath(path)
if _, ok := fi.(*UserSftpConn); ok {
return nil, sftp.ErrSshFxPermissionDenied
}
conn, realPath := u.GetSFTPAndRealPath(req)
if conn == nil {
if _, ok := fi.(*NodeDir); ok {
return nil, sftp.ErrSshFxPermissionDenied
}
logger.Debug("create path:", realPath)
sf, err := conn.client.Create(realPath)
filename := realPath
isSuccess := false
operate := model.OperateUpload
if err == nil {
isSuccess = true
if assetDir, ok := fi.(*AssetDir); ok {
return assetDir.Create(restPath)
}
u.CreateFTPLog(host.asset, su, operate, filename, isSuccess)
return sf, err
return nil, sftp.ErrSshFxPermissionDenied
}
func (u *UserSftp) Open(path string) (*sftp.File, error) {
req := u.ParsePath(path)
if req.host == "" {
return nil, sftp.ErrSshFxPermissionDenied
}
host, ok := u.hosts[req.host]
if !ok {
func (u *UserSftpConn) Open(path string) (*sftp.File, error) {
fi, restPath := u.ParsePath(path)
if _, ok := fi.(*UserSftpConn); ok {
return nil, sftp.ErrSshFxPermissionDenied
}
if req.su == "" {
if _, ok := fi.(*NodeDir); ok {
return nil, sftp.ErrSshFxPermissionDenied
}
host.loadSystemUsers(u.User.ID)
su, ok := host.suMaps[req.su]
if !ok {
return nil, sftp.ErrSshFxNoSuchFile
}
if !u.validatePermission(su, model.DownloadAction) {
return nil, sftp.ErrSshFxPermissionDenied
if assetDir, ok := fi.(*AssetDir); ok {
return assetDir.Open(restPath)
}
conn, realPath := u.GetSFTPAndRealPath(req)
if conn == nil {
return nil, sftp.ErrSshFxPermissionDenied
}
logger.Debug("Open path:", realPath)
sf, err := conn.client.Open(realPath)
filename := realPath
isSuccess := false
operate := model.OperateDownaload
if err == nil {
isSuccess = true
return nil, sftp.ErrSshFxPermissionDenied
}
func (u *UserSftpConn) Close() {
for _, dir := range u.Dirs {
if nodeDir, ok := dir.(*NodeDir); ok {
nodeDir.close()
continue
}
if assetDir, ok := dir.(*AssetDir); ok {
assetDir.close()
continue
}
}
u.CreateFTPLog(host.asset, su, operate, filename, isSuccess)
return sf, err
close(u.closed)
}
func (u *UserSftpConn) Name() string {
return "/"
}
func (u *UserSftpConn) Size() int64 { return 0 }
func (u *UserSftpConn) Mode() os.FileMode {
return os.ModePerm | os.ModeDir
}
func (u *UserSftp) Info() (os.FileInfo, error) {
return NewFakeFile("/", true), nil
func (u *UserSftpConn) ModTime() time.Time { return u.modeTime }
func (u *UserSftpConn) IsDir() bool { return true }
func (u *UserSftpConn) Sys() interface{} {
return &syscall.Stat_t{Uid: 0, Gid: 0}
}
func (u *UserSftp) RootDirInfo() ([]os.FileInfo, error) {
hostDirs := make([]os.FileInfo, 0, len(u.hosts))
for key := range u.hosts {
hostDirs = append(hostDirs, NewFakeFile(key, true))
func (u *UserSftpConn) List() (res []os.FileInfo, err error) {
for _, item := range u.Dirs {
res = append(res, item)
}
sort.Sort(FileInfoList(hostDirs))
return hostDirs, nil
return
}
func (u *UserSftp) ParsePath(path string) (req requestMessage) {
data := strings.Split(strings.TrimPrefix(path, "/"), "/")
if len(data) == 0 {
func (u *UserSftpConn) ParsePath(path string) (fi os.FileInfo, restPath string) {
path = strings.TrimPrefix(path, "/")
data := strings.Split(path, "/")
if len(data) == 1 && data[0] == "" {
fi = u
return
}
host, pathArray := data[0], data[1:]
req.host = host
if suName, unique := u.HostHasUniqueSu(host); unique {
req.suUnique = true
req.su = suName
} else {
if len(pathArray) == 0 {
req.su = ""
} else {
req.su, pathArray = pathArray[0], pathArray[1:]
dirs := u.Dirs
var ok bool
for i := 0; i < len(data); i++ {
fi, ok = dirs[data[i]]
if !ok {
restPath = strings.Join(data[i+1:], "/")
break
}
if nodeDir, ok := fi.(*NodeDir); ok {
nodeDir.loadNodeAsset(u)
dirs = nodeDir.subDirs
continue
}
if assetDir, ok := fi.(*AssetDir); ok {
assetDir.loadSystemUsers()
restPath = strings.Join(data[i+1:], "/")
break
}
}
req.dpath = strings.Join(pathArray, "/")
return
}
func (u *UserSftp) GetSFTPAndRealPath(req requestMessage) (conn *SftpConn, realPath string) {
if host, ok := u.hosts[req.host]; ok {
if su, ok := host.suMaps[req.su]; ok {
key := fmt.Sprintf("%s@%s", su.Name, req.host)
conn, ok := u.sftpClients[key]
if !ok {
var err error
conn, err = u.GetSftpClient(host.asset, su)
if err != nil {
logger.Info("Get Sftp Client err: ", err.Error())
return nil, ""
func (u *UserSftpConn) initial() {
nodeTrees := service.GetUserNodeTreeWithAsset(u.User.ID, "", "")
if u.Dirs == nil {
u.Dirs = map[string]os.FileInfo{}
}
for _, item := range nodeTrees {
typeName, ok := item.Meta["type"].(string)
if !ok {
continue
}
body, err := json.Marshal(item.Meta[typeName])
if err != nil {
logger.Errorf("Json Marshal err: %s", err)
continue
}
switch typeName {
case "node":
node, err := model.ConvertMetaToNode(body)
if err != nil {
logger.Errorf("convert to node err: %s", err)
continue
}
nodeDir := NewNodeDir(node)
folderName := nodeDir.folderName
for {
_, ok := u.Dirs[folderName]
if !ok {
break
}
u.sftpClients[key] = conn
folderName = fmt.Sprintf("%s_", folderName)
}
switch strings.ToLower(u.RootPath) {
case "home", "~", "":
realPath = filepath.Join(conn.HomeDirPath, strings.TrimPrefix(req.dpath, "/"))
default:
realPath = filepath.Join(u.RootPath, strings.TrimPrefix(req.dpath, "/"))
if folderName != nodeDir.folderName {
nodeDir.folderName = folderName
}
return conn, realPath
}
}
return
}
func (u *UserSftp) HostHasUniqueSu(hostKey string) (string, bool) {
if host, ok := u.hosts[hostKey]; ok {
host.loadSystemUsers(u.User.ID)
return host.HasUniqueSu()
}
return "", false
}
func (u *UserSftp) validatePermission(su *model.SystemUser, action string) bool {
for _, pemAction := range su.Actions {
if pemAction == action || pemAction == model.AllAction {
return true
u.Dirs[folderName] = &nodeDir
case "asset":
asset, err := model.ConvertMetaToAsset(body)
if err != nil {
logger.Errorf("convert to asset err: %s", err)
continue
}
if !asset.IsSupportProtocol("ssh") {
continue
}
assetDir := NewAssetDir(u.User, asset, u.Addr, u.logChan)
folderName := assetDir.folderName
for {
_, ok := u.Dirs[folderName]
if !ok {
break
}
folderName = fmt.Sprintf("%s_", folderName)
}
if folderName != assetDir.folderName {
assetDir.folderName = folderName
}
u.Dirs[folderName] = &assetDir
}
}
return false
}
func (u *UserSftp) CreateFTPLog(asset *model.Asset, su *model.SystemUser, operate, filename string, isSuccess bool) {
data := model.FTPLog{
User: fmt.Sprintf("%s (%s)", u.User.Name, u.User.Username),
Hostname: asset.Hostname,
OrgID: asset.OrgID,
SystemUser: su.Name,
RemoteAddr: u.Addr,
Operate: operate,
Path: filename,
DataStart: common.CurrentUTCTime(),
IsSuccess: isSuccess,
}
u.LogChan <- &data
}
func (u *UserSftp) LoopPushFTPLog() {
func (u *UserSftpConn) loopPushFTPLog() {
ftpLogList := make([]*model.FTPLog, 0, 1024)
dataChan := make(chan *model.FTPLog)
go u.SendFTPLog(dataChan)
defer close(dataChan)
tick := time.NewTicker(time.Second * 10)
maxRetry := 0
var err error
tick := time.NewTicker(time.Second * 20)
defer tick.Stop()
for {
select {
case <-u.closed:
if len(ftpLogList) == 0 {
return
}
case <-tick.C:
case logData, ok := <-u.LogChan:
if len(ftpLogList) == 0 {
continue
}
case logData, ok := <-u.logChan:
if !ok {
return
}
ftpLogList = append(ftpLogList, logData)
}
if len(ftpLogList) > 0 {
select {
case dataChan <- ftpLogList[len(ftpLogList)-1]:
ftpLogList = ftpLogList[:len(ftpLogList)-1]
default:
}
}
}
}
func (u *UserSftp) SendFTPLog(dataChan <-chan *model.FTPLog) {
for data := range dataChan {
for i := 0; i < 4; i++ {
err := service.PushFTPLog(data)
if err == nil {
break
}
logger.Errorf("Create FTP log err: %s", err.Error())
}
}
}
func (u *UserSftp) GetSftpClient(asset *model.Asset, sysUser *model.SystemUser) (conn *SftpConn, err error) {
var (
sshClient *SSHClient
ok bool
)
if u.ReuseConnection {
key := MakeReuseSSHClientKey(u.User, asset, sysUser)
switch sysUser.Username {
case "":
sshClient, ok = searchSSHClientFromCache(key)
if ok {
sysUser.Username = sshClient.username
}
default:
sshClient, ok = GetClientFromCache(key)
}
if !ok {
sshClient, err = NewClient(u.User, asset, sysUser, u.Overtime, u.ReuseConnection)
if err != nil {
logger.Errorf("Get new SSH client err: %s", err)
return
}
data := ftpLogList[len(ftpLogList)-1]
err = service.PushFTPLog(data)
if err == nil {
ftpLogList = ftpLogList[:len(ftpLogList)-1]
maxRetry = 0
continue
} else {
logger.Infof("Reuse connection for SFTP: %s->%s@%s. SSH client %p current ref: %d",
u.User.Username, sshClient.username, asset.IP, sshClient, sshClient.RefCount())
logger.Errorf("Create FTP log err: %s", err.Error())
}
} else {
sshClient, err = NewClient(u.User, asset, sysUser, u.Overtime, u.ReuseConnection)
if err != nil {
logger.Errorf("Get new SSH client err: %s", err)
return
if maxRetry > 5 {
ftpLogList = ftpLogList[1:]
}
maxRetry++
}
sftpClient, err := sftp.NewClient(sshClient.client)
if err != nil {
logger.Errorf("SSH client %p start sftp client session err %s", sshClient, err)
RecycleClient(sshClient)
return nil, err
}
HomeDirPath, err := sftpClient.Getwd()
if err != nil {
logger.Errorf("SSH client %p get home dir err %s", sshClient, err)
_ = sftpClient.Close()
RecycleClient(sshClient)
return nil, err
}
logger.Infof("SSH client %p start sftp client session success", sshClient)
conn = &SftpConn{client: sftpClient, conn: sshClient, HomeDirPath: HomeDirPath}
return conn, err
}
func (u *UserSftp) Close() {
for _, client := range u.sftpClients {
if client == nil {
continue
}
client.Close()
func NewUserSftpConn(user *model.User, addr string) *UserSftpConn {
u := UserSftpConn{
User: user,
Addr: addr,
Dirs: map[string]os.FileInfo{},
modeTime: time.Now().UTC(),
logChan: make(chan *model.FTPLog, 1024),
closed: make(chan struct{}),
}
close(u.LogChan)
}
type requestMessage struct {
host string
su string
dpath string
suUnique bool
}
func NewHostnameDir(asset *model.Asset) *HostnameDir {
h := HostnameDir{asset: asset}
return &h
}
type HostnameDir struct {
asset *model.Asset
suMaps map[string]*model.SystemUser
u.initial()
go u.loopPushFTPLog()
return &u
}
func (h *HostnameDir) loadSystemUsers(userID string) {
if h.suMaps == nil {
sus := make(map[string]*model.SystemUser)
SystemUsers := service.GetUserAssetSystemUsers(userID, h.asset.ID)
for i := 0; i < len(SystemUsers); i++ {
if SystemUsers[i].Protocol == "ssh" {
sus[SystemUsers[i].Name] = &SystemUsers[i]
func NewUserSftpConnWithAssets(user *model.User, addr string, assets ...model.Asset) *UserSftpConn {
u := UserSftpConn{
User: user,
Addr: addr,
Dirs: map[string]os.FileInfo{},
modeTime: time.Now().UTC(),
logChan: make(chan *model.FTPLog, 1024),
closed: make(chan struct{}),
}
for _, asset := range assets {
if asset.IsSupportProtocol("ssh") {
assetDir := NewAssetDir(u.User, asset, u.Addr, u.logChan)
folderName := assetDir.folderName
for {
_, ok := u.Dirs[folderName]
if !ok {
break
}
folderName = fmt.Sprintf("%s_", folderName)
}
if folderName != assetDir.folderName {
assetDir.folderName = folderName
}
u.Dirs[assetDir.folderName] = &assetDir
}
h.suMaps = sus
}
}
func (h *HostnameDir) HasUniqueSu() (string, bool) {
sus := h.GetSystemUsers()
if len(sus) == 1 {
return sus[0].Name, true
}
return "", false
}
func (h *HostnameDir) GetSystemUsers() (sus []model.SystemUser) {
sus = make([] model.SystemUser, 0, len(h.suMaps))
for _, item := range h.suMaps {
sus = append(sus, *item)
}
model.SortSystemUserByPriority(sus)
return sus
}
type SftpConn struct {
HomeDirPath string
client *sftp.Client
conn *SSHClient
}
func (s *SftpConn) Close() {
if s.client == nil {
return
}
_ = s.client.Close()
RecycleClient(s.conn)
}
func NewFakeFile(name string, isDir bool) *FakeFileInfo {
return &FakeFileInfo{
name: name,
modtime: time.Now().UTC(),
isDir: isDir,
size: int64(0),
}
}
func NewFakeSymFile(name string) *FakeFileInfo {
return &FakeFileInfo{
name: name,
modtime: time.Now().UTC(),
size: int64(0),
symlink: name,
}
}
type FakeFileInfo struct {
name string
isDir bool
size int64
modtime time.Time
symlink string
}
func (f *FakeFileInfo) Name() string { return f.name }
func (f *FakeFileInfo) Size() int64 { return f.size }
func (f *FakeFileInfo) Mode() os.FileMode {
ret := os.FileMode(0644)
if f.isDir {
ret = os.FileMode(0755) | os.ModeDir
}
if f.symlink != "" {
ret = os.FileMode(0777) | os.ModeSymlink
}
return ret
}
func (f *FakeFileInfo) ModTime() time.Time { return f.modtime }
func (f *FakeFileInfo) IsDir() bool { return f.isDir }
func (f *FakeFileInfo) Sys() interface{} {
return &syscall.Stat_t{Uid: 0, Gid: 0}
}
type FileInfoList []os.FileInfo
func (fl FileInfoList) Len() int {
return len(fl)
go u.loopPushFTPLog()
return &u
}
func (fl FileInfoList) Swap(i, j int) { fl[i], fl[j] = fl[j], fl[i] }
func (fl FileInfoList) Less(i, j int) bool { return fl[i].Name() < fl[j].Name() }
package srvconn
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"syscall"
"time"
"github.com/pkg/sftp"
"github.com/jumpserver/koko/pkg/common"
"github.com/jumpserver/koko/pkg/config"
"github.com/jumpserver/koko/pkg/logger"
"github.com/jumpserver/koko/pkg/model"
"github.com/jumpserver/koko/pkg/service"
)
type NodeDir struct {
node *model.Node
subDirs map[string]os.FileInfo
folderName string
modeTime time.Time
once *sync.Once
}
func (nd *NodeDir) Name() string {
return nd.folderName
}
func (nd *NodeDir) Size() int64 { return 0 }
func (nd *NodeDir) Mode() os.FileMode {
return os.ModePerm | os.ModeDir
}
func (nd *NodeDir) ModTime() time.Time { return nd.modeTime }
func (nd *NodeDir) IsDir() bool { return true }
func (nd *NodeDir) Sys() interface{} {
return &syscall.Stat_t{Uid: 0, Gid: 0}
}
func (nd *NodeDir) List() (res []os.FileInfo, err error) {
for _, item := range nd.subDirs {
res = append(res, item)
}
return
}
func (nd *NodeDir) loadNodeAsset(uSftp *UserSftpConn) {
nd.once.Do(func() {
nodeTrees := service.GetUserNodeTreeWithAsset(uSftp.User.ID, nd.node.ID, "1")
dirs := map[string]os.FileInfo{}
for _, item := range nodeTrees {
typeName, ok := item.Meta["type"].(string)
if !ok {
continue
}
body, err := json.Marshal(item.Meta[typeName])
if err != nil {
continue
}
switch typeName {
case "node":
node, err := model.ConvertMetaToNode(body)
if err != nil {
logger.Errorf("convert node err: %s", err)
continue
}
nodeDir := NewNodeDir(node)
folderName := nodeDir.folderName
for {
_, ok := dirs[folderName]
if !ok {
break
}
folderName = fmt.Sprintf("%s_", folderName)
}
if folderName != nodeDir.folderName {
nodeDir.folderName = folderName
}
dirs[folderName] = &nodeDir
case "asset":
asset, err := model.ConvertMetaToAsset(body)
if err != nil {
logger.Errorf("convert asset err: %s", err)
continue
}
if !asset.IsSupportProtocol("ssh") {
continue
}
assetDir := NewAssetDir(uSftp.User, asset, uSftp.Addr, uSftp.logChan)
folderName := assetDir.folderName
for {
_, ok := dirs[folderName]
if !ok {
break
}
folderName = fmt.Sprintf("%s_", folderName)
}
if folderName != assetDir.folderName {
assetDir.folderName = folderName
}
dirs[folderName] = &assetDir
}
}
nd.subDirs = dirs
})
}
func (nd *NodeDir) close() {
for _, dir := range nd.subDirs {
if nodeDir, ok := dir.(*NodeDir); ok {
nodeDir.close()
continue
}
if assetDir, ok := dir.(*AssetDir); ok {
assetDir.close()
}
}
}
func NewNodeDir(node model.Node) NodeDir {
folderName := node.Value
if strings.Contains(node.Value, "/") {
folderName = strings.ReplaceAll(node.Value, "/", "_")
}
return NodeDir{
node: &node,
folderName: folderName,
subDirs: map[string]os.FileInfo{},
modeTime: time.Now().UTC(),
once: new(sync.Once),
}
}
func NewAssetDir(user *model.User, asset model.Asset, addr string, logChan chan<- *model.FTPLog) AssetDir {
folderName := asset.Hostname
if strings.Contains(folderName, "/") {
folderName = strings.ReplaceAll(folderName, "/", "_")
}
conf := config.GetConf()
return AssetDir{
user: user,
asset: &asset,
folderName: folderName,
modeTime: time.Now().UTC(),
addr: addr,
suMaps: nil,
logChan: logChan,
Overtime: conf.SSHTimeout * time.Second,
RootPath: conf.SftpRoot,
ShowHidden: conf.ShowHiddenFile,
reuse: conf.ReuseConnection,
sftpClients: map[string]*SftpConn{},
}
}
type AssetDir struct {
user *model.User
asset *model.Asset
folderName string
modeTime time.Time
addr string
suMaps map[string]*model.SystemUser
logChan chan<- *model.FTPLog
sftpClients map[string]*SftpConn // systemUser_id
once sync.Once
RootPath string
reuse bool
ShowHidden bool
Overtime time.Duration
}
func (ad *AssetDir) Name() string {
return ad.folderName
}
func (ad *AssetDir) Size() int64 { return 0 }
func (ad *AssetDir) Mode() os.FileMode {
return os.ModePerm | os.ModeDir
}
func (ad *AssetDir) ModTime() time.Time { return ad.modeTime }
func (ad *AssetDir) IsDir() bool { return true }
func (ad *AssetDir) Sys() interface{} {
return &syscall.Stat_t{Uid: 0, Gid: 0}
}
func (ad *AssetDir) loadSystemUsers() {
ad.once.Do(func() {
sus := make(map[string]*model.SystemUser)
SystemUsers := service.GetUserAssetSystemUsers(ad.user.ID, ad.asset.ID)
for i := 0; i < len(SystemUsers); i++ {
if SystemUsers[i].Protocol == "ssh" {
ok := true
folderName := SystemUsers[i].Name
for ok {
if _, ok = sus[folderName]; ok {
folderName = fmt.Sprintf("%s_", folderName)
}
}
sus[folderName] = &SystemUsers[i]
}
}
ad.suMaps = sus
})
}
func (ad *AssetDir) Create(path string) (*sftp.File, error) {
pathData := ad.parsePath(path)
folderName, ok := ad.IsUniqueSu()
if !ok {
if len(pathData) == 1 && pathData[0] == "" {
return nil, sftp.ErrSshFxPermissionDenied
}
folderName = pathData[0]
pathData = pathData[1:]
}
su, ok := ad.suMaps[folderName]
if !ok {
return nil, sftp.ErrSshFxNoSuchFile
}
if !ad.validatePermission(su, model.UploadAction) {
return nil, sftp.ErrSshFxPermissionDenied
}
con, realPath := ad.GetSFTPAndRealPath(su, strings.Join(pathData, "/"))
if con == nil {
return nil, sftp.ErrSshFxConnectionLost
}
sf, err := con.client.Create(realPath)
filename := realPath
isSuccess := false
operate := model.OperateUpload
if err == nil {
isSuccess = true
}
ad.CreateFTPLog(su, operate, filename, isSuccess)
return sf, err
}
func (ad *AssetDir) MkdirAll(path string) (err error) {
pathData := ad.parsePath(path)
folderName, ok := ad.IsUniqueSu()
if !ok {
if len(pathData) == 1 && pathData[0] == "" {
return sftp.ErrSshFxPermissionDenied
}
folderName = pathData[0]
pathData = pathData[1:]
}
su, ok := ad.suMaps[folderName]
if !ok {
return sftp.ErrSshFxNoSuchFile
}
if !ad.validatePermission(su, model.UploadAction) {
return sftp.ErrSshFxPermissionDenied
}
con, realPath := ad.GetSFTPAndRealPath(su, strings.Join(pathData, "/"))
if con == nil {
return sftp.ErrSshFxConnectionLost
}
err = con.client.MkdirAll(realPath)
filename := realPath
isSuccess := false
operate := model.OperateMkdir
if err == nil {
isSuccess = true
}
ad.CreateFTPLog(su, operate, filename, isSuccess)
return
}
func (ad *AssetDir) Open(path string) (*sftp.File, error) {
pathData := ad.parsePath(path)
folderName, ok := ad.IsUniqueSu()
if !ok {
if len(pathData) == 1 && pathData[0] == "" {
return nil, sftp.ErrSshFxPermissionDenied
}
folderName = pathData[0]
pathData = pathData[1:]
}
su, ok := ad.suMaps[folderName]
if !ok {
return nil, sftp.ErrSshFxNoSuchFile
}
if !ad.validatePermission(su, model.DownloadAction) {
return nil, sftp.ErrSshFxPermissionDenied
}
con, realPath := ad.GetSFTPAndRealPath(su, strings.Join(pathData, "/"))
if con == nil {
return nil, sftp.ErrSshFxConnectionLost
}
sf, err := con.client.Open(realPath)
filename := realPath
isSuccess := false
operate := model.OperateDownaload
if err == nil {
isSuccess = true
}
ad.CreateFTPLog(su, operate, filename, isSuccess)
return sf, err
}
func (ad *AssetDir) ReadDir(path string) (res []os.FileInfo, err error) {
pathData := ad.parsePath(path)
folderName, ok := ad.IsUniqueSu()
if !ok {
if len(pathData) == 1 && pathData[0] == "" {
for folderName := range ad.suMaps {
res = append(res, NewFakeFile(folderName, true))
}
return
}
folderName = pathData[0]
}
su, ok := ad.suMaps[folderName]
if !ok {
return nil, sftp.ErrSshFxNoSuchFile
}
if !ad.validatePermission(su, model.ConnectAction) {
return res, sftp.ErrSshFxPermissionDenied
}
con, realPath := ad.GetSFTPAndRealPath(su, strings.Join(pathData[1:], "/"))
if con == nil {
return nil, sftp.ErrSshFxConnectionLost
}
res, err = con.client.ReadDir(realPath)
if !ad.ShowHidden {
noHiddenFiles := make([]os.FileInfo, 0, len(res))
for i := 0; i < len(res); i++ {
if !strings.HasPrefix(res[i].Name(), ".") {
noHiddenFiles = append(noHiddenFiles, res[i])
}
}
return noHiddenFiles, err
}
return
}
func (ad *AssetDir) ReadLink(path string) (res string, err error) {
pathData := ad.parsePath(path)
if len(pathData) == 1 && pathData[0] == "" {
return "", sftp.ErrSshFxOpUnsupported
}
folderName, ok := ad.IsUniqueSu()
if !ok {
folderName = pathData[0]
}
su, ok := ad.suMaps[folderName]
if !ok {
return "", sftp.ErrSshFxNoSuchFile
}
if !ad.validatePermission(su, model.ConnectAction) {
return res, sftp.ErrSshFxPermissionDenied
}
con, realPath := ad.GetSFTPAndRealPath(su, strings.Join(pathData[1:], "/"))
if con == nil {
return "", sftp.ErrSshFxConnectionLost
}
res, err = con.client.ReadLink(realPath)
return
}
func (ad *AssetDir) RemoveDirectory(path string) (err error) {
pathData := ad.parsePath(path)
folderName, ok := ad.IsUniqueSu()
if !ok {
if len(pathData) == 1 && pathData[0] == "" {
return sftp.ErrSshFxPermissionDenied
}
folderName = pathData[0]
pathData = pathData[1:]
}
su, ok := ad.suMaps[folderName]
if !ok {
return sftp.ErrSshFxNoSuchFile
}
if !ad.validatePermission(su, model.UploadAction) {
return sftp.ErrSshFxPermissionDenied
}
con, realPath := ad.GetSFTPAndRealPath(su, strings.Join(pathData, "/"))
if con == nil {
return sftp.ErrSshFxConnectionLost
}
err = ad.removeDirectoryAll(con.client, realPath)
filename := realPath
isSuccess := false
operate := model.OperateRemoveDir
if err == nil {
isSuccess = true
}
ad.CreateFTPLog(su, operate, filename, isSuccess)
return
}
func (ad *AssetDir) Rename(oldNamePath, newNamePath string) (err error) {
oldPathData := ad.parsePath(oldNamePath)
newPathData := ad.parsePath(newNamePath)
folderName, ok := ad.IsUniqueSu()
if !ok {
if oldPathData[0] != newPathData[0] {
return sftp.ErrSshFxNoSuchFile
}
folderName = oldPathData[0]
oldPathData = oldPathData[1:]
newPathData = newPathData[1:]
}
su, ok := ad.suMaps[folderName]
if !ok {
return sftp.ErrSshFxNoSuchFile
}
conn1, oldRealPath := ad.GetSFTPAndRealPath(su, strings.Join(oldPathData, "/"))
conn2, newRealPath := ad.GetSFTPAndRealPath(su, strings.Join(newPathData, "/"))
if conn1 != conn2 {
return sftp.ErrSshFxOpUnsupported
}
err = conn1.client.Rename(oldRealPath, newRealPath)
filename := fmt.Sprintf("%s=>%s", oldRealPath, newRealPath)
isSuccess := false
operate := model.OperateRename
if err == nil {
isSuccess = true
}
ad.CreateFTPLog(su, operate, filename, isSuccess)
return
}
func (ad *AssetDir) Remove(path string) (err error) {
pathData := ad.parsePath(path)
folderName, ok := ad.IsUniqueSu()
if !ok {
if len(pathData) == 1 && pathData[0] == "" {
return sftp.ErrSshFxPermissionDenied
}
folderName = pathData[0]
pathData = pathData[1:]
}
su, ok := ad.suMaps[folderName]
if !ok {
return sftp.ErrSshFxNoSuchFile
}
if !ad.validatePermission(su, model.UploadAction) {
return sftp.ErrSshFxPermissionDenied
}
con, realPath := ad.GetSFTPAndRealPath(su, strings.Join(pathData, "/"))
if con == nil {
return sftp.ErrSshFxConnectionLost
}
err = con.client.Remove(realPath)
filename := realPath
isSuccess := false
operate := model.OperateDelete
if err == nil {
isSuccess = true
}
ad.CreateFTPLog(su, operate, filename, isSuccess)
return
}
func (ad *AssetDir) Stat(path string) (res os.FileInfo, err error) {
pathData := ad.parsePath(path)
if len(pathData) == 1 && pathData[0] == "" {
return ad, nil
}
folderName, ok := ad.IsUniqueSu()
if !ok {
folderName = pathData[0]
}
su, ok := ad.suMaps[folderName]
if !ok {
return nil, sftp.ErrSshFxNoSuchFile
}
if !ad.validatePermission(su, model.ConnectAction) {
return res, sftp.ErrSshFxPermissionDenied
}
con, realPath := ad.GetSFTPAndRealPath(su, strings.Join(pathData[1:], "/"))
if con == nil {
return nil, sftp.ErrSshFxConnectionLost
}
res, err = con.client.Stat(realPath)
return
}
func (ad *AssetDir) Symlink(oldNamePath, newNamePath string) (err error) {
oldPathData := ad.parsePath(oldNamePath)
newPathData := ad.parsePath(newNamePath)
folderName, ok := ad.IsUniqueSu()
if !ok {
if oldPathData[0] != newPathData[0] {
return sftp.ErrSshFxNoSuchFile
}
folderName = oldPathData[0]
oldPathData = oldPathData[1:]
newPathData = newPathData[1:]
}
su, ok := ad.suMaps[folderName]
if !ok {
return sftp.ErrSshFxNoSuchFile
}
if !ad.validatePermission(su, model.UploadAction) {
return sftp.ErrSshFxPermissionDenied
}
conn1, oldRealPath := ad.GetSFTPAndRealPath(su, strings.Join(oldPathData, "/"))
conn2, newRealPath := ad.GetSFTPAndRealPath(su, strings.Join(newPathData, "/"))
if conn1 != conn2 {
return sftp.ErrSshFxOpUnsupported
}
err = conn1.client.Symlink(oldRealPath, newRealPath)
filename := fmt.Sprintf("%s=>%s", oldRealPath, newRealPath)
isSuccess := false
operate := model.OperateSymlink
if err == nil {
isSuccess = true
}
ad.CreateFTPLog(su, operate, filename, isSuccess)
return
}
func (ad *AssetDir) removeDirectoryAll(conn *sftp.Client, path string) error {
var err error
var files []os.FileInfo
files, err = conn.ReadDir(path)
if err != nil {
return err
}
for _, item := range files {
realPath := filepath.Join(path, item.Name())
if item.IsDir() {
err = ad.removeDirectoryAll(conn, realPath)
if err != nil {
return err
}
continue
}
err = conn.Remove(realPath)
if err != nil {
return err
}
}
return conn.RemoveDirectory(path)
}
func (ad *AssetDir) GetSFTPAndRealPath(su *model.SystemUser, path string) (conn *SftpConn, realPath string) {
var ok bool
conn, ok = ad.sftpClients[su.ID]
if !ok {
var err error
conn, err = ad.GetSftpClient(su)
if err != nil {
logger.Errorf("Get Sftp Client err: %s", err.Error())
return nil, ""
}
ad.sftpClients[su.ID] = conn
}
switch strings.ToLower(ad.RootPath) {
case "home", "~", "":
realPath = filepath.Join(conn.HomeDirPath, strings.TrimPrefix(path, "/"))
default:
realPath = filepath.Join(ad.RootPath, strings.TrimPrefix(path, "/"))
}
return
}
func (ad *AssetDir) IsUniqueSu() (folderName string, ok bool) {
sus := ad.getSubFolderNames()
if len(sus) == 1 {
return sus[0], true
}
return
}
func (ad *AssetDir) getSubFolderNames() []string {
sus := make([]string, 0, len(ad.suMaps))
for folderName := range ad.suMaps {
sus = append(sus, folderName)
}
return sus
}
func (ad *AssetDir) validatePermission(su *model.SystemUser, action string) bool {
for _, pemAction := range su.Actions {
if pemAction == action || pemAction == model.AllAction {
return true
}
}
return false
}
func (ad *AssetDir) GetSftpClient(su *model.SystemUser) (conn *SftpConn, err error) {
var (
sshClient *SSHClient
ok bool
)
if ad.reuse {
key := MakeReuseSSHClientKey(ad.user, ad.asset, su)
switch su.Username {
case "":
sshClient, ok = searchSSHClientFromCache(key)
if ok {
su.Username = sshClient.username
}
default:
sshClient, ok = GetClientFromCache(key)
}
if !ok {
sshClient, err = NewClient(ad.user, ad.asset, su, ad.Overtime, ad.reuse)
if err != nil {
logger.Errorf("Get new SSH client err: %s", err)
return
}
} else {
logger.Infof("Reuse connection for SFTP: %s->%s@%s. SSH client %p current ref: %d",
ad.user.Username, sshClient.username, ad.asset.IP, sshClient, sshClient.RefCount())
}
} else {
sshClient, err = NewClient(ad.user, ad.asset, su, ad.Overtime, ad.reuse)
if err != nil {
logger.Errorf("Get new SSH client err: %s", err)
return
}
}
sftpClient, err := sftp.NewClient(sshClient.client)
if err != nil {
logger.Errorf("SSH client %p start sftp client session err %s", sshClient, err)
RecycleClient(sshClient)
return nil, err
}
HomeDirPath, err := sftpClient.Getwd()
if err != nil {
logger.Errorf("SSH client %p get home dir err %s", sshClient, err)
_ = sftpClient.Close()
RecycleClient(sshClient)
return nil, err
}
logger.Infof("SSH client %p start sftp client session success", sshClient)
conn = &SftpConn{client: sftpClient, conn: sshClient, HomeDirPath: HomeDirPath}
return conn, err
}
func (ad *AssetDir) parsePath(path string) []string {
path = strings.TrimPrefix(path, "/")
return strings.Split(path, "/")
}
func (ad *AssetDir) close() {
for _, conn := range ad.sftpClients {
if conn != nil {
conn.Close()
}
}
}
func (ad *AssetDir) CreateFTPLog(su *model.SystemUser, operate, filename string, isSuccess bool) {
data := model.FTPLog{
User: fmt.Sprintf("%s (%s)", ad.user.Name, ad.user.Username),
Hostname: ad.asset.Hostname,
OrgID: ad.asset.OrgID,
SystemUser: su.Name,
RemoteAddr: ad.addr,
Operate: operate,
Path: filename,
DataStart: common.CurrentUTCTime(),
IsSuccess: isSuccess,
}
ad.logChan <- &data
}
type SftpConn struct {
HomeDirPath string
client *sftp.Client
conn *SSHClient
}
func (s *SftpConn) Close() {
if s.client == nil {
return
}
_ = s.client.Close()
RecycleClient(s.conn)
}
func NewFakeFile(name string, isDir bool) *FakeFileInfo {
return &FakeFileInfo{
name: name,
modTime: time.Now().UTC(),
isDir: isDir,
size: int64(0),
}
}
func NewFakeSymFile(name string) *FakeFileInfo {
return &FakeFileInfo{
name: name,
modTime: time.Now().UTC(),
size: int64(0),
symlink: name,
}
}
type FakeFileInfo struct {
name string
isDir bool
size int64
modTime time.Time
symlink string
}
func (f *FakeFileInfo) Name() string { return f.name }
func (f *FakeFileInfo) Size() int64 { return f.size }
func (f *FakeFileInfo) Mode() os.FileMode {
ret := os.FileMode(0644)
if f.isDir {
ret = os.FileMode(0755) | os.ModeDir
}
if f.symlink != "" {
ret = os.FileMode(0777) | os.ModeSymlink
}
return ret
}
func (f *FakeFileInfo) ModTime() time.Time { return f.modTime }
func (f *FakeFileInfo) IsDir() bool { return f.isDir }
func (f *FakeFileInfo) Sys() interface{} {
return &syscall.Stat_t{Uid: 0, Gid: 0}
}
type FileInfoList []os.FileInfo
func (fl FileInfoList) Len() int {
return len(fl)
}
func (fl FileInfoList) Swap(i, j int) { fl[i], fl[j] = fl[j], fl[i] }
func (fl FileInfoList) Less(i, j int) bool { return fl[i].Name() < fl[j].Name() }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment