Compare commits
No commits in common. "4ee241714bd51d8fbfbd416319a2ebe375765df4" and "ad1f9233ca6754954d4195b2f09ef301bd9e13fa" have entirely different histories.
4ee241714b
...
ad1f9233ca
@ -69,11 +69,13 @@ func main() {
|
|||||||
var resourceDir string
|
var resourceDir string
|
||||||
var size uint
|
var size uint
|
||||||
var engineDebug bool
|
var engineDebug bool
|
||||||
|
var stateDebug bool
|
||||||
var host string
|
var host string
|
||||||
var port uint
|
var port uint
|
||||||
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
|
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
|
||||||
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
|
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
|
||||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
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.UintVar(&size, "s", 160, "max size of output")
|
||||||
flag.StringVar(&host, "h", "127.0.0.1", "http host")
|
flag.StringVar(&host, "h", "127.0.0.1", "http host")
|
||||||
flag.UintVar(&port, "p", 7123, "http port")
|
flag.UintVar(&port, "p", 7123, "http port")
|
||||||
@ -89,7 +91,9 @@ func main() {
|
|||||||
OutputSize: uint32(size),
|
OutputSize: uint32(size),
|
||||||
FlagCount: uint32(16),
|
FlagCount: uint32(16),
|
||||||
}
|
}
|
||||||
|
if stateDebug {
|
||||||
|
cfg.StateDebug = true
|
||||||
|
}
|
||||||
if engineDebug {
|
if engineDebug {
|
||||||
cfg.EngineDebug = true
|
cfg.EngineDebug = true
|
||||||
}
|
}
|
||||||
|
@ -41,12 +41,14 @@ func main() {
|
|||||||
var resourceDir string
|
var resourceDir string
|
||||||
var size uint
|
var size uint
|
||||||
var engineDebug bool
|
var engineDebug bool
|
||||||
|
var stateDebug bool
|
||||||
var host string
|
var host string
|
||||||
var port uint
|
var port uint
|
||||||
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
||||||
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
|
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
|
||||||
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
|
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
|
||||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
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.UintVar(&size, "s", 160, "max size of output")
|
||||||
flag.StringVar(&host, "h", "127.0.0.1", "http host")
|
flag.StringVar(&host, "h", "127.0.0.1", "http host")
|
||||||
flag.UintVar(&port, "p", 7123, "http port")
|
flag.UintVar(&port, "p", 7123, "http port")
|
||||||
@ -62,7 +64,9 @@ func main() {
|
|||||||
OutputSize: uint32(size),
|
OutputSize: uint32(size),
|
||||||
FlagCount: uint32(16),
|
FlagCount: uint32(16),
|
||||||
}
|
}
|
||||||
|
if stateDebug {
|
||||||
|
cfg.StateDebug = true
|
||||||
|
}
|
||||||
if engineDebug {
|
if engineDebug {
|
||||||
cfg.EngineDebug = true
|
cfg.EngineDebug = true
|
||||||
}
|
}
|
||||||
|
@ -30,11 +30,13 @@ func main() {
|
|||||||
var resourceDir string
|
var resourceDir string
|
||||||
var size uint
|
var size uint
|
||||||
var engineDebug bool
|
var engineDebug bool
|
||||||
|
var stateDebug bool
|
||||||
var host string
|
var host string
|
||||||
var port uint
|
var port uint
|
||||||
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
|
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
|
||||||
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
|
flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir")
|
||||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
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.UintVar(&size, "s", 160, "max size of output")
|
||||||
flag.StringVar(&host, "h", "127.0.0.1", "http host")
|
flag.StringVar(&host, "h", "127.0.0.1", "http host")
|
||||||
flag.UintVar(&port, "p", 7123, "http port")
|
flag.UintVar(&port, "p", 7123, "http port")
|
||||||
@ -50,7 +52,9 @@ func main() {
|
|||||||
OutputSize: uint32(size),
|
OutputSize: uint32(size),
|
||||||
FlagCount: uint32(16),
|
FlagCount: uint32(16),
|
||||||
}
|
}
|
||||||
|
if stateDebug {
|
||||||
|
cfg.StateDebug = true
|
||||||
|
}
|
||||||
if engineDebug {
|
if engineDebug {
|
||||||
cfg.EngineDebug = true
|
cfg.EngineDebug = true
|
||||||
}
|
}
|
||||||
|
@ -23,10 +23,10 @@ func main() {
|
|||||||
var dbDir string
|
var dbDir string
|
||||||
var size uint
|
var size uint
|
||||||
var sessionId string
|
var sessionId string
|
||||||
var engineDebug bool
|
var debug bool
|
||||||
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
||||||
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
|
flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from")
|
||||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
flag.BoolVar(&debug, "d", false, "use engine debug output")
|
||||||
flag.UintVar(&size, "s", 160, "max size of output")
|
flag.UintVar(&size, "s", 160, "max size of output")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ func main() {
|
|||||||
|
|
||||||
en := lhs.GetEngine()
|
en := lhs.GetEngine()
|
||||||
en = en.WithFirst(hl.Init)
|
en = en.WithFirst(hl.Init)
|
||||||
if engineDebug {
|
if debug {
|
||||||
en = en.WithDebug(nil)
|
en = en.WithDebug(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
34
cmd/ssh/README.md
Normal file
34
cmd/ssh/README.md
Normal file
@ -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 <session_id> [--dbdir <dbpath>] <client_publickey_filepath>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Create a private key for the server
|
||||||
|
|
||||||
|
```
|
||||||
|
ssh-keygen -N "" -f <server_privatekey_filepath>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Run the server
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
go run -v -tags logtrace ./cmd/ssh/main.go -h <host> -p <port> [--dbdir <dbpath>] <server_privatekey_filepath>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Connect to the server
|
||||||
|
|
||||||
|
```
|
||||||
|
ssh [-v] -T -p <port> -i <client_publickey_filepath> <host>
|
||||||
|
```
|
115
cmd/ssh/main.go
Normal file
115
cmd/ssh/main.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
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)
|
||||||
|
}
|
44
cmd/ssh/sshkey/main.go
Normal file
44
cmd/ssh/sshkey/main.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -88,6 +88,7 @@ func (ls *LocalHandlerService) GetHandler() (*ussd.Handlers, error) {
|
|||||||
ls.DbRs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo)
|
ls.DbRs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo)
|
||||||
ls.DbRs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob)
|
ls.DbRs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob)
|
||||||
ls.DbRs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
|
ls.DbRs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
|
||||||
|
ls.DbRs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit)
|
||||||
ls.DbRs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction)
|
ls.DbRs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction)
|
||||||
ls.DbRs.AddLocalFunc("save_temporary_pin", ussdHandlers.SaveTemporaryPin)
|
ls.DbRs.AddLocalFunc("save_temporary_pin", ussdHandlers.SaveTemporaryPin)
|
||||||
ls.DbRs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin)
|
ls.DbRs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin)
|
||||||
|
@ -117,14 +117,17 @@ 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) {
|
func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
var res resource.Result
|
var res resource.Result
|
||||||
|
|
||||||
symbol, _ := h.st.Where()
|
sym, _ = h.st.Where()
|
||||||
code := strings.Split(symbol, "_")[1]
|
|
||||||
|
|
||||||
if !utils.IsValidISO639(code) {
|
switch sym {
|
||||||
return res, nil
|
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:
|
||||||
}
|
}
|
||||||
res.FlagSet = append(res.FlagSet, state.FLAG_LANG)
|
|
||||||
res.Content = code
|
|
||||||
|
|
||||||
languageSetFlag, err := h.flagManager.GetFlag("flag_language_set")
|
languageSetFlag, err := h.flagManager.GetFlag("flag_language_set")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -276,6 +279,32 @@ func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byt
|
|||||||
return res, nil
|
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
|
// VerifyPin checks whether the confirmation PIN is similar to the account PIN
|
||||||
// If similar, it sets the USERFLAG_PIN_SET flag allowing the user
|
// If similar, it sets the USERFLAG_PIN_SET flag allowing the user
|
||||||
// to access the main menu
|
// to access the main menu
|
||||||
@ -406,7 +435,6 @@ func (h *Handlers) SaveLocation(ctx context.Context, sym string, input []byte) (
|
|||||||
|
|
||||||
// SaveGender updates the gender in the gdbm with the provided input.
|
// 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) {
|
func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
symbol, _ := h.st.Where()
|
|
||||||
var res resource.Result
|
var res resource.Result
|
||||||
var err error
|
var err error
|
||||||
sessionId, ok := ctx.Value("SessionId").(string)
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
@ -414,11 +442,21 @@ func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (re
|
|||||||
return res, fmt.Errorf("missing session")
|
return res, fmt.Errorf("missing session")
|
||||||
}
|
}
|
||||||
|
|
||||||
gender := strings.Split(symbol, "_")[1]
|
if len(input) > 0 {
|
||||||
store := h.userdataStore
|
gender := string(input)
|
||||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_GENDER, []byte(gender))
|
switch gender {
|
||||||
if err != nil {
|
case "1":
|
||||||
return res, nil
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
|
64
internal/ssh/keystore.go
Normal file
64
internal/ssh/keystore.go
Normal file
@ -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()
|
||||||
|
}
|
284
internal/ssh/ssh.go
Normal file
284
internal/ssh/ssh.go
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
var isoCodes = map[string]bool{
|
|
||||||
"eng": true, // English
|
|
||||||
"swa": true, // Swahili
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsValidISO639(code string) bool {
|
|
||||||
return isoCodes[code]
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
Something went wrong.Please try again
|
|
@ -1 +0,0 @@
|
|||||||
HALT
|
|
@ -9,5 +9,4 @@ CATCH invalid_amount flag_invalid_amount 1
|
|||||||
INCMP _ 0
|
INCMP _ 0
|
||||||
LOAD get_recipient 12
|
LOAD get_recipient 12
|
||||||
LOAD get_sender 64
|
LOAD get_sender 64
|
||||||
LOAD get_amount 12
|
|
||||||
INCMP transaction_pin *
|
INCMP transaction_pin *
|
||||||
|
@ -11,6 +11,8 @@ MOUT view 7
|
|||||||
MOUT back 0
|
MOUT back 0
|
||||||
HALT
|
HALT
|
||||||
INCMP my_account 0
|
INCMP my_account 0
|
||||||
|
LOAD set_reset_single_edit 0
|
||||||
|
RELOAD set_reset_single_edit
|
||||||
INCMP enter_name 1
|
INCMP enter_name 1
|
||||||
INCMP enter_familyname 2
|
INCMP enter_familyname 2
|
||||||
INCMP select_gender 3
|
INCMP select_gender 3
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
CATCH incorrect_pin flag_incorrect_pin 1
|
CATCH incorrect_pin flag_incorrect_pin 1
|
||||||
CATCH profile_update_success flag_allow_update 1
|
CATCH profile_update_success flag_allow_update 1
|
||||||
|
LOAD save_gender 0
|
||||||
MOUT male 1
|
MOUT male 1
|
||||||
MOUT female 2
|
MOUT female 2
|
||||||
MOUT unspecified 3
|
MOUT unspecified 3
|
||||||
MOUT back 0
|
MOUT back 0
|
||||||
HALT
|
HALT
|
||||||
|
RELOAD save_gender
|
||||||
INCMP _ 0
|
INCMP _ 0
|
||||||
INCMP set_male 1
|
INCMP pin_entry *
|
||||||
INCMP set_female 2
|
|
||||||
INCMP set_unspecified 3
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
MOUT english 0
|
MOUT english 0
|
||||||
MOUT kiswahili 1
|
MOUT kiswahili 1
|
||||||
HALT
|
HALT
|
||||||
INCMP set_eng 0
|
INCMP set_default 0
|
||||||
INCMP set_swa 1
|
INCMP set_swa 1
|
||||||
INCMP . *
|
INCMP . *
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
LOAD save_gender 0
|
|
||||||
CATCH incorrect_pin flag_incorrect_pin 1
|
|
||||||
CATCH profile_update_success flag_allow_update 1
|
|
||||||
MOVE pin_entry
|
|
@ -1,4 +0,0 @@
|
|||||||
LOAD save_gender 0
|
|
||||||
CATCH incorrect_pin flag_incorrect_pin 1
|
|
||||||
CATCH profile_update_success flag_allow_update 1
|
|
||||||
MOVE pin_entry
|
|
@ -1,4 +0,0 @@
|
|||||||
LOAD save_gender 0
|
|
||||||
CATCH incorrect_pin flag_incorrect_pin 1
|
|
||||||
CATCH profile_update_success flag_allow_update 1
|
|
||||||
MOVE pin_entry
|
|
@ -1,7 +1,7 @@
|
|||||||
LOAD reset_incorrect 6
|
LOAD reset_incorrect 6
|
||||||
CATCH incorrect_pin flag_incorrect_pin 1
|
CATCH incorrect_pin flag_incorrect_pin 1
|
||||||
CATCH _ flag_account_authorized 0
|
CATCH _ flag_account_authorized 0
|
||||||
RELOAD get_amount
|
LOAD get_amount 10
|
||||||
MAP get_amount
|
MAP get_amount
|
||||||
RELOAD get_recipient
|
RELOAD get_recipient
|
||||||
MAP get_recipient
|
MAP get_recipient
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
{{.get_recipient}} will receive {{.get_amount}} from {{.get_sender}}
|
{{.get_recipient}} will receive {{.validate_amount}} from {{.get_sender}}
|
||||||
Please enter your PIN to confirm:
|
Please enter your PIN to confirm:
|
@ -1,13 +1,12 @@
|
|||||||
RELOAD get_amount
|
MAP validate_amount
|
||||||
MAP get_amount
|
|
||||||
RELOAD get_recipient
|
RELOAD get_recipient
|
||||||
MAP get_recipient
|
MAP get_recipient
|
||||||
RELOAD get_sender
|
RELOAD get_sender
|
||||||
MAP get_sender
|
MAP get_sender
|
||||||
MOUT back 0
|
MOUT back 0
|
||||||
MOUT quit 9
|
MOUT quit 9
|
||||||
LOAD authorize_account 6
|
|
||||||
HALT
|
HALT
|
||||||
|
LOAD authorize_account 6
|
||||||
RELOAD authorize_account
|
RELOAD authorize_account
|
||||||
CATCH incorrect_pin flag_incorrect_pin 1
|
CATCH incorrect_pin flag_incorrect_pin 1
|
||||||
INCMP _ 0
|
INCMP _ 0
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
{{.get_recipient}} atapokea {{.get_amount}} kutoka kwa {{.get_sender}}
|
{{.get_recipient}} atapokea {{.validate_amount}} kutoka kwa {{.get_sender}}
|
||||||
Tafadhali weka PIN yako kudhibitisha:
|
Tafadhali weka PIN yako kudhibitisha:
|
Loading…
Reference in New Issue
Block a user