Factor out ssh service code
This commit is contained in:
		
							parent
							
								
									19372c17f4
								
							
						
					
					
						commit
						935b777e57
					
				
							
								
								
									
										297
									
								
								cmd/ssh/main.go
									
									
									
									
									
								
							
							
						
						
									
										297
									
								
								cmd/ssh/main.go
									
									
									
									
									
								
							@ -2,25 +2,18 @@ package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"path"
 | 
			
		||||
	"os"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/crypto/ssh"
 | 
			
		||||
 | 
			
		||||
	"git.defalsify.org/vise.git/db"
 | 
			
		||||
	"git.defalsify.org/vise.git/engine"
 | 
			
		||||
	"git.defalsify.org/vise.git/logging"
 | 
			
		||||
	"git.defalsify.org/vise.git/resource"
 | 
			
		||||
	"git.defalsify.org/vise.git/state"
 | 
			
		||||
 | 
			
		||||
	"git.grassecon.net/urdt/ussd/internal/handlers"
 | 
			
		||||
	"git.grassecon.net/urdt/ussd/internal/storage"
 | 
			
		||||
	"git.grassecon.net/urdt/ussd/internal/ssh"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
@ -30,274 +23,6 @@ var (
 | 
			
		||||
	scriptDir = path.Join("services", "registration")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type auther struct {
 | 
			
		||||
	Ctx context.Context
 | 
			
		||||
	auth map[string]string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewAuther(ctx context.Context) *auther {
 | 
			
		||||
	return &auther{
 | 
			
		||||
		Ctx: ctx,
 | 
			
		||||
		auth: make(map[string]string),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func(a *auther) Check(conn ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
 | 
			
		||||
	keyStore.SetLanguage(nil)
 | 
			
		||||
	keyStore.SetPrefix(storage.DATATYPE_CUSTOM)
 | 
			
		||||
	k := append([]byte{0x01}, pubKey.Marshal()...)
 | 
			
		||||
	v, err := keyStore.Get(a.Ctx, k)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	ka := hex.EncodeToString(conn.SessionID())
 | 
			
		||||
	va := string(v)
 | 
			
		||||
	a.auth[ka] = va 
 | 
			
		||||
	fmt.Fprintf(os.Stderr, "connect: %s -> %s\n", ka, v)
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func(a *auther) FromConn(c *ssh.ServerConn) (string, error) {
 | 
			
		||||
	if c == nil {
 | 
			
		||||
		return "", errors.New("nil server conn")
 | 
			
		||||
	}
 | 
			
		||||
	if c.Conn == nil {
 | 
			
		||||
		return "", errors.New("nil underlying conn")
 | 
			
		||||
	}
 | 
			
		||||
	return a.Get(c.Conn.SessionID())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func(a *auther) Get(k []byte) (string, error) {
 | 
			
		||||
	ka := hex.EncodeToString(k)
 | 
			
		||||
	v, ok := a.auth[ka]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return "", errors.New("not found")
 | 
			
		||||
	}
 | 
			
		||||
	return v, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//func serve(ctx context.Context, sessionId string, ch ssh.NewChannel, mss *storage.MenuStorageService, lhs *handlers.LocalHandlerService) error {
 | 
			
		||||
func serve(ctx context.Context, sessionId string, ch ssh.NewChannel, en engine.Engine) error {
 | 
			
		||||
	if ch == nil {
 | 
			
		||||
		return errors.New("nil channel")
 | 
			
		||||
	}
 | 
			
		||||
	if ch.ChannelType() != "session" {
 | 
			
		||||
		ch.Reject(ssh.UnknownChannelType, "that is not the channel you are looking for")
 | 
			
		||||
		return errors.New("not a session")
 | 
			
		||||
	}
 | 
			
		||||
	channel, requests, err := ch.Accept()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	defer channel.Close()
 | 
			
		||||
	wg.Add(1)
 | 
			
		||||
	go func(reqIn <-chan *ssh.Request) {
 | 
			
		||||
		defer wg.Done()
 | 
			
		||||
		for req := range reqIn {
 | 
			
		||||
			req.Reply(req.Type == "shell", nil)	
 | 
			
		||||
		}
 | 
			
		||||
		_ = requests
 | 
			
		||||
	}(requests)
 | 
			
		||||
 | 
			
		||||
	cont, err := en.Exec(ctx, []byte{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("initial engine exec err: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var input [state.INPUT_LIMIT]byte
 | 
			
		||||
	for cont {
 | 
			
		||||
		c, err := en.Flush(ctx, channel)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("flush err: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		_, err = channel.Write([]byte{0x0a})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("newline err: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		c, err = channel.Read(input[:])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("read input fail: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		logg.TraceCtxf(ctx, "input read", "c", c, "input", input[:c-1])
 | 
			
		||||
		cont, err = en.Exec(ctx, input[:c-1])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("engine exec err: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		logg.TraceCtxf(ctx, "exec cont", "cont", cont, "en", en)
 | 
			
		||||
		_ = c
 | 
			
		||||
	}
 | 
			
		||||
	c, err := en.Flush(ctx, channel)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("last flush err: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	_ = c
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type sshRunner struct {
 | 
			
		||||
	Ctx context.Context
 | 
			
		||||
	Cfg engine.Config
 | 
			
		||||
	FlagFile string
 | 
			
		||||
	DbDir string
 | 
			
		||||
	ResourceDir string
 | 
			
		||||
	Debug bool
 | 
			
		||||
	KeyFile string
 | 
			
		||||
	Host string
 | 
			
		||||
	Port uint
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func(s *sshRunner) GetEngine(sessionId string) (engine.Engine, func(), error) {
 | 
			
		||||
	ctx := s.Ctx
 | 
			
		||||
	menuStorageService := storage.NewMenuStorageService(s.DbDir, s.ResourceDir)
 | 
			
		||||
 | 
			
		||||
	err := menuStorageService.EnsureDbDir()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rs, err := menuStorageService.GetResource(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pe, err := menuStorageService.GetPersister(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	userdatastore, err := menuStorageService.GetUserdataDb(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dbResource, ok := rs.(*resource.DbResource)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	lhs, err := handlers.NewLocalHandlerService(s.FlagFile, true, dbResource, s.Cfg, rs)
 | 
			
		||||
	lhs.SetDataStore(&userdatastore)
 | 
			
		||||
	lhs.SetPersister(pe)
 | 
			
		||||
	lhs.Cfg.SessionId = sessionId
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hl, err := lhs.GetHandler()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	en := lhs.GetEngine()
 | 
			
		||||
	en = en.WithFirst(hl.Init)
 | 
			
		||||
	if s.Debug {
 | 
			
		||||
		en = en.WithDebug(nil)
 | 
			
		||||
	}
 | 
			
		||||
	// TODO: this is getting very hacky!
 | 
			
		||||
	closer := func() {
 | 
			
		||||
		err := menuStorageService.Close()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logg.ErrorCtxf(ctx, "menu storage service cleanup fail", "err", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return en, closer, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// adapted example from crypto/ssh package, NewServerConn doc
 | 
			
		||||
func(s *sshRunner) Run(ctx context.Context) {
 | 
			
		||||
	running := true
 | 
			
		||||
 | 
			
		||||
	// TODO: waitgroup should probably not be global
 | 
			
		||||
	defer wg.Wait()
 | 
			
		||||
 | 
			
		||||
	auth := NewAuther(ctx)
 | 
			
		||||
	cfg := ssh.ServerConfig{
 | 
			
		||||
		PublicKeyCallback: auth.Check,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	privateBytes, err := os.ReadFile(s.KeyFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logg.ErrorCtxf(ctx, "Failed to load private key", "err", err)
 | 
			
		||||
	}
 | 
			
		||||
	private, err := ssh.ParsePrivateKey(privateBytes)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logg.ErrorCtxf(ctx, "Failed to parse private key", "err", err)
 | 
			
		||||
	}
 | 
			
		||||
	cfg.AddHostKey(private)
 | 
			
		||||
 | 
			
		||||
	lst, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Host, s.Port))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for running {
 | 
			
		||||
		conn, err := lst.Accept()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			panic(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		go func(conn net.Conn) {
 | 
			
		||||
			defer conn.Close()
 | 
			
		||||
			for true {
 | 
			
		||||
				srvConn, nC, rC, err := ssh.NewServerConn(conn, &cfg)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logg.InfoCtxf(ctx, "rejected client", "err", err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				logg.DebugCtxf(ctx, "ssh client connected", "conn", srvConn)
 | 
			
		||||
 | 
			
		||||
				wg.Add(1)
 | 
			
		||||
				go func() {
 | 
			
		||||
					ssh.DiscardRequests(rC)
 | 
			
		||||
					wg.Done()
 | 
			
		||||
				}()
 | 
			
		||||
				
 | 
			
		||||
				sessionId, err := auth.FromConn(srvConn)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logg.ErrorCtxf(ctx, "Cannot find authentication")
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				en, closer, err := s.GetEngine(sessionId)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logg.ErrorCtxf(ctx, "engine won't start", "err", err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				defer func() {
 | 
			
		||||
					err := en.Finish()
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						logg.ErrorCtxf(ctx, "engine won't stop", "err", err)
 | 
			
		||||
					}
 | 
			
		||||
					closer()
 | 
			
		||||
				}()
 | 
			
		||||
				for ch := range nC {
 | 
			
		||||
					err = serve(ctx, sessionId, ch, en)
 | 
			
		||||
					logg.ErrorCtxf(ctx, "ssh server finish", "err", err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}(conn)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO: This is test code, move to external tool for adding and removing keys
 | 
			
		||||
func sshLoadKeys(ctx context.Context, dbDir string) error {
 | 
			
		||||
	keyStoreFile := path.Join(dbDir, "ssh_authorized_keys.gdbm")
 | 
			
		||||
	keyStore = storage.NewThreadGdbmDb()
 | 
			
		||||
	err := keyStore.Connect(ctx, keyStoreFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCu5rYCxMBsVAL1TEkMQgmElAYEZj5zYDdyHjUxZ6qzHBOZD9GAzdxx9GyQDx2vdYm3329tLH/69ky1YA3nUz8SnJGBD6hC5XrqwN6zo9R9oOHAKTwiPGhey2NTVmheP+9XNHukBnOlkkWOQlpDDvMbWOztaZOWDaA8OIeP0t6qzFqLyelyg65lxzM3BKd7bCmmfzl/64BcP1MotAmB9DUxmY0Wb4Q2hYZfNYBx50Z4xthTgKV+Xoo8CbTduKotIz6hluQGvWdtxlCJQEiZ2f4RYY87JSA6/BAH2fhxuLHMXRpzocJNqARqCWpdcTGSg7bzxbKvTFH9OU4wZtr9ie40OR4zsc1lOBZL0rnp8GLkG8ZmeBQrgEDlmR9TTlz4okgtL+c5TCS37rjZYVjmtGwihws0EL9+wyv2dSQibirklC4wK5eWHKXl5vab19qzw/qRLdoRBK40DxbRKggxA7gqSsKrmrf+z7CuLIz/kxF+169FBLbh1MfBOGdx1awm6aU= lash@furioso"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	k := append([]byte{0x01}, pubKey.Marshal()...)
 | 
			
		||||
	keyStore.SetPrefix(storage.DATATYPE_CUSTOM)
 | 
			
		||||
	return keyStore.Put(ctx, k, []byte("+25113243546"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	var dbDir string
 | 
			
		||||
	var resourceDir string
 | 
			
		||||
@ -338,22 +63,30 @@ func main() {
 | 
			
		||||
	if engineDebug {
 | 
			
		||||
		cfg.EngineDebug = true
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	err = sshLoadKeys(ctx, dbDir)
 | 
			
		||||
 | 
			
		||||
	keyStoreFile := path.Join(dbDir, "ssh_authorized_keys.gdbm")
 | 
			
		||||
	authKeyStore := storage.NewThreadGdbmDb()
 | 
			
		||||
	err = authKeyStore.Connect(ctx, keyStoreFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Fprintf(os.Stderr, err.Error())
 | 
			
		||||
		fmt.Fprintf(os.Stderr, "keystore file open error: %v", err)
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		err := authKeyStore.Close()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logg.ErrorCtxf(ctx, "keystore close error", "err", err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	runner := &sshRunner{
 | 
			
		||||
	runner := &ssh.SshRunner{
 | 
			
		||||
		Cfg: cfg,
 | 
			
		||||
		Debug: engineDebug,
 | 
			
		||||
		FlagFile: pfp,
 | 
			
		||||
		DbDir: dbDir,
 | 
			
		||||
		ResourceDir: resourceDir,
 | 
			
		||||
		KeyFile: sshKeyFile,
 | 
			
		||||
		SrvKeyFile: sshKeyFile,
 | 
			
		||||
		Host: host,
 | 
			
		||||
		Port: port,
 | 
			
		||||
	}
 | 
			
		||||
	runner.Run(ctx)
 | 
			
		||||
	runner.Run(ctx, authKeyStore)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										279
									
								
								internal/ssh/ssh.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										279
									
								
								internal/ssh/ssh.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,279 @@
 | 
			
		||||
package ssh
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"os"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/crypto/ssh"
 | 
			
		||||
 | 
			
		||||
	"git.defalsify.org/vise.git/db"
 | 
			
		||||
	"git.defalsify.org/vise.git/engine"
 | 
			
		||||
	"git.defalsify.org/vise.git/logging"
 | 
			
		||||
	"git.defalsify.org/vise.git/resource"
 | 
			
		||||
	"git.defalsify.org/vise.git/state"
 | 
			
		||||
 | 
			
		||||
	"git.grassecon.net/urdt/ussd/internal/handlers"
 | 
			
		||||
	"git.grassecon.net/urdt/ussd/internal/storage"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	logg = logging.NewVanilla().WithDomain("ssh")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type auther struct {
 | 
			
		||||
	Ctx context.Context
 | 
			
		||||
	keyStore db.Db
 | 
			
		||||
	auth map[string]string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewAuther(ctx context.Context, keyStore db.Db) *auther {
 | 
			
		||||
	return &auther{
 | 
			
		||||
		Ctx: ctx,
 | 
			
		||||
		keyStore: keyStore,
 | 
			
		||||
		auth: make(map[string]string),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func(a *auther) Check(conn ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
 | 
			
		||||
	a.keyStore.SetLanguage(nil)
 | 
			
		||||
	a.keyStore.SetPrefix(storage.DATATYPE_CUSTOM)
 | 
			
		||||
	k := append([]byte{0x01}, pubKey.Marshal()...)
 | 
			
		||||
	v, err := a.keyStore.Get(a.Ctx, k)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	ka := hex.EncodeToString(conn.SessionID())
 | 
			
		||||
	va := string(v)
 | 
			
		||||
	a.auth[ka] = va 
 | 
			
		||||
	fmt.Fprintf(os.Stderr, "connect: %s -> %s\n", ka, v)
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func(a *auther) FromConn(c *ssh.ServerConn) (string, error) {
 | 
			
		||||
	if c == nil {
 | 
			
		||||
		return "", errors.New("nil server conn")
 | 
			
		||||
	}
 | 
			
		||||
	if c.Conn == nil {
 | 
			
		||||
		return "", errors.New("nil underlying conn")
 | 
			
		||||
	}
 | 
			
		||||
	return a.Get(c.Conn.SessionID())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func(a *auther) Get(k []byte) (string, error) {
 | 
			
		||||
	ka := hex.EncodeToString(k)
 | 
			
		||||
	v, ok := a.auth[ka]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return "", errors.New("not found")
 | 
			
		||||
	}
 | 
			
		||||
	return v, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func(s *SshRunner) serve(ctx context.Context, sessionId string, ch ssh.NewChannel, en engine.Engine) error {
 | 
			
		||||
	if ch == nil {
 | 
			
		||||
		return errors.New("nil channel")
 | 
			
		||||
	}
 | 
			
		||||
	if ch.ChannelType() != "session" {
 | 
			
		||||
		ch.Reject(ssh.UnknownChannelType, "that is not the channel you are looking for")
 | 
			
		||||
		return errors.New("not a session")
 | 
			
		||||
	}
 | 
			
		||||
	channel, requests, err := ch.Accept()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
	defer channel.Close()
 | 
			
		||||
	s.wg.Add(1)
 | 
			
		||||
	go func(reqIn <-chan *ssh.Request) {
 | 
			
		||||
		defer s.wg.Done()
 | 
			
		||||
		for req := range reqIn {
 | 
			
		||||
			req.Reply(req.Type == "shell", nil)	
 | 
			
		||||
		}
 | 
			
		||||
		_ = requests
 | 
			
		||||
	}(requests)
 | 
			
		||||
 | 
			
		||||
	cont, err := en.Exec(ctx, []byte{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("initial engine exec err: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var input [state.INPUT_LIMIT]byte
 | 
			
		||||
	for cont {
 | 
			
		||||
		c, err := en.Flush(ctx, channel)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("flush err: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		_, err = channel.Write([]byte{0x0a})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("newline err: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		c, err = channel.Read(input[:])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("read input fail: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		logg.TraceCtxf(ctx, "input read", "c", c, "input", input[:c-1])
 | 
			
		||||
		cont, err = en.Exec(ctx, input[:c-1])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("engine exec err: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		logg.TraceCtxf(ctx, "exec cont", "cont", cont, "en", en)
 | 
			
		||||
		_ = c
 | 
			
		||||
	}
 | 
			
		||||
	c, err := en.Flush(ctx, channel)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("last flush err: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	_ = c
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SshRunner struct {
 | 
			
		||||
	Ctx context.Context
 | 
			
		||||
	Cfg engine.Config
 | 
			
		||||
	FlagFile string
 | 
			
		||||
	DbDir string
 | 
			
		||||
	ResourceDir string
 | 
			
		||||
	Debug bool
 | 
			
		||||
	SrvKeyFile string
 | 
			
		||||
	Host string
 | 
			
		||||
	Port uint
 | 
			
		||||
	wg sync.WaitGroup
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func(s *SshRunner) GetEngine(sessionId string) (engine.Engine, func(), error) {
 | 
			
		||||
	ctx := s.Ctx
 | 
			
		||||
	menuStorageService := storage.NewMenuStorageService(s.DbDir, s.ResourceDir)
 | 
			
		||||
 | 
			
		||||
	err := menuStorageService.EnsureDbDir()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rs, err := menuStorageService.GetResource(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pe, err := menuStorageService.GetPersister(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	userdatastore, err := menuStorageService.GetUserdataDb(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dbResource, ok := rs.(*resource.DbResource)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	lhs, err := handlers.NewLocalHandlerService(s.FlagFile, true, dbResource, s.Cfg, rs)
 | 
			
		||||
	lhs.SetDataStore(&userdatastore)
 | 
			
		||||
	lhs.SetPersister(pe)
 | 
			
		||||
	lhs.Cfg.SessionId = sessionId
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hl, err := lhs.GetHandler()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	en := lhs.GetEngine()
 | 
			
		||||
	en = en.WithFirst(hl.Init)
 | 
			
		||||
	if s.Debug {
 | 
			
		||||
		en = en.WithDebug(nil)
 | 
			
		||||
	}
 | 
			
		||||
	// TODO: this is getting very hacky!
 | 
			
		||||
	closer := func() {
 | 
			
		||||
		err := menuStorageService.Close()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logg.ErrorCtxf(ctx, "menu storage service cleanup fail", "err", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return en, closer, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// adapted example from crypto/ssh package, NewServerConn doc
 | 
			
		||||
func(s *SshRunner) Run(ctx context.Context, keyStore db.Db) {
 | 
			
		||||
	running := true
 | 
			
		||||
 | 
			
		||||
	// TODO: waitgroup should probably not be global
 | 
			
		||||
	defer s.wg.Wait()
 | 
			
		||||
 | 
			
		||||
	auth := NewAuther(ctx, keyStore)
 | 
			
		||||
	cfg := ssh.ServerConfig{
 | 
			
		||||
		PublicKeyCallback: auth.Check,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	privateBytes, err := os.ReadFile(s.SrvKeyFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logg.ErrorCtxf(ctx, "Failed to load private key", "err", err)
 | 
			
		||||
	}
 | 
			
		||||
	private, err := ssh.ParsePrivateKey(privateBytes)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logg.ErrorCtxf(ctx, "Failed to parse private key", "err", err)
 | 
			
		||||
	}
 | 
			
		||||
	cfg.AddHostKey(private)
 | 
			
		||||
 | 
			
		||||
	lst, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Host, s.Port))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for running {
 | 
			
		||||
		conn, err := lst.Accept()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			panic(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		go func(conn net.Conn) {
 | 
			
		||||
			defer conn.Close()
 | 
			
		||||
			for true {
 | 
			
		||||
				srvConn, nC, rC, err := ssh.NewServerConn(conn, &cfg)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logg.InfoCtxf(ctx, "rejected client", "err", err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				logg.DebugCtxf(ctx, "ssh client connected", "conn", srvConn)
 | 
			
		||||
 | 
			
		||||
				s.wg.Add(1)
 | 
			
		||||
				go func() {
 | 
			
		||||
					ssh.DiscardRequests(rC)
 | 
			
		||||
					s.wg.Done()
 | 
			
		||||
				}()
 | 
			
		||||
				
 | 
			
		||||
				sessionId, err := auth.FromConn(srvConn)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logg.ErrorCtxf(ctx, "Cannot find authentication")
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				en, closer, err := s.GetEngine(sessionId)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logg.ErrorCtxf(ctx, "engine won't start", "err", err)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				defer func() {
 | 
			
		||||
					err := en.Finish()
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						logg.ErrorCtxf(ctx, "engine won't stop", "err", err)
 | 
			
		||||
					}
 | 
			
		||||
					closer()
 | 
			
		||||
				}()
 | 
			
		||||
				for ch := range nC {
 | 
			
		||||
					err = s.serve(ctx, sessionId, ch, en)
 | 
			
		||||
					logg.ErrorCtxf(ctx, "ssh server finish", "err", err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}(conn)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user