--- title: "Ah ah ah, you didn't say the magic word.. en skærmlås-prank i Rust" description: "Hvorfor jeg droppede ANSI C til fordel for Rust, og hvordan du installerer ah-fail på Wayland, X11 og macOS." date: 2026-06-11 taxonomies: tags: [rust, pam, gtklock, wayland, linux, macos] draft: false extra: godkendt: "v1:c8d80c56e01731b150a66ec9dcdfca911d932ea1721145fb79e3cbf46bc26046" --- Et af mine mere fjollede sideprojekter: **ah-fail**. Taster du dit kodeord forkert på låseskærmen, popper der en loopende animation op af **mit eget ansigt photoshoppet ind på Dennis Nedrys krop** fra Jurassic Park, mens "ah ah ah, you didn't say the magic word" spiller i fuld lydstyrke. Hvert forkert gæt tilføjer en ny sprite på en tilfældig position på skærmen. Lydstyrken tvinges til 100 % på første fejl og sættes tilbage, når du endelig låser op. Det er en prank. Men vejen dertil lærte mig en del om, hvor følsomt authentication-laget på et system egentlig er. ## Hvorfor jeg skrottede ANSI C Jeg startede projektet i **ANSI C**. Det giver god mening på papiret: det her hooker ind i **PAM**, og PAM er skrevet i C. Men der er et problem.. PAM sidder **direkte i authentication-kritiske sti**. Min kode bliver indlæst ind i selve den proces, der afgør, om du må komme ind på maskinen. Og med manuel hukommelsesstyring blev det hurtigt for risikabelt. Jeg jagtede use-after-free og små lækager i en kontekst, hvor en enkelt fejl i værste fald **låser dig ude af dit eget system** eller svækker login. En segfault i et lille hobby-PAM-modul er ikke bare en crash.. det er en crash i din låseskærm. Så jeg skrev det hele om til **Rust**. Hukommelsessikkerheden er ikke et nice-to-have her, den er hele pointen: jeg vil have compileren til at fange den slags fejl, før de nogensinde rammer en authentication-proces. Resten af projektet blev til en **Cargo workspace**, bygget med **Meson**, og koden, der rører PAM, blev holdt så minimal som overhovedet muligt. ## Sådan hænger det sammen Integrationen afhænger af din display-server, og det er her, det bliver lidt sjovt: | Platform | Display-server | Modul | Hvordan | |---|---|---|---| | Linux | **Wayland** | `ahfail-module.so` | Indlæses direkte af `gtklock` via dens modul-API | | Linux | **X11** | `libahfail_pam.so` + `ahfail-display` | PAM-hook spawner display-binæren efter hver fejlet auth | | macOS | .. | `libahfail_pam.so` + `ahfail-display` | Samme PAM-tilgang, hooker ind i screensaver-stakken | På **Wayland** kan jeg bruge gtklocks interne vindues-API og tegne sprites direkte oven på låseskærmen. På **X11 og macOS** findes der ikke sådan et plugin-API, så her gør jeg det via et **PAM-modul**: det registrerer et cleanup-callback, der fyrer ved hver fejlet authentication, og **double-forker** en selvstændig display-binær (`ahfail-display`), som kører helt uafhængigt af PAM-stakken. Det vigtigste: PAM-modulet **rører aldrig dine credentials**. Det reagerer kun på, at et forsøg fejlede. Al den fælles logik (animation, lyd, deadzone, volumen) ligger i én delt crate, `ahfail-ui`, så gtklock-modulet og display-binæren deler præcis samme adfærd. ## Installation: Wayland (gtklock) På Arch henter du afhængighederne og bygger med Meson: ```bash sudo pacman -S meson ninja rust gtk3 gstreamer gst-plugins-base \ gst-plugins-good gst-plugins-bad gtklock meson setup builddir --prefix=/usr meson compile -C builddir sudo meson install -C builddir ``` `--prefix=/usr` er vigtig.. uden den defaulter Meson til `/usr/local`, og så matcher den indbyggede sti i binæren ikke der, hvor filerne faktisk lander. Derefter starter du bare gtklock med modulet: ```bash gtklock -m /usr/lib/gtklock/ahfail-module.so ``` Hold dit password-felt frit for sprites med en deadzone: ```bash gtklock -m /usr/lib/gtklock/ahfail-module.so -- --deadzone=860,440,200,200 ``` ## Installation: X11 (i3lock, xscreensaver, ..) PAM-modulet virker med enhver X11-locker, der authenticator via PAM. Du tilføjer en linje til lockerens PAM-service-fil (fx `/etc/pam.d/i3lock` eller `/etc/pam.d/xscreensaver`). Her er **den fælde der kostede mig mest tid**: `ahfail`-linjen skal stå **før** `auth include system-auth`. Står den efter, så betyder PAM's interne flow-kontrol inde i `system-auth` (`pam_faillock` med `[default=die]`), at mit modul aldrig bliver nået ved et fejlet forsøg.. det bliver kun kaldt ved succes, hvilket er det stik modsatte af, hvad jeg vil. Stien til modulet afhænger af distro: ```bash # Arch / standard (--prefix=/usr) #%PAM-1.0 auth optional /usr/lib/ahfail/libahfail_pam.so auth include system-auth ``` ```bash # Debian / Ubuntu (multiarch) #%PAM-1.0 auth optional /usr/lib/x86_64-linux-gnu/ahfail/libahfail_pam.so auth include system-auth ``` ```bash # Fedora / RHEL (multilib) #%PAM-1.0 auth optional /usr/lib64/ahfail/libahfail_pam.so auth include system-auth ``` Den fulde sti er nødvendig.. `$(libdir)/ahfail` ligger ikke i PAM's standard-søgesti. ## Installation: macOS Den nemme vej er **Homebrew**: ```bash brew tap agw/ahfail https://gitea.weircon.dk/agw/homebrew-ahfail brew install ahfail ``` Bagefter printer Homebrew den ene PAM-linje, du skal tilføje til `/etc/pam.d/screensaverui` (macOS 13 og nyere) eller `/etc/pam.d/screensaver` (macOS 12 og ældre). Den manuelle ét-linjes-rettelse er det eneste trin tilbage, og opgradering er bare `brew upgrade ahfail`. Vil du hellere bygge fra kilde, klarer install-scriptet dependencies, build og PAM-konfiguration på én gang: ```bash git clone https://gitea.weircon.dk/agw/gtk-ahfail.git cd gtk-ahfail bash scripts/install-macos.sh ``` Et lille Apple Silicon-gotcha jeg ramte: Homebrew bruger `/opt/homebrew` som prefix på ARM, men `/usr/local` på Intel. Den indbyggede default-sti skal sættes ved build-tid (`-Dlibdir`), ellers peger PAM-modulet på den forkerte placering på den ene af de to. ## En ærlig advarsel Det her hooker ind i PAM og/eller din lockers plugin-API. **PAM sidder i authentication-kritiske sti**.. en fejl i modulet kan i værste fald låse dig ude eller svække dit login. Jeg giver ingen garantier. Installerer du det, ændrer du en sikkerhedskritisk komponent i dit system og påtager dig selv ansvaret. Det er fjollet i sin natur, men det skal behandles seriøst. ## Hvad der ikke virker (endnu) På **swaylock** og **hyprlock** fyrer PAM-modulet fint ved fejlede forsøg, men animationen bliver ikke vist. Waylands session-lock-protokol (`ext-session-lock-v1`) tillader kun lockeren selv at tegne, så compositoren undertrykker min sprite, og kun lyden spiller. Fuld support kræver, at swaylock og hyprlock eksponerer et plugin-API ligesom gtklock. Indtil da er det Wayland via gtklock, eller X11. ## Koden Hele projektet ligger offentligt på [gitea.weircon.dk/agw/gtk-ahfail](https://gitea.weircon.dk/agw/gtk-ahfail). Spriten er mit ansigt på Nedrys krop, men du kan nemt bytte den ud med dit eget PNG-sekvens i `assets/` og bygge om. God fornøjelse.. og husk din deadzone.