From 19372c17f401702fcd299e3f08c58738fdea22a4 Mon Sep 17 00:00:00 2001 From: lash Date: Sun, 22 Sep 2024 03:06:24 +0100 Subject: [PATCH 01/21] Reinstate ssh executable --- cmd/ssh/main.go | 359 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 359 insertions(+) create mode 100644 cmd/ssh/main.go diff --git a/cmd/ssh/main.go b/cmd/ssh/main.go new file mode 100644 index 0000000..2741798 --- /dev/null +++ b/cmd/ssh/main.go @@ -0,0 +1,359 @@ +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" +) + +var ( + wg sync.WaitGroup + keyStore db.Db + logg = logging.NewVanilla() + 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 + var size uint + var engineDebug bool + var stateDebug bool + var host string + var port uint + flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") + flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir") + flag.BoolVar(&engineDebug, "engine-debug", false, "use engine debug output") + flag.BoolVar(&stateDebug, "state-debug", false, "use engine debug output") + flag.UintVar(&size, "s", 160, "max size of output") + flag.StringVar(&host, "h", "127.0.0.1", "http host") + flag.UintVar(&port, "p", 7122, "http port") + flag.Parse() + + sshKeyFile := flag.Arg(0) + _, err := os.Stat(sshKeyFile) + if err != nil { + fmt.Fprintf(os.Stderr, "cannot open ssh server private key file: %v\n", err) + os.Exit(1) + } + + logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size, "keyfile", sshKeyFile, "host", host, "port", port) + + ctx := context.Background() + pfp := path.Join(scriptDir, "pp.csv") + + cfg := engine.Config{ + Root: "root", + OutputSize: uint32(size), + FlagCount: uint32(16), + } + if stateDebug { + cfg.StateDebug = true + } + if engineDebug { + cfg.EngineDebug = true + } + + err = sshLoadKeys(ctx, dbDir) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + runner := &sshRunner{ + Cfg: cfg, + Debug: engineDebug, + FlagFile: pfp, + DbDir: dbDir, + ResourceDir: resourceDir, + KeyFile: sshKeyFile, + Host: host, + Port: port, + } + runner.Run(ctx) +} From 935b777e57901adab793454309fb9445a9330436 Mon Sep 17 00:00:00 2001 From: lash Date: Sun, 22 Sep 2024 14:41:01 +0100 Subject: [PATCH 02/21] Factor out ssh service code --- cmd/ssh/main.go | 297 +++----------------------------------------- internal/ssh/ssh.go | 279 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 294 insertions(+), 282 deletions(-) create mode 100644 internal/ssh/ssh.go diff --git a/cmd/ssh/main.go b/cmd/ssh/main.go index 2741798..972bdaf 100644 --- a/cmd/ssh/main.go +++ b/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) } diff --git a/internal/ssh/ssh.go b/internal/ssh/ssh.go new file mode 100644 index 0000000..9cb00b3 --- /dev/null +++ b/internal/ssh/ssh.go @@ -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) + } +} From ada1f26b680331d9c6fb1e3ff5ea0b37a9df35ee Mon Sep 17 00:00:00 2001 From: lash Date: Sun, 22 Sep 2024 15:41:55 +0100 Subject: [PATCH 03/21] Add keystore class, separate keystore tool executable --- cmd/ssh/main.go | 37 ++++++++++++++++++----- cmd/ssh/sshkey/main.go | 44 +++++++++++++++++++++++++++ internal/ssh/keystore.go | 64 ++++++++++++++++++++++++++++++++++++++++ internal/ssh/ssh.go | 29 +++++++++--------- 4 files changed, 153 insertions(+), 21 deletions(-) create mode 100644 cmd/ssh/sshkey/main.go create mode 100644 internal/ssh/keystore.go diff --git a/cmd/ssh/main.go b/cmd/ssh/main.go index 972bdaf..0227616 100644 --- a/cmd/ssh/main.go +++ b/cmd/ssh/main.go @@ -6,13 +6,14 @@ import ( "fmt" "path" "os" + "os/signal" "sync" + "syscall" "git.defalsify.org/vise.git/db" "git.defalsify.org/vise.git/engine" "git.defalsify.org/vise.git/logging" - "git.grassecon.net/urdt/ussd/internal/storage" "git.grassecon.net/urdt/ussd/internal/ssh" ) @@ -47,9 +48,15 @@ func main() { os.Exit(1) } + ctx := context.Background() + logg.WarnCtxf(ctx, "!!!!! WARNING WARNING WARNING") + logg.WarnCtxf(ctx, "!!!!! =======================") + logg.WarnCtxf(ctx, "!!!!! This is not a production ready server!") + logg.WarnCtxf(ctx, "!!!!! Do not expose to internet and only use with tunnel!") + logg.WarnCtxf(ctx, "!!!!! (See ssh -L <...>)") + logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size, "keyfile", sshKeyFile, "host", host, "port", port) - ctx := context.Background() pfp := path.Join(scriptDir, "pp.csv") cfg := engine.Config{ @@ -64,20 +71,24 @@ func main() { cfg.EngineDebug = true } - keyStoreFile := path.Join(dbDir, "ssh_authorized_keys.gdbm") - authKeyStore := storage.NewThreadGdbmDb() - err = authKeyStore.Connect(ctx, keyStoreFile) + authKeyStore, err := ssh.NewSshKeyStore(ctx, dbDir) if err != nil { fmt.Fprintf(os.Stderr, "keystore file open error: %v", err) os.Exit(1) } - defer func() { - err := authKeyStore.Close() + defer func () { + logg.TraceCtxf(ctx, "shutdown auth key store reached") + err = authKeyStore.Close() if err != nil { logg.ErrorCtxf(ctx, "keystore close error", "err", err) } }() + cint := make(chan os.Signal) + cterm := make(chan os.Signal) + signal.Notify(cint, os.Interrupt, syscall.SIGINT) + signal.Notify(cterm, os.Interrupt, syscall.SIGTERM) + runner := &ssh.SshRunner{ Cfg: cfg, Debug: engineDebug, @@ -88,5 +99,17 @@ func main() { Host: host, Port: port, } + go func() { + select { + case _ = <-cint: + case _ = <-cterm: + } + logg.TraceCtxf(ctx, "shutdown runner reached") + err := runner.Stop() + if err != nil { + logg.ErrorCtxf(ctx, "runner stop error", "err", err) + } + + }() runner.Run(ctx, authKeyStore) } diff --git a/cmd/ssh/sshkey/main.go b/cmd/ssh/sshkey/main.go new file mode 100644 index 0000000..87b89a3 --- /dev/null +++ b/cmd/ssh/sshkey/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + + "git.grassecon.net/urdt/ussd/internal/ssh" +) + +func main() { + var dbDir string + var sessionId string + flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") + flag.StringVar(&sessionId, "i", "", "session id") + flag.Parse() + + if sessionId == "" { + fmt.Fprintf(os.Stderr, "empty session id\n") + os.Exit(1) + } + + ctx := context.Background() + + sshKeyFile := flag.Arg(0) + if sshKeyFile == "" { + fmt.Fprintf(os.Stderr, "missing key file argument\n") + os.Exit(1) + } + + store, err := ssh.NewSshKeyStore(ctx, dbDir) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } + defer store.Close() + + err = store.AddFromFile(ctx, sshKeyFile, sessionId) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } +} diff --git a/internal/ssh/keystore.go b/internal/ssh/keystore.go new file mode 100644 index 0000000..fed7233 --- /dev/null +++ b/internal/ssh/keystore.go @@ -0,0 +1,64 @@ +package ssh + +import ( + "context" + "fmt" + "os" + "path" + + "golang.org/x/crypto/ssh" + + "git.defalsify.org/vise.git/db" + + "git.grassecon.net/urdt/ussd/internal/storage" +) + +type SshKeyStore struct { + store db.Db +} + +func NewSshKeyStore(ctx context.Context, dbDir string) (*SshKeyStore, error) { + keyStore := &SshKeyStore{} + keyStoreFile := path.Join(dbDir, "ssh_authorized_keys.gdbm") + keyStore.store = storage.NewThreadGdbmDb() + err := keyStore.store.Connect(ctx, keyStoreFile) + if err != nil { + return nil, err + } + return keyStore, nil +} + +func(s *SshKeyStore) AddFromFile(ctx context.Context, fp string, sessionId string) error { + _, err := os.Stat(fp) + if err != nil { + return fmt.Errorf("cannot open ssh server public key file: %v\n", err) + } + + publicBytes, err := os.ReadFile(fp) + if err != nil { + return fmt.Errorf("Failed to load public key: %v", err) + } + pubKey, _, _, _, err := ssh.ParseAuthorizedKey(publicBytes) + if err != nil { + return fmt.Errorf("Failed to parse public key: %v", err) + } + k := append([]byte{0x01}, pubKey.Marshal()...) + s.store.SetPrefix(storage.DATATYPE_CUSTOM) + logg.Infof("Added key", "sessionId", sessionId, "public key", string(publicBytes)) + return s.store.Put(ctx, k, []byte(sessionId)) +} + +func(s *SshKeyStore) Get(ctx context.Context, pubKey ssh.PublicKey) (string, error) { + s.store.SetLanguage(nil) + s.store.SetPrefix(storage.DATATYPE_CUSTOM) + k := append([]byte{0x01}, pubKey.Marshal()...) + v, err := s.store.Get(ctx, k) + if err != nil { + return "", err + } + return string(v), nil +} + +func(s *SshKeyStore) Close() error { + return s.store.Close() +} diff --git a/internal/ssh/ssh.go b/internal/ssh/ssh.go index 9cb00b3..31cbc3e 100644 --- a/internal/ssh/ssh.go +++ b/internal/ssh/ssh.go @@ -11,7 +11,6 @@ import ( "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" @@ -27,11 +26,11 @@ var ( type auther struct { Ctx context.Context - keyStore db.Db + keyStore *SshKeyStore auth map[string]string } -func NewAuther(ctx context.Context, keyStore db.Db) *auther { +func NewAuther(ctx context.Context, keyStore *SshKeyStore) *auther { return &auther{ Ctx: ctx, keyStore: keyStore, @@ -40,17 +39,13 @@ func NewAuther(ctx context.Context, keyStore db.Db) *auther { } 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) + va, err := a.keyStore.Get(a.Ctx, pubKey) 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) + fmt.Fprintf(os.Stderr, "connect: %s -> %s\n", ka, va) return nil, nil } @@ -142,6 +137,11 @@ type SshRunner struct { Host string Port uint wg sync.WaitGroup + lst net.Listener +} + +func(s *SshRunner) Stop() error { + return s.lst.Close() } func(s *SshRunner) GetEngine(sessionId string) (engine.Engine, func(), error) { @@ -203,7 +203,7 @@ func(s *SshRunner) GetEngine(sessionId string) (engine.Engine, func(), error) { } // adapted example from crypto/ssh package, NewServerConn doc -func(s *SshRunner) Run(ctx context.Context, keyStore db.Db) { +func(s *SshRunner) Run(ctx context.Context, keyStore *SshKeyStore) { running := true // TODO: waitgroup should probably not be global @@ -224,18 +224,19 @@ func(s *SshRunner) Run(ctx context.Context, keyStore db.Db) { } cfg.AddHostKey(private) - lst, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Host, s.Port)) + s.lst, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.Host, s.Port)) if err != nil { panic(err) } for running { - conn, err := lst.Accept() + conn, err := s.lst.Accept() if err != nil { - panic(err) + logg.ErrorCtxf(ctx, "ssh accept error", "err", err) + running = false + continue } - go func(conn net.Conn) { defer conn.Close() for true { From e4c3e9f015bc908ccb2c91eb63a170cc30ec76c3 Mon Sep 17 00:00:00 2001 From: lash Date: Sun, 22 Sep 2024 16:09:57 +0100 Subject: [PATCH 04/21] Add ssh instructions, log host key --- cmd/ssh/README.md | 34 ++++++++++++++++++++++++++++++++++ internal/ssh/ssh.go | 4 ++++ 2 files changed, 38 insertions(+) create mode 100644 cmd/ssh/README.md diff --git a/cmd/ssh/README.md b/cmd/ssh/README.md new file mode 100644 index 0000000..071557b --- /dev/null +++ b/cmd/ssh/README.md @@ -0,0 +1,34 @@ +# URDT-USSD SSH server + +An SSH server entry point for the vise engine. + + +## Adding public keys for access + +Map your (client) public key to a session identifier (e.g. phone number) + +``` +go run -v -tags logtrace ./cmd/ssh/sshkey/main.go -i [--dbdir ] +``` + + +## Create a private key for the server + +``` +ssh-keygen -N "" -f +``` + + +## Run the server + + +``` +go run -v -tags logtrace ./cmd/ssh/main.go -h -p [--dbdir ] +``` + + +## Connect to the server + +``` +ssh -T -p +``` diff --git a/internal/ssh/ssh.go b/internal/ssh/ssh.go index 31cbc3e..394f55f 100644 --- a/internal/ssh/ssh.go +++ b/internal/ssh/ssh.go @@ -3,6 +3,7 @@ package ssh import ( "context" "encoding/hex" + "encoding/base64" "errors" "fmt" "net" @@ -222,6 +223,9 @@ func(s *SshRunner) Run(ctx context.Context, keyStore *SshKeyStore) { if err != nil { logg.ErrorCtxf(ctx, "Failed to parse private key", "err", err) } + srvPub := private.PublicKey() + srvPubStr := base64.StdEncoding.EncodeToString(srvPub.Marshal()) + logg.InfoCtxf(ctx, "have server key", "type", srvPub.Type(), "public", srvPubStr) cfg.AddHostKey(private) s.lst, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.Host, s.Port)) From 3f3dbf414c5063bba8395999fed13ab1a2d75798 Mon Sep 17 00:00:00 2001 From: lash Date: Sun, 22 Sep 2024 16:13:30 +0100 Subject: [PATCH 05/21] Clarify keyfile use in readme --- cmd/ssh/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/ssh/README.md b/cmd/ssh/README.md index 071557b..3cffae1 100644 --- a/cmd/ssh/README.md +++ b/cmd/ssh/README.md @@ -8,14 +8,14 @@ An SSH server entry point for the vise engine. Map your (client) public key to a session identifier (e.g. phone number) ``` -go run -v -tags logtrace ./cmd/ssh/sshkey/main.go -i [--dbdir ] +go run -v -tags logtrace ./cmd/ssh/sshkey/main.go -i [--dbdir ] ``` ## Create a private key for the server ``` -ssh-keygen -N "" -f +ssh-keygen -N "" -f ``` @@ -23,12 +23,12 @@ ssh-keygen -N "" -f ``` -go run -v -tags logtrace ./cmd/ssh/main.go -h -p [--dbdir ] +go run -v -tags logtrace ./cmd/ssh/main.go -h -p [--dbdir ] ``` ## Connect to the server ``` -ssh -T -p +ssh -T -p -i ``` From c2b68231f5652f45752db96ec7008f8086a4cae6 Mon Sep 17 00:00:00 2001 From: lash Date: Sun, 22 Sep 2024 16:15:40 +0100 Subject: [PATCH 06/21] Update gitignore --- .gitignore | 2 ++ cmd/ssh/README.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 54ad3ff..ddccccf 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ go.work* **/*/*.bin **/*/.state/ cmd/.state/ +id_* +*.gdbm diff --git a/cmd/ssh/README.md b/cmd/ssh/README.md index 3cffae1..ff325d7 100644 --- a/cmd/ssh/README.md +++ b/cmd/ssh/README.md @@ -30,5 +30,5 @@ go run -v -tags logtrace ./cmd/ssh/main.go -h -p [--dbdir ## Connect to the server ``` -ssh -T -p -i +ssh [-v] -T -p -i ``` From ddc8f6dad12ea9f3e28d22de66fc27d9f966a848 Mon Sep 17 00:00:00 2001 From: lash Date: Sun, 22 Sep 2024 16:24:01 +0100 Subject: [PATCH 07/21] Update module dep --- go.mod | 5 ++++- go.sum | 14 ++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 7cceaa6..486f8ee 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,15 @@ module git.grassecon.net/urdt/ussd go 1.22.6 require ( - git.defalsify.org/vise.git v0.1.0-rc.3.0.20240920144308-b2d2c5f18f38 + git.defalsify.org/vise.git v0.1.0-rc.3.0.20240922152136-7ea16f9137b4 github.com/alecthomas/assert/v2 v2.2.2 github.com/peteole/testdata-loader v0.3.0 + golang.org/x/crypto v0.17.0 gopkg.in/leonelquinteros/gotext.v1 v1.3.1 ) +require golang.org/x/sys v0.15.0 // indirect + require ( github.com/alecthomas/participle/v2 v2.0.0 // indirect github.com/alecthomas/repr v0.2.0 // indirect diff --git a/go.sum b/go.sum index 5d235cc..8dfead8 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,5 @@ -git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911231817-0d23e0dbb57f h1:CuJvG3NyMoRtHUim4aZdrfjjJBg2AId7z0yp7Q97bRM= -git.defalsify.org/vise.git v0.1.0-rc.3.0.20240911231817-0d23e0dbb57f/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M= -git.defalsify.org/vise.git v0.1.0-rc.3.0.20240914163514-577f56f43bea h1:6ZYT+dIjd/f5vn9y5AJDZ7SQQckA6w5ZfUoKygyI11o= -git.defalsify.org/vise.git v0.1.0-rc.3.0.20240914163514-577f56f43bea/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M= -git.defalsify.org/vise.git v0.1.0-rc.3.0.20240920144308-b2d2c5f18f38 h1:4aAZijIcq33ixnZ+U48ckDIkwSfZL3St/CqoXZcC5K8= -git.defalsify.org/vise.git v0.1.0-rc.3.0.20240920144308-b2d2c5f18f38/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M= +git.defalsify.org/vise.git v0.1.0-rc.3.0.20240922152136-7ea16f9137b4 h1:IMVUK9OkZ/QtYZPHgTZ+XUs5VQ4eIewIaTyVSCF/nAY= +git.defalsify.org/vise.git v0.1.0-rc.3.0.20240922152136-7ea16f9137b4/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g= @@ -32,6 +28,12 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/leonelquinteros/gotext.v1 v1.3.1 h1:8d9/fdTG0kn/B7NNGV1BsEyvektXFAbkMsTZS2sFSCc= From 5c85ecffd1b7ffa10649ea86646421be6ad0c503 Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Tue, 24 Sep 2024 09:00:13 +0300 Subject: [PATCH 08/21] remove unused code --- internal/handlers/handlerservice.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/handlers/handlerservice.go b/internal/handlers/handlerservice.go index 93c5a10..ad591bf 100644 --- a/internal/handlers/handlerservice.go +++ b/internal/handlers/handlerservice.go @@ -88,7 +88,6 @@ func (localHandlerService *LocalHandlerService) GetHandler() (*ussd.Handlers, er localHandlerService.DbRs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo) localHandlerService.DbRs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob) localHandlerService.DbRs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob) - localHandlerService.DbRs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit) localHandlerService.DbRs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction) localHandlerService.DbRs.AddLocalFunc("save_temporary_pin", ussdHandlers.SaveTemporaryPin) localHandlerService.DbRs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin) From eff2cbde8b864ddbd7c69231ecfb200118ee11ed Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Tue, 24 Sep 2024 09:00:31 +0300 Subject: [PATCH 09/21] setup check for unused code --- internal/utils/isocode.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 internal/utils/isocode.go diff --git a/internal/utils/isocode.go b/internal/utils/isocode.go new file mode 100644 index 0000000..34365b0 --- /dev/null +++ b/internal/utils/isocode.go @@ -0,0 +1,15 @@ +package utils + + + + +var isoCodes = map[string]bool{ + "eng": true, // English + "swa": true, // Swahili + +} + +func IsValidISO639(code string) bool { + return isoCodes[code] +} + From bbfe46f162e64aa22f1caf78f3bbd75c16107881 Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Tue, 24 Sep 2024 09:01:19 +0300 Subject: [PATCH 10/21] use current rendering symbol for case selection --- internal/handlers/ussd/menuhandler.go | 62 ++++++--------------------- 1 file changed, 12 insertions(+), 50 deletions(-) diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go index ff8d8bd..8ffecc3 100644 --- a/internal/handlers/ussd/menuhandler.go +++ b/internal/handlers/ussd/menuhandler.go @@ -117,17 +117,14 @@ func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) { var res resource.Result - sym, _ = h.st.Where() + symbol, _ := h.st.Where() + code := strings.Split(symbol, "_")[1] - switch sym { - case "set_default": - res.FlagSet = append(res.FlagSet, state.FLAG_LANG) - res.Content = "eng" - case "set_swa": - res.FlagSet = append(res.FlagSet, state.FLAG_LANG) - res.Content = "swa" - default: + if !utils.IsValidISO639(code) { + return res, nil } + res.FlagSet = append(res.FlagSet, state.FLAG_LANG) + res.Content = code languageSetFlag, err := h.flagManager.GetFlag("flag_language_set") if err != nil { @@ -279,32 +276,6 @@ func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byt return res, nil } -// SetResetSingleEdit sets and resets flags to allow gradual editing of profile information. -func (h *Handlers) SetResetSingleEdit(ctx context.Context, sym string, input []byte) (resource.Result, error) { - var res resource.Result - - menuOption := string(input) - - flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") - flag_single_edit, _ := h.flagManager.GetFlag("flag_single_edit") - - switch menuOption { - case "2": - res.FlagReset = append(res.FlagReset, flag_allow_update) - res.FlagSet = append(res.FlagSet, flag_single_edit) - case "3": - res.FlagReset = append(res.FlagReset, flag_allow_update) - res.FlagSet = append(res.FlagSet, flag_single_edit) - case "4": - res.FlagReset = append(res.FlagReset, flag_allow_update) - res.FlagSet = append(res.FlagSet, flag_single_edit) - default: - res.FlagReset = append(res.FlagReset, flag_single_edit) - } - - return res, nil -} - // VerifyPin checks whether the confirmation PIN is similar to the account PIN // If similar, it sets the USERFLAG_PIN_SET flag allowing the user // to access the main menu @@ -435,6 +406,7 @@ func (h *Handlers) SaveLocation(ctx context.Context, sym string, input []byte) ( // SaveGender updates the gender in the gdbm with the provided input. func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (resource.Result, error) { + symbol, _ := h.st.Where() var res resource.Result var err error sessionId, ok := ctx.Value("SessionId").(string) @@ -442,21 +414,11 @@ func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (re return res, fmt.Errorf("missing session") } - if len(input) > 0 { - gender := string(input) - switch gender { - case "1": - gender = "Male" - case "2": - gender = "Female" - case "3": - gender = "Unspecified" - } - store := h.userdataStore - err = store.WriteEntry(ctx, sessionId, utils.DATA_GENDER, []byte(gender)) - if err != nil { - return res, nil - } + gender := strings.Split(symbol, "_")[1] + store := h.userdataStore + err = store.WriteEntry(ctx, sessionId, utils.DATA_GENDER, []byte(gender)) + if err != nil { + return res, nil } return res, nil From 1bd96d0689f442110f46e43604e58b8838f19390 Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Tue, 24 Sep 2024 09:02:04 +0300 Subject: [PATCH 11/21] setup dedicated nodes for case selection --- services/registration/edit_profile.vis | 2 -- services/registration/select_gender.vis | 8 +++++--- services/registration/select_language.vis | 2 +- services/registration/{set_default.vis => set_eng.vis} | 0 services/registration/set_female.vis | 4 ++++ services/registration/set_male.vis | 4 ++++ services/registration/set_unspecified.vis | 4 ++++ 7 files changed, 18 insertions(+), 6 deletions(-) rename services/registration/{set_default.vis => set_eng.vis} (100%) create mode 100644 services/registration/set_female.vis create mode 100644 services/registration/set_male.vis create mode 100644 services/registration/set_unspecified.vis diff --git a/services/registration/edit_profile.vis b/services/registration/edit_profile.vis index 6c1986f..9d45ec9 100644 --- a/services/registration/edit_profile.vis +++ b/services/registration/edit_profile.vis @@ -11,8 +11,6 @@ MOUT view 7 MOUT back 0 HALT INCMP my_account 0 -LOAD set_reset_single_edit 0 -RELOAD set_reset_single_edit INCMP enter_name 1 INCMP enter_familyname 2 INCMP select_gender 3 diff --git a/services/registration/select_gender.vis b/services/registration/select_gender.vis index 25e53d3..1082cef 100644 --- a/services/registration/select_gender.vis +++ b/services/registration/select_gender.vis @@ -1,13 +1,15 @@ CATCH incorrect_pin flag_incorrect_pin 1 CATCH profile_update_success flag_allow_update 1 -LOAD save_gender 0 MOUT male 1 MOUT female 2 MOUT unspecified 3 MOUT back 0 HALT -RELOAD save_gender INCMP _ 0 -INCMP pin_entry * +INCMP set_male 1 +INCMP set_female 2 +INCMP set_unspecified 3 + + diff --git a/services/registration/select_language.vis b/services/registration/select_language.vis index aa83e0c..54f08e9 100644 --- a/services/registration/select_language.vis +++ b/services/registration/select_language.vis @@ -1,6 +1,6 @@ MOUT english 0 MOUT kiswahili 1 HALT -INCMP set_default 0 +INCMP set_eng 0 INCMP set_swa 1 INCMP . * diff --git a/services/registration/set_default.vis b/services/registration/set_eng.vis similarity index 100% rename from services/registration/set_default.vis rename to services/registration/set_eng.vis diff --git a/services/registration/set_female.vis b/services/registration/set_female.vis new file mode 100644 index 0000000..723b080 --- /dev/null +++ b/services/registration/set_female.vis @@ -0,0 +1,4 @@ +LOAD save_gender 0 +CATCH incorrect_pin flag_incorrect_pin 1 +CATCH profile_update_success flag_allow_update 1 +MOVE pin_entry diff --git a/services/registration/set_male.vis b/services/registration/set_male.vis new file mode 100644 index 0000000..723b080 --- /dev/null +++ b/services/registration/set_male.vis @@ -0,0 +1,4 @@ +LOAD save_gender 0 +CATCH incorrect_pin flag_incorrect_pin 1 +CATCH profile_update_success flag_allow_update 1 +MOVE pin_entry diff --git a/services/registration/set_unspecified.vis b/services/registration/set_unspecified.vis new file mode 100644 index 0000000..723b080 --- /dev/null +++ b/services/registration/set_unspecified.vis @@ -0,0 +1,4 @@ +LOAD save_gender 0 +CATCH incorrect_pin flag_incorrect_pin 1 +CATCH profile_update_success flag_allow_update 1 +MOVE pin_entry From 0dea34daab9517ffd54f8cb2a328d1cfbe72f064 Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Tue, 24 Sep 2024 09:18:21 +0300 Subject: [PATCH 12/21] resolve confilict --- internal/handlers/handlerservice.go | 72 ++++++++++++++--------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/internal/handlers/handlerservice.go b/internal/handlers/handlerservice.go index c507d35..4cedd26 100644 --- a/internal/handlers/handlerservice.go +++ b/internal/handlers/handlerservice.go @@ -57,42 +57,42 @@ func (ls *LocalHandlerService) GetHandler() (*ussd.Handlers, error) { if err != nil { return nil, err } - ussdHandlers = ussdHandlers.WithPersister(localHandlerService.Pe) - localHandlerService.DbRs.AddLocalFunc("set_language", ussdHandlers.SetLanguage) - localHandlerService.DbRs.AddLocalFunc("create_account", ussdHandlers.CreateAccount) - localHandlerService.DbRs.AddLocalFunc("save_pin", ussdHandlers.SavePin) - localHandlerService.DbRs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin) - localHandlerService.DbRs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier) - localHandlerService.DbRs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus) - localHandlerService.DbRs.AddLocalFunc("authorize_account", ussdHandlers.Authorize) - localHandlerService.DbRs.AddLocalFunc("quit", ussdHandlers.Quit) - localHandlerService.DbRs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance) - localHandlerService.DbRs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient) - localHandlerService.DbRs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset) - localHandlerService.DbRs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount) - localHandlerService.DbRs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount) - localHandlerService.DbRs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount) - localHandlerService.DbRs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient) - localHandlerService.DbRs.AddLocalFunc("get_sender", ussdHandlers.GetSender) - localHandlerService.DbRs.AddLocalFunc("get_amount", ussdHandlers.GetAmount) - localHandlerService.DbRs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin) - localHandlerService.DbRs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname) - localHandlerService.DbRs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname) - localHandlerService.DbRs.AddLocalFunc("save_gender", ussdHandlers.SaveGender) - localHandlerService.DbRs.AddLocalFunc("save_location", ussdHandlers.SaveLocation) - localHandlerService.DbRs.AddLocalFunc("save_yob", ussdHandlers.SaveYob) - localHandlerService.DbRs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings) - localHandlerService.DbRs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance) - localHandlerService.DbRs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized) - localHandlerService.DbRs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate) - localHandlerService.DbRs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo) - localHandlerService.DbRs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob) - localHandlerService.DbRs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob) - localHandlerService.DbRs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction) - localHandlerService.DbRs.AddLocalFunc("save_temporary_pin", ussdHandlers.SaveTemporaryPin) - localHandlerService.DbRs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin) - localHandlerService.DbRs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange) - localHandlerService.DbRs.AddLocalFunc("quit_with_help", ussdHandlers.QuitWithHelp) + ussdHandlers = ussdHandlers.WithPersister(ls.Pe) + ls.DbRs.AddLocalFunc("set_language", ussdHandlers.SetLanguage) + ls.DbRs.AddLocalFunc("create_account", ussdHandlers.CreateAccount) + ls.DbRs.AddLocalFunc("save_pin", ussdHandlers.SavePin) + ls.DbRs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin) + ls.DbRs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier) + ls.DbRs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus) + ls.DbRs.AddLocalFunc("authorize_account", ussdHandlers.Authorize) + ls.DbRs.AddLocalFunc("quit", ussdHandlers.Quit) + ls.DbRs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance) + ls.DbRs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient) + ls.DbRs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset) + ls.DbRs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount) + ls.DbRs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount) + ls.DbRs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount) + ls.DbRs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient) + ls.DbRs.AddLocalFunc("get_sender", ussdHandlers.GetSender) + ls.DbRs.AddLocalFunc("get_amount", ussdHandlers.GetAmount) + ls.DbRs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin) + ls.DbRs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname) + ls.DbRs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname) + ls.DbRs.AddLocalFunc("save_gender", ussdHandlers.SaveGender) + ls.DbRs.AddLocalFunc("save_location", ussdHandlers.SaveLocation) + ls.DbRs.AddLocalFunc("save_yob", ussdHandlers.SaveYob) + ls.DbRs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings) + ls.DbRs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance) + ls.DbRs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized) + ls.DbRs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate) + ls.DbRs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo) + ls.DbRs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob) + ls.DbRs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob) + ls.DbRs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction) + ls.DbRs.AddLocalFunc("save_temporary_pin", ussdHandlers.SaveTemporaryPin) + ls.DbRs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin) + ls.DbRs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange) + ls.DbRs.AddLocalFunc("quit_with_help", ussdHandlers.QuitWithHelp) return ussdHandlers, nil } From 6ad67f6adc97095d08b35028ef94e08b6e0103a0 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Mon, 23 Sep 2024 11:38:07 +0300 Subject: [PATCH 13/21] change position of LOAD --- services/registration/transaction_pin.vis | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/registration/transaction_pin.vis b/services/registration/transaction_pin.vis index cadbdcf..a0941ed 100644 --- a/services/registration/transaction_pin.vis +++ b/services/registration/transaction_pin.vis @@ -5,8 +5,8 @@ RELOAD get_sender MAP get_sender MOUT back 0 MOUT quit 9 -HALT LOAD authorize_account 6 +HALT RELOAD authorize_account CATCH incorrect_pin flag_incorrect_pin 1 INCMP _ 0 From 4db862bc97147eac7609f31fa4b20b20ca3cab7c Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Mon, 23 Sep 2024 14:13:26 +0300 Subject: [PATCH 14/21] LOAD and RELOAD get_amount --- services/registration/transaction_initiated.vis | 2 +- services/registration/transaction_pin | 2 +- services/registration/transaction_pin.vis | 3 ++- services/registration/transaction_pin_swa | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/services/registration/transaction_initiated.vis b/services/registration/transaction_initiated.vis index 3b8b9f6..f8cf19c 100644 --- a/services/registration/transaction_initiated.vis +++ b/services/registration/transaction_initiated.vis @@ -1,7 +1,7 @@ LOAD reset_incorrect 6 CATCH incorrect_pin flag_incorrect_pin 1 CATCH _ flag_account_authorized 0 -LOAD get_amount 10 +RELOAD get_amount MAP get_amount RELOAD get_recipient MAP get_recipient diff --git a/services/registration/transaction_pin b/services/registration/transaction_pin index 7b6bddb..a1b7125 100644 --- a/services/registration/transaction_pin +++ b/services/registration/transaction_pin @@ -1,2 +1,2 @@ -{{.get_recipient}} will receive {{.validate_amount}} from {{.get_sender}} +{{.get_recipient}} will receive {{.get_amount}} from {{.get_sender}} Please enter your PIN to confirm: \ No newline at end of file diff --git a/services/registration/transaction_pin.vis b/services/registration/transaction_pin.vis index a0941ed..0388f0c 100644 --- a/services/registration/transaction_pin.vis +++ b/services/registration/transaction_pin.vis @@ -1,4 +1,5 @@ -MAP validate_amount +RELOAD get_amount +MAP get_amount RELOAD get_recipient MAP get_recipient RELOAD get_sender diff --git a/services/registration/transaction_pin_swa b/services/registration/transaction_pin_swa index 8529f0e..1924166 100644 --- a/services/registration/transaction_pin_swa +++ b/services/registration/transaction_pin_swa @@ -1,2 +1,2 @@ -{{.get_recipient}} atapokea {{.validate_amount}} kutoka kwa {{.get_sender}} +{{.get_recipient}} atapokea {{.get_amount}} kutoka kwa {{.get_sender}} Tafadhali weka PIN yako kudhibitisha: \ No newline at end of file From b15ba367c468571e32d3dde6d795507b6dfad2e2 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Mon, 23 Sep 2024 14:16:01 +0300 Subject: [PATCH 15/21] change position of LOAD function --- services/registration/amount.vis | 1 + services/registration/change_language.vis | 1 - services/registration/main.vis | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/services/registration/amount.vis b/services/registration/amount.vis index 9b2970a..b491fab 100644 --- a/services/registration/amount.vis +++ b/services/registration/amount.vis @@ -9,4 +9,5 @@ CATCH invalid_amount flag_invalid_amount 1 INCMP _ 0 LOAD get_recipient 12 LOAD get_sender 64 +LOAD get_amount 12 INCMP transaction_pin * diff --git a/services/registration/change_language.vis b/services/registration/change_language.vis index 05ca95b..b33acf1 100644 --- a/services/registration/change_language.vis +++ b/services/registration/change_language.vis @@ -1,4 +1,3 @@ -LOAD reset_account_authorized 0 LOAD reset_incorrect 0 CATCH incorrect_pin flag_incorrect_pin 1 CATCH pin_entry flag_account_authorized 0 diff --git a/services/registration/main.vis b/services/registration/main.vis index d883dca..4e27a79 100644 --- a/services/registration/main.vis +++ b/services/registration/main.vis @@ -1,3 +1,4 @@ +LOAD reset_account_authorized 0 LOAD check_balance 64 RELOAD check_balance MAP check_balance From 77e4c5d43a6c67f2db8fc9f7ea0add7f8f0fed62 Mon Sep 17 00:00:00 2001 From: alfred-mk Date: Mon, 23 Sep 2024 21:20:57 +0300 Subject: [PATCH 16/21] reset the positon of LOAD function --- services/registration/change_language.vis | 1 + services/registration/main.vis | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/services/registration/change_language.vis b/services/registration/change_language.vis index b33acf1..05ca95b 100644 --- a/services/registration/change_language.vis +++ b/services/registration/change_language.vis @@ -1,3 +1,4 @@ +LOAD reset_account_authorized 0 LOAD reset_incorrect 0 CATCH incorrect_pin flag_incorrect_pin 1 CATCH pin_entry flag_account_authorized 0 diff --git a/services/registration/main.vis b/services/registration/main.vis index 4e27a79..d883dca 100644 --- a/services/registration/main.vis +++ b/services/registration/main.vis @@ -1,4 +1,3 @@ -LOAD reset_account_authorized 0 LOAD check_balance 64 RELOAD check_balance MAP check_balance From 8648ea599cadba0b3c3de1f010346acad84c6f9d Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Tue, 24 Sep 2024 13:37:14 +0300 Subject: [PATCH 17/21] add catch node --- services/registration/_catch | 1 + services/registration/_catch.vis | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 services/registration/_catch create mode 100644 services/registration/_catch.vis diff --git a/services/registration/_catch b/services/registration/_catch new file mode 100644 index 0000000..e81b8e9 --- /dev/null +++ b/services/registration/_catch @@ -0,0 +1 @@ +Something went wrong.Please try again \ No newline at end of file diff --git a/services/registration/_catch.vis b/services/registration/_catch.vis new file mode 100644 index 0000000..0d20253 --- /dev/null +++ b/services/registration/_catch.vis @@ -0,0 +1,2 @@ +HALT +MOVE ^ From cc760e7698aa60011a4cefd12cd1cdfa3f602258 Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Tue, 24 Sep 2024 21:21:35 +0300 Subject: [PATCH 18/21] remove move --- services/registration/_catch.vis | 1 - 1 file changed, 1 deletion(-) diff --git a/services/registration/_catch.vis b/services/registration/_catch.vis index 0d20253..72e55ad 100644 --- a/services/registration/_catch.vis +++ b/services/registration/_catch.vis @@ -1,2 +1 @@ HALT -MOVE ^ From 6dbd2506946dac86aaf860f0acd64afc32757fa3 Mon Sep 17 00:00:00 2001 From: Carlosokumu Date: Wed, 25 Sep 2024 09:29:26 +0300 Subject: [PATCH 19/21] remove extra spaces --- internal/utils/isocode.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/utils/isocode.go b/internal/utils/isocode.go index 34365b0..3bdfbeb 100644 --- a/internal/utils/isocode.go +++ b/internal/utils/isocode.go @@ -1,8 +1,5 @@ package utils - - - var isoCodes = map[string]bool{ "eng": true, // English "swa": true, // Swahili @@ -12,4 +9,3 @@ var isoCodes = map[string]bool{ func IsValidISO639(code string) bool { return isoCodes[code] } - From c220cbb767c0678d5d7a40f1d0ca053824fe77d5 Mon Sep 17 00:00:00 2001 From: lash Date: Thu, 26 Sep 2024 15:05:17 +0100 Subject: [PATCH 20/21] Revert accidental merge of ssh --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 54ad3ff..ddccccf 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ go.work* **/*/*.bin **/*/.state/ cmd/.state/ +id_* +*.gdbm From 4ee241714bd51d8fbfbd416319a2ebe375765df4 Mon Sep 17 00:00:00 2001 From: lash Date: Thu, 26 Sep 2024 15:08:15 +0100 Subject: [PATCH 21/21] Attempt 2 revert changes from ssh --- cmd/ssh/README.md | 34 ----- cmd/ssh/main.go | 115 ---------------- cmd/ssh/sshkey/main.go | 44 ------ internal/ssh/keystore.go | 64 --------- internal/ssh/ssh.go | 284 --------------------------------------- 5 files changed, 541 deletions(-) delete mode 100644 cmd/ssh/README.md delete mode 100644 cmd/ssh/main.go delete mode 100644 cmd/ssh/sshkey/main.go delete mode 100644 internal/ssh/keystore.go delete mode 100644 internal/ssh/ssh.go diff --git a/cmd/ssh/README.md b/cmd/ssh/README.md deleted file mode 100644 index ff325d7..0000000 --- a/cmd/ssh/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# URDT-USSD SSH server - -An SSH server entry point for the vise engine. - - -## Adding public keys for access - -Map your (client) public key to a session identifier (e.g. phone number) - -``` -go run -v -tags logtrace ./cmd/ssh/sshkey/main.go -i [--dbdir ] -``` - - -## Create a private key for the server - -``` -ssh-keygen -N "" -f -``` - - -## Run the server - - -``` -go run -v -tags logtrace ./cmd/ssh/main.go -h -p [--dbdir ] -``` - - -## Connect to the server - -``` -ssh [-v] -T -p -i -``` diff --git a/cmd/ssh/main.go b/cmd/ssh/main.go deleted file mode 100644 index 0227616..0000000 --- a/cmd/ssh/main.go +++ /dev/null @@ -1,115 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "path" - "os" - "os/signal" - "sync" - "syscall" - - "git.defalsify.org/vise.git/db" - "git.defalsify.org/vise.git/engine" - "git.defalsify.org/vise.git/logging" - - "git.grassecon.net/urdt/ussd/internal/ssh" -) - -var ( - wg sync.WaitGroup - keyStore db.Db - logg = logging.NewVanilla() - scriptDir = path.Join("services", "registration") -) - -func main() { - var dbDir string - var resourceDir string - var size uint - var engineDebug bool - var stateDebug bool - var host string - var port uint - flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") - flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir") - flag.BoolVar(&engineDebug, "engine-debug", false, "use engine debug output") - flag.BoolVar(&stateDebug, "state-debug", false, "use engine debug output") - flag.UintVar(&size, "s", 160, "max size of output") - flag.StringVar(&host, "h", "127.0.0.1", "http host") - flag.UintVar(&port, "p", 7122, "http port") - flag.Parse() - - sshKeyFile := flag.Arg(0) - _, err := os.Stat(sshKeyFile) - if err != nil { - fmt.Fprintf(os.Stderr, "cannot open ssh server private key file: %v\n", err) - os.Exit(1) - } - - ctx := context.Background() - logg.WarnCtxf(ctx, "!!!!! WARNING WARNING WARNING") - logg.WarnCtxf(ctx, "!!!!! =======================") - logg.WarnCtxf(ctx, "!!!!! This is not a production ready server!") - logg.WarnCtxf(ctx, "!!!!! Do not expose to internet and only use with tunnel!") - logg.WarnCtxf(ctx, "!!!!! (See ssh -L <...>)") - - logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size, "keyfile", sshKeyFile, "host", host, "port", port) - - pfp := path.Join(scriptDir, "pp.csv") - - cfg := engine.Config{ - Root: "root", - OutputSize: uint32(size), - FlagCount: uint32(16), - } - if stateDebug { - cfg.StateDebug = true - } - if engineDebug { - cfg.EngineDebug = true - } - - authKeyStore, err := ssh.NewSshKeyStore(ctx, dbDir) - if err != nil { - fmt.Fprintf(os.Stderr, "keystore file open error: %v", err) - os.Exit(1) - } - defer func () { - logg.TraceCtxf(ctx, "shutdown auth key store reached") - err = authKeyStore.Close() - if err != nil { - logg.ErrorCtxf(ctx, "keystore close error", "err", err) - } - }() - - cint := make(chan os.Signal) - cterm := make(chan os.Signal) - signal.Notify(cint, os.Interrupt, syscall.SIGINT) - signal.Notify(cterm, os.Interrupt, syscall.SIGTERM) - - runner := &ssh.SshRunner{ - Cfg: cfg, - Debug: engineDebug, - FlagFile: pfp, - DbDir: dbDir, - ResourceDir: resourceDir, - SrvKeyFile: sshKeyFile, - Host: host, - Port: port, - } - go func() { - select { - case _ = <-cint: - case _ = <-cterm: - } - logg.TraceCtxf(ctx, "shutdown runner reached") - err := runner.Stop() - if err != nil { - logg.ErrorCtxf(ctx, "runner stop error", "err", err) - } - - }() - runner.Run(ctx, authKeyStore) -} diff --git a/cmd/ssh/sshkey/main.go b/cmd/ssh/sshkey/main.go deleted file mode 100644 index 87b89a3..0000000 --- a/cmd/ssh/sshkey/main.go +++ /dev/null @@ -1,44 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "os" - - "git.grassecon.net/urdt/ussd/internal/ssh" -) - -func main() { - var dbDir string - var sessionId string - flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") - flag.StringVar(&sessionId, "i", "", "session id") - flag.Parse() - - if sessionId == "" { - fmt.Fprintf(os.Stderr, "empty session id\n") - os.Exit(1) - } - - ctx := context.Background() - - sshKeyFile := flag.Arg(0) - if sshKeyFile == "" { - fmt.Fprintf(os.Stderr, "missing key file argument\n") - os.Exit(1) - } - - store, err := ssh.NewSshKeyStore(ctx, dbDir) - if err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) - os.Exit(1) - } - defer store.Close() - - err = store.AddFromFile(ctx, sshKeyFile, sessionId) - if err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) - os.Exit(1) - } -} diff --git a/internal/ssh/keystore.go b/internal/ssh/keystore.go deleted file mode 100644 index fed7233..0000000 --- a/internal/ssh/keystore.go +++ /dev/null @@ -1,64 +0,0 @@ -package ssh - -import ( - "context" - "fmt" - "os" - "path" - - "golang.org/x/crypto/ssh" - - "git.defalsify.org/vise.git/db" - - "git.grassecon.net/urdt/ussd/internal/storage" -) - -type SshKeyStore struct { - store db.Db -} - -func NewSshKeyStore(ctx context.Context, dbDir string) (*SshKeyStore, error) { - keyStore := &SshKeyStore{} - keyStoreFile := path.Join(dbDir, "ssh_authorized_keys.gdbm") - keyStore.store = storage.NewThreadGdbmDb() - err := keyStore.store.Connect(ctx, keyStoreFile) - if err != nil { - return nil, err - } - return keyStore, nil -} - -func(s *SshKeyStore) AddFromFile(ctx context.Context, fp string, sessionId string) error { - _, err := os.Stat(fp) - if err != nil { - return fmt.Errorf("cannot open ssh server public key file: %v\n", err) - } - - publicBytes, err := os.ReadFile(fp) - if err != nil { - return fmt.Errorf("Failed to load public key: %v", err) - } - pubKey, _, _, _, err := ssh.ParseAuthorizedKey(publicBytes) - if err != nil { - return fmt.Errorf("Failed to parse public key: %v", err) - } - k := append([]byte{0x01}, pubKey.Marshal()...) - s.store.SetPrefix(storage.DATATYPE_CUSTOM) - logg.Infof("Added key", "sessionId", sessionId, "public key", string(publicBytes)) - return s.store.Put(ctx, k, []byte(sessionId)) -} - -func(s *SshKeyStore) Get(ctx context.Context, pubKey ssh.PublicKey) (string, error) { - s.store.SetLanguage(nil) - s.store.SetPrefix(storage.DATATYPE_CUSTOM) - k := append([]byte{0x01}, pubKey.Marshal()...) - v, err := s.store.Get(ctx, k) - if err != nil { - return "", err - } - return string(v), nil -} - -func(s *SshKeyStore) Close() error { - return s.store.Close() -} diff --git a/internal/ssh/ssh.go b/internal/ssh/ssh.go deleted file mode 100644 index 394f55f..0000000 --- a/internal/ssh/ssh.go +++ /dev/null @@ -1,284 +0,0 @@ -package ssh - -import ( - "context" - "encoding/hex" - "encoding/base64" - "errors" - "fmt" - "net" - "os" - "sync" - - "golang.org/x/crypto/ssh" - - "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 *SshKeyStore - auth map[string]string -} - -func NewAuther(ctx context.Context, keyStore *SshKeyStore) *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) { - va, err := a.keyStore.Get(a.Ctx, pubKey) - if err != nil { - return nil, err - } - ka := hex.EncodeToString(conn.SessionID()) - a.auth[ka] = va - fmt.Fprintf(os.Stderr, "connect: %s -> %s\n", ka, va) - 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 - lst net.Listener -} - -func(s *SshRunner) Stop() error { - return s.lst.Close() -} - -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 *SshKeyStore) { - 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) - } - srvPub := private.PublicKey() - srvPubStr := base64.StdEncoding.EncodeToString(srvPub.Marshal()) - logg.InfoCtxf(ctx, "have server key", "type", srvPub.Type(), "public", srvPubStr) - cfg.AddHostKey(private) - - s.lst, err = net.Listen("tcp", fmt.Sprintf("%s:%d", s.Host, s.Port)) - if err != nil { - panic(err) - } - - for running { - conn, err := s.lst.Accept() - if err != nil { - logg.ErrorCtxf(ctx, "ssh accept error", "err", err) - running = false - continue - } - - 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) - } -}