-;;; snitch-log.el -*- lexical-binding: t; -*-
+;;; snitch-log.el -- part of snitch -*- lexical-binding: t; -*-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; See snitch.el for full details.
"Name of the buffer for the log filter 'wizard' popup window.")
(defvar snitch--log-filter-buffer nil
- "Buffer in the log filter 'wizard' popup window")
+ "Buffer in the log filter 'wizard' popup window.")
(defvar snitch--log-prune-timer nil
- "Periodic timer to prune snitch log buffer to its maximum
+ "Timer to prune snitch log.
+
+Periodic timer to prune snitch log buffer to its maximum
permitted size.")
(defun snitch--exact-log-match (policies)
- "Return true if any of policies are explicitly defined in
-snitch-log-policy."
+ "Check if log policies are explicitly enabled.
+
+Return true if any of POLICIES are explicitly defined in
+‘snitch-log-policy’."
(seq-some 'identity
(mapcar (lambda (l) (member l snitch-log-policy))
policies)))
(defun snitch--log-policy-match (policies)
- "Return true of any of the log policies in POLICIES are
-covered by one of the currently enabled policies in
-‘snitch-log-policy’.
+ "Check of current log policy matches given policies.
+
+Return true of any of the log policies in POLICIES are covered by
+one of the currently enabled policies in ‘snitch-log-policy’.
This does not require exact matches. For instance, if POLICIES
contains ‘process-whitelisted’ and ‘snitch-log-policy’ contains
(member 'blocked snitch-log-policy)) t)))
(defun snitch--pretty-obj-string (event)
- "Return an event eieio object in a 'pretty-printed' form, which
-can be used to deserialize back into an object with eval."
+ "Pretty-print a snitch event.
+
+Take an event eieio object, EVENT, and return it as a
+'pretty-printed' string."
;; write eieio object out as a pretty string by redirecting
;; standard output stream to a function that consumes the output
;; char by char. This must be reversed and concatenated to
pretty-obj))
(defun snitch--propertize (logmsg event)
- "Add text properties to LOGMSG with elements from EVENT. This
+ "Add snitch event as properties to log message.
+
+Add text properties to LOGMSG with elements from EVENT. This
allows the log filter commands to re-assemble an event from its
log message."
(cond
'snitch-family (oref event family)))))
(defun snitch--run-filter-log-hooks (logmsg)
- "Run all hooks registered in ‘snitch-log-functions’ with the
-given log message. Return the original log message if all hooks
-return t (or none are defined), or return nil or a modified new
-log string based on the first hook to return something other than
-t."
+ "Run hooks to filter snitch log messages.
+
+Run all hooks registered in ‘snitch-log-functions’ with the given
+log message, LOGMSG. Return the original log message if all
+hooks return t (or none are defined), or return nil or a modified
+new log string based on the first hook to return something other
+than t."
(if (null snitch-log-functions)
logmsg
(cl-loop for fn in snitch-log-functions with res = nil
finally return logmsg)))
(defun snitch--log (evt-type event)
- "Log a snitch event to the dedicated snitch firewall log
-buffer. EVENT is an event object, and EVT-TYPE is any policy
-type from ‘snitch-log-policies’."
+ "Log a snitch event.
+
+Log a snitch event to the dedicated snitch firewall log buffer.
+EVENT is an event object, and EVT-TYPE is any policy type from
+‘snitch-log-policies’."
(when (snitch--log-policy-match (list evt-type))
(let* ((name (cond ((eq evt-type 'all) "event")
((eq evt-type 'whitelisted) "whitelisted")
:data event)))))
(defun snitch--prune-log-buffer ()
- "Prune the size of log buffer to at most
+ "Prune the snitch log buffer.
+
+Prune the size of log buffer to at most
‘snitch-log-buffer-max-lines’ lines long."
;; ensure timer is stopped. it will be started again by the next
;; log event. it’s wasteful to have a timer running when we know
(setq buffer-read-only t))))))
(defun snitch--maybe-start-log-prune-timer ()
- "Start the snitch log pruning timer if it is not already
+ "Possibly start the snitch log pruning timer.
+
+Start the snitch log pruning timer if it is not already
running."
(unless snitch--log-prune-timer
(snitch--start-log-prune-timer)))
(defun snitch--start-log-prune-timer ()
- "Start the snitch log pruning timer. This is a non-repeating
-timer that calls snitch--prune-log-buffer after a period of
+ "Start the snitch log pruning timer.
+
+Start the snitch log pruning timer. This is a non-repeating
+timer that calls ‘snitch--prune-log-buffer’ after a period of
idle."
(setq snitch--log-prune-timer
(run-with-idle-timer 30 nil #'snitch--prune-log-buffer)))
;;;###autoload
(defun snitch-filter-from-log ()
- "Opens an interactive 'wizard' to create a new snitch
+ "Open snitch ’log filter’ wizard on selected log entry.
+
+Opens an interactive 'wizard' to create a new snitch
whitelist/blacklist rule based on the event log under the cursor.
To use the wizard, move the cursor over an item in the snitch
:args args)))))))
(defun snitch--run-log-filter-wizard (event)
- "Runs the snitch log filter 'wizard', an interactive popup
-window to help a user create a new blacklist or whitelist filter
-based on a log entry. This function sets up the window,
-populates it, loops over user keypresses, and eventually saves
-the filter to the customization variable if appropriate."
+ "Run user interface for ’log filter’ wizard.
+
+Runs the snitch log filter 'wizard', an interactive popup window
+to help a user create a new blacklist or whitelist filter based
+on a log entry which has been converted back into a snitch event,
+EVENT. This function sets up the window, populates it, loops
+over user keypresses, and eventually saves the filter to the
+customization variable if appropriate."
;; create buffer if needed
(when (null snitch--log-filter-buffer)
(snitch--init-log-filter-buffer))
(customize-save-variable orig-list new-list)))))
(defun snitch--log-filter-map-slot-from-key (map key)
- "Given a map from ‘snitch--log-filter-map’, returns the slot
-matching to the given keypress, or nil."
+ "Return field matching key press in snitch log filter.
+
+Given a map from ‘snitch--log-filter-map’, MAP, returns the slot
+matching to the given keypress, KEY, or nil."
(cl-loop for (slot . plist) in map
when (string-equal (plist-get plist 'key) key)
return slot
finally return nil))
(defun snitch--log-filter-map (event)
- "Returns an alist of (SLOT . PLIST) pairs, where each PLIST
+ "Return a mapping of event fields to names and keymaps.
+
+Returns an alist of (SLOT . PLIST) pairs, where each PLIST
contains a field name, a key to press to select it, and a
‘mnemonic’ version of the name with the key highlighted in square
brackets. The correct set of fields is returned based on the
-given event type. All of this stuff is used to display the
+type of event in EVENT. All of this stuff is used to display the
fields, and to interpret which field to select when receiving
user keypresses."
(let ((common-alist nil)
(t common-alist))))
(defun snitch--redraw-log-filter-buffer (evt selected)
- "Draw the text contents of the log-filter menu based on the
-given event and list of currently selected fields. Each field
-name is drawn on a separate line, along with its value in the
-current event. The ‘mnemonic’ version of the field name is
-displayed, with the character to press surrounded by square
+ "Draw contents of snitch log filter buffer.
+
+Draw the text contents of the log-filter menu based on the given
+event, EVT, and list of currently selected fields, SELECTED.
+Each field name is drawn on a separate line, along with its value
+in the current event. The ‘mnemonic’ version of the field name
+is displayed, with the character to press surrounded by square
brackets. Fields that are currently selected display in a
different font."
(with-current-buffer snitch--log-filter-buffer
(goto-char (point-min)))))
(defun snitch--init-log-filter-buffer ()
- "Initialize buffer for displaying UI to generate a snitch
-filter from an existing log line."
+ "Initialize log filter UI.
+
+Initialize buffer for displaying UI to generate a snitch filter
+from an existing log line."
;; logic looted from which-key
(unless (buffer-live-p snitch--log-filter-buffer)
(setq snitch--log-filter-buffer
(setq-local show-trailing-whitespace nil))))
(defun snitch--hide-log-filter-window (buffer)
- "Hide snitch log filter window, which is the window currently
+ "Hide snitch log filter UI.
+
+Hide snitch log filter window, which is the window currently
displaying BUFFER."
;; based on which-key
(when (buffer-live-p buffer)
(run-hooks 'snitch-log-filter-window-close-hook)))
(defun snitch--log-filter-window-size-to-fit (window)
- "Resize log filter window, WINDOW, to a reasonable height and
+ "Resize snitch log filter window.
+
+Resize log filter window, WINDOW, to a reasonable height and
maximum width."
;; based on which-key
;; cap at 30% of the vertical height
(fit-window-to-buffer window max-height)))
(defun snitch--show-log-filter-window ()
- "Open or switch focus to the log filter window, resizing it as
+ "Show snitch log filter window.
+
+Open or switch focus to the log filter window, resizing it as
necessary."
;; based on which-key
(let* ((alist
-;;; snitch-timer.el -*- lexical-binding: t; -*-
+;;; snitch-timer.el -- part of snitch -*- lexical-binding: t; -*-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; See snitch.el for full details.
;;; Code:
(defvar snitch--timer-alist '()
- "Cache all timers registered with emacs, along with their
+ "Cache of Emacs timers tracked by snitch.
+
+Cache all timers registered with Emacs, along with their
backtrace and a timeout. Stored as a list of (TIMER . METADATA)
cons cell entries, where each METADATA item is a (BACKTRACE
-. TIMEOUT) cons cell. TIMER is a standard emacs timer object,
-BACKTRACE is a snitch backtrace, and TIMEOUT is a standard emacs
+. TIMEOUT) cons cell. TIMER is a standard Emacs timer object,
+BACKTRACE is a snitch backtrace, and TIMEOUT is a standard Emacs
time object.")
(defvar snitch--timer-removal-queue '()
- "List of timers to be removed from snitch’s backtrace tracking
+ "List of Emacs timers to be removed from snitch’s tracking.
+
+List of timers to be removed from snitch’s backtrace tracking
when the timer call stack is empty. Timers are queued to be
removed instead of removed immediately because of the (likely)
possibility of recursive removals. If the timer is removed deep
gone.")
(defvar snitch--timer-count 0
- "Total number of timers snitch has saved (timers registered
-with emacs and intercepted by snitch).")
+ "Total number of timers snitch has tracked.
+
+Total number of timers snitch has saved (timers registered with
+Emacs and intercepted by snitch).")
(defvar snitch--timer-removed-count 0
- "Total number of timers snitch has removed (timers fired or
+ "Total number of timers snitch has finished tracking.
+
+Total number of timers snitch has removed (timers fired or
cancelled that snitch knew about).")
(defvar snitch--timer-missed-count 0
- "Total number of timers snitch has missed. This is timers that
+ "Total number of timers that snitch failed to track.
+
+Total number of timers snitch has missed. This is timers that
are removed (cancelled or triggered) while not currently tracked
-in snitch--timer-alist. This can happen naturally if snitch is
+in ‘snitch--timer-alist’. This can happen naturally if snitch is
started when timers already exist, but could also indicate bugs
causing snitch to lose track of timers.")
resulting in several calls to remove the same timer.")
(defvar snitch--max-timer-backtraces 1000
- "Maximum number of timer backtraces that snitch should keep
+ "Max number of timer backtrace snitch tracks at a time.
+
+Maximum number of timer backtraces that snitch should keep
track of. If more timers than this are started without ending,
new timers are ignored.")
(defvar snitch--save-unique-timer-fns nil
- "While t, snitch saves a list of the unique functions
+ "Whether snitch saves names of timers tracked.
+
+While t, snitch saves a list of the unique functions
registered as timers, along with a count of how many times they
were seen. This allows tracking which high-frequency timers are
-common in your emacs, so they can be added to the timer
+common in your Emacs, so they can be added to the timer
blacklist.")
(defvar snitch--unique-timer-fns '()
- "A list of unique timer functions encountered, and how many
+ "List of unique timer functions snitch has tracked.
+
+A list of unique timer functions encountered, and how many
times they were seen during the period that
-snitch--save-unique-timer-fns was t.")
+‘snitch--save-unique-timer-fns’ was t.")
(defun snitch-monitor-unique-timer-fns (&optional time no-reset)
- "Keeps a running count of each unique timer function that
-arrives during time period TIME. After TIME has elapsed, prints
-all timers seen along with the number of times each was seen
-during the monitoring time period.
+ "Print names of timer functions snitch recently tracked.
+
+Keeps a running count of each unique timer function that arrives
+during time period TIME. After TIME has elapsed, prints all
+timers seen along with the number of times each was seen during
+the monitoring time period.
Each call to this function resets the seen timer list to empty.
To continue capturing without clearing the list, set NO-RESET to
do (message "%s: %d" timer count)))))
(defun snitch--timer-test-idle-timeout (time)
- "Return t if an idle timer has timed out (current idle time
+ "Whether a tracked idle timer has timed out.
+
+Return t if an idle timer has timed out (current idle time
greater than TIME)."
(let ((idle (current-idle-time)))
(when idle
(time-less-p time idle))))
(defun snitch--timer-test-timeout (time)
- "Return t if a regular timer has timed out (current absolute time greater than TIME)."
+ "Whether a tracked normal timer has timed out.
+
+Return t if a regular timer has timed out (current absolute time
+greater than TIME)."
(time-less-p time (current-time)))
(defun snitch--timer-timeout (timer)
- "Calculate a timeout for a timer, a few minutes longer than it
-is originally scheduled to fire."
+ "Calculate timeout period for a tracked timer.
+
+Calculate a timeout for a timer, TIMER, a few minutes longer than
+it is originally scheduled to fire."
(time-add (timer--time timer) (time-convert (* 60 5))))
(defun snitch--fn-repr (fn)
- "Encode FN in a semi-human-readable form if it is a compiled
+ "Output function in human-readable format.
+
+Encode FN in a semi-human-readable form if it is a compiled
function."
(cond
((byte-code-function-p fn)
(t fn)))
(defun snitch--save-timer-function (fn)
- "Save timer function FN in SNITCH--UNIQUE-TIMER-FNS if it does
+ "Save recently tracked timer in cache.
+
+Save timer function FN in SNITCH--UNIQUE-TIMER-FNS if it does
not already exist, otherwise increment its counter. Byte
compiled functions are stored as a hash, since their names are
unknown."
(cons (cons fn-rep 1) snitch--unique-timer-fns)))))
(defun snitch--save-timer-backtrace (orig-fn &rest args)
- "Cache a timer and its associated backtrace. This function is
-hooked around all functions that register new timers with emacs.
+ "Save timer and its backtrace in snitch’s timer cache.
+
+Cache a timer and its associated backtrace. This function is
+hooked around all functions that register new timers with Emacs.
It saves the backtrace and a timeout period for when snitch
should stop listening for it in case the timer is somehow lost.
-It calls the original emacs timer registration function without
-modification and returns the result."
+It calls the original Emacs timer registration function without
+modification and returns the result.
+
+Always calls the original function ORIG-FN is called with its
+arguments ARGS unmodified."
(let* ((bt (snitch--backtrace))
;;(bt '()) ;;(snitch--backtrace))
(timer (nth 0 args))
result))
(defun snitch--remove-timed-out-timers ()
- "Iterate of all of snitch's saved timer backtraces and remove
+ "Remove tracked timers that have timed out.
+
+Iterate of all of snitch's saved timer backtraces and remove
any that have timed out."
(cl-loop for (timer . (_bt . timeout-fn)) in snitch--timer-alist
when (funcall timeout-fn)
(delq match snitch--timer-alist))))))
(defun snitch--remove-timers (timers)
- "Remove all timers in TIMERS from the timer backtrace cache, if
+ "Remove a list of timers from snitch’s tracking.
+
+Remove all timers in TIMERS from the timer backtrace cache, if
present."
(let ((total-timers (length timers))
(removed-timers 0))
(list removed-timers total-timers)))
(defun snitch--remove-timer-backtrace (orig-fn timer)
- "Remove a timer from snitch’s cache. This function is wrapped
+ "Remove a timer from snitch’s tracking cache.
+
+Remove a timer from snitch’s cache. This function is wrapped
around ‘timer-event-handler’ and ‘cancel-timer’, triggering
whenever a timer either fires or is explicitly cancelled. It
removes snitch’s decorated copy and calls the originally
-requested function as normal."
+requested function as normal.
+
+Always calls the original function ORIG-FN with its original
+argument, TIMER."
(setq snitch--wrap-timer-depth (+ snitch--wrap-timer-depth 1))
(let* ((result (apply orig-fn (list timer))))
;; TODO: this is probably wrong. What if one timer removed a
#'snitch--remove-timer-backtrace))
(defun snitch--register-timer-hooks ()
- "Add timer hooks so snitch can provide backtraces all the way
+ "Register snitch’s timer tracing hooks.
+
+Add timer hooks so snitch can provide backtraces all the way
to the source of whichever function registered the timer."
(setq snitch--timer-alist '()
snitch--timer-removal-queue '()
#'snitch--remove-timer-backtrace))
(defun snitch--debug-print-timer-state (&optional alist)
- "Print current state of snitch’s timer tracing to the messages
+ "Print state of snitch’s timer tracing.
+
+Print current state of snitch’s timer tracing to the messages
log. If ALIST is t, also prints the currently cached timers."
(interactive)
(message "%s" (current-time-string))
do (message "timeout? %s" (funcall timeout-fn)))))
(defun snitch--activate-timer-trace ()
- "Activate snitch timer tracing by hooking the appropriate
+ "Activate snitch timer tracing.
+
+Activate snitch timer tracing by hooking the appropriate
functions."
(interactive)
(snitch--register-timer-hooks))
(snitch--remove-timer-hooks))
(defun snitch--debug-test-print-timers ()
- "Print snitch’s cached timers, and all of emacs’ currently
+ "Print snitch’s cached timer state.
+
+Print snitch’s cached timers, and all of Emacs’ currently
registered timers."
(cl-loop for (timer . meta) in snitch--timer-alist
do
-;;; snitch.el --- An emacs firewall -*- lexical-binding: t; -*-
+;;; snitch.el --- An Emacs firewall -*- lexical-binding: t; -*-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Copyright (C) 2020 Trevor Bentley
(defconst snitch-source-package-types
'(built-in site-lisp user)
- "Possible types for a snitch event's package source, as found
-in the ‘src-pkg’ field of each event object. In addition to
-these pre-defined types, any loaded package name (as a symbol) is
-a permitted type as well.
+ "Possible types for a snitch event's package source.
+
+Types are specified in the ‘src-pkg’ field of each event object.
+
+In addition to these pre-defined types, any loaded package
+name (as a symbol) is a permitted type as well.
nil -- unknown source, including lambdas, closures, and
compiled functions.
- 'built-in' -- package provided by emacs, and responds true to
+ 'built-in' -- package provided by Emacs, and responds true to
the ‘package-built-in-p’ function.
- 'site-lisp' -- package is found in one of the emacs common
+ 'site-lisp' -- package is found in one of the Emacs common
directories (i.e. a system-wide shared elisp directory), but does
not report itself as a built-in.
network-allowed
network-whitelisted
network-blacklisted)
- "All of the logging policies for snitch. Provide a list of
-these symbols to ‘snitch-log-policy’ to enable logging of events of
-the corresponding type.
+ "Permitted logging policies for snitch.
+
+Provide a list of these symbols to ‘snitch-log-policy’ to enable
+logging of events of the corresponding type. Any combination can
+be combined, or use ‘all’ to include everything.
'all' -- logs every event, before a decision is made.
;;
(defun snitch--service-to-port (service)
- "Convert SERVICE argument of ‘make-network-process’ into a symbol
-or number."
+ "Convert SERVICE into a symbol or number.
+
+SERVICE is the service field passed to ‘make-network-process’,
+representing the port to connect to."
(cond
((symbolp service) service)
;; TODO: handle special service names, ex: "https"
list-hook-fns
default-evt-type
default-hook-fns)
- "Return t if EVENT is to be filtered differently from the
-default policy, nil if default action is to be taken. The choice
-of DECISION-LIST (whitelist or blacklist) and the event types
-(LIST-EVT-TYPE and DEFAULT-EVT-TYPE) determines whether default
-is block/allow. Registered user hooks are called, and potentially
-alter the decision.
+ "Decide whether an event should use the default action.
+
+Return t if EVENT is to be filtered differently from the default
+policy, nil if default action is to be taken. The choice of
+DECISION-LIST (whitelist or blacklist) and the event types
+\(LIST-EVT-TYPE and DEFAULT-EVT-TYPE) determines whether default
+is block/allow. Registered user hooks are called, and
+potentially alter the decision: LIST-HOOK-FNS if the function was
+in the list, or DEFAULT-HOOK-FNS if it was not.
This function only generates a decision. It does not perform the
actual block or pass action.
t)))
(defun snitch--wrap-internal (event prefix orig-fun args)
- "Execute the wrapped function, ORIG-FUN with its original
+ "Perform snitch’s core firewall decision.
+
+Execute the wrapped function, ORIG-FUN with its original
arguments ARGS if EVENT is allowed by default policy or
whitelist. PREFIX is the string 'process' or 'network' to
indicate the type of event. Registered hooks are called before
(defun snitch--wrap-make-process (orig-fun &rest args)
- "Wrap a call to make-process in the snitch firewall decision
+ "Wrap subprocesses with snitch firewall.
+
+Wrap a call to ‘make-process’ in the snitch firewall decision
engine. ORIG-FUN is called only if the snitch firewall rules
-permit it."
+permit it, receiving its default arguments ARGS."
(let* ((bt (snitch--backtrace t))
(caller (snitch--responsible-caller bt))
(event (snitch-process-entry
(snitch--wrap-internal event "process" orig-fun args)))
(defun snitch--wrap-make-network-process (orig-fun &rest args)
- "Wrap a call to make-network-process in the snitch firewall
+ "Wrap network connections with snitch firewall.
+
+Wrap a call to ‘make-network-process’ in the snitch firewall
decision engine. ORIG-FUN is called only if the snitch firewall
-rules permit it."
+rules permit it, receiving its default arguments ARGS."
(let* ((bt (snitch--backtrace t))
(caller (snitch--responsible-caller bt))
(event (snitch-network-entry
(snitch--wrap-internal event "network" orig-fun args)))
(defun snitch--register-wrapper-fns ()
- "Add snitch decision engine around the lowest-level emacs
+ "Enable snitch firewall wrapping functions.
+
+Add snitch decision engine around the lowest-level Emacs
functions responsible for launching subprocesses and opening
network connections."
;; lowest-level functions, implemented in C
;;;###autoload
(defun snitch-restart ()
- "Restart the snitch firewall, unloading and reloading all
-hooks."
+ "Restart the snitch firewall.
+
+Unload and reload all hooks and timers."
(interactive)
(when (snitch--deinit)
(snitch--init)))