diff --git a/cmd/http/main.go b/cmd/http/main.go deleted file mode 100644 index babf128..0000000 --- a/cmd/http/main.go +++ /dev/null @@ -1,165 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "net/http" - "os" - "os/signal" - "path" - "strconv" - "syscall" - - "github.com/grassrootseconomics/go-vise/engine" - "github.com/grassrootseconomics/go-vise/lang" - "github.com/grassrootseconomics/go-vise/resource" - slogging "github.com/grassrootseconomics/go-vise/slog" - - "git.grassecon.net/grassrootseconomics/sarafu-vise/config" - "git.grassecon.net/grassrootseconomics/visedriver/request" - httprequest "git.grassecon.net/grassrootseconomics/visedriver/request/http" - "git.grassecon.net/grassrootseconomics/visedriver/storage" - - "git.grassecon.net/grassrootseconomics/sarafu-vise/args" - "git.grassecon.net/grassrootseconomics/sarafu-vise/handlers" - "git.grassecon.net/grassrootseconomics/sarafu-vise/services" -) - -var ( - logg = slogging.Get().With("component", "HTTP Server") - scriptDir = path.Join("services", "registration") - menuSeparator = ": " -) - -func main() { - config.LoadConfig() - - override := config.NewOverride() - var size uint - var engineDebug bool - var host string - var port uint - var err error - var gettextDir string - var langs args.LangVar - var logDbConnStr string - - flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)") - flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string") - flag.StringVar(&override.ResourceConn, "resource", "?", "resource data directory") - flag.StringVar(&override.StateConn, "state", "?", "state store connection string") - - flag.BoolVar(&engineDebug, "d", false, "use engine debug output") - flag.UintVar(&size, "s", 160, "max size of output") - flag.StringVar(&host, "h", config.Host(), "http host") - flag.UintVar(&port, "p", config.Port(), "http port") - flag.StringVar(&gettextDir, "gettext", "", "use gettext translations from given directory") - flag.Var(&langs, "language", "add symbol resolution for language") - flag.StringVar(&logDbConnStr, "log-c", "db-logs", "log db connection string") - flag.Parse() - - config.Apply(override) - conns, err := config.GetConns() - if err != nil { - fmt.Fprintf(os.Stderr, "conn specification error: %v\n", err) - os.Exit(1) - } - - logg.Infof("start command", "conn", conns, "outputsize", size) - - ctx := context.Background() - - ln, err := lang.LanguageFromCode(config.Language()) - if err != nil { - fmt.Fprintf(os.Stderr, "default language set error: %v", err) - os.Exit(1) - } - ctx = context.WithValue(ctx, "Language", ln) - - pfp := path.Join(scriptDir, "pp.csv") - - cfg := engine.Config{ - Root: "root", - OutputSize: uint32(size), - FlagCount: uint32(128), - MenuSeparator: menuSeparator, - ResetOnEmptyInput: true, - } - - if engineDebug { - cfg.EngineDebug = true - } - - menuStorageService := storage.NewMenuStorageService(conns) - - rs, err := menuStorageService.GetResource(ctx) - if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) - os.Exit(1) - } - - userdataStore, err := menuStorageService.GetUserdataDb(ctx) - if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) - os.Exit(1) - } - - dbResource, ok := rs.(*resource.DbResource) - if !ok { - os.Exit(1) - } - - lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs) - lhs.SetDataStore(&userdataStore) - - if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) - os.Exit(1) - } - - accountService := services.New(ctx, menuStorageService) - - hl, err := lhs.GetHandler(accountService) - if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) - os.Exit(1) - } - - stateStore, err := menuStorageService.GetStateStore(ctx) - if err != nil { - fmt.Fprintf(os.Stderr, err.Error()) - os.Exit(1) - } - - //accountService := services.New(ctx, menuStorageService, connData) - - rp := &httprequest.DefaultRequestParser{} - bsh := request.NewBaseRequestHandler(cfg, rs, stateStore, userdataStore, rp, hl) - bsh = bsh.WithEngineFunc(lhs.GetEngine) - sh := httprequest.NewHTTPRequestHandler(bsh) - s := &http.Server{ - Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))), - Handler: sh, - } - shutdownFunc := func() { - sh.Shutdown(ctx) - } - s.RegisterOnShutdown(shutdownFunc) - - cint := make(chan os.Signal) - cterm := make(chan os.Signal) - signal.Notify(cint, os.Interrupt, syscall.SIGINT) - signal.Notify(cterm, os.Interrupt, syscall.SIGTERM) - go func() { - select { - case _ = <-cint: - case _ = <-cterm: - } - s.Shutdown(ctx) - }() - err = s.ListenAndServe() - if err != nil { - logg.Infof("Server closed with error", "err", err) - } -} 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 9b1f8ea..0000000 --- a/cmd/ssh/main.go +++ /dev/null @@ -1,136 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "os" - "os/signal" - "path" - "sync" - "syscall" - - "github.com/grassrootseconomics/go-vise/db" - "github.com/grassrootseconomics/go-vise/engine" - slogging "github.com/grassrootseconomics/go-vise/slog" - - "git.grassecon.net/grassrootseconomics/sarafu-vise/config" - "git.grassecon.net/grassrootseconomics/sarafu-vise/ssh" - "git.grassecon.net/grassrootseconomics/visedriver/storage" -) - -var ( - wg sync.WaitGroup - keyStore db.Db - logg = slogging.Get() - scriptDir = path.Join("services", "registration") - - build = "dev" -) - -func main() { - config.LoadConfig() - - override := config.NewOverride() - var authConnStr string - var size uint - var engineDebug bool - var stateDebug bool - var host string - var port uint - - flag.StringVar(&override.DbConn, "c", "?", "default connection string (replaces all unspecified strings)") - flag.StringVar(&override.ResourceConn, "resource", "?", "resource connection string") - flag.StringVar(&override.UserConn, "userdata", "?", "userdata store connection string") - flag.StringVar(&override.StateConn, "state", "?", "state store connection string") - flag.BoolVar(&engineDebug, "d", false, "use engine debug output") - flag.UintVar(&size, "s", 160, "max size of output") - flag.StringVar(&host, "h", config.HostSSH(), "socket host") - flag.UintVar(&port, "p", config.PortSSH(), "socket port") - flag.Parse() - - config.Apply(override) - conns, err := config.GetConns() - if err != nil { - fmt.Fprintf(os.Stderr, "conn specification error: %v\n", err) - os.Exit(1) - } - - authConnData, err := storage.ToConnData(authConnStr) - if err != nil { - fmt.Fprintf(os.Stderr, "auth connstr err: %v", err) - os.Exit(1) - } - - 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", "conn", conns, "authconn", authConnData, "outputsize", size, "keyfile", sshKeyFile, "host", host, "port", port) - - pfp := path.Join(scriptDir, "pp.csv") - - cfg := engine.Config{ - Root: "root", - OutputSize: uint32(size), - FlagCount: uint32(128), - ResetOnEmptyInput: true, - } - if stateDebug { - cfg.StateDebug = true - } - if engineDebug { - cfg.EngineDebug = true - } - - authKeyStore, err := ssh.NewSshKeyStore(ctx, authConnData.String()) - 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(ctx) - 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, - Conn: conns, - 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 b05fe06..0000000 --- a/cmd/ssh/sshkey/main.go +++ /dev/null @@ -1,44 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "os" - - "git.grassecon.net/grassrootseconomics/sarafu-vise/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(ctx) - - err = store.AddFromFile(ctx, sshKeyFile, sessionId) - if err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) - os.Exit(1) - } -} diff --git a/ssh/keystore.go b/ssh/keystore.go deleted file mode 100644 index 37ebdef..0000000 --- a/ssh/keystore.go +++ /dev/null @@ -1,68 +0,0 @@ -package ssh - -import ( - "context" - "fmt" - "os" - "path" - - "golang.org/x/crypto/ssh" - - "git.defalsify.org/vise.git/db" - - "git.grassecon.net/grassrootseconomics/visedriver/storage" - dbstorage "git.grassecon.net/grassrootseconomics/visedriver/storage/db/gdbm" -) - -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 = dbstorage.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.SetLanguage(nil) - s.store.SetSession("") - s.store.SetPrefix(storage.DATATYPE_EXTEND) - 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.SetSession("") - s.store.SetPrefix(storage.DATATYPE_EXTEND) - 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(ctx context.Context) error { - return s.store.Close(ctx) -} diff --git a/ssh/ssh.go b/ssh/ssh.go deleted file mode 100644 index 3ec8238..0000000 --- a/ssh/ssh.go +++ /dev/null @@ -1,276 +0,0 @@ -package ssh - -import ( - "context" - "encoding/base64" - "encoding/hex" - "errors" - "fmt" - "net" - "os" - "sync" - - "golang.org/x/crypto/ssh" - - "git.grassecon.net/grassrootseconomics/sarafu-vise/handlers" - "git.grassecon.net/grassrootseconomics/sarafu-vise/services" - "git.grassecon.net/grassrootseconomics/visedriver/storage" - "github.com/grassrootseconomics/go-vise/engine" - "github.com/grassrootseconomics/go-vise/resource" - slogging "github.com/grassrootseconomics/go-vise/slog" - "github.com/grassrootseconomics/go-vise/state" -) - -var ( - logg = slogging.Get().With("component", "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) { - logg.TraceCtxf(a.Ctx, "looking for publickey", "pubkey", fmt.Sprintf("%x", pubKey)) - 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 -} - -type SshRunner struct { - Ctx context.Context - Cfg engine.Config - FlagFile string - Conn storage.Conns - Debug bool - SrvKeyFile string - Host string - Port uint - wg sync.WaitGroup - lst net.Listener -} - -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 -} - -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.Conn) - - 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(ctx, s.FlagFile, true, dbResource, s.Cfg, rs) - lhs.SetDataStore(&userdatastore) - lhs.SetPersister(pe) - lhs.Cfg.SessionId = sessionId - - if err != nil { - return nil, nil, err - } - - // TODO: this is getting very hacky! - accountService := services.New(ctx, menuStorageService) - _, err = lhs.GetHandler(accountService) - if err != nil { - fmt.Fprintf(os.Stderr, "get accounts service handler: %v\n", err) - os.Exit(1) - } - en := lhs.GetEngine(lhs.Cfg, rs, pe) - closer := func() { - err := menuStorageService.Close(ctx) - 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) { - s.Ctx = ctx - 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(ctx) - 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) - } -}