2021-12-19 17:30:51 +01:00

191 lines
4.7 KiB
Go

package ssh
import (
"context"
"crypto/md5"
c "docker4ssh/config"
"docker4ssh/database"
"docker4ssh/docker"
"docker4ssh/terminal"
"encoding/hex"
"fmt"
"go.uber.org/zap"
"golang.org/x/crypto/ssh"
"net"
"regexp"
"strings"
)
var (
users = make([]*User, 0)
profiles c.Profiles
dynamicProfile c.Profile
)
type User struct {
*ssh.ServerConn
ID string
IP string
Profile *c.Profile
Terminal *terminal.Terminal
Container *docker.SimpleContainer
}
func GetUser(ip string) *User {
for _, user := range users {
if container := user.Container; container != nil && container.Network.IP == ip {
return user
}
}
return nil
}
type extras struct {
containerID string
}
func StartServing(config *c.Config, serverConfig *ssh.ServerConfig) (errChan chan error, closer func() error) {
errChan = make(chan error, 1)
var err error
profiles, err = c.LoadProfileDir(config.Profile.Dir, c.DefaultPreProfileFromConfig(config))
if err != nil {
errChan <- err
return
}
zap.S().Debugf("Loaded %d profile(s)", len(profiles))
if config.Profile.Dynamic.Enable {
dynamicProfile, err = c.DynamicProfileFromConfig(config, c.DefaultPreProfileFromConfig(config))
if err != nil {
errChan <- err
return
}
zap.S().Debugf("Loaded dynamic profile")
}
cli, err := docker.InitCli()
if err != nil {
errChan <- err
return
}
zap.S().Debugf("Initialized docker cli")
network, err := docker.InitNetwork(context.Background(), cli, config)
if err != nil {
errChan <- err
return
}
zap.S().Debugf("Initialized docker networks")
client := &docker.Client{
Client: cli,
Database: database.GetDatabase(),
Network: network,
}
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", config.SSH.Port))
if err != nil {
errChan <- err
return
}
zap.S().Debugf("Created ssh listener")
var closed bool
go func() {
db := database.GetDatabase()
for {
conn, err := listener.Accept()
if err != nil {
if closed {
return
}
zap.S().Errorf("Failed to accept new ssh user: %v", err)
continue
}
serverConn, chans, requests, err := ssh.NewServerConn(conn, serverConfig)
if err != nil {
zap.S().Errorf("Failed to establish new ssh connection: %v", err)
continue
}
idBytes := md5.Sum([]byte(strings.Split(serverConn.User(), ":")[0]))
idString := hex.EncodeToString(idBytes[:])
zap.S().Infof("New ssh connection from %s with %s (%s)", serverConn.RemoteAddr().String(), serverConn.ClientVersion(), idString)
var profile *c.Profile
if name, ok := serverConn.Permissions.CriticalOptions["profile"]; ok {
if name == "dynamic" {
if image, ok := serverConn.Permissions.CriticalOptions["image"]; ok {
tempDynamicProfile := dynamicProfile
tempDynamicProfile.Image = image
profile = &tempDynamicProfile
}
}
if profile == nil {
if profile, ok = profiles.GetByName(name); !ok {
zap.S().Errorf("Failed to get profile %s", name)
continue
}
}
} else if containerID, ok := serverConn.Permissions.CriticalOptions["containerID"]; ok {
if settings, err := db.SettingsByContainerID(containerID); err == nil {
profile = &c.Profile{
NetworkMode: *settings.NetworkMode,
Configurable: *settings.Configurable,
RunLevel: *settings.RunLevel,
StartupInformation: *settings.StartupInformation,
ExitAfter: *settings.ExitAfter,
KeepOnExit: *settings.KeepOnExit,
ContainerID: containerID,
}
} else {
for _, container := range allContainers {
if container.ContainerID == containerID {
cconfig := c.GetConfig()
profile = &c.Profile{
Password: regexp.MustCompile(cconfig.Profile.Default.Password),
NetworkMode: cconfig.Profile.Default.NetworkMode,
Configurable: cconfig.Profile.Default.Configurable,
RunLevel: cconfig.Profile.Default.RunLevel,
StartupInformation: cconfig.Profile.Default.StartupInformation,
ExitAfter: cconfig.Profile.Default.ExitAfter,
KeepOnExit: cconfig.Profile.Default.KeepOnExit,
Image: "",
ContainerID: containerID,
}
}
}
}
}
zap.S().Debugf("User %s has profile %s", idString, profile.Name())
user := &User{
ServerConn: serverConn,
ID: idString,
Terminal: &terminal.Terminal{},
Profile: profile,
}
users = append(users, user)
go ssh.DiscardRequests(requests)
go handleChannels(chans, client, user)
}
}()
return errChan, func() error {
closed = true
// close all containers
closeAllContainers(context.Background())
// close the listener
return listener.Close()
}
}