package guard import ( "errors" "os/exec" "strconv" "strings" ) func Check() error { _, err := exec.LookPath("usbguard") if err != nil { return ErrNotFound } return nil } func ListDevices() ([]Device, error) { out, err := exec.Command("usbguard", "list-devices").Output() if err != nil { return nil, wrapExecError(err) } rules := listRules() var devices []Device for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { if line == "" { continue } d, err := parseLine(line) if err == nil { d.Permanent = rules[d.VidPid] == d.Status devices = append(devices, d) } } return devices, nil } func listRules() map[string]Status { out, err := exec.Command("usbguard", "list-rules").Output() if err != nil { return nil } rules := make(map[string]Status) for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { if line == "" { continue } d, err := parseLine(line) if err == nil { rules[d.VidPid] = d.Status } } return rules } func AllowDevice(id int, permanent bool) error { return applyPolicy("allow-device", id, permanent) } func BlockDevice(id int, permanent bool) error { return applyPolicy("block-device", id, permanent) } func RejectDevice(id int, permanent bool) error { return applyPolicy("reject-device", id, permanent) } func DaemonStatus() string { out, err := exec.Command("systemctl", "is-active", "usbguard").Output() if err != nil { return "unknown" } return strings.TrimSpace(string(out)) } func applyPolicy(cmd string, id int, permanent bool) error { args := []string{cmd} if permanent { args = append(args, "-p") } args = append(args, strconv.Itoa(id)) out, err := exec.Command("usbguard", args...).CombinedOutput() if err != nil { return classifyError(string(out)) } return nil } func wrapExecError(err error) error { var exitErr *exec.ExitError if errors.As(err, &exitErr) { return classifyError(string(exitErr.Stderr)) } return err } func classifyError(output string) error { lower := strings.ToLower(output) switch { case strings.Contains(lower, "permission denied"), strings.Contains(lower, "not authorized"): return ErrPermission case strings.Contains(lower, "read-only"), strings.Contains(lower, "immutable"): return ErrReadOnly default: return errors.New(strings.TrimSpace(output)) } }