diff --git a/build/desktop/dwl-config.h b/build/desktop/dwl-config.h index a1305940..fcf21f0c 100644 --- a/build/desktop/dwl-config.h +++ b/build/desktop/dwl-config.h @@ -85,9 +85,8 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA { MODKEY|WLR_MODIFIER_SHIFT, SKEY, tag, {.ui = 1 << TAG} }, \ { MODKEY|WLR_MODIFIER_CTRL|WLR_MODIFIER_SHIFT,SKEY,toggletag, {.ui = 1 << TAG} } -/* commands: zone launches, where --ask has the chrome prompt for a passphrase - * if the zone needs one, and the chrome's own menu, a text program that needs - * a zone 0 terminal of its own, as its window at login has */ +/* commands: zone launches (the chrome asks for a passphrase if needed) and the + * chrome's menu, a text program in a zone 0 terminal of its own */ static const char *termcmd[] = { "kryptik-launch", "--ask", "work", "--", "havoc", NULL }; static const char *personalcmd[] = { "kryptik-launch", "--ask", "personal", "--", "havoc", NULL }; static const char *untrustedcmd[] = { "kryptik-launch", "--ask", "untrusted", "--", "havoc", "lynx", NULL }; diff --git a/docs/architecture.md b/docs/architecture.md index 68856569..0803d563 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -19,8 +19,8 @@ A zone is the unit of isolation, and every process belongs to exactly one. The trusted base, like Qubes' `dom0`: PID 1, the services, kryptikd, the compositor and the desktop session. It has no route out and runs no user applications (ADR-003), which the desktop suite checks process by process. -kryptikd creates the other zones as root; unprivileged -user namespaces are off ([privileged launch](design/privileged-launch.md)). +kryptikd creates the other zones as root; unprivileged user namespaces are +off ([privileged launch](design/privileged-launch.md)). ### Shipped zones @@ -52,11 +52,11 @@ The chrome's menu is its menu window. ## Between zones -Nothing crosses by default. The broker in kryptikd identifies a caller by its -socket's peer uid and carries two things ([broker](design/broker.md)): a file -transfer, one file one way to a zone the sender's policy names, after the user -approves it; and the clipboard, one per zone, moved between zones only by a -user gesture in zone 0. +Nothing crosses by default. The [broker](design/broker.md) in kryptikd +identifies a caller by its socket's peer uid and carries two things. A file +transfer sends one file, one way, to a zone listed in the sender's policy, +once the user approves it. Each zone has one clipboard, moved to another zone +only by a user gesture in zone 0. Routed zones reach the network through isolated ports on the `net` zone's bridge. The `net` zone can also send zone 0 a clock offset and releases, both diff --git a/tools/desktop/dwl-zone-borders.py b/tools/desktop/dwl-zone-borders.py index ea5de94c..4083f25e 100755 --- a/tools/desktop/dwl-zone-borders.py +++ b/tools/desktop/dwl-zone-borders.py @@ -105,7 +105,7 @@ """ EDITS = [ - # (1) Client: the zone's colour, the band that marks it unfocused, the bar. + # Client: the zone's colour, the band that marks it unfocused, the bar. (""" struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ """, """ struct wlr_scene_rect *border[4]; /* top, bottom, left, right */ @@ -114,7 +114,7 @@ struct wlr_scene_rect *bands[4]; /* top, bottom, left, right */ struct wlr_scene_tree *bar; /* Kryptik: names the zone above a fullscreen window */ """), - # (2) The ZoneColor type, beside Rule so config.h can define the table. + # The ZoneColor type, beside Rule so config.h can define the table. ("""typedef struct { const char *id; const char *title; @@ -136,7 +136,7 @@ const float border[4]; } ZoneColor; """), - # (3) The chooser, defined before applyrules (its first neighbour). + # The chooser, placed before applyrules. ("""void applyrules(Client *c) { @@ -173,7 +173,7 @@ applyrules(Client *c) { """), - # (4) mapnotify: zone-coloured borders, then the band over them as four + # mapnotify: zone-coloured borders, then the band over them as four # strips. The surface stays below both, or a buffer larger than its # configure would paint over the right and bottom borders. A new window # starts unfocused, so the band starts enabled; the bar starts hidden. @@ -197,7 +197,7 @@ c->bar = wlr_scene_tree_create(c->scene); wlr_scene_node_set_enabled(&c->bar->node, 0); """), - # (5) resize: the band is the ring of the border nearest the surface. + # resize: the band is the ring of the border nearest the surface. (""" wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); """, """ wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw); @@ -210,7 +210,7 @@ wlr_scene_node_set_position(&c->bands[2]->node, c->bw - bandpx, c->bw); wlr_scene_node_set_position(&c->bands[3]->node, c->geom.width - c->bw, c->bw); """), - # (6) focusclient: the colour is the zone's either way; focus hides the band. + # focusclient: the colour is the zone's either way; focus hides the band. (""" if (!exclusive_focus && !seat->drag) client_set_border_color(c, focuscolor); """, @@ -220,7 +220,7 @@ wlr_scene_node_set_enabled(&c->band->node, 0); } """), - # (7) focusclient, the window losing focus: its band comes back. + # focusclient, the window losing focus: its band comes back. (""" } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { client_set_border_color(old_c, bordercolor); """, @@ -228,7 +228,7 @@ client_set_border_color(old_c, old_c->zoneborder); wlr_scene_node_set_enabled(&old_c->band->node, 1); """), - # (8) setfullscreen: keep the border; dwl's 0 would let a window hide its zone. + # setfullscreen: keep the border; dwl's 0 would let a window hide its zone. (""" c->bw = fullscreen ? 0 : borderpx; client_set_fullscreen(c, fullscreen); """, @@ -464,8 +464,7 @@ \t\t\treturn c; \t} """), - # setmon also chooses focus after mapping; preserve another zone's actual - # keyboard focus even when the selected monitor has changed. + # setmon also picks focus after mapping: keep another zone's keyboard focus across monitors. ("""\t\tsetfloating(c, c->isfloating); \t} \tfocusclient(focustop(selmon), 1); diff --git a/tools/desktop/kryptik-chrome b/tools/desktop/kryptik-chrome index f582fbf8..7a3650f6 100755 --- a/tools/desktop/kryptik-chrome +++ b/tools/desktop/kryptik-chrome @@ -21,24 +21,20 @@ # window, then launch (kryptik-launch hands # over here when it has no terminal) # kryptik-chrome --focus print the focused zone, from the record -# kryptik-chrome --confirm ID ask the broker's question ID in a trusted -# window and write back the answer: yes only +# kryptik-chrome --confirm ID ask the broker's question ID in a trusted window: yes only # for the code it shows, typed after it shows # # Passphrases are read by kryptik-launch on the chrome's own terminal and go # to the daemon as a descriptor, never in argv or the environment. -# The verified root's zone files, not /etc's: /etc is an overlay the state -# partition can shadow, and the labels shown must match the colours dwl draws. +# The verified root's zone files, which dwl's colours match: the state partition can shadow /etc. ZONES=/usr/lib/kryptik/zones LAUNCH=/usr/bin/kryptik-launch -# `havoc [option...] [program [args...]]`: no -e, and an unknown option makes -# it print its usage and exit. +# havoc takes the program as plain arguments: it has no -e, and exits on an unknown option. TERMINAL=/usr/bin/havoc RT="${XDG_RUNTIME_DIR:-/tmp}" FOCUS="$RT/kryptik/focus" -# The last window from a zone. The menu shows this one: opening the menu -# focuses the menu, so the plain record would only ever name zone 0. +# The last zone window, for the menu: once the menu has focus, the focus record names zone 0. FOCUS_ZONE="$RT/kryptik/focus.zone" CONSENT=/run/kryptik-consent @@ -99,17 +95,15 @@ run_status() { mkdir -p "$RT/kryptik" 2>/dev/null # The menu window, so a fresh session shows something usable. "$TERMINAL" /usr/bin/kryptik-chrome --menu & - # The consent watcher: one trusted window per question the broker leaves. - # Holding watcher.lock exclusively tells the broker someone is here to ask - # (consent.rs refuses at once if it can take the lock shared); the kernel - # drops it when this subshell dies. Confirm windows must not inherit it, - # and a second session's watcher exits quietly. + # The consent watcher. Holding watcher.lock tells the broker someone is here + # to ask: consent.rs refuses at once if it can take the lock shared. The + # kernel drops it with this subshell, and a second session's watcher exits + # quietly. ( exec 9> "$CONSENT/watcher.lock" 2>/dev/null || exit 0 flock -n 9 || exit 0 - # From here a write creates, never follows or clobbers: any member of - # group kryptik can plant a link in the directory. That also makes the - # .dialog claim one exclusive create. + # Writes create, never follow or clobber: group kryptik can plant links here. + # That also makes each .dialog claim one exclusive create. set -C while :; do for ask in "$CONSENT"/*.ask; do @@ -117,10 +111,10 @@ run_status() { id="${ask##*/}"; id="${id%.ask}" [ -e "$CONSENT/$id.dialog" ] && continue : > "$CONSENT/$id.dialog" 2>/dev/null || continue + # A window must not hold the lock past the watcher's death. "$TERMINAL" /usr/bin/kryptik-chrome --confirm "$id" 9>&- & done - # A window writes its code and answer even for a question - # withdrawn meanwhile; whatever outlives its question goes. + # A window may write its code and answer after its question is withdrawn. for f in "$CONSENT"/*.dialog "$CONSENT"/*.code "$CONSENT"/*.answer; do [ -e "$f" ] || continue [ -e "${f%.*}.ask" ] || rm -f "$f" @@ -206,8 +200,7 @@ run_menu() { b) set -- "$TERMINAL" /usr/bin/lynx ;; esac echo "launching in zone $z: $*" - # --ask: a passphrase is read on this zone 0 terminal, - # never on a zone's. + # --ask: a passphrase is read on this zone 0 terminal, never a zone's. "$LAUNCH" --ask "$z" -- "$@" || sleep 3 ;; *) echo "?"; sleep 1 ;; @@ -236,8 +229,7 @@ run_confirm() { # ID # A question with no kind is a transfer. kind="$(sed -n 's/^kind=//p' "$ask")" if [ "$kind" = clock ]; then - # The clock (docs/design/time.md): the network's time is too far from - # this machine's to accept unasked. Only the user, with a watch, can say. + # The network's time is too far from this machine's to accept unasked (docs/design/time.md). now="$(sed -n 's/^now=//p' "$ask")"; proposed="$(sed -n 's/^proposed=//p' "$ask")" sources="$(sed -n 's/^sources=//p' "$ask")" echo "================ KRYPTIK - set the clock? (zone 0) ================" @@ -274,14 +266,11 @@ run_confirm() { # ID [ "$(cat | wc -c)" -eq 0 ] || echo "(what was typed before this question showed is ignored)" stty "$was" fi - # The answer is a code drawn for this question alone: no zone sees this - # window, so none can type it. It is kept beside the question for zone 0's - # tests; whatever can read this directory could write the answer anyway. - # Writes here create, never follow or clobber: any member of group kryptik - # can plant a link in the directory. An answer that cannot be written is - # no answer, which the broker takes as a refusal. + # Writes create, never follow or clobber: any member of group kryptik can plant a link. set -C + # A code for this question alone: no zone sees this window, so none can type it. code=$(( $(od -An -N1 -tu1 /dev/urandom) % 90 + 10 )) + # Kept for zone 0's tests; whatever can read it could write the answer anyway. (umask 077; printf '%s\n' "$code" > "$CONSENT/$id.code") 2>/dev/null \ || echo "kryptik-chrome --confirm $id: the code could not be written beside the question" >> "$RT/kryptik/session.log" printf 'Type %s and Enter to allow; anything else refuses: ' "$code" @@ -289,6 +278,7 @@ run_confirm() { # ID if read -r reply; then [ "$reply" = "$code" ] && answer=yes fi + # An answer that cannot be written is none: the broker takes that as a refusal. if printf '%s\n' "$answer" > "$CONSENT/$id.answer.tmp" && mv -f "$CONSENT/$id.answer.tmp" "$CONSENT/$id.answer"; then echo "-> $answer" else