mirror of
https://github.com/anotherhadi/spilltea.git
synced 2026-05-21 02:02:34 +02:00
plugin's config is now in yaml
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
This commit is contained in:
+1
-6
@@ -72,12 +72,7 @@ CREATE TABLE IF NOT EXISTS replay_entries (
|
||||
status_code INTEGER NOT NULL,
|
||||
error_msg TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS plugins (
|
||||
name TEXT PRIMARY KEY,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
config_text TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS findings (
|
||||
CREATE TABLE IF NOT EXISTS findings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
plugin_name TEXT NOT NULL,
|
||||
dedup_key TEXT NOT NULL,
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package db
|
||||
|
||||
type PluginState struct {
|
||||
Name string
|
||||
Enabled bool
|
||||
ConfigText string
|
||||
}
|
||||
|
||||
func (d *DB) SavePluginState(name string, enabled bool, configText string) error {
|
||||
_, err := d.conn.Exec(
|
||||
`INSERT INTO plugins (name, enabled, config_text) VALUES (?, ?, ?)
|
||||
ON CONFLICT(name) DO UPDATE SET enabled = excluded.enabled, config_text = excluded.config_text`,
|
||||
name, enabled, configText,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *DB) LoadPluginStates() ([]PluginState, error) {
|
||||
rows, err := d.conn.Query(`SELECT name, enabled, config_text FROM plugins`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []PluginState
|
||||
for rows.Next() {
|
||||
var s PluginState
|
||||
var enabled int
|
||||
if err := rows.Scan(&s.Name, &enabled, &s.ConfigText); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Enabled = enabled != 0
|
||||
out = append(out, s)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/anotherhadi/spilltea/internal/db"
|
||||
goproxy "github.com/lqqyt2423/go-mitmproxy/proxy"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func newLuaState(mgr *Manager, p *Plugin) *lua.LState {
|
||||
@@ -175,6 +176,27 @@ func registerUtilities(L *lua.LState, mgr *Manager, p *Plugin) {
|
||||
return 0
|
||||
}))
|
||||
|
||||
L.SetGlobal("get_config", L.NewFunction(func(L *lua.LState) int {
|
||||
// p.mu is already held by the hook caller - do not lock again.
|
||||
configText := p.ConfigText
|
||||
if configText == "" {
|
||||
L.Push(L.NewTable())
|
||||
return 1
|
||||
}
|
||||
var data interface{}
|
||||
if err := yaml.Unmarshal([]byte(configText), &data); err != nil || data == nil {
|
||||
L.Push(L.NewTable())
|
||||
return 1
|
||||
}
|
||||
lv := goToLuaValue(L, data)
|
||||
if _, ok := lv.(*lua.LTable); !ok {
|
||||
L.Push(L.NewTable())
|
||||
return 1
|
||||
}
|
||||
L.Push(lv)
|
||||
return 1
|
||||
}))
|
||||
|
||||
L.SetGlobal("shell_pipe", L.NewFunction(func(L *lua.LState) int {
|
||||
cmd := L.CheckString(1)
|
||||
input := L.OptString(2, "")
|
||||
@@ -201,6 +223,35 @@ func registerUtilities(L *lua.LState, mgr *Manager, p *Plugin) {
|
||||
}))
|
||||
}
|
||||
|
||||
func goToLuaValue(L *lua.LState, v interface{}) lua.LValue {
|
||||
switch val := v.(type) {
|
||||
case map[string]interface{}:
|
||||
t := L.NewTable()
|
||||
for k, v2 := range val {
|
||||
L.SetField(t, k, goToLuaValue(L, v2))
|
||||
}
|
||||
return t
|
||||
case []interface{}:
|
||||
t := L.NewTable()
|
||||
for i, v2 := range val {
|
||||
L.RawSetInt(t, i+1, goToLuaValue(L, v2))
|
||||
}
|
||||
return t
|
||||
case string:
|
||||
return lua.LString(val)
|
||||
case int:
|
||||
return lua.LNumber(val)
|
||||
case float64:
|
||||
return lua.LNumber(val)
|
||||
case bool:
|
||||
if val {
|
||||
return lua.LTrue
|
||||
}
|
||||
return lua.LFalse
|
||||
}
|
||||
return lua.LNil
|
||||
}
|
||||
|
||||
func luaTableString(t *lua.LTable, key string) string {
|
||||
v := t.RawGetString(key)
|
||||
if s, ok := v.(lua.LString); ok {
|
||||
|
||||
+35
-50
@@ -19,8 +19,9 @@ type Manager struct {
|
||||
mu sync.RWMutex
|
||||
plugins []*Plugin
|
||||
|
||||
db *db.DB
|
||||
broker *intercept.Broker
|
||||
db *db.DB
|
||||
pluginsFile *PluginsFile
|
||||
broker *intercept.Broker
|
||||
|
||||
Notifs chan PluginNotifMsg
|
||||
Quit chan string
|
||||
@@ -43,6 +44,10 @@ func (m *Manager) SetDB(d *db.DB) {
|
||||
m.db = d
|
||||
}
|
||||
|
||||
func (m *Manager) SetPluginsFile(pf *PluginsFile) {
|
||||
m.pluginsFile = pf
|
||||
}
|
||||
|
||||
func (m *Manager) LoadFromDir(dir string) error {
|
||||
entries, err := os.ReadDir(dir)
|
||||
if os.IsNotExist(err) {
|
||||
@@ -52,17 +57,6 @@ func (m *Manager) LoadFromDir(dir string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var states map[string]db.PluginState
|
||||
if m.db != nil {
|
||||
list, err := m.db.LoadPluginStates()
|
||||
if err == nil {
|
||||
states = make(map[string]db.PluginState, len(list))
|
||||
for _, s := range list {
|
||||
states[s.Name] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, e := range entries {
|
||||
if e.IsDir() || !strings.HasSuffix(e.Name(), ".lua") {
|
||||
continue
|
||||
@@ -73,9 +67,13 @@ func (m *Manager) LoadFromDir(dir string) error {
|
||||
log.Printf("plugin load error %s: %v", path, err)
|
||||
continue
|
||||
}
|
||||
if s, ok := states[p.Name]; ok {
|
||||
p.Enabled = s.Enabled
|
||||
p.ConfigText = s.ConfigText
|
||||
if m.pluginsFile != nil {
|
||||
if enabled, configText, found := m.pluginsFile.get(p.Name); found {
|
||||
p.Enabled = enabled
|
||||
p.ConfigText = configText
|
||||
} else {
|
||||
m.pluginsFile.register(p.Name, p.Enabled)
|
||||
}
|
||||
}
|
||||
m.mu.Lock()
|
||||
m.plugins = append(m.plugins, p)
|
||||
@@ -131,12 +129,6 @@ func (m *Manager) loadPlugin(path string) (*Plugin, error) {
|
||||
"on_response": false,
|
||||
"on_history_entry": false,
|
||||
}
|
||||
// Fixed-sync hooks: always sync, not configurable.
|
||||
fixedSyncHooks := map[string]struct{}{
|
||||
"on_config": {},
|
||||
"on_quit": {},
|
||||
}
|
||||
|
||||
for hookName, defaultSync := range configurableHooks {
|
||||
if tbl, ok := pluginTable.RawGetString(hookName).(*lua.LTable); ok {
|
||||
p.hooks[hookName] = HookConfig{Sync: tbl.RawGetString("sync") == lua.LTrue}
|
||||
@@ -146,9 +138,9 @@ func (m *Manager) loadPlugin(path string) (*Plugin, error) {
|
||||
p.hooks[hookName] = HookConfig{Sync: defaultSync}
|
||||
}
|
||||
}
|
||||
for hookName := range fixedSyncHooks {
|
||||
if p.L.GetGlobal(hookName) != lua.LNil {
|
||||
p.hooks[hookName] = HookConfig{Sync: true}
|
||||
for _, fixedSync := range []string{"on_config", "on_quit"} {
|
||||
if p.L.GetGlobal(fixedSync) != lua.LNil {
|
||||
p.hooks[fixedSync] = HookConfig{Sync: true}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,10 +171,9 @@ func (m *Manager) TogglePlugin(name string) {
|
||||
found.mu.Lock()
|
||||
found.Enabled = !found.Enabled
|
||||
enabled := found.Enabled
|
||||
configText := found.ConfigText
|
||||
found.mu.Unlock()
|
||||
if m.db != nil {
|
||||
if err := m.db.SavePluginState(name, enabled, configText); err != nil {
|
||||
if m.pluginsFile != nil {
|
||||
if err := m.pluginsFile.setEnabled(name, enabled); err != nil {
|
||||
log.Printf("plugin %s: save state: %v", name, err)
|
||||
}
|
||||
}
|
||||
@@ -196,8 +187,8 @@ func (m *Manager) TogglePlugin(name string) {
|
||||
disableIfFalse := func(p *Plugin, ret lua.LValue) {
|
||||
if ret == lua.LFalse {
|
||||
p.Enabled = false
|
||||
if m.db != nil {
|
||||
if err := m.db.SavePluginState(p.Name, false, p.ConfigText); err != nil {
|
||||
if m.pluginsFile != nil {
|
||||
if err := m.pluginsFile.setEnabled(p.Name, false); err != nil {
|
||||
log.Printf("plugin %s: save state: %v", p.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -241,41 +232,35 @@ func (m *Manager) SaveConfig(name, configText string) {
|
||||
}
|
||||
found.mu.Lock()
|
||||
found.ConfigText = configText
|
||||
enabled := found.Enabled
|
||||
_, hasOnConfig := found.hooks["on_config"]
|
||||
found.mu.Unlock()
|
||||
if m.db != nil {
|
||||
if err := m.db.SavePluginState(name, enabled, configText); err != nil {
|
||||
log.Printf("plugin %s: save state: %v", name, err)
|
||||
if m.pluginsFile != nil {
|
||||
if err := m.pluginsFile.setConfig(name, configText); err != nil {
|
||||
log.Printf("plugin %s: save config: %v", name, err)
|
||||
}
|
||||
}
|
||||
if !hasOnConfig {
|
||||
if _, ok := found.hooks["on_config"]; !ok {
|
||||
return
|
||||
}
|
||||
// on_config is always sync.
|
||||
found.mu.Lock()
|
||||
if _, err := callHook(found, "on_config", lua.LString(configText)); err != nil {
|
||||
log.Printf("plugin %s on_config (config reload): %v", name, err)
|
||||
if _, err := callHook(found, "on_config"); err != nil {
|
||||
log.Printf("plugin %s on_config: %v", name, err)
|
||||
}
|
||||
found.mu.Unlock()
|
||||
}
|
||||
|
||||
func (m *Manager) RunOnStart() {
|
||||
// on_config runs first, always sync, for every enabled plugin that has it.
|
||||
for _, p := range m.GetPlugins() {
|
||||
if !p.Enabled {
|
||||
continue
|
||||
}
|
||||
if _, ok := p.hooks["on_config"]; !ok {
|
||||
continue
|
||||
if _, ok := p.hooks["on_config"]; ok {
|
||||
p.mu.Lock()
|
||||
if _, err := callHook(p, "on_config"); err != nil {
|
||||
log.Printf("plugin %s on_config: %v", p.Name, err)
|
||||
}
|
||||
p.mu.Unlock()
|
||||
}
|
||||
p.mu.Lock()
|
||||
if _, err := callHook(p, "on_config", lua.LString(p.ConfigText)); err != nil {
|
||||
log.Printf("plugin %s on_config: %v", p.Name, err)
|
||||
}
|
||||
p.mu.Unlock()
|
||||
}
|
||||
// on_start runs after, sync or async depending on plugin config.
|
||||
for _, p := range m.GetPlugins() {
|
||||
if !p.Enabled {
|
||||
continue
|
||||
@@ -287,8 +272,8 @@ func (m *Manager) RunOnStart() {
|
||||
disableIfFalse := func(p *Plugin, ret lua.LValue) {
|
||||
if ret == lua.LFalse {
|
||||
p.Enabled = false
|
||||
if m.db != nil {
|
||||
if err := m.db.SavePluginState(p.Name, false, p.ConfigText); err != nil {
|
||||
if m.pluginsFile != nil {
|
||||
if err := m.pluginsFile.setEnabled(p.Name, false); err != nil {
|
||||
log.Printf("plugin %s: save state: %v", p.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type pluginFileEntry struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
Config string `yaml:"config,omitempty"`
|
||||
}
|
||||
|
||||
type pluginsFileData struct {
|
||||
Plugins map[string]pluginFileEntry `yaml:"plugins"`
|
||||
}
|
||||
|
||||
type PluginsFile struct {
|
||||
path string
|
||||
data pluginsFileData
|
||||
}
|
||||
|
||||
func OpenPluginsFile(dbPath string) (*PluginsFile, error) {
|
||||
path := filepath.Join(filepath.Dir(dbPath), "plugins.yaml")
|
||||
pf := &PluginsFile{
|
||||
path: path,
|
||||
data: pluginsFileData{Plugins: make(map[string]pluginFileEntry)},
|
||||
}
|
||||
raw, err := os.ReadFile(path)
|
||||
if os.IsNotExist(err) {
|
||||
return pf, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := yaml.Unmarshal(raw, &pf.data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pf.data.Plugins == nil {
|
||||
pf.data.Plugins = make(map[string]pluginFileEntry)
|
||||
}
|
||||
return pf, nil
|
||||
}
|
||||
|
||||
func (pf *PluginsFile) save() error {
|
||||
raw, err := yaml.Marshal(&pf.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(pf.path, raw, 0o600)
|
||||
}
|
||||
|
||||
func (pf *PluginsFile) get(name string) (enabled bool, config string, found bool) {
|
||||
e, ok := pf.data.Plugins[name]
|
||||
return e.Enable, e.Config, ok
|
||||
}
|
||||
|
||||
func (pf *PluginsFile) register(name string, defaultEnabled bool) {
|
||||
if _, ok := pf.data.Plugins[name]; !ok {
|
||||
pf.data.Plugins[name] = pluginFileEntry{Enable: defaultEnabled}
|
||||
_ = pf.save()
|
||||
}
|
||||
}
|
||||
|
||||
func (pf *PluginsFile) setEnabled(name string, enabled bool) error {
|
||||
e := pf.data.Plugins[name]
|
||||
e.Enable = enabled
|
||||
pf.data.Plugins[name] = e
|
||||
return pf.save()
|
||||
}
|
||||
|
||||
func (pf *PluginsFile) setConfig(name string, configText string) error {
|
||||
e := pf.data.Plugins[name]
|
||||
e.Config = configText
|
||||
pf.data.Plugins[name] = e
|
||||
return pf.save()
|
||||
}
|
||||
@@ -106,6 +106,12 @@ func New(broker *intercept.Broker, name, path string) Model {
|
||||
m.findingsPage.SetDB(d)
|
||||
mgr.SetDB(d)
|
||||
|
||||
pf, err := plugins.OpenPluginsFile(path)
|
||||
if err != nil {
|
||||
log.Printf("plugins file: %v", err)
|
||||
}
|
||||
mgr.SetPluginsFile(pf)
|
||||
|
||||
pluginsDir := config.ExpandPath(cfg.App.PluginsDir)
|
||||
if err := mgr.LoadFromDir(pluginsDir); err != nil {
|
||||
log.Printf("plugins: %v", err)
|
||||
|
||||
Reference in New Issue
Block a user