You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fw-daemon/fw-settings/rules.go

444 lines
11 KiB

package main
import (
"fmt"
"os"
"strings"
"sync"
"github.com/subgraph/fw-daemon/sgfw"
"github.com/gotk3/gotk3/gtk"
"github.com/gotk3/gotk3/glib"
)
type ruleList struct {
lock *sync.Mutex
app *fwApp
mode sgfw.RuleMode
rows []*ruleRow
rules []sgfw.DbusRule
rowsByIndex map[int]*ruleRow
list *gtk.ListBox
col0 *gtk.SizeGroup
col1 *gtk.SizeGroup
col2 *gtk.SizeGroup
col3 *gtk.SizeGroup
raHandlerID glib.SignalHandle
}
type ruleRow struct {
*gtk.ListBoxRow
rl *ruleList
rule *sgfw.DbusRule
gtkBox *gtk.Box
gtkSep *gtk.Separator
gtkGrid *gtk.Grid
gtkLabelApp *gtk.Label
gtkLabelTarget *gtk.Label
gtkButtonEdit *gtk.Button
gtkButtonSave *gtk.Button
gtkButtonDelete *gtk.Button
gtkAppIcon *gtk.Image
gtkIconVerb *gtk.Image
}
func newRuleList(app *fwApp, list *gtk.ListBox, mode sgfw.RuleMode) *ruleList {
rl := &ruleList{app: app, list: list}
rl.lock = new(sync.Mutex)
rl.mode = mode
rl.list.SetSelectionMode(gtk.SELECTION_NONE)
rl.col0, _ = gtk.SizeGroupNew(gtk.SIZE_GROUP_HORIZONTAL)
rl.col1, _ = gtk.SizeGroupNew(gtk.SIZE_GROUP_HORIZONTAL)
rl.col2, _ = gtk.SizeGroupNew(gtk.SIZE_GROUP_HORIZONTAL)
rl.col3, _ = gtk.SizeGroupNew(gtk.SIZE_GROUP_HORIZONTAL)
rl.list.SetActivateOnSingleClick(false)
return rl
}
func (rl *ruleList) loadRules(noAdd bool) error {
rl.lock.Lock()
defer rl.lock.Unlock()
rules, err := rl.app.Dbus.listRules()
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %+v\n", err)
return err
}
for i := (len(rules) - 1); i >= 0; i-- {
if sgfw.RuleMode(rules[i].Mode) != rl.mode {
rules = append(rules[:i], rules[i+1:]...)
}
}
rules = rl.sortRules(rules)
rl.rules = rules
if !noAdd {
rl.addRules(rules)
}
return nil
}
func (rl *ruleList) reloadRules(filter string) {
rl.lock.Lock()
defer rl.lock.Unlock()
filter = strings.ToLower(filter)
rules := make([]sgfw.DbusRule, len(rl.rules))
copy(rules, rl.rules)
if filter != "" {
for i := (len(rules) - 1); i >= 0; i-- {
if !strings.Contains(strings.ToLower(rules[i].Path), filter) && !strings.Contains(strings.ToLower(rules[i].Sandbox), filter) {
rules = append(rules[:i], rules[i+1:]...)
}
}
}
rules = rl.sortRules(rules)
for i, _ := range rl.rows {
rl.col0.RemoveWidget(rl.rows[i].gtkAppIcon)
rl.col1.RemoveWidget(rl.rows[i].gtkLabelApp)
rl.col2.RemoveWidget(rl.rows[i].gtkIconVerb)
rl.col3.RemoveWidget(rl.rows[i].gtkLabelTarget)
rl.rows[i].gtkLabelApp.Destroy()
rl.rows[i].gtkLabelApp = nil
rl.rows[i].gtkLabelTarget.Destroy()
rl.rows[i].gtkLabelTarget = nil
rl.rows[i].gtkButtonEdit.Destroy()
rl.rows[i].gtkButtonEdit = nil
rl.rows[i].gtkButtonSave.Destroy()
rl.rows[i].gtkButtonSave = nil
rl.rows[i].gtkButtonDelete.Destroy()
rl.rows[i].gtkButtonDelete = nil
rl.rows[i].gtkAppIcon.Destroy()
rl.rows[i].gtkAppIcon = nil
rl.rows[i].gtkIconVerb.Destroy()
rl.rows[i].gtkIconVerb = nil
rl.rows[i].gtkGrid.Destroy()
rl.rows[i].gtkGrid = nil
rl.rows[i].gtkSep.Destroy()
rl.rows[i].gtkSep = nil
rl.rows[i].gtkBox.Destroy()
rl.rows[i].gtkBox = nil
rl.list.Remove(rl.rows[i])
rl.rows[i].ListBoxRow.Destroy()
rl.rows[i].ListBoxRow = nil
//rl.rows[i].Destroy()
rl.rows[i].rule = nil
rl.rows[i].rl = nil
rl.rows[i] = nil
}
rl.rows = rl.rows[:0]
for i, _ := range rl.rowsByIndex {
delete(rl.rowsByIndex, i)
}
rules = rl.sortRules(rules)
rl.addRules(rules)
}
func (rl *ruleList) addRules(rules []sgfw.DbusRule) {
pi := 0
rl.rowsByIndex = make(map[int]*ruleRow, len(rules))
if rl.raHandlerID > 0 {
rl.list.HandlerDisconnect(rl.raHandlerID)
}
for i := 0; i < len(rules); i++ {
row := rl.createWidget(&rules[i])
rl.col0.AddWidget(row.gtkAppIcon)
rl.col1.AddWidget(row.gtkLabelApp)
rl.col2.AddWidget(row.gtkIconVerb)
rl.col3.AddWidget(row.gtkLabelTarget)
rl.list.Add(row)
rl.rowsByIndex[row.GetIndex()] = row
row.ShowAll()
if i > 0 && rules[pi].Path == rules[i].Path && rules[pi].Sandbox == rules[i].Sandbox {
row.hideTitle()
}
rl.rows = append(rl.rows, row)
pi = i
}
rl.raHandlerID, _ = rl.list.Connect("row-activated", rl.showInformation)
}
func (rl *ruleList) createWidget(rule *sgfw.DbusRule) *ruleRow {
row := &ruleRow{rl: rl}
row.rule = rule
builder := newBuilder("RuleItem")
builder.getItems(
"grid", &row.gtkGrid,
"app_label", &row.gtkLabelApp,
"verb_icon", &row.gtkIconVerb,
"target_label", &row.gtkLabelTarget,
"edit_button", &row.gtkButtonEdit,
"save_button", &row.gtkButtonSave,
"delete_button", &row.gtkButtonDelete,
"app_icon", &row.gtkAppIcon,
)
switch sgfw.RuleMode(rule.Mode) {
case sgfw.RULE_MODE_SYSTEM:
row.gtkButtonEdit.SetVisible(false)
row.gtkButtonEdit.SetNoShowAll(true)
row.gtkButtonDelete.SetSensitive(false)
row.gtkButtonDelete.SetTooltipText("Cannot delete system rules")
break
case sgfw.RULE_MODE_PROCESS:
row.gtkButtonSave.SetSensitive(true)
row.gtkButtonSave.SetNoShowAll(false)
break
case sgfw.RULE_MODE_SESSION:
row.gtkButtonSave.SetSensitive(true)
row.gtkButtonSave.SetNoShowAll(false)
break
}
builder.ConnectSignals(map[string]interface{}{
"on_edit_rule": row.onEdit,
"on_save_rule": row.onSaveAsNew,
"on_delete_rule": row.onDelete,
})
row.gtkBox, _ = gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
row.gtkSep, _ = gtk.SeparatorNew(gtk.ORIENTATION_HORIZONTAL)
row.ListBoxRow, _ = gtk.ListBoxRowNew()
row.gtkBox.Add(row.gtkGrid)
row.gtkBox.Add(row.gtkSep)
row.Add(row.gtkBox)
row.SetProperty("selectable", false)
row.SetProperty("activatable", true)
row.showTitle()
row.update()
//builder.Object.Unref()
builder = nil
return row
}
func (rl *ruleList) showInformation(list *gtk.ListBox, row *gtk.ListBoxRow) bool {
rr := rl.rowsByIndex[row.GetIndex()]
rr.runNewEditor(DIALOG_MODE_INFO)
return true
}
func (rr *ruleRow) update() {
rr.gtkLabelApp.SetTooltipText(rr.rule.Path)
rr.setVerbIcon()
tt := getTargetText(rr.rule)
if rr.rule.UID > -1 || rr.rule.GID > -1 {
tt = tt + " for "
}
if rr.rule.UID > -1 {
tt = tt + rr.rl.app.LookupUsername(rr.rule.Sandbox, rr.rule.UID)
}
if rr.rule.UID > -1 && rr.rule.GID > -1 {
tt = tt + ":"
}
if rr.rule.GID > -1 {
tt = tt + rr.rl.app.LookupGroup(rr.rule.Sandbox, rr.rule.GID)
}
rr.gtkLabelTarget.SetText(tt)
}
func (rr *ruleRow) hideTitle() {
rr.gtkLabelApp.SetText("")
rr.gtkAppIcon.Clear()
}
func (rr *ruleRow) showTitle() {
in := []string{rr.rule.App}
if rr.rule.Sandbox != "" {
in = append([]string{rr.rule.Sandbox}, in...)
}
if rr.rule.App == "[unknown]" {
in = []string{"image-missing"}
}
it, err := gtk.IconThemeGetDefault()
if err != nil {
fmt.Println("Error getting icon theme.")
} else {
found := false
for _, ia := range in {
pb, _ := it.LoadIcon(ia, int(gtk.ICON_SIZE_BUTTON), gtk.ICON_LOOKUP_USE_BUILTIN)
if pb != nil {
rr.gtkAppIcon.SetFromIconName(ia, gtk.ICON_SIZE_BUTTON)
found = true
break
}
}
if !found {
rr.gtkAppIcon.SetFromIconName("terminal", gtk.ICON_SIZE_BUTTON)
}
}
rr.gtkLabelApp.SetText(rr.rule.App)
}
func (rr *ruleRow) setVerbIcon() {
it, err := gtk.IconThemeGetDefault()
in := ""
tt := ""
if sgfw.RuleAction(rr.rule.Verb) == sgfw.RULE_ACTION_DENY {
in = "gtk-no"
tt = "Deny"
} else if sgfw.RuleAction(rr.rule.Verb) == sgfw.RULE_ACTION_ALLOW {
in = "gtk-yes"
tt = "Allow"
} else if sgfw.RuleAction(rr.rule.Verb) == sgfw.RULE_ACTION_ALLOW_TLSONLY {
in = "gtk-yes"
tt = "Allow TLS"
}
if err != nil {
fmt.Println("Error getting icon theme.")
return
}
pb, _ := it.LoadIcon(in, int(gtk.ICON_SIZE_BUTTON), gtk.ICON_LOOKUP_USE_BUILTIN)
if pb == nil {
fmt.Println("Error getting icon theme.")
return
}
rr.gtkIconVerb.SetFromIconName(in, gtk.ICON_SIZE_BUTTON)
rr.gtkIconVerb.SetTooltipText(tt)
}
func getVerbText(rule *sgfw.DbusRule) string {
if sgfw.RuleAction(rule.Verb) == sgfw.RULE_ACTION_ALLOW {
return sgfw.RuleActionString[sgfw.RULE_ACTION_ALLOW] + ":"
}
if sgfw.RuleAction(rule.Verb) == sgfw.RULE_ACTION_ALLOW_TLSONLY {
return sgfw.RuleActionString[sgfw.RULE_ACTION_ALLOW_TLSONLY] + ":"
}
return sgfw.RuleActionString[sgfw.RULE_ACTION_DENY] + ":"
}
func getTargetText(rule *sgfw.DbusRule) string {
verb := "Deny"
if sgfw.RuleAction(rule.Verb) == sgfw.RULE_ACTION_ALLOW || sgfw.RuleAction(rule.Verb) == sgfw.RULE_ACTION_ALLOW_TLSONLY {
verb = "Allow"
}
if rule.Target == "*:*" {
ct := "any"
if sgfw.RuleAction(rule.Verb) == sgfw.RULE_ACTION_DENY {
ct = "all"
}
res := []string{verb, ct, "connections"}
if sgfw.RuleAction(rule.Verb) == sgfw.RULE_ACTION_ALLOW_TLSONLY {
res = append(res, "with TLS")
}
return strings.Join(res, " ")
}
items := strings.Split(rule.Target, ":")
if len(items) != 2 {
return strings.Join([]string{verb, rule.Target}, " ")
}
ct := "connections"
if rule.Proto != "tcp" {
ct = "data"
}
target := []string{verb, strings.ToUpper(rule.Proto), ct}
if sgfw.RuleAction(rule.Verb) == sgfw.RULE_ACTION_ALLOW_TLSONLY {
target = append(target, "with TLS")
}
if rule.Origin != "" {
target = append(target, "from ", rule.Origin)
}
if items[0] == "*" {
if rule.Proto == "tcp" {
target = append(target, fmt.Sprintf("to ALL hosts on port %s", items[1]))
} else if rule.Proto == "icmp" {
target = append(target, fmt.Sprintf("to ALL hosts with code %s", items[1]))
} else {
target = append(target, fmt.Sprintf("to ALL hosts on port %s", items[1]))
}
return strings.Join(target, " ")
}
if items[1] == "*" {
if rule.Proto == "tcp" {
target = append(target, fmt.Sprintf("to host %s", items[0]))
} else if rule.Proto == "icmp" {
target = append(target, fmt.Sprintf("to host %s", items[0]))
} else {
target = append(target, fmt.Sprintf("to host %s", items[0]))
}
return strings.Join(target, " ")
}
ps := "port"
if rule.Proto == "icmp" {
ps = "code"
}
target = append(target, fmt.Sprintf("to %s on %s %s", items[0], ps, items[1]))
return strings.Join(target, " ")
}
func (rr *ruleRow) onSaveAsNew() {
rr.runNewEditor(DIALOG_MODE_SAVEAS)
}
func (rr *ruleRow) onEdit() {
rr.runNewEditor(DIALOG_MODE_EDIT)
}
func (rr *ruleRow) onDelete() {
var body string
if rr.rule.Sandbox != "" {
ss := `Are you sure you want to delete this rule:
<b>Path:</b> %s
<b>Sandbox:</b> %s
<b>Rule:</b> %s`
body = fmt.Sprintf(ss, rr.rule.Path, rr.rule.Sandbox, getTargetText(rr.rule))
} else {
ss := `Are you sure you want to delete this rule:
<b>Path:</b> %s
<b>Rule:</b> %s`
body = fmt.Sprintf(ss, rr.rule.Path, getTargetText(rr.rule))
}
d := gtk.MessageDialogNewWithMarkup(
rr.rl.app.win,
gtk.DIALOG_DESTROY_WITH_PARENT,
gtk.MESSAGE_QUESTION,
gtk.BUTTONS_OK_CANCEL,
"")
d.SetMarkup(body)
if d.Run() == (int)(gtk.RESPONSE_OK) {
rr.delete()
}
d.Destroy()
}
func (rl *ruleList) remove(rr *ruleRow) {
rl.col0.RemoveWidget(rr.gtkAppIcon)
rl.col1.RemoveWidget(rr.gtkLabelApp)
rl.col2.RemoveWidget(rr.gtkIconVerb)
rl.col3.RemoveWidget(rr.gtkLabelTarget)
rl.list.Remove(rr.ListBoxRow)
for i := (len(rl.rules) - 1); i >= 0; i-- {
if *rr.rule == rl.rules[i] {
rl.rules = append(rl.rules[:i], rl.rules[i+1:]...)
break;
}
}
}
func (rr *ruleRow) delete() {
idx := rr.ListBoxRow.GetIndex()
ndx := idx + 1
pdx := idx - 1
if ndx < len(rr.rl.rows) {
if pdx != -1 {
if rr.rl.rows[pdx].rule.Path != rr.rule.Path || rr.rl.rows[pdx].rule.Sandbox != rr.rule.Sandbox {
rr.rl.rows[ndx].showTitle()
}
} else {
rr.rl.rows[ndx].showTitle()
}
}
rr.rl.remove(rr)
rr.rl.app.Dbus.deleteRule(rr.rule.ID)
rr.rl.rows = append(rr.rl.rows[:idx], rr.rl.rows[idx+1:]...)
}