<commit_before>package sshego

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"sync"
	"time"

	scrypt "github.com/elithrar/simple-scrypt"
	"github.com/pquerna/otp"
	"golang.org/x/crypto/ssh"
)

//go:generate msgp

// LoginRecord is per public key.
type LoginRecord struct {
	FirstTm       time.Time
	LastTm        time.Time
	SeenCount     int64
	AcceptedCount int64
	PubFinger     string
}

func (r LoginRecord) String() string {
	return fmt.Sprintf(`LoginRecord{ FirstTm:"%s", LastTm:"%s", SeenCount:%v, AcceptedCount: %v, PubFinger:"%s"}`,
		r.FirstTm, r.LastTm, r.SeenCount, r.AcceptedCount, r.PubFinger)
}

// User represents a user authorized
// to login to the embedded sshd.
type User struct {
	MyEmail    string
	MyFullname string
	MyLogin    string

	PublicKeyPath  string
	PrivateKeyPath string
	TOTPpath       string
	QrPath         string

	Issuer     string
	publicKey  ssh.PublicKey
	SeenPubKey map[string]LoginRecord

	ScryptedPassword []byte
	ClearPw          string // only on network, never on disk.
	TOTPorig         string
	oneTime          *TOTP

	FirstLoginTime time.Time
	LastLoginTime  time.Time
	LastLoginAddr  string
	IPwhitelist    []string
	DisabledAcct   bool

	mut sync.Mutex
}

func NewUser() *User {
	u := &User{
		SeenPubKey: make(map[string]LoginRecord),
	}
	return u
}

type HostDb struct {

	// Users: key is MyLogin; value is *User.
	Users *AtomicUserMap

	HostPrivateKeyPath string

	hostSshSigner ssh.Signer
	cfg           *SshegoConfig

	loadedFromDisk bool

	saveMut sync.Mutex

	userTcp TcpPort

	boltdb *boltdb
}

func (cfg *SshegoConfig) NewHostDb() error {
	h := &HostDb{
		cfg:     cfg,
		Users:   NewAtomicUserMap(),
		userTcp: TcpPort{Port: cfg.SshegoSystemMutexPort},
	}
	cfg.HostDb = h
	return h.init()
}

func (h *HostDb) privpath() string {
	return h.cfg.EmbeddedSSHdHostDbPath + ".hostkey"
}

func (h *HostDb) init() error {
	h.HostPrivateKeyPath = h.privpath()
	err := h.loadOrCreate()
	return err
}

func (h *HostDb) generateHostKey() error {
	err := h.gendir()
	if err != nil {
		return err
	}
	path := h.privpath()
	bits := h.cfg.BitLenRSAkeys // default 4096

	p("\n bits = %v\n", bits)
	host, _ := os.Hostname()
	_, signer, err := GenRSAKeyPair(path, bits, host)
	if err != nil {
		return err
	}
	h.hostSshSigner = signer
	return nil
}

func (h *HostDb) gendir() error {

	path := h.cfg.EmbeddedSSHdHostDbPath
	if fileExists(path) {
		return nil
	}
	err := os.MkdirAll(path, 0777)
	if err != nil {
		return fmt.Errorf("HostDb: MkdirAll on '%s' failed: %v",
			path, err)
	}
	return nil
}

func makeway(path string) error {
	dir := filepath.Dir(path)
	return os.MkdirAll(dir, 0777)
}

func (h *HostDb) msgpath() string {
	return h.cfg.EmbeddedSSHdHostDbPath + "/msgp.boltdb"
}

func (h *HostDb) userpath(username string) string {
	return h.cfg.EmbeddedSSHdHostDbPath + "/users/" + username
}

func (h *HostDb) rsapath(username string) string {
	return h.cfg.EmbeddedSSHdHostDbPath + "/users/" + username + "/id_rsa"
}

func (h *HostDb) toptpath(username string) string {
	return h.cfg.EmbeddedSSHdHostDbPath + "/users/" + username + "/topt"
}

const skiplock = false
const lockit = true

