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 ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"encoding/hex" |  | ||||||
| 	"errors" |  | ||||||
| 	"flag" | 	"flag" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net" |  | ||||||
| 	"path" | 	"path" | ||||||
| 	"os" | 	"os" | ||||||
| 	"sync" | 	"sync" | ||||||
| 
 | 
 | ||||||
| 	"golang.org/x/crypto/ssh" |  | ||||||
| 
 |  | ||||||
| 	"git.defalsify.org/vise.git/db" | 	"git.defalsify.org/vise.git/db" | ||||||
| 	"git.defalsify.org/vise.git/engine" | 	"git.defalsify.org/vise.git/engine" | ||||||
| 	"git.defalsify.org/vise.git/logging" | 	"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/storage" | ||||||
|  | 	"git.grassecon.net/urdt/ussd/internal/ssh" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
| @ -30,274 +23,6 @@ var ( | |||||||
| 	scriptDir = path.Join("services", "registration") | 	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() { | func main() { | ||||||
| 	var dbDir string | 	var dbDir string | ||||||
| 	var resourceDir string | 	var resourceDir string | ||||||
| @ -338,22 +63,30 @@ func main() { | |||||||
| 	if engineDebug { | 	if engineDebug { | ||||||
| 		cfg.EngineDebug = true | 		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 { | 	if err != nil { | ||||||
| 		fmt.Fprintf(os.Stderr, err.Error()) | 		fmt.Fprintf(os.Stderr, "keystore file open error: %v", err) | ||||||
| 		os.Exit(1) | 		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, | 		Cfg: cfg, | ||||||
| 		Debug: engineDebug, | 		Debug: engineDebug, | ||||||
| 		FlagFile: pfp, | 		FlagFile: pfp, | ||||||
| 		DbDir: dbDir, | 		DbDir: dbDir, | ||||||
| 		ResourceDir: resourceDir, | 		ResourceDir: resourceDir, | ||||||
| 		KeyFile: sshKeyFile, | 		SrvKeyFile: sshKeyFile, | ||||||
| 		Host: host, | 		Host: host, | ||||||
| 		Port: port, | 		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