// always opens h.msgpath()
func (h *HostDb) opendb() error {
	if h.boltdb == nil {
		err := h.gendir()
		if err != nil {
			return err
		}

		bolt, err := newBoltdb(h.msgpath())
		if err != nil {
			return fmt.Errorf("HostDb.save(): create newBoltdb at '%s' failed: %v",
				h.msgpath(), err)
		}
		h.boltdb = bolt
	}
	return nil
}

// There should only one writer to disk at a time...
// Let this be the main handshake/user auth goroutine
// that listens for sshd connections.
func (h *HostDb) save(lock bool) error {
	if lock == lockit {
		h.saveMut.Lock()
		defer h.saveMut.Unlock()
	}

	err := h.opendb()
	if err != nil {
		return fmt.Errorf("HostDb.save(): opendb() at path '%s' gave error '%v'",
			h.msgpath(), err)
	}

	bts, err := h.MarshalMsg(nil)
	if err != nil {
		return err
	}
	err = h.boltdb.writeKey(hostDbKey, bts)
	if err != nil {
		return fmt.Errorf("HostDb.Save bolt.writeKey hostDbKey='%s' gave error = '%v'", string(hostDbKey), err)
	}
	return nil
}

func (h *HostDb) loadOrCreate() error {

	path := h.msgpath()

	err := h.opendb()
	if err != nil {
		return fmt.Errorf("HostDb.loadOrCreate(): opendb() at path '%s' gave error '%v'",
			h.msgpath(), err)
	}
	by, err := h.boltdb.readKey(hostDbKey)

	if len(by) > 0 {
		_, err = h.UnmarshalMsg(by)
		if err != nil {
			return err
		}
	} else {
		p("loadOrCreate path = '%s' doesn't exist; make a host key...", path)

		// no db, so make a host key
		err := h.generateHostKey()
		if err != nil {
			return err
		}<commit_msg>atg. latest<commit_after>package sshego

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"sync"
	"time"

	scrypt "github.com/elithrar/simple-scrypt"
	"github.com/pquerna/otp"
	"golang.org/x/crypto/ssh"
)

//go:generate msgp

// LoginRecord is per public key.
type LoginRecord struct {
	FirstTm       time.Time
	LastTm        time.Time
	SeenCount     int64
	AcceptedCount int64
	PubFinger     string
}

func (r LoginRecord) String() string {
	return fmt.Sprintf(`LoginRecord{ FirstTm:"%s", LastTm:"%s", SeenCount:%v, AcceptedCount: %v, PubFinger:"%s"}`,
		r.FirstTm, r.LastTm, r.SeenCount, r.AcceptedCount, r.PubFinger)
}

// User represents a user authorized
// to login to the embedded sshd.
type User struct {
	MyEmail    string
	MyFullname string
	MyLogin    string

	PublicKeyPath  string
	PrivateKeyPath string
	TOTPpath       string
	QrPath         string

	Issuer     string
	publicKey  ssh.PublicKey
	SeenPubKey map[string]LoginRecord

	ScryptedPassword []byte
	ClearPw          string // only on network, never on disk.
	TOTPorig         string
	oneTime          *TOTP

	FirstLoginTime time.Time
	LastLoginTime  time.Time
	LastLoginAddr  string
	IPwhitelist    []string
	DisabledAcct   bool

	mut sync.Mutex
}

func NewUser() *User {
	u := &User{
		SeenPubKey: make(map[string]LoginRecord),
	}
	return u
}

type HostDb struct {

	// Users: key is MyLogin; value is *User.
	Users *AtomicUserMap

	HostPrivateKeyPath string

	hostSshSigner ssh.Signer
	cfg           *SshegoConfig

	loadedFromDisk bool

	saveMut sync.Mutex

	userTcp TcpPort

	boltdb *boltdb
}

func (cfg *SshegoConfig) NewHostDb() error {
	h := &HostDb{
		cfg:     cfg,
		Users:   NewAtomicUserMap(),
		userTcp: TcpPort{Port: cfg.SshegoSystemMutexPort},
	}
	cfg.HostDb = h
	return h.init()
}

func (h *HostDb) privpath() string {
	return h.cfg.EmbeddedSSHdHostDbPath + ".hostkey"
}

func (h *HostDb) init() error {
	h.HostPrivateKeyPath = h.privpath()
	err := h.loadOrCreate()
	return err
}

func (h *HostDb) generateHostKey() error {
	err := h.gendir()
	if err != nil {
		return err
	}
	path := h.privpath()
	bits := h.cfg.BitLenRSAkeys // default 4096

	p("\n bits = %v\n", bits)
	host, _ := os.Hostname()
	_, signer, err := GenRSAKeyPair(path, bits, host)
	if err != nil {
		return err
	}
	h.hostSshSigner = signer
	return nil
}

func (h *HostDb) gendir() error {

	path := h.cfg.EmbeddedSSHdHostDbPath
	if dirExists(path) {
		return nil
	}
	err := os.MkdirAll(path, 0777)
	if err != nil {
		return fmt.Errorf("HostDb: MkdirAll on '%s' failed: %v",
			path, err)
	}
	return nil
}

func makeway(path string) error {
	dir := filepath.Dir(path)
	return os.MkdirAll(dir, 0777)
}

func (h *HostDb) msgpath() string {
	return h.cfg.EmbeddedSSHdHostDbPath + "/msgp.boltdb"
}

func (h *HostDb) userpath(username string) string {
	return h.cfg.EmbeddedSSHdHostDbPath + "/users/" + username
}

func (h *HostDb) rsapath(username string) string {
	return h.cfg.EmbeddedSSHdHostDbPath + "/users/" + username + "/id_rsa"
}

func (h *HostDb) toptpath(username string) string {
	return h.cfg.EmbeddedSSHdHostDbPath + "/users/" + username + "/topt"
}

const skiplock = false
const lockit = true

// always opens h.msgpath()
func (h *HostDb) opendb() error {
	if h.boltdb == nil {
		err := h.gendir()
		if err != nil {
			return err
		}

		bolt, err := newBoltdb(h.msgpath())
		if err != nil {
			return fmt.Errorf("HostDb.save(): create newBoltdb at '%s' failed: %v",
				h.msgpath(), err)
		}
		h.boltdb = bolt
	}
	return nil
}

// There should only one writer to disk at a time...
// Let this be the main handshake/user auth goroutine
// that listens for sshd connections.
func (h *HostDb) save(lock bool) error {
	if lock == lockit {
		h.saveMut.Lock()
		defer h.saveMut.Unlock()
	}

	err := h.opendb()
	if err != nil {
		return fmt.Errorf("HostDb.save(): opendb() at path '%s' gave error '%v'",
			h.msgpath(), err)
	}

	bts, err := h.MarshalMsg(nil)
	if err != nil {
		return err
	}
	err = h.boltdb.writeKey(hostDbKey, bts)
	if err != nil {
		return fmt.Errorf("HostDb.Save bolt.writeKey hostDbKey='%s' gave error = '%v'", string(hostDbKey), err)
	}
	return nil
}

func (h *HostDb) loadOrCreate() error {

	err := h.opendb()
	if err != nil {
		return fmt.Errorf("HostDb.loadOrCreate(): opendb() at path '%s' gave error '%v'",
			h.msgpath(), err)
	}
	by, err := h.boltdb.readKey(hostDbKey)

	if len(by) > 0 {

		_, err = h.UnmarshalMsg(by)
		if err != nil {
			return err
		}
	} else {

		p("loadOrCreate path = '%s' doesn't exist; make a host key...", h.msgpath())

		// no db, so make a host key
		err := h.generateHostKey()
		if err != nil {
			return err
		}
{'fraction_non_alphanumeric': 0.1342462799223636, 'fraction_numerical': 0.003666163467759327, 'mean_word_length': 3.3099442379182156, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2686588', 'n_tokens_mistral': 3767, 'n_tokens_neox': 3578, 'n_words': 957}